diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 004127d646..dd19bc04b4 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -88,6 +88,11 @@ + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 08957ab3e8..b7a3387801 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -224,4 +224,7 @@ perform an unknown action + + %d of %d apps allowed + diff --git a/src/com/android/packageinstaller/permission/model/PermissionApps.java b/src/com/android/packageinstaller/permission/model/PermissionApps.java index 7191381dd2..06ef798105 100644 --- a/src/com/android/packageinstaller/permission/model/PermissionApps.java +++ b/src/com/android/packageinstaller/permission/model/PermissionApps.java @@ -29,6 +29,9 @@ import android.os.UserManager; import android.util.ArrayMap; import android.util.Log; +import android.util.SparseArray; + +import com.android.packageinstaller.permission.utils.Utils; import java.util.ArrayList; import java.util.Collection; @@ -43,25 +46,63 @@ public class PermissionApps { private final PackageManager mPm; private final Callback mCallback; + private final PmCache mCache; + private CharSequence mLabel; private Drawable mIcon; private List mPermApps; // Map (pkg|uid) -> AppPermission private ArrayMap mAppLookup; + private boolean mSkipUi; + public PermissionApps(Context context, String groupName, Callback callback) { + this(context, groupName, callback, null); + } + + public PermissionApps(Context context, String groupName, Callback callback, PmCache cache) { + mCache = cache; mContext = context; mPm = mContext.getPackageManager(); mGroupName = groupName; mCallback = callback; loadGroupInfo(); - new PermissionAppsLoader().execute(); } - public void refresh() { + public void loadNowWithoutUi() { + mSkipUi = true; + createMap(loadPermissionApps()); + } + + public void refresh(boolean getUiInfo) { + mSkipUi = !getUiInfo; new PermissionAppsLoader().execute(); } + public int getGrantedCount() { + int count = 0; + for (PermissionApp app : mPermApps) { + if (!Utils.shouldShowPermission(app)) { + continue; + } + if (app.areRuntimePermissionsGranted()) { + count++; + } + } + return count; + } + + public int getTotalCount() { + int count = 0; + for (PermissionApp app : mPermApps) { + if (!Utils.shouldShowPermission(app)) { + continue; + } + count++; + } + return count; + } + public Collection getApps() { return mPermApps; } @@ -78,6 +119,114 @@ public Drawable getIcon() { return mIcon; } + private List loadPermissionApps() { + PackageItemInfo groupInfo = getGroupInfo(mGroupName); + if (groupInfo == null) { + return Collections.emptyList(); + } + + List groupPermInfos = getGroupPermissionInfos(mGroupName); + if (groupPermInfos == null) { + return Collections.emptyList(); + } + + ArrayList permApps = new ArrayList<>(); + + for (UserHandle user : UserManager.get(mContext).getUserProfiles()) { + List apps = mCache != null ? mCache.getPackages(user.getIdentifier()) + : mPm.getInstalledPackages(PackageManager.GET_PERMISSIONS, + user.getIdentifier()); + + final int N = apps.size(); + for (int i = 0; i < N; i++) { + PackageInfo app = apps.get(i); + if (app.requestedPermissions == null) { + continue; + } + + for (int j = 0; j < app.requestedPermissions.length; j++) { + String requestedPerm = app.requestedPermissions[j]; + + boolean requestsPermissionInGroup = false; + + for (PermissionInfo groupPermInfo : groupPermInfos) { + if (groupPermInfo.name.equals(requestedPerm)) { + requestsPermissionInGroup = true; + break; + } + } + + if (!requestsPermissionInGroup) { + continue; + } + + AppPermissionGroup group = AppPermissionGroup.create(mContext, + app, groupInfo, groupPermInfos); + + String label = mSkipUi ? app.packageName + : app.applicationInfo.loadLabel(mPm).toString(); + PermissionApp permApp = new PermissionApp(app.packageName, + group, label, getBadgedIcon(app.applicationInfo)); + + permApps.add(permApp); + } + } + } + + Collections.sort(permApps); + + return permApps; + } + + private void createMap(List result) { + mAppLookup = new ArrayMap<>(); + for (PermissionApp app : result) { + mAppLookup.put(app.getKey(), app); + } + mPermApps = result; + } + + private PackageItemInfo getGroupInfo(String groupName) { + try { + return mContext.getPackageManager().getPermissionGroupInfo(groupName, 0); + } catch (NameNotFoundException e) { + /* ignore */ + } + try { + return mContext.getPackageManager().getPermissionInfo(groupName, 0); + } catch (NameNotFoundException e2) { + /* ignore */ + } + return null; + } + + private List getGroupPermissionInfos(String groupName) { + try { + return mContext.getPackageManager().queryPermissionsByGroup(groupName, 0); + } catch (NameNotFoundException e) { + /* ignore */ + } + try { + PermissionInfo permissionInfo = mContext.getPackageManager() + .getPermissionInfo(groupName, 0); + List permissions = new ArrayList<>(); + permissions.add(permissionInfo); + return permissions; + } catch (NameNotFoundException e2) { + /* ignore */ + } + return null; + } + + private Drawable getBadgedIcon(ApplicationInfo appInfo) { + if (mSkipUi) { + return null; + } + Drawable unbadged = appInfo.loadUnbadgedIcon(mPm); + return mPm.getUserBadgedIcon(unbadged, + new UserHandle(UserHandle.getUserId(appInfo.uid))); + } + private void loadGroupInfo() { PackageItemInfo info; try { @@ -177,120 +326,51 @@ public int compareTo(PermissionApp another) { return result; } - private int getUid() { + public int getUid() { return mAppPermissionGroup.getApp().applicationInfo.uid; } } private class PermissionAppsLoader extends AsyncTask> { + @Override protected List doInBackground(Void... args) { - PackageItemInfo groupInfo = getGroupInfo(mGroupName); - if (groupInfo == null) { - return Collections.emptyList(); - } - - List groupPermInfos = getGroupPermissionInfos(mGroupName); - if (groupPermInfos == null) { - return Collections.emptyList(); - } - - ArrayList permApps = new ArrayList<>(); - - for (UserHandle user : UserManager.get(mContext).getUserProfiles()) { - List apps = mPm.getInstalledPackages( - PackageManager.GET_PERMISSIONS, user.getIdentifier()); - - final int N = apps.size(); - for (int i = 0; i < N; i++) { - PackageInfo app = apps.get(i); - if (app.requestedPermissions == null) { - continue; - } - - for (int j = 0; j < app.requestedPermissions.length; j++) { - String requestedPerm = app.requestedPermissions[j]; - - boolean requestsPermissionInGroup = false; - - for (PermissionInfo groupPermInfo : groupPermInfos) { - if (groupPermInfo.name.equals(requestedPerm)) { - requestsPermissionInGroup = true; - break; - } - } - - if (!requestsPermissionInGroup) { - continue; - } - - AppPermissionGroup group = AppPermissionGroup.create(mContext, - app, groupInfo, groupPermInfos); - - PermissionApp permApp = new PermissionApp(app.packageName, - group, app.applicationInfo.loadLabel(mPm).toString(), - getBadgedIcon(app.applicationInfo)); - - permApps.add(permApp); - } - } - } - - Collections.sort(permApps); - - return permApps; + return loadPermissionApps(); } - private PackageItemInfo getGroupInfo(String groupName) { - try { - return mContext.getPackageManager().getPermissionGroupInfo(groupName, 0); - } catch (NameNotFoundException e) { - /* ignore */ - } - try { - return mContext.getPackageManager().getPermissionInfo(groupName, 0); - } catch (NameNotFoundException e2) { - /* ignore */ - } - return null; - } - - private List getGroupPermissionInfos(String groupName) { - try { - return mContext.getPackageManager().queryPermissionsByGroup(groupName, 0); - } catch (NameNotFoundException e) { - /* ignore */ - } - try { - PermissionInfo permissionInfo = mContext.getPackageManager() - .getPermissionInfo(groupName, 0); - List permissions = new ArrayList<>(); - permissions.add(permissionInfo); - return permissions; - } catch (NameNotFoundException e2) { - /* ignore */ + @Override + protected void onPostExecute(List result) { + createMap(result); + if (mCallback != null) { + mCallback.onPermissionsLoaded(PermissionApps.this); } - return null; } + } - private Drawable getBadgedIcon(ApplicationInfo appInfo) { - Drawable unbadged = appInfo.loadUnbadgedIcon(mPm); - return mPm.getUserBadgedIcon(unbadged, - new UserHandle(UserHandle.getUserId(appInfo.uid))); + /** + * Class used to reduce the number of calls to the package manager. + * This caches app information so it should only be used across parallel PermissionApps + * instances, and should not be retained across UI refresh. + */ + public static class PmCache { + private final SparseArray> mPackageInfoCache = new SparseArray<>(); + private final PackageManager mPm; + + public PmCache(PackageManager pm) { + mPm = pm; } - @Override - protected void onPostExecute(List result) { - mAppLookup = new ArrayMap<>(); - for (PermissionApp app : result) { - mAppLookup.put(app.getKey(), app); + public synchronized List getPackages(int userId) { + List ret = mPackageInfoCache.get(userId); + if (ret == null) { + ret = mPm.getInstalledPackages(PackageManager.GET_PERMISSIONS, userId); + mPackageInfoCache.put(userId, ret); } - mPermApps = result; - mCallback.onPermissionsLoaded(); + return ret; } } public interface Callback { - void onPermissionsLoaded(); + void onPermissionsLoaded(PermissionApps permissionApps); } } diff --git a/src/com/android/packageinstaller/permission/model/PermissionStatusReceiver.java b/src/com/android/packageinstaller/permission/model/PermissionStatusReceiver.java new file mode 100644 index 0000000000..f84a0757e4 --- /dev/null +++ b/src/com/android/packageinstaller/permission/model/PermissionStatusReceiver.java @@ -0,0 +1,93 @@ +/* + * 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.packageinstaller.permission.model; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.util.SparseArray; + +import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp; +import com.android.packageinstaller.permission.utils.Utils; + +public class PermissionStatusReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + int[] result = new int[2]; + boolean succeeded = false; + Intent responseIntent = new Intent(intent.getStringExtra( + Intent.EXTRA_GET_PERMISSIONS_RESPONSE_INTENT)); + responseIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); + if (intent.hasExtra(Intent.EXTRA_PACKAGE_NAME)) { + String pkg = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); + succeeded = getPermissionsCount(context, pkg, result); + } else { + succeeded = getAppsWithPermissionsCount(context, result); + } + responseIntent.putExtra(Intent.EXTRA_GET_PERMISSIONS_COUNT_RESULT, + succeeded ? result : null); + context.sendBroadcast(responseIntent); + } + + public boolean getPermissionsCount(Context context, String pkg, int[] counts) { + try { + PackageInfo packageInfo = + context.getPackageManager().getPackageInfo(pkg, PackageManager.GET_PERMISSIONS); + AppPermissions appPermissions = + new AppPermissions(context, packageInfo, null, null); + int grantedCount = 0; + int totalCount = 0; + for (AppPermissionGroup group : appPermissions.getPermissionGroups()) { + if (Utils.shouldShowPermission(group, false)) { + totalCount++; + if (group.areRuntimePermissionsGranted()) { + grantedCount++; + } + } + } + counts[0] = grantedCount; + counts[1] = totalCount; + return true; + } catch (NameNotFoundException e) { + return false; + } + } + + public boolean getAppsWithPermissionsCount(Context context, int[] counts) { + // Indexed by uid. + SparseArray grantedApps = new SparseArray<>(); + SparseArray allApps = new SparseArray<>(); + for (String group : Utils.MODERN_PERMISSION_GROUPS) { + PermissionApps permissionApps = new PermissionApps(context, + group, null); + permissionApps.loadNowWithoutUi(); + for (PermissionApp app : permissionApps.getApps()) { + int uid = app.getUid(); + if (app.areRuntimePermissionsGranted()) { + grantedApps.put(uid, true); + } + allApps.put(uid, true); + } + } + counts[0] = grantedApps.size(); + counts[1] = allApps.size(); + return true; + } +} diff --git a/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java index f328dd8ae9..50aa2afd93 100644 --- a/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java +++ b/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java @@ -62,8 +62,6 @@ public final class AppPermissionsFragment extends SettingsWithHeader private static final String LOG_TAG = "ManagePermsFragment"; - private static final String OS_PKG = "android"; - private static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton"; private List mToggledGroups; @@ -222,24 +220,8 @@ private void bindPermissionsUi() { extraPerms.setTitle(R.string.additional_permissions); for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) { - // We currently will not show permissions fixed by the system. - // which is what the system does for system components. - if (group.isSystemFixed()) { - continue; - } - - // Yes this is possible. We have leftover permissions that - // are not in the final groups and we want to get rid of, - // therefore we do not have app ops for legacy support. - if (!group.hasRuntimePermission() && !group.hasAppOpPermission()) { - continue; - } - - final boolean isPlatformPermission = group.getDeclaringPackage().equals(OS_PKG); - - // Show legacy permissions only if the user chose that. - if (isPlatformPermission && !mShowLegacyPermissions - && !Utils.isModernPermissionGroup(group.getName())) { + final boolean isPlatformPermission = group.getDeclaringPackage().equals(Utils.OS_PKG); + if (!Utils.shouldShowPermission(group, mShowLegacyPermissions)) { continue; } diff --git a/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java index 1d7ca6d0be..d543cbcc05 100644 --- a/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java +++ b/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java @@ -34,8 +34,10 @@ import android.view.View; import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.model.PermissionApps; import com.android.packageinstaller.permission.model.PermissionGroup; import com.android.packageinstaller.permission.model.PermissionGroups; +import com.android.packageinstaller.permission.model.PermissionApps.PmCache; import com.android.packageinstaller.permission.utils.Utils; import java.util.List; @@ -152,6 +154,9 @@ private void updatePermissionsUi() { } } + // Use this to speed up getting the info for all of the PermissionApps below. + // Create a new one for each refresh to make sure it has fresh data. + PmCache cache = new PmCache(getContext().getPackageManager()); for (PermissionGroup group : groups) { // Show legacy permissions only if the user chose that. if (!mShowLegacyPermissions && group.getDeclaringPackage().equals(OS_PKG) @@ -159,13 +164,22 @@ private void updatePermissionsUi() { continue; } - Preference preference = new Preference(activity); + final Preference preference = new Preference(activity); preference.setOnPreferenceClickListener(this); preference.setKey(group.getName()); preference.setIcon(Utils.applyTint(getContext(), group.getIcon(), android.R.attr.colorControlNormal)); preference.setTitle(group.getLabel()); preference.setPersistent(false); + new PermissionApps(getContext(), group.getName(), new PermissionApps.Callback() { + @Override + public void onPermissionsLoaded(PermissionApps permissionApps) { + int granted = permissionApps.getGrantedCount(); + int total = permissionApps.getTotalCount(); + preference.setSummary(getString(R.string.app_permissions_group_summary, + granted, total)); + } + }, cache).refresh(false); if (group.getDeclaringPackage().equals(OS_PKG)) { screen.addPreference(preference); diff --git a/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java b/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java index cbfcc52843..1cd8f936cf 100644 --- a/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java +++ b/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java @@ -42,6 +42,7 @@ import com.android.packageinstaller.permission.model.PermissionApps.Callback; import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp; import com.android.packageinstaller.permission.utils.SafetyNetLogger; +import com.android.packageinstaller.permission.utils.Utils; import java.util.ArrayList; import java.util.List; @@ -75,7 +76,7 @@ public void onCreate(Bundle savedInstanceState) { @Override public void onResume() { super.onResume(); - mPermissionApps.refresh(); + mPermissionApps.refresh(true); } @Override @@ -134,7 +135,7 @@ private void bindUi() { } @Override - public void onPermissionsLoaded() { + public void onPermissionsLoaded(PermissionApps permissionApps) { Context context = getActivity(); if (context == null) { @@ -148,16 +149,7 @@ public void onPermissionsLoaded() { } preferences.removeAll(); for (PermissionApp app : mPermissionApps.getApps()) { - // We currently will not show permissions fixed by the system - // which is what the system does for system components. - if (app.isSystemFixed()) { - continue; - } - - // Yes this is possible. We have leftover permissions that - // are not in the final groups and we want to get rid of, - // therefore we do not have app ops for legacy support. - if (!app.hasRuntimePermissions() && !app.hasAppOpPermissions()) { + if (!Utils.shouldShowPermission(app)) { continue; } diff --git a/src/com/android/packageinstaller/permission/utils/Utils.java b/src/com/android/packageinstaller/permission/utils/Utils.java index 856826d57e..1cd984b16e 100644 --- a/src/com/android/packageinstaller/permission/utils/Utils.java +++ b/src/com/android/packageinstaller/permission/utils/Utils.java @@ -25,9 +25,26 @@ import android.util.Log; import android.util.TypedValue; +import com.android.packageinstaller.permission.model.AppPermissionGroup; +import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp; + public class Utils { + private static final String LOG_TAG = "Utils"; + public static final String OS_PKG = "android"; + + public static final String[] MODERN_PERMISSION_GROUPS = { + Manifest.permission_group.CALENDAR, + Manifest.permission_group.CAMERA, + Manifest.permission_group.CONTACTS, + Manifest.permission_group.LOCATION, + Manifest.permission_group.SENSORS, + Manifest.permission_group.SMS, + Manifest.permission_group.PHONE, + Manifest.permission_group.MICROPHONE, + }; + private Utils() { /* do nothing - hide constructor */ } @@ -42,22 +59,51 @@ public static Drawable loadDrawable(PackageManager pm, String pkg, int resId) { } public static boolean isModernPermissionGroup(String name) { - switch (name) { - case Manifest.permission_group.CALENDAR: - case Manifest.permission_group.CAMERA: - case Manifest.permission_group.CONTACTS: - case Manifest.permission_group.LOCATION: - case Manifest.permission_group.SENSORS: - case Manifest.permission_group.SMS: - case Manifest.permission_group.PHONE: - case Manifest.permission_group.MICROPHONE: { + for (String modernGroup : MODERN_PERMISSION_GROUPS) { + if (modernGroup.equals(name)) { return true; } + } + return false; + } - default: { - return false; - } + public static boolean shouldShowPermission(AppPermissionGroup group, boolean showLegacy) { + // We currently will not show permissions fixed by the system. + // which is what the system does for system components. + if (group.isSystemFixed()) { + return false; + } + + // Yes this is possible. We have leftover permissions that + // are not in the final groups and we want to get rid of, + // therefore we do not have app ops for legacy support. + if (!group.hasRuntimePermission() && !group.hasAppOpPermission()) { + return false; + } + + final boolean isPlatformPermission = group.getDeclaringPackage().equals(OS_PKG); + // Show legacy permissions only if the user chose that. + if (isPlatformPermission && !showLegacy + && !Utils.isModernPermissionGroup(group.getName())) { + return false; + } + return true; + } + + public static boolean shouldShowPermission(PermissionApp app) { + // We currently will not show permissions fixed by the system + // which is what the system does for system components. + if (app.isSystemFixed()) { + return false; + } + + // Yes this is possible. We have leftover permissions that + // are not in the final groups and we want to get rid of, + // therefore we do not have app ops for legacy support. + if (!app.hasRuntimePermissions() && !app.hasAppOpPermissions()) { + return false; } + return true; } public static Drawable applyTint(Context context, Drawable icon, int attr) {