New UX for app usage screen
Now uses ManageApplications base, and has a details screen which has a switch and a link to optional app settings. Bug: 20290386 Change-Id: If32ce8d82e55f3908644c575925b3f6506a68e6e
This commit is contained in:
@@ -1228,7 +1228,7 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
|
||||
android:value="com.android.settings.UsageAccessSettings" />
|
||||
android:value="com.android.settings.applications.ManageApplications" />
|
||||
<meta-data android:name="com.android.settings.TOP_LEVEL_HEADER_ID"
|
||||
android:resource="@id/security_settings" />
|
||||
</activity>
|
||||
|
@@ -4497,10 +4497,6 @@
|
||||
|
||||
<!-- Title of Usage Access preference item [CHAR LIMIT=30] -->
|
||||
<string name="usage_access_title">Apps with usage access</string>
|
||||
<!-- AlertDialog title for warning user when enabling usage access [CHAR LIMIT=30] -->
|
||||
<string name="allow_usage_access_title">Allow access?</string>
|
||||
<!-- AlertDialog message for warning user when enabling usage access [CHAR LIMIT=NONE] -->
|
||||
<string name="allow_usage_access_message">If you allow access, this app can view general information about the apps on your device, such as how often you use them.</string>
|
||||
|
||||
<!-- Sound settings screen, setting check box label -->
|
||||
<string name="emergency_tone_title">Emergency tone</string>
|
||||
@@ -6461,4 +6457,16 @@
|
||||
<!-- Title of app storage screen [CHAR LIMIT=30] -->
|
||||
<string name="apps_storage">Apps storage</string>
|
||||
|
||||
<!-- Title of usage access screen [CHAR LIMIT=30] -->
|
||||
<string name="usage_access">Usage access</string>
|
||||
|
||||
<!-- Label for setting which controls whether app has usage access [CHAR LIMIT=45] -->
|
||||
<string name="permit_usage_access">Permit usage access</string>
|
||||
|
||||
<!-- Link to the apps page for app usage settings [CHAR LIMIT=45] -->
|
||||
<string name="app_usage_preference">App usage preferences</string>
|
||||
|
||||
<!-- Description of the usage access setting [CHAR LIMIT=NONE] -->
|
||||
<string name="usage_access_description">Usage access allows an app to track what other apps you\'re using and how often, as well as your carrier, language settings, and other details.</string>
|
||||
|
||||
</resources>
|
||||
|
@@ -117,7 +117,11 @@
|
||||
|
||||
<Preference android:key="usage_access"
|
||||
android:title="@string/usage_access_title"
|
||||
android:fragment="com.android.settings.UsageAccessSettings"/>
|
||||
android:fragment="com.android.settings.applications.ManageApplications">
|
||||
<extra
|
||||
android:name="classname"
|
||||
android:value="com.android.settings.Settings$UsageAccessSettingsActivity" />
|
||||
</Preference>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
33
res/xml/usage_access_details.xml
Normal file
33
res/xml/usage_access_details.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
|
||||
android:title="@string/usage_access">
|
||||
|
||||
<SwitchPreference
|
||||
android:key="usage_switch"
|
||||
android:title="@string/permit_usage_access" />
|
||||
|
||||
<Preference
|
||||
android:key="app_usage_preference"
|
||||
android:title="@string/app_usage_preference" />
|
||||
|
||||
<Preference
|
||||
android:summary="@string/usage_access_description"
|
||||
android:selectable="false" />
|
||||
|
||||
</PreferenceScreen>
|
@@ -28,6 +28,7 @@ public abstract class InstrumentedFragment extends PreferenceFragment {
|
||||
|
||||
public static final int VIEW_CATEGORY_DEFAULT_APPS = VIEW_CATEGORY_UNDECLARED + 1;
|
||||
public static final int VIEW_CATEGORY_STORAGE_APPS = VIEW_CATEGORY_UNDECLARED + 2;
|
||||
public static final int VIEW_CATEGORY_USAGE_ACCESS_DETAIL = VIEW_CATEGORY_UNDECLARED + 3;
|
||||
|
||||
/**
|
||||
* Declare the view of this category.
|
||||
|
@@ -76,6 +76,7 @@ import com.android.settings.accounts.AccountSyncSettings;
|
||||
import com.android.settings.applications.InstalledAppDetails;
|
||||
import com.android.settings.applications.ManageApplications;
|
||||
import com.android.settings.applications.ProcessStatsUi;
|
||||
import com.android.settings.applications.UsageAccessDetails;
|
||||
import com.android.settings.bluetooth.BluetoothSettings;
|
||||
import com.android.settings.dashboard.DashboardCategory;
|
||||
import com.android.settings.dashboard.DashboardSummary;
|
||||
@@ -300,7 +301,7 @@ public class SettingsActivity extends Activity
|
||||
NotificationStation.class.getName(),
|
||||
LocationSettings.class.getName(),
|
||||
SecuritySettings.class.getName(),
|
||||
UsageAccessSettings.class.getName(),
|
||||
UsageAccessDetails.class.getName(),
|
||||
PrivacySettings.class.getName(),
|
||||
DeviceAdminSettings.class.getName(),
|
||||
AccessibilitySettings.class.getName(),
|
||||
|
@@ -1,569 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings;
|
||||
|
||||
import com.android.internal.content.PackageMonitor;
|
||||
import android.Manifest;
|
||||
import android.app.ActivityThread;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Collections;
|
||||
|
||||
public class UsageAccessSettings extends SettingsPreferenceFragment implements
|
||||
Preference.OnPreferenceChangeListener {
|
||||
|
||||
private static final String TAG = "UsageAccessSettings";
|
||||
private static final String BUNDLE_KEY_PROFILEID = "profileId";
|
||||
|
||||
private static final String[] PM_USAGE_STATS_PERMISSION = new String[] {
|
||||
Manifest.permission.PACKAGE_USAGE_STATS
|
||||
};
|
||||
|
||||
private static final int[] APP_OPS_OP_CODES = new int[] {
|
||||
AppOpsManager.OP_GET_USAGE_STATS
|
||||
};
|
||||
|
||||
private static class PackageEntry implements Comparable<PackageEntry> {
|
||||
public PackageEntry(String packageName, UserHandle userHandle) {
|
||||
this.packageName = packageName;
|
||||
this.appOpMode = AppOpsManager.MODE_DEFAULT;
|
||||
this.userHandle = userHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PackageEntry another) {
|
||||
return packageName.compareTo(another.packageName);
|
||||
}
|
||||
|
||||
final String packageName;
|
||||
PackageInfo packageInfo;
|
||||
boolean permissionGranted;
|
||||
int appOpMode;
|
||||
UserHandle userHandle;
|
||||
|
||||
SwitchPreference preference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the list of Apps that are requesting access to the UsageStats API and updates
|
||||
* the PreferenceScreen with the results when complete.
|
||||
*/
|
||||
private class AppsRequestingAccessFetcher extends
|
||||
AsyncTask<Void, Void, SparseArray<ArrayMap<String, PackageEntry>>> {
|
||||
|
||||
private final Context mContext;
|
||||
private final PackageManager mPackageManager;
|
||||
private final IPackageManager mIPackageManager;
|
||||
private final UserManager mUserManager;
|
||||
private final List<UserHandle> mProfiles;
|
||||
|
||||
public AppsRequestingAccessFetcher(Context context) {
|
||||
mContext = context;
|
||||
mPackageManager = context.getPackageManager();
|
||||
mIPackageManager = ActivityThread.getPackageManager();
|
||||
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
|
||||
mProfiles = mUserManager.getUserProfiles();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SparseArray<ArrayMap<String, PackageEntry>> doInBackground(Void... params) {
|
||||
final String[] packages;
|
||||
SparseArray<ArrayMap<String, PackageEntry>> entries;
|
||||
try {
|
||||
packages = mIPackageManager.getAppOpPermissionPackages(
|
||||
Manifest.permission.PACKAGE_USAGE_STATS);
|
||||
|
||||
if (packages == null) {
|
||||
// No packages are requesting permission to use the UsageStats API.
|
||||
return null;
|
||||
}
|
||||
|
||||
entries = new SparseArray<>();
|
||||
for (final UserHandle profile : mProfiles) {
|
||||
final ArrayMap<String, PackageEntry> entriesForProfile = new ArrayMap<>();
|
||||
final int profileId = profile.getIdentifier();
|
||||
entries.put(profileId, entriesForProfile);
|
||||
for (final String packageName : packages) {
|
||||
final boolean isAvailable = mIPackageManager.isPackageAvailable(packageName,
|
||||
profileId);
|
||||
if (!shouldIgnorePackage(packageName) && isAvailable) {
|
||||
final PackageEntry newEntry = new PackageEntry(packageName, profile);
|
||||
entriesForProfile.put(packageName, newEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "PackageManager is dead. Can't get list of packages requesting "
|
||||
+ Manifest.permission.PACKAGE_USAGE_STATS);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load the packages that have been granted the PACKAGE_USAGE_STATS permission.
|
||||
try {
|
||||
for (final UserHandle profile : mProfiles) {
|
||||
final int profileId = profile.getIdentifier();
|
||||
final ArrayMap<String, PackageEntry> entriesForProfile = entries.get(profileId);
|
||||
if (entriesForProfile == null) {
|
||||
continue;
|
||||
}
|
||||
final List<PackageInfo> packageInfos = mIPackageManager
|
||||
.getPackagesHoldingPermissions(PM_USAGE_STATS_PERMISSION, 0, profileId)
|
||||
.getList();
|
||||
final int packageInfoCount = packageInfos != null ? packageInfos.size() : 0;
|
||||
for (int i = 0; i < packageInfoCount; i++) {
|
||||
final PackageInfo packageInfo = packageInfos.get(i);
|
||||
final PackageEntry pe = entriesForProfile.get(packageInfo.packageName);
|
||||
if (pe != null) {
|
||||
pe.packageInfo = packageInfo;
|
||||
pe.permissionGranted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "PackageManager is dead. Can't get list of packages granted "
|
||||
+ Manifest.permission.PACKAGE_USAGE_STATS);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load the remaining packages that have requested but don't have the
|
||||
// PACKAGE_USAGE_STATS permission.
|
||||
for (final UserHandle profile : mProfiles) {
|
||||
final int profileId = profile.getIdentifier();
|
||||
final ArrayMap<String, PackageEntry> entriesForProfile = entries.get(profileId);
|
||||
if (entriesForProfile == null) {
|
||||
continue;
|
||||
}
|
||||
int packageCount = entriesForProfile.size();
|
||||
for (int i = packageCount - 1; i >= 0; --i) {
|
||||
final PackageEntry pe = entriesForProfile.valueAt(i);
|
||||
if (pe.packageInfo == null) {
|
||||
try {
|
||||
pe.packageInfo = mIPackageManager.getPackageInfo(pe.packageName, 0,
|
||||
profileId);
|
||||
} catch (RemoteException e) {
|
||||
// This package doesn't exist. This may occur when an app is
|
||||
// uninstalled for one user, but it is not removed from the system.
|
||||
entriesForProfile.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find out which packages have been granted permission from AppOps.
|
||||
final List<AppOpsManager.PackageOps> packageOps = mAppOpsManager.getPackagesForOps(
|
||||
APP_OPS_OP_CODES);
|
||||
final int packageOpsCount = packageOps != null ? packageOps.size() : 0;
|
||||
for (int i = 0; i < packageOpsCount; i++) {
|
||||
final AppOpsManager.PackageOps packageOp = packageOps.get(i);
|
||||
final int userId = UserHandle.getUserId(packageOp.getUid());
|
||||
if (!isThisUserAProfileOfCurrentUser(userId)) {
|
||||
// This AppOp does not belong to any of this user's profiles.
|
||||
continue;
|
||||
}
|
||||
|
||||
final ArrayMap<String, PackageEntry> entriesForProfile = entries.get(userId);
|
||||
if (entriesForProfile == null) {
|
||||
continue;
|
||||
}
|
||||
final PackageEntry pe = entriesForProfile.get(packageOp.getPackageName());
|
||||
if (pe == null) {
|
||||
Log.w(TAG, "AppOp permission exists for package " + packageOp.getPackageName()
|
||||
+ " of user " + userId +
|
||||
" but package doesn't exist or did not request UsageStats access");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (packageOp.getOps().size() < 1) {
|
||||
Log.w(TAG, "No AppOps permission exists for package "
|
||||
+ packageOp.getPackageName());
|
||||
continue;
|
||||
}
|
||||
|
||||
pe.appOpMode = packageOp.getOps().get(0).getMode();
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(SparseArray<ArrayMap<String, PackageEntry>> newEntries) {
|
||||
mLastFetcherTask = null;
|
||||
|
||||
if (getActivity() == null) {
|
||||
// We must have finished the Activity while we were processing in the background.
|
||||
return;
|
||||
}
|
||||
|
||||
if (newEntries == null) {
|
||||
mPackageEntryMap.clear();
|
||||
mPreferenceScreen.removeAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the deleted entries and remove them from the PreferenceScreen.
|
||||
final int oldProfileCount = mPackageEntryMap.size();
|
||||
for (int profileIndex = 0; profileIndex < oldProfileCount; ++profileIndex) {
|
||||
final int profileId = mPackageEntryMap.keyAt(profileIndex);
|
||||
final ArrayMap<String, PackageEntry> oldEntriesForProfile = mPackageEntryMap
|
||||
.valueAt(profileIndex);
|
||||
final int oldPackageCount = oldEntriesForProfile.size();
|
||||
|
||||
final ArrayMap<String, PackageEntry> newEntriesForProfile = newEntries.get(
|
||||
profileId);
|
||||
|
||||
for (int i = 0; i < oldPackageCount; i++) {
|
||||
final PackageEntry oldPackageEntry = oldEntriesForProfile.valueAt(i);
|
||||
|
||||
PackageEntry newPackageEntry = null;
|
||||
if (newEntriesForProfile != null) {
|
||||
newPackageEntry = newEntriesForProfile.get(oldPackageEntry.packageName);
|
||||
}
|
||||
if (newPackageEntry == null) {
|
||||
// This package has been removed.
|
||||
mPreferenceScreen.removePreference(oldPackageEntry.preference);
|
||||
} else {
|
||||
// This package already exists in the preference hierarchy, so reuse that
|
||||
// Preference.
|
||||
newPackageEntry.preference = oldPackageEntry.preference;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now add new packages to the PreferenceScreen.
|
||||
final int newProfileCount = newEntries.size();
|
||||
for (int profileIndex = 0; profileIndex < newProfileCount; ++profileIndex) {
|
||||
final int profileId = newEntries.keyAt(profileIndex);
|
||||
final ArrayMap<String, PackageEntry> newEntriesForProfile = newEntries.get(
|
||||
profileId);
|
||||
final int packageCount = newEntriesForProfile.size();
|
||||
for (int i = 0; i < packageCount; i++) {
|
||||
final PackageEntry packageEntry = newEntriesForProfile.valueAt(i);
|
||||
if (packageEntry.preference == null) {
|
||||
packageEntry.preference = new SwitchPreference(mContext);
|
||||
packageEntry.preference.setPersistent(false);
|
||||
packageEntry.preference.setOnPreferenceChangeListener(
|
||||
UsageAccessSettings.this);
|
||||
mPreferenceScreen.addPreference(packageEntry.preference);
|
||||
}
|
||||
updatePreference(packageEntry);
|
||||
}
|
||||
}
|
||||
mPackageEntryMap.clear();
|
||||
mPackageEntryMap = newEntries;
|
||||
|
||||
// Add/remove headers if necessary. If there are package entries only for one user and
|
||||
// that user is not the managed profile then do not show headers.
|
||||
if (mPackageEntryMap.size() == 1 &&
|
||||
mPackageEntryMap.keyAt(0) == UserHandle.myUserId()) {
|
||||
for (int i = 0; i < mCategoryHeaders.length; ++i) {
|
||||
if (mCategoryHeaders[i] != null) {
|
||||
mPreferenceScreen.removePreference(mCategoryHeaders[i]);
|
||||
}
|
||||
mCategoryHeaders[i] = null;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < mCategoryHeaders.length; ++i) {
|
||||
if (mCategoryHeaders[i] == null) {
|
||||
final Preference preference = new Preference(mContext, null,
|
||||
com.android.internal.R.attr.preferenceCategoryStyle, 0);
|
||||
mCategoryHeaders[i] = preference;
|
||||
preference.setTitle(mCategoryHeaderTitleResIds[i]);
|
||||
preference.setEnabled(false);
|
||||
mPreferenceScreen.addPreference(preference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort preferences alphabetically within categories
|
||||
int order = 0;
|
||||
final int profileCount = mProfiles.size();
|
||||
for (int i = 0; i < profileCount; ++i) {
|
||||
Preference header = mCategoryHeaders[i];
|
||||
if (header != null) {
|
||||
header.setOrder(order++);
|
||||
}
|
||||
ArrayMap<String, PackageEntry> entriesForProfile =
|
||||
mPackageEntryMap.get(mProfiles.get(i).getIdentifier());
|
||||
if (entriesForProfile != null) {
|
||||
List<PackageEntry> sortedEntries = Collections.list(
|
||||
Collections.enumeration(entriesForProfile.values()));
|
||||
Collections.sort(sortedEntries);
|
||||
for (PackageEntry pe : sortedEntries) {
|
||||
pe.preference.setOrder(order++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePreference(PackageEntry pe) {
|
||||
final int profileId = pe.userHandle.getIdentifier();
|
||||
// Set something as default
|
||||
pe.preference.setEnabled(false);
|
||||
pe.preference.setTitle(pe.packageName);
|
||||
pe.preference.setIcon(mUserManager.getBadgedIconForUser(mPackageManager
|
||||
.getDefaultActivityIcon(), pe.userHandle));
|
||||
try {
|
||||
// Try setting real title and icon
|
||||
final ApplicationInfo info = mIPackageManager.getApplicationInfo(pe.packageName,
|
||||
0 /* no flags */, profileId);
|
||||
if (info != null) {
|
||||
pe.preference.setEnabled(true);
|
||||
pe.preference.setTitle(info.loadLabel(mPackageManager).toString());
|
||||
pe.preference.setIcon(mUserManager.getBadgedIconForUser(info.loadIcon(
|
||||
mPackageManager), pe.userHandle));
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "PackageManager is dead. Can't get app info for package " +
|
||||
pe.packageName + " of user " + profileId);
|
||||
// Keep going to update other parts of the preference
|
||||
}
|
||||
|
||||
pe.preference.setKey(pe.packageName);
|
||||
Bundle extra = pe.preference.getExtras();
|
||||
extra.putInt(BUNDLE_KEY_PROFILEID, profileId);
|
||||
|
||||
boolean check = false;
|
||||
if (pe.appOpMode == AppOpsManager.MODE_ALLOWED) {
|
||||
check = true;
|
||||
} else if (pe.appOpMode == AppOpsManager.MODE_DEFAULT) {
|
||||
// If the default AppOps mode is set, then fall back to
|
||||
// whether the app has been granted permission by PackageManager.
|
||||
check = pe.permissionGranted;
|
||||
}
|
||||
|
||||
if (check != pe.preference.isChecked()) {
|
||||
pe.preference.setChecked(check);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isThisUserAProfileOfCurrentUser(final int userId) {
|
||||
final int profilesMax = mProfiles.size();
|
||||
for (int i = 0; i < profilesMax; ++i) {
|
||||
if (mProfiles.get(i).getIdentifier() == userId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static boolean shouldIgnorePackage(String packageName) {
|
||||
return packageName.equals("android") || packageName.equals("com.android.settings");
|
||||
}
|
||||
|
||||
private AppsRequestingAccessFetcher mLastFetcherTask;
|
||||
SparseArray<ArrayMap<String, PackageEntry>> mPackageEntryMap = new SparseArray<>();
|
||||
AppOpsManager mAppOpsManager;
|
||||
PreferenceScreen mPreferenceScreen;
|
||||
private Preference[] mCategoryHeaders = new Preference[2];
|
||||
private static int[] mCategoryHeaderTitleResIds = new int[] {
|
||||
R.string.category_personal,
|
||||
R.string.category_work
|
||||
};
|
||||
|
||||
@Override
|
||||
protected int getMetricsCategory() {
|
||||
return MetricsLogger.USAGE_ACCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
addPreferencesFromResource(R.xml.usage_access_settings);
|
||||
mPreferenceScreen = getPreferenceScreen();
|
||||
mPreferenceScreen.setOrderingAsAdded(false);
|
||||
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
updateInterestedApps();
|
||||
mPackageMonitor.register(getActivity(), Looper.getMainLooper(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
mPackageMonitor.unregister();
|
||||
if (mLastFetcherTask != null) {
|
||||
mLastFetcherTask.cancel(true);
|
||||
mLastFetcherTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateInterestedApps() {
|
||||
if (mLastFetcherTask != null) {
|
||||
// Canceling can only fail for some obscure reason since mLastFetcherTask would be
|
||||
// null if the task has already completed. So we ignore the result of cancel and
|
||||
// spawn a new task to get fresh data. AsyncTask executes tasks serially anyways,
|
||||
// so we are safe from running two tasks at the same time.
|
||||
mLastFetcherTask.cancel(true);
|
||||
}
|
||||
|
||||
mLastFetcherTask = new AppsRequestingAccessFetcher(getActivity());
|
||||
mLastFetcherTask.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
final String packageName = preference.getKey();
|
||||
final int profileId = preference.getExtras().getInt(BUNDLE_KEY_PROFILEID);
|
||||
final PackageEntry pe = getPackageEntry(packageName, profileId);
|
||||
if (pe == null) {
|
||||
Log.w(TAG, "Preference change event handling failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(newValue instanceof Boolean)) {
|
||||
Log.w(TAG, "Preference change event for package " + packageName + " of user " +
|
||||
profileId + " had non boolean value of type " + newValue.getClass().getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
final int newMode = (Boolean) newValue ?
|
||||
AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
|
||||
|
||||
// Check if we need to do any work.
|
||||
if (pe.appOpMode != newMode) {
|
||||
if (newMode != AppOpsManager.MODE_ALLOWED) {
|
||||
// Turning off the setting has no warning.
|
||||
setNewMode(pe, newMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Turning on the setting has a Warning.
|
||||
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
|
||||
Fragment prev = getChildFragmentManager().findFragmentByTag("warning");
|
||||
if (prev != null) {
|
||||
ft.remove(prev);
|
||||
}
|
||||
WarningDialogFragment.newInstance(pe).show(ft, "warning");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void setNewMode(PackageEntry pe, int newMode) {
|
||||
mAppOpsManager.setMode(AppOpsManager.OP_GET_USAGE_STATS,
|
||||
pe.packageInfo.applicationInfo.uid, pe.packageName, newMode);
|
||||
pe.appOpMode = newMode;
|
||||
}
|
||||
|
||||
void allowAccess(String packageName, int profileId) {
|
||||
final PackageEntry entry = getPackageEntry(packageName, profileId);
|
||||
if (entry == null) {
|
||||
Log.w(TAG, "Unable to give access");
|
||||
return;
|
||||
}
|
||||
|
||||
setNewMode(entry, AppOpsManager.MODE_ALLOWED);
|
||||
entry.preference.setChecked(true);
|
||||
}
|
||||
|
||||
private PackageEntry getPackageEntry(String packageName, int profileId) {
|
||||
ArrayMap<String, PackageEntry> entriesForProfile = mPackageEntryMap.get(profileId);
|
||||
if (entriesForProfile == null) {
|
||||
Log.w(TAG, "getPackageEntry fails for package " + packageName + " of user " +
|
||||
profileId + ": user does not seem to be valid.");
|
||||
return null;
|
||||
}
|
||||
final PackageEntry entry = entriesForProfile.get(packageName);
|
||||
if (entry == null) {
|
||||
Log.w(TAG, "getPackageEntry fails for package " + packageName + " of user " +
|
||||
profileId + ": package does not exist.");
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
|
||||
@Override
|
||||
public void onPackageAdded(String packageName, int uid) {
|
||||
updateInterestedApps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageRemoved(String packageName, int uid) {
|
||||
updateInterestedApps();
|
||||
}
|
||||
};
|
||||
|
||||
public static class WarningDialogFragment extends DialogFragment
|
||||
implements DialogInterface.OnClickListener {
|
||||
private static final String ARG_PACKAGE_NAME = "package";
|
||||
private static final String ARG_PROFILE_ID = "profileId";
|
||||
|
||||
public static WarningDialogFragment newInstance(PackageEntry pe) {
|
||||
WarningDialogFragment dialog = new WarningDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_PACKAGE_NAME, pe.packageName);
|
||||
args.putInt(ARG_PROFILE_ID, pe.userHandle.getIdentifier());
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.allow_usage_access_title)
|
||||
.setMessage(R.string.allow_usage_access_message)
|
||||
.setIconAttribute(android.R.attr.alertDialogIcon)
|
||||
.setNegativeButton(R.string.cancel, this)
|
||||
.setPositiveButton(android.R.string.ok, this)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||
((UsageAccessSettings) getParentFragment()).allowAccess(
|
||||
getArguments().getString(ARG_PACKAGE_NAME),
|
||||
getArguments().getInt(ARG_PROFILE_ID));
|
||||
} else {
|
||||
dialog.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -32,7 +32,6 @@ import android.os.IBinder;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settings.InstrumentedPreferenceFragment;
|
||||
|
158
src/com/android/settings/applications/AppStateBaseBridge.java
Normal file
158
src/com/android/settings/applications/AppStateBaseBridge.java
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.settings.applications;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
|
||||
import com.android.settings.applications.ApplicationsState.AppEntry;
|
||||
import com.android.settings.applications.ApplicationsState.Session;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Common base class for bridging information to ApplicationsState.
|
||||
*/
|
||||
public abstract class AppStateBaseBridge implements ApplicationsState.Callbacks {
|
||||
|
||||
protected final ApplicationsState mAppState;
|
||||
protected final Session mAppSession;
|
||||
protected final Callback mCallback;
|
||||
protected final BackgroundHandler mHandler;
|
||||
protected final MainHandler mMainHandler;
|
||||
|
||||
public AppStateBaseBridge(ApplicationsState appState, Callback callback) {
|
||||
mAppState = appState;
|
||||
mAppSession = mAppState != null ? mAppState.newSession(this) : null;
|
||||
mCallback = callback;
|
||||
// Running on the same background thread as the ApplicationsState lets
|
||||
// us run in the background and make sure they aren't doing updates at
|
||||
// the same time as us as well.
|
||||
mHandler = new BackgroundHandler(mAppState.getBackgroundLooper());
|
||||
mMainHandler = new MainHandler();
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
mHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL);
|
||||
mAppSession.resume();
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
mAppSession.pause();
|
||||
}
|
||||
|
||||
public void release() {
|
||||
mAppSession.release();
|
||||
}
|
||||
|
||||
public void forceUpdate(String pkg, int uid) {
|
||||
mHandler.obtainMessage(BackgroundHandler.MSG_FORCE_LOAD_PKG, uid, 0, pkg).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageListChanged() {
|
||||
mHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadEntriesCompleted() {
|
||||
mHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRunningStateChanged(boolean running) {
|
||||
// No op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRebuildComplete(ArrayList<AppEntry> apps) {
|
||||
// No op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageIconChanged() {
|
||||
// No op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageSizeChanged(String packageName) {
|
||||
// No op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllSizesComputed() {
|
||||
// No op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLauncherInfoChanged() {
|
||||
// No op.
|
||||
}
|
||||
|
||||
protected abstract void loadAllExtraInfo();
|
||||
protected abstract void updateExtraInfo(AppEntry app, String pkg, int uid);
|
||||
|
||||
private class MainHandler extends Handler {
|
||||
private static final int MSG_INFO_UPDATED = 1;
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_INFO_UPDATED:
|
||||
mCallback.onExtraInfoUpdated();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BackgroundHandler extends Handler {
|
||||
private static final int MSG_LOAD_ALL = 1;
|
||||
private static final int MSG_FORCE_LOAD_PKG = 2;
|
||||
|
||||
public BackgroundHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_LOAD_ALL:
|
||||
loadAllExtraInfo();
|
||||
mMainHandler.sendEmptyMessage(MainHandler.MSG_INFO_UPDATED);
|
||||
break;
|
||||
case MSG_FORCE_LOAD_PKG:
|
||||
ArrayList<AppEntry> apps = mAppSession.getAllApps();
|
||||
final int N = apps.size();
|
||||
String pkg = (String) msg.obj;
|
||||
int uid = msg.arg1;
|
||||
for (int i = 0; i < N; i++) {
|
||||
AppEntry app = apps.get(i);
|
||||
if (app.info.uid == uid && pkg.equals(app.info.packageName)) {
|
||||
updateExtraInfo(app, pkg, uid);
|
||||
}
|
||||
}
|
||||
mMainHandler.sendEmptyMessage(MainHandler.MSG_INFO_UPDATED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface Callback {
|
||||
void onExtraInfoUpdated();
|
||||
}
|
||||
}
|
@@ -16,156 +16,43 @@
|
||||
package com.android.settings.applications;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
|
||||
import com.android.settings.applications.ApplicationsState.AppEntry;
|
||||
import com.android.settings.applications.ApplicationsState.AppFilter;
|
||||
import com.android.settings.applications.ApplicationsState.Session;
|
||||
import com.android.settings.notification.NotificationBackend;
|
||||
import com.android.settings.notification.NotificationBackend.AppRow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Connects the info provided by ApplicationsState and the NotificationBackend.
|
||||
* Also provides app filters that can use the notification data.
|
||||
*/
|
||||
public class AppStateNotificationBridge implements ApplicationsState.Callbacks {
|
||||
public class AppStateNotificationBridge extends AppStateBaseBridge {
|
||||
|
||||
private final ApplicationsState mAppState;
|
||||
private final NotificationBackend mNotifBackend;
|
||||
private final Session mAppSession;
|
||||
private final Callback mCallback;
|
||||
private final BackgroundHandler mHandler;
|
||||
private final MainHandler mMainHandler;
|
||||
private final PackageManager mPm;
|
||||
|
||||
public AppStateNotificationBridge(PackageManager pm, ApplicationsState appState,
|
||||
NotificationBackend notifBackend, Callback callback) {
|
||||
mAppState = appState;
|
||||
Callback callback, NotificationBackend notifBackend) {
|
||||
super(appState, callback);
|
||||
mPm = pm;
|
||||
mAppSession = mAppState.newSession(this);
|
||||
mNotifBackend = notifBackend;
|
||||
mCallback = callback;
|
||||
// Running on the same background thread as the ApplicationsState lets
|
||||
// us run in the background and make sure they aren't doing updates at
|
||||
// the same time as us as well.
|
||||
mHandler = new BackgroundHandler(mAppState.getBackgroundLooper());
|
||||
mMainHandler = new MainHandler();
|
||||
mHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL);
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
mHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL);
|
||||
mAppSession.resume();
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
mAppSession.pause();
|
||||
}
|
||||
|
||||
public void release() {
|
||||
mAppSession.release();
|
||||
}
|
||||
|
||||
public void forceUpdate(String pkg, int uid) {
|
||||
mHandler.obtainMessage(BackgroundHandler.MSG_FORCE_LOAD_PKG, uid, 0, pkg).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageListChanged() {
|
||||
mHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadEntriesCompleted() {
|
||||
mHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRunningStateChanged(boolean running) {
|
||||
// No op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRebuildComplete(ArrayList<AppEntry> apps) {
|
||||
// No op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageIconChanged() {
|
||||
// No op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageSizeChanged(String packageName) {
|
||||
// No op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllSizesComputed() {
|
||||
// No op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLauncherInfoChanged() {
|
||||
// No op.
|
||||
}
|
||||
|
||||
private class MainHandler extends Handler {
|
||||
private static final int MSG_NOTIF_UPDATED = 1;
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_NOTIF_UPDATED:
|
||||
mCallback.onNotificationInfoUpdated();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BackgroundHandler extends Handler {
|
||||
private static final int MSG_LOAD_ALL = 1;
|
||||
private static final int MSG_FORCE_LOAD_PKG = 2;
|
||||
|
||||
public BackgroundHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
List<AppEntry> apps = mAppSession.getAllApps();
|
||||
protected void loadAllExtraInfo() {
|
||||
ArrayList<AppEntry> apps = mAppSession.getAllApps();
|
||||
final int N = apps.size();
|
||||
switch (msg.what) {
|
||||
case MSG_LOAD_ALL:
|
||||
for (int i = 0; i < N; i++) {
|
||||
AppEntry app = apps.get(i);
|
||||
app.extraInfo = mNotifBackend.loadAppRow(mPm, app.info);
|
||||
}
|
||||
mMainHandler.sendEmptyMessage(MainHandler.MSG_NOTIF_UPDATED);
|
||||
break;
|
||||
case MSG_FORCE_LOAD_PKG:
|
||||
String pkg = (String) msg.obj;
|
||||
int uid = msg.arg1;
|
||||
for (int i = 0; i < N; i++) {
|
||||
AppEntry app = apps.get(i);
|
||||
if (app.info.uid == uid && pkg.equals(app.info.packageName)) {
|
||||
app.extraInfo = mNotifBackend.loadAppRow(mPm, app.info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mMainHandler.sendEmptyMessage(MainHandler.MSG_NOTIF_UPDATED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void onNotificationInfoUpdated();
|
||||
@Override
|
||||
protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
|
||||
app.extraInfo = mNotifBackend.loadAppRow(mPm, app.info);
|
||||
}
|
||||
|
||||
public static final AppFilter FILTER_APP_NOTIFICATION_BLOCKED = new AppFilter() {
|
||||
|
265
src/com/android/settings/applications/AppStateUsageBridge.java
Normal file
265
src/com/android/settings/applications/AppStateUsageBridge.java
Normal file
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.settings.applications;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.AppOpsManager.PackageOps;
|
||||
import android.content.Context;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.settings.applications.ApplicationsState.AppEntry;
|
||||
import com.android.settings.applications.ApplicationsState.AppFilter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Connects app usage info to the ApplicationsState.
|
||||
* Also provides app filters that can use the info.
|
||||
*/
|
||||
public class AppStateUsageBridge extends AppStateBaseBridge {
|
||||
|
||||
private static final String TAG = "AppStateUsageBridge";
|
||||
|
||||
private static final String[] PM_USAGE_STATS_PERMISSION = {
|
||||
Manifest.permission.PACKAGE_USAGE_STATS
|
||||
};
|
||||
|
||||
private static final int[] APP_OPS_OP_CODES = {
|
||||
AppOpsManager.OP_GET_USAGE_STATS
|
||||
};
|
||||
|
||||
private final IPackageManager mIPackageManager;
|
||||
private final UserManager mUserManager;
|
||||
private final List<UserHandle> mProfiles;
|
||||
private final AppOpsManager mAppOpsManager;
|
||||
private final Context mContext;
|
||||
|
||||
public AppStateUsageBridge(Context context, ApplicationsState appState, Callback callback) {
|
||||
super(appState, callback);
|
||||
mContext = context;
|
||||
mIPackageManager = AppGlobals.getPackageManager();
|
||||
mUserManager = UserManager.get(context);
|
||||
mProfiles = mUserManager.getUserProfiles();
|
||||
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
|
||||
}
|
||||
|
||||
private boolean isThisUserAProfileOfCurrentUser(final int userId) {
|
||||
final int profilesMax = mProfiles.size();
|
||||
for (int i = 0; i < profilesMax; i++) {
|
||||
if (mProfiles.get(i).getIdentifier() == userId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
|
||||
app.extraInfo = getUsageInfo(pkg, uid);
|
||||
}
|
||||
|
||||
public UsageState getUsageInfo(String pkg, int uid) {
|
||||
UsageState usageState = new UsageState(pkg, new UserHandle(UserHandle.getUserId(uid)));
|
||||
try {
|
||||
usageState.packageInfo = mIPackageManager.getPackageInfo(pkg,
|
||||
PackageManager.GET_PERMISSIONS, usageState.userHandle.getIdentifier());
|
||||
// Check permission state.
|
||||
String[] requestedPermissions = usageState.packageInfo.requestedPermissions;
|
||||
int[] permissionFlags = usageState.packageInfo.requestedPermissionsFlags;
|
||||
if (requestedPermissions != null) {
|
||||
for (int i = 0; i < requestedPermissions.length; i++) {
|
||||
if (Manifest.permission.PACKAGE_USAGE_STATS.equals(requestedPermissions[i])
|
||||
&& (permissionFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED)
|
||||
!= 0) {
|
||||
usageState.permissionGranted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check app op state.
|
||||
List<PackageOps> ops = mAppOpsManager.getOpsForPackage(uid, pkg, APP_OPS_OP_CODES);
|
||||
if (ops != null && ops.size() > 0 && ops.get(0).getOps().size() > 0) {
|
||||
usageState.appOpMode = ops.get(0).getOps().get(0).getMode();
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "PackageManager is dead. Can't get package info " + pkg, e);
|
||||
}
|
||||
return usageState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadAllExtraInfo() {
|
||||
SparseArray<ArrayMap<String, UsageState>> entries = getEntries();
|
||||
|
||||
// Load state info.
|
||||
loadPermissionsStates(entries);
|
||||
loadAppOpsStates(entries);
|
||||
|
||||
// Map states to application info.
|
||||
List<AppEntry> apps = mAppSession.getAllApps();
|
||||
final int N = apps.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
AppEntry app = apps.get(i);
|
||||
int userId = UserHandle.getUserId(app.info.uid);
|
||||
ArrayMap<String, UsageState> userMap = entries.get(userId);
|
||||
app.extraInfo = userMap != null ? userMap.get(app.info.packageName) : null;
|
||||
}
|
||||
}
|
||||
|
||||
private SparseArray<ArrayMap<String, UsageState>> getEntries() {
|
||||
try {
|
||||
final String[] packages = mIPackageManager.getAppOpPermissionPackages(
|
||||
Manifest.permission.PACKAGE_USAGE_STATS);
|
||||
|
||||
if (packages == null) {
|
||||
// No packages are requesting permission to use the UsageStats API.
|
||||
return null;
|
||||
}
|
||||
|
||||
SparseArray<ArrayMap<String, UsageState>> entries = new SparseArray<>();
|
||||
for (final UserHandle profile : mProfiles) {
|
||||
final ArrayMap<String, UsageState> entriesForProfile = new ArrayMap<>();
|
||||
final int profileId = profile.getIdentifier();
|
||||
entries.put(profileId, entriesForProfile);
|
||||
for (final String packageName : packages) {
|
||||
final boolean isAvailable = mIPackageManager.isPackageAvailable(packageName,
|
||||
profileId);
|
||||
if (!shouldIgnorePackage(packageName) && isAvailable) {
|
||||
final UsageState newEntry = new UsageState(packageName, profile);
|
||||
entriesForProfile.put(packageName, newEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "PackageManager is dead. Can't get list of packages requesting "
|
||||
+ Manifest.permission.PACKAGE_USAGE_STATS, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadPermissionsStates(SparseArray<ArrayMap<String, UsageState>> entries) {
|
||||
// Load the packages that have been granted the PACKAGE_USAGE_STATS permission.
|
||||
try {
|
||||
for (final UserHandle profile : mProfiles) {
|
||||
final int profileId = profile.getIdentifier();
|
||||
final ArrayMap<String, UsageState> entriesForProfile = entries.get(profileId);
|
||||
if (entriesForProfile == null) {
|
||||
continue;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
final List<PackageInfo> packageInfos = mIPackageManager
|
||||
.getPackagesHoldingPermissions(PM_USAGE_STATS_PERMISSION, 0, profileId)
|
||||
.getList();
|
||||
final int packageInfoCount = packageInfos != null ? packageInfos.size() : 0;
|
||||
for (int i = 0; i < packageInfoCount; i++) {
|
||||
final PackageInfo packageInfo = packageInfos.get(i);
|
||||
final UsageState pe = entriesForProfile.get(packageInfo.packageName);
|
||||
if (pe != null) {
|
||||
pe.packageInfo = packageInfo;
|
||||
pe.permissionGranted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "PackageManager is dead. Can't get list of packages granted "
|
||||
+ Manifest.permission.PACKAGE_USAGE_STATS, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAppOpsStates(SparseArray<ArrayMap<String, UsageState>> entries) {
|
||||
// Find out which packages have been granted permission from AppOps.
|
||||
final List<AppOpsManager.PackageOps> packageOps = mAppOpsManager.getPackagesForOps(
|
||||
APP_OPS_OP_CODES);
|
||||
final int packageOpsCount = packageOps != null ? packageOps.size() : 0;
|
||||
for (int i = 0; i < packageOpsCount; i++) {
|
||||
final AppOpsManager.PackageOps packageOp = packageOps.get(i);
|
||||
final int userId = UserHandle.getUserId(packageOp.getUid());
|
||||
if (!isThisUserAProfileOfCurrentUser(userId)) {
|
||||
// This AppOp does not belong to any of this user's profiles.
|
||||
continue;
|
||||
}
|
||||
|
||||
final ArrayMap<String, UsageState> entriesForProfile = entries.get(userId);
|
||||
if (entriesForProfile == null) {
|
||||
continue;
|
||||
}
|
||||
final UsageState pe = entriesForProfile.get(packageOp.getPackageName());
|
||||
if (pe == null) {
|
||||
Log.w(TAG, "AppOp permission exists for package " + packageOp.getPackageName()
|
||||
+ " of user " + userId +
|
||||
" but package doesn't exist or did not request UsageStats access");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (packageOp.getOps().size() < 1) {
|
||||
Log.w(TAG, "No AppOps permission exists for package "
|
||||
+ packageOp.getPackageName());
|
||||
continue;
|
||||
}
|
||||
|
||||
pe.appOpMode = packageOp.getOps().get(0).getMode();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldIgnorePackage(String packageName) {
|
||||
return packageName.equals("android") || packageName.equals(mContext.getPackageName());
|
||||
}
|
||||
|
||||
public static class UsageState {
|
||||
public final String packageName;
|
||||
public final UserHandle userHandle;
|
||||
public PackageInfo packageInfo;
|
||||
public boolean permissionGranted;
|
||||
public int appOpMode;
|
||||
|
||||
public UsageState(String packageName, UserHandle userHandle) {
|
||||
this.packageName = packageName;
|
||||
this.appOpMode = AppOpsManager.MODE_DEFAULT;
|
||||
this.userHandle = userHandle;
|
||||
}
|
||||
|
||||
public boolean hasAccess() {
|
||||
if (appOpMode == AppOpsManager.MODE_DEFAULT) {
|
||||
return permissionGranted;
|
||||
}
|
||||
return appOpMode == AppOpsManager.MODE_ALLOWED;
|
||||
}
|
||||
}
|
||||
|
||||
public static final AppFilter FILTER_APP_USAGE = new AppFilter() {
|
||||
@Override
|
||||
public void init() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean filterApp(AppEntry info) {
|
||||
return info.extraInfo != null;
|
||||
}
|
||||
};
|
||||
}
|
@@ -58,8 +58,10 @@ import com.android.settings.Settings.AllApplicationsActivity;
|
||||
import com.android.settings.Settings.DomainsURLsAppListActivity;
|
||||
import com.android.settings.Settings.NotificationAppListActivity;
|
||||
import com.android.settings.Settings.StorageUseActivity;
|
||||
import com.android.settings.Settings.UsageAccessSettingsActivity;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settings.applications.AppStateUsageBridge.UsageState;
|
||||
import com.android.settings.applications.ApplicationsState.AppEntry;
|
||||
import com.android.settings.applications.ApplicationsState.AppFilter;
|
||||
import com.android.settings.applications.ApplicationsState.CompoundFilter;
|
||||
@@ -117,6 +119,7 @@ public class ManageApplications extends InstrumentedFragment
|
||||
public static final int FILTER_APPS_PERSONAL = 9;
|
||||
public static final int FILTER_APPS_WORK = 10;
|
||||
public static final int FILTER_APPS_WITH_DOMAIN_URLS = 11;
|
||||
public static final int FILTER_APPS_USAGE_ACCESS = 12;
|
||||
|
||||
// This is the string labels for the filter modes above, the order must be kept in sync.
|
||||
public static final int[] FILTER_LABELS = new int[] {
|
||||
@@ -132,6 +135,7 @@ public class ManageApplications extends InstrumentedFragment
|
||||
R.string.filter_personal_apps, // Personal
|
||||
R.string.filter_work_apps, // Work
|
||||
R.string.filter_with_domain_urls_apps, // Domain URLs
|
||||
R.string.filter_all_apps, // Usage access screen, never displayed
|
||||
};
|
||||
// This is the actual mapping to filters from FILTER_ constants above, the order must
|
||||
// be kept in sync.
|
||||
@@ -152,6 +156,7 @@ public class ManageApplications extends InstrumentedFragment
|
||||
ApplicationsState.FILTER_PERSONAL, // Personal
|
||||
ApplicationsState.FILTER_WORK, // Work
|
||||
ApplicationsState.FILTER_WITH_DOMAIN_URLS, // Apps with Domain URLs
|
||||
AppStateUsageBridge.FILTER_APP_USAGE, // Apps with Domain URLs
|
||||
};
|
||||
|
||||
// sort order
|
||||
@@ -190,6 +195,7 @@ public class ManageApplications extends InstrumentedFragment
|
||||
public static final int LIST_TYPE_NOTIFICATION = 1;
|
||||
public static final int LIST_TYPE_DOMAINS_URLS = 2;
|
||||
public static final int LIST_TYPE_STORAGE = 3;
|
||||
public static final int LIST_TYPE_USAGE_ACCESS = 4;
|
||||
|
||||
private View mRootView;
|
||||
|
||||
@@ -230,6 +236,9 @@ public class ManageApplications extends InstrumentedFragment
|
||||
mListType = LIST_TYPE_MAIN;
|
||||
mSortOrder = R.id.sort_order_size;
|
||||
}
|
||||
} else if (className.equals(UsageAccessSettingsActivity.class.getName())) {
|
||||
mListType = LIST_TYPE_USAGE_ACCESS;
|
||||
getActivity().getActionBar().setTitle(R.string.usage_access_title);
|
||||
} else {
|
||||
mListType = LIST_TYPE_MAIN;
|
||||
}
|
||||
@@ -300,7 +309,7 @@ public class ManageApplications extends InstrumentedFragment
|
||||
contentParent.addView(mSpinnerHeader, 0);
|
||||
|
||||
mFilterAdapter.enableFilter(getDefaultFilter());
|
||||
if (mListType != LIST_TYPE_STORAGE) {
|
||||
if (mListType == LIST_TYPE_MAIN || mListType == LIST_TYPE_NOTIFICATION) {
|
||||
if (UserManager.get(getActivity()).getUserProfiles().size() > 1) {
|
||||
mFilterAdapter.enableFilter(FILTER_APPS_PERSONAL);
|
||||
mFilterAdapter.enableFilter(FILTER_APPS_WORK);
|
||||
@@ -331,6 +340,8 @@ public class ManageApplications extends InstrumentedFragment
|
||||
return mShowSystem ? FILTER_APPS_ALL : FILTER_APPS_DOWNLOADED_AND_LAUNCHER;
|
||||
case LIST_TYPE_DOMAINS_URLS:
|
||||
return FILTER_APPS_WITH_DOMAIN_URLS;
|
||||
case LIST_TYPE_USAGE_ACCESS:
|
||||
return FILTER_APPS_USAGE_ACCESS;
|
||||
default:
|
||||
return FILTER_APPS_ALL;
|
||||
}
|
||||
@@ -347,6 +358,8 @@ public class ManageApplications extends InstrumentedFragment
|
||||
return MetricsLogger.MANAGE_DOMAIN_URLS;
|
||||
case LIST_TYPE_STORAGE:
|
||||
return InstrumentedFragment.VIEW_CATEGORY_STORAGE_APPS;
|
||||
case LIST_TYPE_USAGE_ACCESS:
|
||||
return MetricsLogger.USAGE_ACCESS;
|
||||
default:
|
||||
return MetricsLogger.VIEW_UNKNOWN;
|
||||
}
|
||||
@@ -398,7 +411,7 @@ public class ManageApplications extends InstrumentedFragment
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) {
|
||||
if (mListType == LIST_TYPE_NOTIFICATION) {
|
||||
mApplications.mNotifBridge.forceUpdate(mCurrentPkgName, mCurrentUid);
|
||||
mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid);
|
||||
} else {
|
||||
mApplicationsState.requestSize(mCurrentPkgName, UserHandle.getUserId(mCurrentUid));
|
||||
}
|
||||
@@ -408,30 +421,39 @@ public class ManageApplications extends InstrumentedFragment
|
||||
// utility method used to start sub activity
|
||||
private void startApplicationDetailsActivity() {
|
||||
Activity activity = getActivity();
|
||||
if (mListType == LIST_TYPE_NOTIFICATION) {
|
||||
switch (mListType) {
|
||||
case LIST_TYPE_NOTIFICATION:
|
||||
activity.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
.putExtra(Settings.EXTRA_APP_PACKAGE, mCurrentPkgName)
|
||||
.putExtra(Settings.EXTRA_APP_UID, mCurrentUid));
|
||||
} else if (mListType == LIST_TYPE_DOMAINS_URLS) {
|
||||
final String title = getString(R.string.auto_launch_label);
|
||||
startAppInfoFragment(AppLaunchSettings.class, title);
|
||||
} else {
|
||||
break;
|
||||
case LIST_TYPE_DOMAINS_URLS:
|
||||
startAppInfoFragment(AppLaunchSettings.class, R.string.auto_launch_label);
|
||||
break;
|
||||
case LIST_TYPE_USAGE_ACCESS:
|
||||
startAppInfoFragment(UsageAccessDetails.class, R.string.usage_access);
|
||||
break;
|
||||
case LIST_TYPE_STORAGE:
|
||||
startAppInfoFragment(AppStorageSettings.class, R.string.storage_settings);
|
||||
break;
|
||||
// TODO: Figure out if there is a way where we can spin up the profile's settings
|
||||
// process ahead of time, to avoid a long load of data when user clicks on a managed app.
|
||||
// Maybe when they load the list of apps that contains managed profile apps.
|
||||
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
intent.setData(Uri.fromParts("package", mCurrentPkgName, null));
|
||||
activity.startActivityAsUser(intent, new UserHandle(UserHandle.getUserId(mCurrentUid)));
|
||||
default:
|
||||
startAppInfoFragment(InstalledAppDetails.class, R.string.application_info_label);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void startAppInfoFragment(Class<? extends AppInfoBase> fragment, CharSequence title) {
|
||||
private void startAppInfoFragment(Class<? extends AppInfoBase> fragment, int titleRes) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString("package", mCurrentPkgName);
|
||||
args.putString(AppInfoBase.ARG_PACKAGE_NAME, mCurrentPkgName);
|
||||
|
||||
SettingsActivity sa = (SettingsActivity) getActivity();
|
||||
sa.startPreferencePanel(fragment.getName(), args, -1, title, this, 0);
|
||||
Intent intent = Utils.onBuildStartFragmentIntent(getActivity(), fragment.getName(), args,
|
||||
null, titleRes, null, false);
|
||||
getActivity().startActivityForResultAsUser(intent, INSTALLED_APP_DETAILS,
|
||||
new UserHandle(UserHandle.getUserId(mCurrentUid)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -653,14 +675,14 @@ public class ManageApplications extends InstrumentedFragment
|
||||
* The order of applications in the list is mirrored in mAppLocalList
|
||||
*/
|
||||
static class ApplicationsAdapter extends BaseAdapter implements Filterable,
|
||||
ApplicationsState.Callbacks, AppStateNotificationBridge.Callback,
|
||||
ApplicationsState.Callbacks, AppStateBaseBridge.Callback,
|
||||
AbsListView.RecyclerListener {
|
||||
private final ApplicationsState mState;
|
||||
private final ApplicationsState.Session mSession;
|
||||
private final ManageApplications mManageApplications;
|
||||
private final Context mContext;
|
||||
private final ArrayList<View> mActive = new ArrayList<View>();
|
||||
private final AppStateNotificationBridge mNotifBridge;
|
||||
private final AppStateBaseBridge mExtraInfoBridge;
|
||||
private int mFilterMode;
|
||||
private ArrayList<ApplicationsState.AppEntry> mBaseEntries;
|
||||
private ArrayList<ApplicationsState.AppEntry> mEntries;
|
||||
@@ -700,11 +722,12 @@ public class ManageApplications extends InstrumentedFragment
|
||||
mPm = mContext.getPackageManager();
|
||||
mFilterMode = filterMode;
|
||||
if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
|
||||
mNotifBridge = new AppStateNotificationBridge(
|
||||
mContext.getPackageManager(), mState,
|
||||
manageApplications.mNotifBackend, this);
|
||||
mExtraInfoBridge = new AppStateNotificationBridge(mContext.getPackageManager(),
|
||||
mState, this, manageApplications.mNotifBackend);
|
||||
} else if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
|
||||
mExtraInfoBridge = new AppStateUsageBridge(mContext, mState, this);
|
||||
} else {
|
||||
mNotifBridge = null;
|
||||
mExtraInfoBridge = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,8 +747,8 @@ public class ManageApplications extends InstrumentedFragment
|
||||
mResumed = true;
|
||||
mSession.resume();
|
||||
mLastSortMode = sort;
|
||||
if (mNotifBridge != null) {
|
||||
mNotifBridge.resume();
|
||||
if (mExtraInfoBridge != null) {
|
||||
mExtraInfoBridge.resume();
|
||||
}
|
||||
rebuild(true);
|
||||
} else {
|
||||
@@ -737,16 +760,16 @@ public class ManageApplications extends InstrumentedFragment
|
||||
if (mResumed) {
|
||||
mResumed = false;
|
||||
mSession.pause();
|
||||
if (mNotifBridge != null) {
|
||||
mNotifBridge.pause();
|
||||
if (mExtraInfoBridge != null) {
|
||||
mExtraInfoBridge.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
mSession.release();
|
||||
if (mNotifBridge != null) {
|
||||
mNotifBridge.release();
|
||||
if (mExtraInfoBridge != null) {
|
||||
mExtraInfoBridge.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,6 +832,10 @@ public class ManageApplications extends InstrumentedFragment
|
||||
Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
|
||||
mManageApplications.mListContainer, true, true);
|
||||
}
|
||||
if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
|
||||
// No enabled or disabled filters for usage access.
|
||||
return;
|
||||
}
|
||||
|
||||
mManageApplications.setHasDisabled(hasDisabledApps());
|
||||
}
|
||||
@@ -849,11 +876,9 @@ public class ManageApplications extends InstrumentedFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationInfoUpdated() {
|
||||
if (mFilterMode != mManageApplications.getDefaultFilter()) {
|
||||
public void onExtraInfoUpdated() {
|
||||
rebuild(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRunningStateChanged(boolean running) {
|
||||
@@ -897,9 +922,7 @@ public class ManageApplications extends InstrumentedFragment
|
||||
AppViewHolder holder = (AppViewHolder)mActive.get(i).getTag();
|
||||
if (holder.entry.info.packageName.equals(packageName)) {
|
||||
synchronized (holder.entry) {
|
||||
if (mManageApplications.mListType != LIST_TYPE_NOTIFICATION) {
|
||||
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
|
||||
}
|
||||
updateSummary(holder);
|
||||
}
|
||||
if (holder.entry.info.packageName.equals(mManageApplications.mCurrentPkgName)
|
||||
&& mLastSortMode == R.id.sort_order_size) {
|
||||
@@ -967,24 +990,7 @@ public class ManageApplications extends InstrumentedFragment
|
||||
if (entry.icon != null) {
|
||||
holder.appIcon.setImageDrawable(entry.icon);
|
||||
}
|
||||
switch (mManageApplications.mListType) {
|
||||
case LIST_TYPE_NOTIFICATION:
|
||||
if (entry.extraInfo != null) {
|
||||
holder.summary.setText(InstalledAppDetails.getNotificationSummary(
|
||||
(AppRow) entry.extraInfo, mContext));
|
||||
} else {
|
||||
holder.summary.setText("");
|
||||
}
|
||||
break;
|
||||
|
||||
case LIST_TYPE_DOMAINS_URLS:
|
||||
holder.summary.setText(getDomainsSummary(entry.info.packageName));
|
||||
break;
|
||||
|
||||
default:
|
||||
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
|
||||
break;
|
||||
}
|
||||
updateSummary(holder);
|
||||
convertView.setEnabled(isAppEntryViewEnabled(entry));
|
||||
if ((entry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
|
||||
holder.disabled.setVisibility(View.VISIBLE);
|
||||
@@ -1002,6 +1008,36 @@ public class ManageApplications extends InstrumentedFragment
|
||||
return convertView;
|
||||
}
|
||||
|
||||
private void updateSummary(AppViewHolder holder) {
|
||||
switch (mManageApplications.mListType) {
|
||||
case LIST_TYPE_NOTIFICATION:
|
||||
if (holder.entry.extraInfo != null) {
|
||||
holder.summary.setText(InstalledAppDetails.getNotificationSummary(
|
||||
(AppRow) holder.entry.extraInfo, mContext));
|
||||
} else {
|
||||
holder.summary.setText(null);
|
||||
}
|
||||
break;
|
||||
|
||||
case LIST_TYPE_DOMAINS_URLS:
|
||||
holder.summary.setText(getDomainsSummary(holder.entry.info.packageName));
|
||||
break;
|
||||
|
||||
case LIST_TYPE_USAGE_ACCESS:
|
||||
if (holder.entry.extraInfo != null) {
|
||||
holder.summary.setText(((UsageState) holder.entry.extraInfo).hasAccess() ?
|
||||
R.string.switch_on_text : R.string.switch_off_text);
|
||||
} else {
|
||||
holder.summary.setText(null);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return mFilter;
|
||||
|
148
src/com/android/settings/applications/UsageAccessDetails.java
Normal file
148
src/com/android/settings/applications/UsageAccessDetails.java
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.settings.applications;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.Preference.OnPreferenceChangeListener;
|
||||
import android.preference.Preference.OnPreferenceClickListener;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settings.InstrumentedFragment;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.applications.AppStateUsageBridge.UsageState;
|
||||
|
||||
public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenceChangeListener,
|
||||
OnPreferenceClickListener {
|
||||
|
||||
private static final String KEY_USAGE_SWITCH = "usage_switch";
|
||||
private static final String KEY_USAGE_PREFS = "app_usage_preference";
|
||||
|
||||
// Use a bridge to get the usage stats but don't initialize it to connect with all state.
|
||||
// TODO: Break out this functionality into its own class.
|
||||
private AppStateUsageBridge mUsageBridge;
|
||||
private AppOpsManager mAppOpsManager;
|
||||
private SwitchPreference mSwitchPref;
|
||||
private Preference mUsagePrefs;
|
||||
private Intent mSettingsIntent;
|
||||
private UsageState mUsageState;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Context context = getActivity();
|
||||
mUsageBridge = new AppStateUsageBridge(context, mState, null);
|
||||
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
|
||||
|
||||
addPreferencesFromResource(R.xml.usage_access_details);
|
||||
mSwitchPref = (SwitchPreference) findPreference(KEY_USAGE_SWITCH);
|
||||
mUsagePrefs = findPreference(KEY_USAGE_PREFS);
|
||||
|
||||
mSwitchPref.setOnPreferenceChangeListener(this);
|
||||
mUsagePrefs.setOnPreferenceClickListener(this);
|
||||
|
||||
mSettingsIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Settings.INTENT_CATEGORY_USAGE_ACCESS_CONFIG)
|
||||
.setPackage(mPackageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (preference == mUsagePrefs) {
|
||||
if (mSettingsIntent != null) {
|
||||
try {
|
||||
getActivity().startActivityAsUser(mSettingsIntent, new UserHandle(mUserId));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.w(TAG, "Unable to launch app usage access settings " + mSettingsIntent, e);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
if (preference == mSwitchPref) {
|
||||
if (mUsageState != null && (Boolean) newValue != mUsageState.hasAccess()) {
|
||||
setHasAccess(!mUsageState.hasAccess());
|
||||
refreshUi();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setHasAccess(boolean newState) {
|
||||
mAppOpsManager.setMode(AppOpsManager.OP_GET_USAGE_STATS, mPackageInfo.applicationInfo.uid,
|
||||
mPackageName, newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean refreshUi() {
|
||||
mUsageState = mUsageBridge.getUsageInfo(mPackageName,
|
||||
mPackageInfo.applicationInfo.uid);
|
||||
|
||||
boolean hasAccess = mUsageState.hasAccess();
|
||||
mSwitchPref.setChecked(hasAccess);
|
||||
mUsagePrefs.setEnabled(hasAccess);
|
||||
|
||||
ResolveInfo resolveInfo = mPm.resolveActivityAsUser(mSettingsIntent,
|
||||
PackageManager.GET_META_DATA, mUserId);
|
||||
if (resolveInfo != null) {
|
||||
if (findPreference(KEY_USAGE_PREFS) == null) {
|
||||
getPreferenceScreen().addPreference(mUsagePrefs);
|
||||
}
|
||||
Bundle metaData = resolveInfo.activityInfo.metaData;
|
||||
mSettingsIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName,
|
||||
resolveInfo.activityInfo.name));
|
||||
if (metaData != null
|
||||
&& metaData.containsKey(Settings.METADATA_USAGE_ACCESS_REASON)) {
|
||||
mSwitchPref.setSummary(
|
||||
metaData.getString(Settings.METADATA_USAGE_ACCESS_REASON));
|
||||
}
|
||||
} else {
|
||||
if (findPreference(KEY_USAGE_PREFS) != null) {
|
||||
getPreferenceScreen().removePreference(mUsagePrefs);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AlertDialog createDialog(int id, int errorCode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMetricsCategory() {
|
||||
return InstrumentedFragment.VIEW_CATEGORY_USAGE_ACCESS_DETAIL;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user