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
diff --git a/src/com/android/settings/UsageAccessSettings.java b/src/com/android/settings/UsageAccessSettings.java
index 89e184e..fd98b51 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.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.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 @@
             AppOpsManager.OP_GET_USAGE_STATS
     };
 
-    private static class PackageEntry {
-        public PackageEntry(String packageName) {
+    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;
     }
@@ -75,69 +90,102 @@
      * the PreferenceScreen with the results when complete.
      */
     private class AppsRequestingAccessFetcher extends
-            AsyncTask<Void, Void, ArrayMap<String, PackageEntry>> {
+            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 ArrayMap<String, PackageEntry> doInBackground(Void... params) {
+        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;
             }
 
-            if (packages == null) {
-                // No packages are requesting permission to use the UsageStats API.
-                return null;
-            }
-
-            ArrayMap<String, PackageEntry> 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<PackageInfo> 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<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.
-            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<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);
+                        }
                     }
                 }
             }
@@ -148,15 +196,21 @@
             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<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;
                 }
 
@@ -173,7 +227,7 @@
         }
 
         @Override
-        protected void onPostExecute(ArrayMap<String, PackageEntry> newEntries) {
+        protected void onPostExecute(SparseArray<ArrayMap<String, PackageEntry>> newEntries) {
             mLastFetcherTask = null;
 
             if (getActivity() == null) {
@@ -188,41 +242,126 @@
             }
 
             // 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<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 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<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);
                 }
-                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) {
-            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 @@
                 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 @@
     }
 
     private AppsRequestingAccessFetcher mLastFetcherTask;
-    ArrayMap<String, PackageEntry> mPackageEntryMap = new ArrayMap<>();
+    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
     public void onCreate(Bundle icicle) {
@@ -293,16 +447,16 @@
     @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 @@
             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 @@
         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 @@
         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) {
@@ -361,11 +530,13 @@
     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 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();
             }