From 2bfd97ede94041ba180b40f00340bb8ffdd06db6 Mon Sep 17 00:00:00 2001 From: Zoltan Szatmary-Ban Date: Wed, 28 Jan 2015 16:45:06 +0000 Subject: [PATCH] Make Settings > Security > Apps with Usage Access multiprofile aware Show badged apps with usage access together with non-badged ones. Bug: 19157660 Change-Id: Ia294719678d5581c49b1fae3836a0b585ca5c140 --- .../android/settings/UsageAccessSettings.java | 332 +++++++++++++----- 1 file changed, 252 insertions(+), 80 deletions(-) diff --git a/src/com/android/settings/UsageAccessSettings.java b/src/com/android/settings/UsageAccessSettings.java index 89e184e34a0..fd98b51a542 100644 --- a/src/com/android/settings/UsageAccessSettings.java +++ b/src/com/android/settings/UsageAccessSettings.java @@ -17,6 +17,7 @@ package com.android.settings; import com.android.internal.content.PackageMonitor; +import com.android.settings.DataUsageSummary.AppItem; import android.Manifest; import android.app.ActivityThread; @@ -28,6 +29,7 @@ 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; @@ -35,18 +37,24 @@ 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.AttributeSet; import android.util.Log; +import android.util.SparseArray; 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 @@ -56,16 +64,23 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements AppOpsManager.OP_GET_USAGE_STATS }; - private static class PackageEntry { - public PackageEntry(String packageName) { + private static class PackageEntry implements Comparable { + 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; } @@ -75,69 +90,102 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements * the PreferenceScreen with the results when complete. */ private class AppsRequestingAccessFetcher extends - AsyncTask> { + AsyncTask>> { private final Context mContext; private final PackageManager mPackageManager; private final IPackageManager mIPackageManager; + private final UserManager mUserManager; + private final List 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 ArrayMap doInBackground(Void... params) { + protected SparseArray> doInBackground(Void... params) { final String[] packages; + SparseArray> 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 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; } - if (packages == null) { - // No packages are requesting permission to use the UsageStats API. - return null; - } - - ArrayMap entries = new ArrayMap<>(); - for (final String packageName : packages) { - if (!shouldIgnorePackage(packageName)) { - entries.put(packageName, new PackageEntry(packageName)); - } - } - // Load the packages that have been granted the PACKAGE_USAGE_STATS permission. - final List packageInfos = mPackageManager.getPackagesHoldingPermissions( - PM_USAGE_STATS_PERMISSION, 0); - final int packageInfoCount = packageInfos != null ? packageInfos.size() : 0; - for (int i = 0; i < packageInfoCount; i++) { - final PackageInfo packageInfo = packageInfos.get(i); - final PackageEntry pe = entries.get(packageInfo.packageName); - if (pe != null) { - pe.packageInfo = packageInfo; - pe.permissionGranted = true; + try { + for (final UserHandle profile : mProfiles) { + final int profileId = profile.getIdentifier(); + final ArrayMap entriesForProfile = entries.get(profileId); + if (entriesForProfile == null) { + continue; + } + final List 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. - int packageCount = entries.size(); - for (int i = 0; i < packageCount; i++) { - final PackageEntry pe = entries.valueAt(i); - if (pe.packageInfo == null) { - try { - pe.packageInfo = mPackageManager.getPackageInfo(pe.packageName, 0); - } catch (PackageManager.NameNotFoundException 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. - entries.removeAt(i); - i--; - packageCount--; + for (final UserHandle profile : mProfiles) { + final int profileId = profile.getIdentifier(); + final ArrayMap 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); + } } } } @@ -148,15 +196,21 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements final int packageOpsCount = packageOps != null ? packageOps.size() : 0; for (int i = 0; i < packageOpsCount; i++) { final AppOpsManager.PackageOps packageOp = packageOps.get(i); - final PackageEntry pe = entries.get(packageOp.getPackageName()); - if (pe == null) { - Log.w(TAG, "AppOp permission exists for package " + packageOp.getPackageName() - + " but package doesn't exist or did not request UsageStats access"); + final int userId = UserHandle.getUserId(packageOp.getUid()); + if (!isThisUserAProfileOfCurrentUser(userId)) { + // This AppOp does not belong to any of this user's profiles. continue; } - if (packageOp.getUid() != pe.packageInfo.applicationInfo.uid) { - // This AppOp does not belong to this user. + final ArrayMap 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; } @@ -173,7 +227,7 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements } @Override - protected void onPostExecute(ArrayMap newEntries) { + protected void onPostExecute(SparseArray> newEntries) { mLastFetcherTask = null; if (getActivity() == null) { @@ -188,41 +242,126 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements } // Find the deleted entries and remove them from the PreferenceScreen. - final int oldPackageCount = mPackageEntryMap.size(); - for (int i = 0; i < oldPackageCount; i++) { - final PackageEntry oldPackageEntry = mPackageEntryMap.valueAt(i); - final PackageEntry newPackageEntry = newEntries.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; + final int oldProfileCount = mPackageEntryMap.size(); + for (int profileIndex = 0; profileIndex < oldProfileCount; ++profileIndex) { + final int profileId = mPackageEntryMap.keyAt(profileIndex); + final ArrayMap oldEntriesForProfile = mPackageEntryMap + .valueAt(profileIndex); + final int oldPackageCount = oldEntriesForProfile.size(); + + final ArrayMap 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 packageCount = newEntries.size(); - for (int i = 0; i < packageCount; i++) { - final PackageEntry packageEntry = newEntries.valueAt(i); - if (packageEntry.preference == null) { - packageEntry.preference = new SwitchPreference(mContext); - packageEntry.preference.setPersistent(false); - packageEntry.preference.setOnPreferenceChangeListener(UsageAccessSettings.this); - mPreferenceScreen.addPreference(packageEntry.preference); + final int newProfileCount = newEntries.size(); + for (int profileIndex = 0; profileIndex < newProfileCount; ++profileIndex) { + final int profileId = newEntries.keyAt(profileIndex); + final ArrayMap 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); } - 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 entriesForProfile = + mPackageEntryMap.get(mProfiles.get(i).getIdentifier()); + if (entriesForProfile != null) { + List sortedEntries = Collections.list( + Collections.enumeration(entriesForProfile.values())); + Collections.sort(sortedEntries); + for (PackageEntry pe : sortedEntries) { + pe.preference.setOrder(order++); + } + } + } } private void updatePreference(PackageEntry pe) { - pe.preference.setIcon(pe.packageInfo.applicationInfo.loadIcon(mPackageManager)); - pe.preference.setTitle(pe.packageInfo.applicationInfo.loadLabel(mPackageManager)); + 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) { @@ -237,6 +376,16 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements 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) { @@ -244,9 +393,14 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements } private AppsRequestingAccessFetcher mLastFetcherTask; - ArrayMap mPackageEntryMap = new ArrayMap<>(); + SparseArray> 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 public void onCreate(Bundle icicle) { @@ -293,16 +447,16 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements @Override public boolean onPreferenceChange(Preference preference, Object newValue) { final String packageName = preference.getKey(); - final PackageEntry pe = mPackageEntryMap.get(packageName); + final int profileId = preference.getExtras().getInt(BUNDLE_KEY_PROFILEID); + final PackageEntry pe = getPackageEntry(packageName, profileId); if (pe == null) { - Log.w(TAG, "Preference change event for package " + packageName - + " but that package is no longer valid."); + Log.w(TAG, "Preference change event handling failed"); return false; } if (!(newValue instanceof Boolean)) { - Log.w(TAG, "Preference change event for package " + packageName - + " had non boolean value of type " + newValue.getClass().getName()); + Log.w(TAG, "Preference change event for package " + packageName + " of user " + + profileId + " had non boolean value of type " + newValue.getClass().getName()); return false; } @@ -323,7 +477,7 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements if (prev != null) { ft.remove(prev); } - WarningDialogFragment.newInstance(pe.packageName).show(ft, "warning"); + WarningDialogFragment.newInstance(pe).show(ft, "warning"); return false; } return true; @@ -335,10 +489,10 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements pe.appOpMode = newMode; } - void allowAccess(String packageName) { - final PackageEntry entry = mPackageEntryMap.get(packageName); + void allowAccess(String packageName, int profileId) { + final PackageEntry entry = getPackageEntry(packageName, profileId); if (entry == null) { - Log.w(TAG, "Unable to give access to package " + packageName + ": it does not exist."); + Log.w(TAG, "Unable to give access"); return; } @@ -346,6 +500,21 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements entry.preference.setChecked(true); } + private PackageEntry getPackageEntry(String packageName, int profileId) { + ArrayMap 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) { @@ -361,11 +530,13 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements 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(String packageName) { + public static WarningDialogFragment newInstance(PackageEntry pe) { WarningDialogFragment dialog = new WarningDialogFragment(); Bundle args = new Bundle(); - args.putString(ARG_PACKAGE_NAME, packageName); + args.putString(ARG_PACKAGE_NAME, pe.packageName); + args.putInt(ARG_PROFILE_ID, pe.userHandle.getIdentifier()); dialog.setArguments(args); return dialog; } @@ -385,7 +556,8 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_POSITIVE) { ((UsageAccessSettings) getParentFragment()).allowAccess( - getArguments().getString(ARG_PACKAGE_NAME)); + getArguments().getString(ARG_PACKAGE_NAME), + getArguments().getInt(ARG_PROFILE_ID)); } else { dialog.cancel(); }