Merge "Make Settings > Security > Apps with Usage Access multiprofile aware"

This commit is contained in:
Zoltan Szatmary-Ban
2015-02-09 15:03:00 +00:00
committed by Android (Google) Code Review

View File

@@ -17,6 +17,7 @@
package com.android.settings; package com.android.settings;
import com.android.internal.content.PackageMonitor; import com.android.internal.content.PackageMonitor;
import com.android.settings.DataUsageSummary.AppItem;
import android.Manifest; import android.Manifest;
import android.app.ActivityThread; import android.app.ActivityThread;
@@ -28,6 +29,7 @@ import android.app.Fragment;
import android.app.FragmentTransaction; import android.app.FragmentTransaction;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager; import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@@ -35,18 +37,24 @@ import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Looper; import android.os.Looper;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.preference.SwitchPreference; import android.preference.SwitchPreference;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.util.SparseArray;
import java.util.List; import java.util.List;
import java.util.Collections;
public class UsageAccessSettings extends SettingsPreferenceFragment implements public class UsageAccessSettings extends SettingsPreferenceFragment implements
Preference.OnPreferenceChangeListener { Preference.OnPreferenceChangeListener {
private static final String TAG = "UsageAccessSettings"; private static final String TAG = "UsageAccessSettings";
private static final String BUNDLE_KEY_PROFILEID = "profileId";
private static final String[] PM_USAGE_STATS_PERMISSION = new String[] { private static final String[] PM_USAGE_STATS_PERMISSION = new String[] {
Manifest.permission.PACKAGE_USAGE_STATS Manifest.permission.PACKAGE_USAGE_STATS
@@ -56,16 +64,23 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
AppOpsManager.OP_GET_USAGE_STATS AppOpsManager.OP_GET_USAGE_STATS
}; };
private static class PackageEntry { private static class PackageEntry implements Comparable<PackageEntry> {
public PackageEntry(String packageName) { public PackageEntry(String packageName, UserHandle userHandle) {
this.packageName = packageName; this.packageName = packageName;
this.appOpMode = AppOpsManager.MODE_DEFAULT; this.appOpMode = AppOpsManager.MODE_DEFAULT;
this.userHandle = userHandle;
}
@Override
public int compareTo(PackageEntry another) {
return packageName.compareTo(another.packageName);
} }
final String packageName; final String packageName;
PackageInfo packageInfo; PackageInfo packageInfo;
boolean permissionGranted; boolean permissionGranted;
int appOpMode; int appOpMode;
UserHandle userHandle;
SwitchPreference preference; SwitchPreference preference;
} }
@@ -75,69 +90,102 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
* the PreferenceScreen with the results when complete. * the PreferenceScreen with the results when complete.
*/ */
private class AppsRequestingAccessFetcher extends private class AppsRequestingAccessFetcher extends
AsyncTask<Void, Void, ArrayMap<String, PackageEntry>> { AsyncTask<Void, Void, SparseArray<ArrayMap<String, PackageEntry>>> {
private final Context mContext; private final Context mContext;
private final PackageManager mPackageManager; private final PackageManager mPackageManager;
private final IPackageManager mIPackageManager; private final IPackageManager mIPackageManager;
private final UserManager mUserManager;
private final List<UserHandle> mProfiles;
public AppsRequestingAccessFetcher(Context context) { public AppsRequestingAccessFetcher(Context context) {
mContext = context; mContext = context;
mPackageManager = context.getPackageManager(); mPackageManager = context.getPackageManager();
mIPackageManager = ActivityThread.getPackageManager(); mIPackageManager = ActivityThread.getPackageManager();
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mProfiles = mUserManager.getUserProfiles();
} }
@Override @Override
protected ArrayMap<String, PackageEntry> doInBackground(Void... params) { protected SparseArray<ArrayMap<String, PackageEntry>> doInBackground(Void... params) {
final String[] packages; final String[] packages;
SparseArray<ArrayMap<String, PackageEntry>> entries;
try { try {
packages = mIPackageManager.getAppOpPermissionPackages( packages = mIPackageManager.getAppOpPermissionPackages(
Manifest.permission.PACKAGE_USAGE_STATS); Manifest.permission.PACKAGE_USAGE_STATS);
} 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) { if (packages == null) {
// No packages are requesting permission to use the UsageStats API. // No packages are requesting permission to use the UsageStats API.
return null; return null;
} }
ArrayMap<String, PackageEntry> entries = new ArrayMap<>(); 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) { for (final String packageName : packages) {
if (!shouldIgnorePackage(packageName)) { final boolean isAvailable = mIPackageManager.isPackageAvailable(packageName,
entries.put(packageName, new PackageEntry(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. // Load the packages that have been granted the PACKAGE_USAGE_STATS permission.
final List<PackageInfo> packageInfos = mPackageManager.getPackagesHoldingPermissions( try {
PM_USAGE_STATS_PERMISSION, 0); 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; final int packageInfoCount = packageInfos != null ? packageInfos.size() : 0;
for (int i = 0; i < packageInfoCount; i++) { for (int i = 0; i < packageInfoCount; i++) {
final PackageInfo packageInfo = packageInfos.get(i); final PackageInfo packageInfo = packageInfos.get(i);
final PackageEntry pe = entries.get(packageInfo.packageName); final PackageEntry pe = entriesForProfile.get(packageInfo.packageName);
if (pe != null) { if (pe != null) {
pe.packageInfo = packageInfo; pe.packageInfo = packageInfo;
pe.permissionGranted = true; 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 // Load the remaining packages that have requested but don't have the
// PACKAGE_USAGE_STATS permission. // PACKAGE_USAGE_STATS permission.
int packageCount = entries.size(); for (final UserHandle profile : mProfiles) {
for (int i = 0; i < packageCount; i++) { final int profileId = profile.getIdentifier();
final PackageEntry pe = entries.valueAt(i); 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) { if (pe.packageInfo == null) {
try { try {
pe.packageInfo = mPackageManager.getPackageInfo(pe.packageName, 0); pe.packageInfo = mIPackageManager.getPackageInfo(pe.packageName, 0,
} catch (PackageManager.NameNotFoundException e) { profileId);
// This package doesn't exist. This may occur when an app is uninstalled for } catch (RemoteException e) {
// one user, but it is not removed from the system. // This package doesn't exist. This may occur when an app is
entries.removeAt(i); // uninstalled for one user, but it is not removed from the system.
i--; entriesForProfile.removeAt(i);
packageCount--; }
} }
} }
} }
@@ -148,15 +196,21 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
final int packageOpsCount = packageOps != null ? packageOps.size() : 0; final int packageOpsCount = packageOps != null ? packageOps.size() : 0;
for (int i = 0; i < packageOpsCount; i++) { for (int i = 0; i < packageOpsCount; i++) {
final AppOpsManager.PackageOps packageOp = packageOps.get(i); final AppOpsManager.PackageOps packageOp = packageOps.get(i);
final PackageEntry pe = entries.get(packageOp.getPackageName()); final int userId = UserHandle.getUserId(packageOp.getUid());
if (pe == null) { if (!isThisUserAProfileOfCurrentUser(userId)) {
Log.w(TAG, "AppOp permission exists for package " + packageOp.getPackageName() // This AppOp does not belong to any of this user's profiles.
+ " but package doesn't exist or did not request UsageStats access");
continue; continue;
} }
if (packageOp.getUid() != pe.packageInfo.applicationInfo.uid) { final ArrayMap<String, PackageEntry> entriesForProfile = entries.get(userId);
// This AppOp does not belong to this user. 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; continue;
} }
@@ -173,7 +227,7 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
} }
@Override @Override
protected void onPostExecute(ArrayMap<String, PackageEntry> newEntries) { protected void onPostExecute(SparseArray<ArrayMap<String, PackageEntry>> newEntries) {
mLastFetcherTask = null; mLastFetcherTask = null;
if (getActivity() == null) { if (getActivity() == null) {
@@ -188,10 +242,23 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
} }
// Find the deleted entries and remove them from the PreferenceScreen. // Find the deleted entries and remove them from the PreferenceScreen.
final int oldPackageCount = mPackageEntryMap.size(); 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++) { for (int i = 0; i < oldPackageCount; i++) {
final PackageEntry oldPackageEntry = mPackageEntryMap.valueAt(i); final PackageEntry oldPackageEntry = oldEntriesForProfile.valueAt(i);
final PackageEntry newPackageEntry = newEntries.get(oldPackageEntry.packageName);
PackageEntry newPackageEntry = null;
if (newEntriesForProfile != null) {
newPackageEntry = newEntriesForProfile.get(oldPackageEntry.packageName);
}
if (newPackageEntry == null) { if (newPackageEntry == null) {
// This package has been removed. // This package has been removed.
mPreferenceScreen.removePreference(oldPackageEntry.preference); mPreferenceScreen.removePreference(oldPackageEntry.preference);
@@ -201,28 +268,100 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
newPackageEntry.preference = oldPackageEntry.preference; newPackageEntry.preference = oldPackageEntry.preference;
} }
} }
}
// Now add new packages to the PreferenceScreen. // Now add new packages to the PreferenceScreen.
final int packageCount = newEntries.size(); 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++) { for (int i = 0; i < packageCount; i++) {
final PackageEntry packageEntry = newEntries.valueAt(i); final PackageEntry packageEntry = newEntriesForProfile.valueAt(i);
if (packageEntry.preference == null) { if (packageEntry.preference == null) {
packageEntry.preference = new SwitchPreference(mContext); packageEntry.preference = new SwitchPreference(mContext);
packageEntry.preference.setPersistent(false); packageEntry.preference.setPersistent(false);
packageEntry.preference.setOnPreferenceChangeListener(UsageAccessSettings.this); packageEntry.preference.setOnPreferenceChangeListener(
UsageAccessSettings.this);
mPreferenceScreen.addPreference(packageEntry.preference); mPreferenceScreen.addPreference(packageEntry.preference);
} }
updatePreference(packageEntry); updatePreference(packageEntry);
} }
}
mPackageEntryMap.clear(); mPackageEntryMap.clear();
mPackageEntryMap = newEntries; 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) { private void updatePreference(PackageEntry pe) {
pe.preference.setIcon(pe.packageInfo.applicationInfo.loadIcon(mPackageManager)); final int profileId = pe.userHandle.getIdentifier();
pe.preference.setTitle(pe.packageInfo.applicationInfo.loadLabel(mPackageManager)); // 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); pe.preference.setKey(pe.packageName);
Bundle extra = pe.preference.getExtras();
extra.putInt(BUNDLE_KEY_PROFILEID, profileId);
boolean check = false; boolean check = false;
if (pe.appOpMode == AppOpsManager.MODE_ALLOWED) { if (pe.appOpMode == AppOpsManager.MODE_ALLOWED) {
@@ -237,6 +376,16 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
pe.preference.setChecked(check); 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) { static boolean shouldIgnorePackage(String packageName) {
@@ -244,9 +393,14 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
} }
private AppsRequestingAccessFetcher mLastFetcherTask; private AppsRequestingAccessFetcher mLastFetcherTask;
ArrayMap<String, PackageEntry> mPackageEntryMap = new ArrayMap<>(); SparseArray<ArrayMap<String, PackageEntry>> mPackageEntryMap = new SparseArray<>();
AppOpsManager mAppOpsManager; AppOpsManager mAppOpsManager;
PreferenceScreen mPreferenceScreen; PreferenceScreen mPreferenceScreen;
private Preference[] mCategoryHeaders = new Preference[2];
private static int[] mCategoryHeaderTitleResIds = new int[] {
R.string.category_personal,
R.string.category_work
};
@Override @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
@@ -293,16 +447,16 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
final String packageName = preference.getKey(); 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) { if (pe == null) {
Log.w(TAG, "Preference change event for package " + packageName Log.w(TAG, "Preference change event handling failed");
+ " but that package is no longer valid.");
return false; return false;
} }
if (!(newValue instanceof Boolean)) { if (!(newValue instanceof Boolean)) {
Log.w(TAG, "Preference change event for package " + packageName Log.w(TAG, "Preference change event for package " + packageName + " of user " +
+ " had non boolean value of type " + newValue.getClass().getName()); profileId + " had non boolean value of type " + newValue.getClass().getName());
return false; return false;
} }
@@ -323,7 +477,7 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
if (prev != null) { if (prev != null) {
ft.remove(prev); ft.remove(prev);
} }
WarningDialogFragment.newInstance(pe.packageName).show(ft, "warning"); WarningDialogFragment.newInstance(pe).show(ft, "warning");
return false; return false;
} }
return true; return true;
@@ -335,10 +489,10 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
pe.appOpMode = newMode; pe.appOpMode = newMode;
} }
void allowAccess(String packageName) { void allowAccess(String packageName, int profileId) {
final PackageEntry entry = mPackageEntryMap.get(packageName); final PackageEntry entry = getPackageEntry(packageName, profileId);
if (entry == null) { 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; return;
} }
@@ -346,6 +500,21 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
entry.preference.setChecked(true); 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() { private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override @Override
public void onPackageAdded(String packageName, int uid) { public void onPackageAdded(String packageName, int uid) {
@@ -361,11 +530,13 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
public static class WarningDialogFragment extends DialogFragment public static class WarningDialogFragment extends DialogFragment
implements DialogInterface.OnClickListener { implements DialogInterface.OnClickListener {
private static final String ARG_PACKAGE_NAME = "package"; 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(); WarningDialogFragment dialog = new WarningDialogFragment();
Bundle args = new Bundle(); 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); dialog.setArguments(args);
return dialog; return dialog;
} }
@@ -385,7 +556,8 @@ public class UsageAccessSettings extends SettingsPreferenceFragment implements
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) { if (which == DialogInterface.BUTTON_POSITIVE) {
((UsageAccessSettings) getParentFragment()).allowAccess( ((UsageAccessSettings) getParentFragment()).allowAccess(
getArguments().getString(ARG_PACKAGE_NAME)); getArguments().getString(ARG_PACKAGE_NAME),
getArguments().getInt(ARG_PROFILE_ID));
} else { } else {
dialog.cancel(); dialog.cancel();
} }