From 267c2bd39c248eea2679c0a8efa334d3155e262c Mon Sep 17 00:00:00 2001 From: Svet Ganov Date: Sat, 16 May 2015 22:45:19 -0700 Subject: Hide platform platform legacy permissions behind a menu option - package installer. bug:21195624 Change-Id: If6de516d76969c3627316d091893da58f81af832 --- AndroidManifest.xml | 1 + res/drawable/ic_perm_device_info.xml | 24 + res/menu/toggle_legacy_permissions.xml | 24 + res/values/strings.xml | 6 + .../permission/model/AppPermissionGroup.java | 535 +++++++++++++++++++++ .../permission/model/AppPermissions.java | 18 +- .../permission/model/PermissionApps.java | 28 +- .../permission/model/PermissionGroup.java | 467 +----------------- .../permission/model/PermissionGroups.java | 222 +++++++++ .../permission/ui/AppPermissionsFragment.java | 70 ++- .../permission/ui/GrantPermissionsActivity.java | 12 +- .../permission/ui/ManagePermissionsActivity.java | 45 +- .../permission/ui/ManagePermissionsFragment.java | 216 +++++++++ .../permission/ui/PermissionAppsFragment.java | 13 +- .../permission/utils/SafetyNetLogger.java | 10 +- .../packageinstaller/permission/utils/Utils.java | 20 + 16 files changed, 1189 insertions(+), 522 deletions(-) create mode 100644 res/drawable/ic_perm_device_info.xml create mode 100644 res/menu/toggle_legacy_permissions.xml create mode 100644 src/com/android/packageinstaller/permission/model/AppPermissionGroup.java create mode 100644 src/com/android/packageinstaller/permission/model/PermissionGroups.java create mode 100644 src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index dedf0c48..0a39a43d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -81,6 +81,7 @@ android:theme="@style/Settings" android:permission="android.permission.GRANT_REVOKE_PERMISSIONS"> + diff --git a/res/drawable/ic_perm_device_info.xml b/res/drawable/ic_perm_device_info.xml new file mode 100644 index 00000000..ef91c746 --- /dev/null +++ b/res/drawable/ic_perm_device_info.xml @@ -0,0 +1,24 @@ + + + + diff --git a/res/menu/toggle_legacy_permissions.xml b/res/menu/toggle_legacy_permissions.xml new file mode 100644 index 00000000..544c4793 --- /dev/null +++ b/res/menu/toggle_legacy_permissions.xml @@ -0,0 +1,24 @@ + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 38f70273..955c0e5d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -215,4 +215,10 @@ This app was designed for an older version of Android. Denying permission may cause it to no longer function as intended. + + Show legacy + + + Hide legacy + diff --git a/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java b/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java new file mode 100644 index 00000000..e3c9160a --- /dev/null +++ b/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java @@ -0,0 +1,535 @@ +/* + * 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.app.ActivityManager; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.os.Build; +import android.os.UserHandle; +import android.util.ArrayMap; + +import com.android.packageinstaller.R; + +import java.util.ArrayList; +import java.util.List; + +public final class AppPermissionGroup implements Comparable { + private static final String PLATFORM_PACKAGE_NAME = "android"; + + private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed"; + + private final Context mContext; + private final UserHandle mUserHandle; + private final PackageManager mPackageManager; + private final AppOpsManager mAppOps; + private final ActivityManager mActivityManager; + + private final PackageInfo mPackageInfo; + private final String mName; + private final String mDeclaringPackage; + private final CharSequence mLabel; + private final ArrayMap mPermissions = new ArrayMap<>(); + private final String mIconPkg; + private final int mIconResId; + + private final boolean mAppSupportsRuntimePermissions; + + public static AppPermissionGroup create(Context context, PackageInfo packageInfo, + String permissionName) { + PermissionInfo permissionInfo; + try { + permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + + if (permissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) { + return null; + } + + PackageItemInfo groupInfo = permissionInfo; + if (permissionInfo.group != null) { + try { + groupInfo = context.getPackageManager().getPermissionGroupInfo( + permissionInfo.group, 0); + } catch (PackageManager.NameNotFoundException e) { + /* ignore */ + } + } + + List permissionInfos = null; + if (groupInfo instanceof PermissionGroupInfo) { + try { + permissionInfos = context.getPackageManager().queryPermissionsByGroup( + groupInfo.name, 0); + } catch (PackageManager.NameNotFoundException e) { + /* ignore */ + } + } + + return create(context, packageInfo, groupInfo, permissionInfos); + } + + public static AppPermissionGroup create(Context context, PackageInfo packageInfo, + PackageItemInfo groupInfo, List permissionInfos) { + + AppPermissionGroup group = new AppPermissionGroup(context, packageInfo, groupInfo.name, + groupInfo.packageName, groupInfo.loadLabel(context.getPackageManager()), + groupInfo.packageName, groupInfo.icon); + + if (groupInfo instanceof PermissionInfo) { + permissionInfos = new ArrayList<>(); + permissionInfos.add((PermissionInfo) groupInfo); + } + + if (permissionInfos == null || permissionInfos.isEmpty()) { + return null; + } + + final int permissionCount = packageInfo.requestedPermissions.length; + for (int i = 0; i < permissionCount; i++) { + String requestedPermission = packageInfo.requestedPermissions[i]; + + PermissionInfo requestedPermissionInfo = null; + + for (PermissionInfo permissionInfo : permissionInfos) { + if (requestedPermission.equals(permissionInfo.name)) { + requestedPermissionInfo = permissionInfo; + break; + } + } + + if (requestedPermissionInfo == null) { + continue; + } + + // Collect only runtime permissions. + if (requestedPermissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) { + continue; + } + + // Don't allow toggle of non platform defined permissions for legacy apps via app ops. + if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1 + && !PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)) { + continue; + } + + final boolean granted = (packageInfo.requestedPermissionsFlags[i] + & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0; + + final int appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName) + ? AppOpsManager.permissionToOpCode(requestedPermissionInfo.name) + : AppOpsManager.OP_NONE; + + final boolean appOpAllowed = appOp != AppOpsManager.OP_NONE + && context.getSystemService(AppOpsManager.class).checkOp(appOp, + packageInfo.applicationInfo.uid, packageInfo.packageName) + == AppOpsManager.MODE_ALLOWED; + + final int flags = context.getPackageManager().getPermissionFlags( + requestedPermission, packageInfo.packageName, + new UserHandle(context.getUserId())); + + Permission permission = new Permission(requestedPermission, granted, + appOp, appOpAllowed, flags); + group.addPermission(permission); + } + + return group; + } + + private AppPermissionGroup(Context context, PackageInfo packageInfo, String name, + String declaringPackage, CharSequence label, String iconPkg, int iconResId) { + mContext = context; + mUserHandle = new UserHandle(mContext.getUserId()); + mPackageManager = mContext.getPackageManager(); + mPackageInfo = packageInfo; + mAppSupportsRuntimePermissions = packageInfo.applicationInfo + .targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1; + mAppOps = context.getSystemService(AppOpsManager.class); + mActivityManager = context.getSystemService(ActivityManager.class); + mDeclaringPackage = declaringPackage; + mName = name; + mLabel = label; + mIconPkg = iconPkg; + mIconResId = iconResId != 0 ? iconResId : R.drawable.ic_perm_device_info; + } + + public boolean hasRuntimePermission() { + return mAppSupportsRuntimePermissions; + } + + public boolean hasAppOpPermission() { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + Permission permission = mPermissions.valueAt(i); + if (permission.getAppOp() != AppOpsManager.OP_NONE) { + return true; + } + } + return false; + } + + public PackageInfo getApp() { + return mPackageInfo; + } + + public String getName() { + return mName; + } + + public String getDeclaringPackage() { + return mDeclaringPackage; + } + + public String getIconPkg() { + return mIconPkg; + } + + public int getIconResId() { + return mIconResId; + } + + public CharSequence getLabel() { + return mLabel; + } + + public boolean hasPermission(String permission) { + return mPermissions.get(permission) != null; + } + + public boolean areRuntimePermissionsGranted() { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + Permission permission = mPermissions.valueAt(i); + if (mAppSupportsRuntimePermissions) { + if (!permission.isGranted()) { + return false; + } + } else if (permission.isGranted() && permission.getAppOp() + != AppOpsManager.OP_NONE && !permission.isAppOpAllowed()) { + return false; + } + } + return true; + } + + public boolean grantRuntimePermissions(boolean fixedByTheUser) { + final boolean isSharedUser = mPackageInfo.sharedUserId != null; + final int uid = mPackageInfo.applicationInfo.uid; + + // We toggle permissions only to apps that support runtime + // permissions, otherwise we toggle the app op corresponding + // to the permission if the permission is granted to the app. + for (Permission permission : mPermissions.values()) { + if (mAppSupportsRuntimePermissions) { + // Grant the permission if needed. + if (!permission.isGranted()) { + permission.setGranted(true); + mPackageManager.grantRuntimePermission(mPackageInfo.packageName, + permission.getName(), new UserHandle(mContext.getUserId())); + } + + // Update the permission flags. + if (!fixedByTheUser) { + // Now the apps can ask for the permission as the user + // no longer has it fixed in a denied state. + if (permission.isUserFixed() || !permission.isUserSet()) { + permission.setUserFixed(false); + permission.setUserSet(true); + mPackageManager.updatePermissionFlags(permission.getName(), + mPackageInfo.packageName, + PackageManager.FLAG_PERMISSION_USER_FIXED + | PackageManager.FLAG_PERMISSION_USER_SET, + PackageManager.FLAG_PERMISSION_USER_SET, + mUserHandle); + } + } + + // Enable the permission app op. + if (permission.hasAppOp() && !permission.isAppOpAllowed()) { + permission.setAppOpAllowed(true); + mAppOps.setMode(permission.getAppOp(), android.os.Process.myUid(), + mPackageInfo.packageName, AppOpsManager.MODE_ALLOWED); + } + } else { + // Legacy apps cannot have a not granted permission but just in case. + // Also if the permissions has no corresponding app op, then it is a + // third-party one and we do not offer toggling of such permissions. + if (!permission.isGranted() || !permission.hasAppOp()) { + continue; + } + + if (!permission.isAppOpAllowed()) { + permission.setAppOpAllowed(true); + // It this is a shared user we want to enable the app op for all + // packages in the shared user to match the behavior of this + // shared user having a runtime permission. + if (isSharedUser) { + // Enable the app op. + String[] packageNames = mPackageManager.getPackagesForUid(uid); + for (String packageName : packageNames) { + mAppOps.setMode(permission.getAppOp(), uid, packageName, + AppOpsManager.MODE_ALLOWED); + } + } else { + // Enable the app op. + mAppOps.setMode(permission.getAppOp(), uid, mPackageInfo.packageName, + AppOpsManager.MODE_ALLOWED); + } + + // Mark that the permission should not be be granted on upgrade + // when the app begins supporting runtime permissions. + if (permission.shouldRevokeOnUpgrade()) { + permission.setRevokeOnUpgrade(false); + mPackageManager.updatePermissionFlags(permission.getName(), + mPackageInfo.packageName, + PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, + 0, mUserHandle); + } + + // Legacy apps do not know that they have to retry access to a + // resource due to changes in runtime permissions (app ops in this + // case). Therefore, we restart them on app op change, so they + // can pick up the change. + mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE); + } + } + } + + return true; + } + + public boolean revokeRuntimePermissions(boolean fixedByTheUser) { + final boolean isSharedUser = mPackageInfo.sharedUserId != null; + final int uid = mPackageInfo.applicationInfo.uid; + + // We toggle permissions only to apps that support runtime + // permissions, otherwise we toggle the app op corresponding + // to the permission if the permission is granted to the app. + for (Permission permission : mPermissions.values()) { + if (mAppSupportsRuntimePermissions) { + // Revoke the permission if needed. + if (permission.isGranted()) { + permission.setGranted(false); + mPackageManager.revokeRuntimePermission(mPackageInfo.packageName, + permission.getName(), mUserHandle); + } + + // Update the permission flags. + if (fixedByTheUser) { + // Take a note that the user fixed the permission. + if (permission.isUserSet() || !permission.isUserFixed()) { + permission.setUserSet(false); + permission.setUserFixed(true); + mPackageManager.updatePermissionFlags(permission.getName(), + mPackageInfo.packageName, + PackageManager.FLAG_PERMISSION_USER_SET + | PackageManager.FLAG_PERMISSION_USER_FIXED, + PackageManager.FLAG_PERMISSION_USER_FIXED, + mUserHandle); + } + } else { + if (!permission.isUserSet()) { + permission.setUserSet(true); + // Take a note that the user already chose once. + mPackageManager.updatePermissionFlags(permission.getName(), + mPackageInfo.packageName, + PackageManager.FLAG_PERMISSION_USER_SET, + PackageManager.FLAG_PERMISSION_USER_SET, + mUserHandle); + } + } + + // Disable the permission app op. + if (permission.hasAppOp() && permission.isAppOpAllowed()) { + permission.setAppOpAllowed(false); + mAppOps.setMode(permission.getAppOp(), android.os.Process.myUid(), + mPackageInfo.packageName, AppOpsManager.MODE_IGNORED); + } + } else { + // Legacy apps cannot have a non-granted permission but just in case. + // Also if the permission has no corresponding app op, then it is a + // third-party one and we do not offer toggling of such permissions. + if (!permission.isGranted() || !permission.hasAppOp()) { + continue; + } + + if (permission.isAppOpAllowed()) { + permission.setAppOpAllowed(false); + // It this is a shared user we want to enable the app op for all + // packages the the shared user to match the behavior of this + // shared user having a runtime permission. + if (isSharedUser) { + String[] packageNames = mPackageManager.getPackagesForUid(uid); + for (String packageName : packageNames) { + // Disable the app op. + mAppOps.setMode(permission.getAppOp(), uid, + packageName, AppOpsManager.MODE_IGNORED); + } + } else { + // Disable the app op. + mAppOps.setMode(permission.getAppOp(), uid, + mPackageInfo.packageName, AppOpsManager.MODE_IGNORED); + } + + // Mark that the permission should not be granted on upgrade + // when the app begins supporting runtime permissions. + if (!permission.shouldRevokeOnUpgrade()) { + permission.setRevokeOnUpgrade(true); + mPackageManager.updatePermissionFlags(permission.getName(), + mPackageInfo.packageName, + PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, + PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, + mUserHandle); + } + + // Disabling an app op may put the app in a situation in which it + // has a handle to state it shouldn't have, so we have to kill the + // app. This matches the revoke runtime permission behavior. + mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE); + } + } + } + + return true; + } + + public List getPermissions() { + return new ArrayList<>(mPermissions.values()); + } + + public int getFlags() { + int flags = 0; + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + Permission permission = mPermissions.valueAt(i); + flags |= permission.getFlags(); + } + return flags; + } + + public boolean isUserFixed() { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + Permission permission = mPermissions.valueAt(i); + if (!permission.isUserFixed()) { + return false; + } + } + return true; + } + + public boolean isPolicyFixed() { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + Permission permission = mPermissions.valueAt(i); + if (permission.isPolicyFixed()) { + return true; + } + } + return false; + } + + public boolean isUserSet() { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + Permission permission = mPermissions.valueAt(i); + if (!permission.isUserSet()) { + return false; + } + } + return true; + } + + public boolean isSystemFixed() { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + Permission permission = mPermissions.valueAt(i); + if (permission.isSystemFixed()) { + return true; + } + } + return false; + } + + @Override + public int compareTo(AppPermissionGroup another) { + final int result = mLabel.toString().compareTo(another.mLabel.toString()); + if (result == 0) { + // Unbadged before badged. + return mPackageInfo.applicationInfo.uid + - another.mPackageInfo.applicationInfo.uid; + } + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (getClass() != obj.getClass()) { + return false; + } + + AppPermissionGroup other = (AppPermissionGroup) obj; + + if (mName == null) { + if (other.mName != null) { + return false; + } + } else if (!mName.equals(other.mName)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return mName != null ? mName.hashCode() : 0; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(getClass().getSimpleName()); + builder.append("{name=").append(mName); + if (!mPermissions.isEmpty()) { + builder.append(", }"); + } else { + builder.append('}'); + } + return builder.toString(); + } + + private void addPermission(Permission permission) { + mPermissions.put(permission.getName(), permission); + } +} diff --git a/src/com/android/packageinstaller/permission/model/AppPermissions.java b/src/com/android/packageinstaller/permission/model/AppPermissions.java index 8258126a..17014774 100644 --- a/src/com/android/packageinstaller/permission/model/AppPermissions.java +++ b/src/com/android/packageinstaller/permission/model/AppPermissions.java @@ -28,7 +28,7 @@ import java.util.Collections; import java.util.List; public final class AppPermissions { - private final ArrayMap mGroups = new ArrayMap<>(); + private final ArrayMap mGroups = new ArrayMap<>(); private final Context mContext; @@ -63,11 +63,11 @@ public final class AppPermissions { return mAppLabel; } - public PermissionGroup getPermissionGroup(String name) { + public AppPermissionGroup getPermissionGroup(String name) { return mGroups.get(name); } - public List getPermissionGroups() { + public List getPermissionGroups() { return new ArrayList<>(mGroups.values()); } @@ -84,7 +84,7 @@ public final class AppPermissions { } private void loadPermissionGroups() { - List groups = new ArrayList<>(); + List groups = new ArrayList<>(); if (mPackageInfo.requestedPermissions == null) { return; @@ -97,7 +97,7 @@ public final class AppPermissions { continue; } - PermissionGroup group = PermissionGroup.create(mContext, + AppPermissionGroup group = AppPermissionGroup.create(mContext, mPackageInfo, requestedPerm); if (group == null) { continue; @@ -109,7 +109,7 @@ public final class AppPermissions { if (!ArrayUtils.isEmpty(mFilterPermissions)) { final int groupCount = groups.size(); for (int i = groupCount - 1; i >= 0; i--) { - PermissionGroup group = groups.get(i); + AppPermissionGroup group = groups.get(i); boolean groupHasPermission = false; for (String filterPerm : mFilterPermissions) { if (group.hasPermission(filterPerm)) { @@ -126,14 +126,14 @@ public final class AppPermissions { Collections.sort(groups); mGroups.clear(); - for (PermissionGroup group : groups) { + for (AppPermissionGroup group : groups) { mGroups.put(group.getName(), group); } } private static boolean hasGroupForPermission(String permission, - List groups) { - for (PermissionGroup group : groups) { + List groups) { + for (AppPermissionGroup group : groups) { if (group.hasPermission(permission)) { return true; } diff --git a/src/com/android/packageinstaller/permission/model/PermissionApps.java b/src/com/android/packageinstaller/permission/model/PermissionApps.java index 10ec9596..7191381d 100644 --- a/src/com/android/packageinstaller/permission/model/PermissionApps.java +++ b/src/com/android/packageinstaller/permission/model/PermissionApps.java @@ -107,14 +107,14 @@ public class PermissionApps { public static class PermissionApp implements Comparable { private final String mPackageName; - private final PermissionGroup mPermissionGroup; + private final AppPermissionGroup mAppPermissionGroup; private final String mLabel; private final Drawable mIcon; - public PermissionApp(String packageName, PermissionGroup permissionGroup, + public PermissionApp(String packageName, AppPermissionGroup appPermissionGroup, String label, Drawable icon) { mPackageName = packageName; - mPermissionGroup = permissionGroup; + mAppPermissionGroup = appPermissionGroup; mLabel = label; mIcon = icon; } @@ -132,39 +132,39 @@ public class PermissionApps { } public boolean areRuntimePermissionsGranted() { - return mPermissionGroup.areRuntimePermissionsGranted(); + return mAppPermissionGroup.areRuntimePermissionsGranted(); } public void grantRuntimePermissions() { - mPermissionGroup.grantRuntimePermissions(false); + mAppPermissionGroup.grantRuntimePermissions(false); } public void revokeRuntimePermissions() { - mPermissionGroup.revokeRuntimePermissions(false); + mAppPermissionGroup.revokeRuntimePermissions(false); } public boolean isPolicyFixed() { - return mPermissionGroup.isPolicyFixed(); + return mAppPermissionGroup.isPolicyFixed(); } public boolean isSystemFixed() { - return mPermissionGroup.isSystemFixed(); + return mAppPermissionGroup.isSystemFixed(); } public boolean hasRuntimePermissions() { - return mPermissionGroup.hasRuntimePermission(); + return mAppPermissionGroup.hasRuntimePermission(); } public boolean hasAppOpPermissions() { - return mPermissionGroup.hasAppOpPermission(); + return mAppPermissionGroup.hasAppOpPermission(); } public String getPackageName() { return mPackageName; } - public PermissionGroup getPermissionGroup() { - return mPermissionGroup; + public AppPermissionGroup getPermissionGroup() { + return mAppPermissionGroup; } @Override @@ -178,7 +178,7 @@ public class PermissionApps { } private int getUid() { - return mPermissionGroup.getApp().applicationInfo.uid; + return mAppPermissionGroup.getApp().applicationInfo.uid; } } @@ -224,7 +224,7 @@ public class PermissionApps { continue; } - PermissionGroup group = PermissionGroup.create(mContext, + AppPermissionGroup group = AppPermissionGroup.create(mContext, app, groupInfo, groupPermInfos); PermissionApp permApp = new PermissionApp(app.packageName, diff --git a/src/com/android/packageinstaller/permission/model/PermissionGroup.java b/src/com/android/packageinstaller/permission/model/PermissionGroup.java index 901f1a4e..8a602b44 100644 --- a/src/com/android/packageinstaller/permission/model/PermissionGroup.java +++ b/src/com/android/packageinstaller/permission/model/PermissionGroup.java @@ -16,466 +16,41 @@ package com.android.packageinstaller.permission.model; -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageItemInfo; -import android.content.pm.PackageManager; -import android.content.pm.PermissionGroupInfo; -import android.content.pm.PermissionInfo; -import android.os.Build; -import android.os.UserHandle; -import android.util.ArrayMap; - -import java.util.ArrayList; -import java.util.List; +import android.graphics.drawable.Drawable; public final class PermissionGroup implements Comparable { - private static final String PLATFORM_PACKAGE_NAME = "android"; - - private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed"; - - private final Context mContext; - private final UserHandle mUserHandle; - private final PackageManager mPackageManager; - private final AppOpsManager mAppOps; - private final ActivityManager mActivityManager; - - private final PackageInfo mPackageInfo; private final String mName; + private final String mDeclaringPackage; private final CharSequence mLabel; - private final ArrayMap mPermissions = new ArrayMap<>(); - private final String mIconPkg; - private final int mIconResId; - - private final boolean mAppSupportsRuntimePermissions; - - public static PermissionGroup create(Context context, PackageInfo packageInfo, - String permissionName) { - PermissionInfo permissionInfo; - try { - permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0); - } catch (PackageManager.NameNotFoundException e) { - return null; - } - - if (permissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) { - return null; - } - - PackageItemInfo groupInfo = permissionInfo; - if (permissionInfo.group != null) { - try { - groupInfo = context.getPackageManager().getPermissionGroupInfo( - permissionInfo.group, 0); - } catch (PackageManager.NameNotFoundException e) { - /* ignore */ - } - } - - List permissionInfos = null; - if (groupInfo instanceof PermissionGroupInfo) { - try { - permissionInfos = context.getPackageManager().queryPermissionsByGroup( - groupInfo.name, 0); - } catch (PackageManager.NameNotFoundException e) { - /* ignore */ - } - } - - return create(context, packageInfo, groupInfo, permissionInfos); - - } - - public static PermissionGroup create(Context context, PackageInfo packageInfo, - PackageItemInfo groupInfo, List permissionInfos) { - - PermissionGroup group = new PermissionGroup(context, packageInfo, groupInfo.name, - groupInfo.loadLabel(context.getPackageManager()), groupInfo.packageName, - groupInfo.icon); - - if (groupInfo instanceof PermissionInfo) { - permissionInfos = new ArrayList<>(); - permissionInfos.add((PermissionInfo) groupInfo); - } - - if (permissionInfos == null || permissionInfos.isEmpty()) { - return null; - } - - final int permissionCount = packageInfo.requestedPermissions.length; - for (int i = 0; i < permissionCount; i++) { - String requestedPermission = packageInfo.requestedPermissions[i]; - - PermissionInfo requestedPermissionInfo = null; - - for (PermissionInfo permissionInfo : permissionInfos) { - if (requestedPermission.equals(permissionInfo.name)) { - requestedPermissionInfo = permissionInfo; - break; - } - } - - if (requestedPermissionInfo == null) { - continue; - } - - // Collect only runtime permissions. - if (requestedPermissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) { - continue; - } - - // Don't allow toggle of non platform defined permissions for legacy apps via app ops. - if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1 - && !PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)) { - continue; - } - - final boolean granted = (packageInfo.requestedPermissionsFlags[i] - & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0; - - final int appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName) - ? AppOpsManager.permissionToOpCode(requestedPermissionInfo.name) - : AppOpsManager.OP_NONE; - - final boolean appOpAllowed = appOp != AppOpsManager.OP_NONE - && context.getSystemService(AppOpsManager.class).checkOp(appOp, - packageInfo.applicationInfo.uid, packageInfo.packageName) - == AppOpsManager.MODE_ALLOWED; + private final Drawable mIcon; - final int flags = context.getPackageManager().getPermissionFlags( - requestedPermission, packageInfo.packageName, - new UserHandle(context.getUserId())); - - Permission permission = new Permission(requestedPermission, granted, - appOp, appOpAllowed, flags); - group.addPermission(permission); - } - - return group; - } - - private PermissionGroup(Context context, PackageInfo packageInfo, String name, - CharSequence label, String iconPkg, int iconResId) { - mContext = context; - mUserHandle = new UserHandle(mContext.getUserId()); - mPackageManager = mContext.getPackageManager(); - mPackageInfo = packageInfo; - mAppSupportsRuntimePermissions = packageInfo.applicationInfo - .targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1; - mAppOps = context.getSystemService(AppOpsManager.class); - mActivityManager = context.getSystemService(ActivityManager.class); + PermissionGroup(String name, String declaringPackage, + CharSequence label, Drawable icon) { + mDeclaringPackage = declaringPackage; mName = name; mLabel = label; - mIconPkg = iconPkg; - mIconResId = iconResId != 0 ? iconResId - : com.android.internal.R.drawable.ic_perm_device_info; - } - - public boolean hasRuntimePermission() { - return mAppSupportsRuntimePermissions; - } - - public boolean hasAppOpPermission() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.getAppOp() != AppOpsManager.OP_NONE) { - return true; - } - } - return false; - } - - public PackageInfo getApp() { - return mPackageInfo; + mIcon = icon; } public String getName() { return mName; } - public String getIconPkg() { - return mIconPkg; - } - - public int getIconResId() { - return mIconResId; + public String getDeclaringPackage() { + return mDeclaringPackage; } public CharSequence getLabel() { return mLabel; } - public boolean hasPermission(String permission) { - return mPermissions.get(permission) != null; - } - - public boolean areRuntimePermissionsGranted() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (mAppSupportsRuntimePermissions) { - if (!permission.isGranted()) { - return false; - } - } else if (permission.isGranted() && permission.getAppOp() - != AppOpsManager.OP_NONE && !permission.isAppOpAllowed()) { - return false; - } - } - return true; - } - - public boolean grantRuntimePermissions(boolean fixedByTheUser) { - final boolean isSharedUser = mPackageInfo.sharedUserId != null; - final int uid = mPackageInfo.applicationInfo.uid; - - // We toggle permissions only to apps that support runtime - // permissions, otherwise we toggle the app op corresponding - // to the permission if the permission is granted to the app. - for (Permission permission : mPermissions.values()) { - if (mAppSupportsRuntimePermissions) { - // Grant the permission if needed. - if (!permission.isGranted()) { - permission.setGranted(true); - mPackageManager.grantRuntimePermission(mPackageInfo.packageName, - permission.getName(), new UserHandle(mContext.getUserId())); - } - - // Update the permission flags. - if (!fixedByTheUser) { - // Now the apps can ask for the permission as the user - // no longer has it fixed in a denied state. - if (permission.isUserFixed() || !permission.isUserSet()) { - permission.setUserFixed(false); - permission.setUserSet(true); - mPackageManager.updatePermissionFlags(permission.getName(), - mPackageInfo.packageName, - PackageManager.FLAG_PERMISSION_USER_FIXED - | PackageManager.FLAG_PERMISSION_USER_SET, - PackageManager.FLAG_PERMISSION_USER_SET, - mUserHandle); - } - } - - // Enable the permission app op. - if (permission.hasAppOp() && !permission.isAppOpAllowed()) { - permission.setAppOpAllowed(true); - mAppOps.setMode(permission.getAppOp(), android.os.Process.myUid(), - mPackageInfo.packageName, AppOpsManager.MODE_ALLOWED); - } - } else { - // Legacy apps cannot have a not granted permission but just in case. - // Also if the permissions has no corresponding app op, then it is a - // third-party one and we do not offer toggling of such permissions. - if (!permission.isGranted() || !permission.hasAppOp()) { - continue; - } - - if (!permission.isAppOpAllowed()) { - permission.setAppOpAllowed(true); - // It this is a shared user we want to enable the app op for all - // packages in the shared user to match the behavior of this - // shared user having a runtime permission. - if (isSharedUser) { - // Enable the app op. - String[] packageNames = mPackageManager.getPackagesForUid(uid); - for (String packageName : packageNames) { - mAppOps.setMode(permission.getAppOp(), uid, packageName, - AppOpsManager.MODE_ALLOWED); - } - } else { - // Enable the app op. - mAppOps.setMode(permission.getAppOp(), uid, mPackageInfo.packageName, - AppOpsManager.MODE_ALLOWED); - } - - // Mark that the permission should not be be granted on upgrade - // when the app begins supporting runtime permissions. - if (permission.shouldRevokeOnUpgrade()) { - permission.setRevokeOnUpgrade(false); - mPackageManager.updatePermissionFlags(permission.getName(), - mPackageInfo.packageName, - PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, - 0, mUserHandle); - } - - // Legacy apps do not know that they have to retry access to a - // resource due to changes in runtime permissions (app ops in this - // case). Therefore, we restart them on app op change, so they - // can pick up the change. - mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE); - } - } - } - - return true; - } - - public boolean revokeRuntimePermissions(boolean fixedByTheUser) { - final boolean isSharedUser = mPackageInfo.sharedUserId != null; - final int uid = mPackageInfo.applicationInfo.uid; - - // We toggle permissions only to apps that support runtime - // permissions, otherwise we toggle the app op corresponding - // to the permission if the permission is granted to the app. - for (Permission permission : mPermissions.values()) { - if (mAppSupportsRuntimePermissions) { - // Revoke the permission if needed. - if (permission.isGranted()) { - permission.setGranted(false); - mPackageManager.revokeRuntimePermission(mPackageInfo.packageName, - permission.getName(), mUserHandle); - } - - // Update the permission flags. - if (fixedByTheUser) { - // Take a note that the user fixed the permission. - if (permission.isUserSet() || !permission.isUserFixed()) { - permission.setUserSet(false); - permission.setUserFixed(true); - mPackageManager.updatePermissionFlags(permission.getName(), - mPackageInfo.packageName, - PackageManager.FLAG_PERMISSION_USER_SET - | PackageManager.FLAG_PERMISSION_USER_FIXED, - PackageManager.FLAG_PERMISSION_USER_FIXED, - mUserHandle); - } - } else { - if (!permission.isUserSet()) { - permission.setUserSet(true); - // Take a note that the user already chose once. - mPackageManager.updatePermissionFlags(permission.getName(), - mPackageInfo.packageName, - PackageManager.FLAG_PERMISSION_USER_SET, - PackageManager.FLAG_PERMISSION_USER_SET, - mUserHandle); - } - } - - // Disable the permission app op. - if (permission.hasAppOp() && permission.isAppOpAllowed()) { - permission.setAppOpAllowed(false); - mAppOps.setMode(permission.getAppOp(), android.os.Process.myUid(), - mPackageInfo.packageName, AppOpsManager.MODE_IGNORED); - } - } else { - // Legacy apps cannot have a non-granted permission but just in case. - // Also if the permission has no corresponding app op, then it is a - // third-party one and we do not offer toggling of such permissions. - if (!permission.isGranted() || !permission.hasAppOp()) { - continue; - } - - if (permission.isAppOpAllowed()) { - permission.setAppOpAllowed(false); - // It this is a shared user we want to enable the app op for all - // packages the the shared user to match the behavior of this - // shared user having a runtime permission. - if (isSharedUser) { - String[] packageNames = mPackageManager.getPackagesForUid(uid); - for (String packageName : packageNames) { - // Disable the app op. - mAppOps.setMode(permission.getAppOp(), uid, - packageName, AppOpsManager.MODE_IGNORED); - } - } else { - // Disable the app op. - mAppOps.setMode(permission.getAppOp(), uid, - mPackageInfo.packageName, AppOpsManager.MODE_IGNORED); - } - - // Mark that the permission should not be granted on upgrade - // when the app begins supporting runtime permissions. - if (!permission.shouldRevokeOnUpgrade()) { - permission.setRevokeOnUpgrade(true); - mPackageManager.updatePermissionFlags(permission.getName(), - mPackageInfo.packageName, - PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, - PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, - mUserHandle); - } - - // Disabling an app op may put the app in a situation in which it - // has a handle to state it shouldn't have, so we have to kill the - // app. This matches the revoke runtime permission behavior. - mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE); - } - } - } - - return true; - } - - public List getPermissions() { - return new ArrayList<>(mPermissions.values()); - } - - public int getFlags() { - int flags = 0; - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - flags |= permission.getFlags(); - } - return flags; - } - - public boolean isUserFixed() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (!permission.isUserFixed()) { - return false; - } - } - return true; - } - - public boolean isPolicyFixed() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.isPolicyFixed()) { - return true; - } - } - return false; - } - - public boolean isUserSet() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (!permission.isUserSet()) { - return false; - } - } - return true; - } - - public boolean isSystemFixed() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.isSystemFixed()) { - return true; - } - } - return false; + public Drawable getIcon() { + return mIcon; } @Override public int compareTo(PermissionGroup another) { - final int result = mLabel.toString().compareTo(another.mLabel.toString()); - if (result == 0) { - // Unbadged before badged. - return mPackageInfo.applicationInfo.uid - - another.mPackageInfo.applicationInfo.uid; - } - return result; + return mLabel.toString().compareTo(another.mLabel.toString()); } @Override @@ -509,22 +84,4 @@ public final class PermissionGroup implements Comparable { public int hashCode() { return mName != null ? mName.hashCode() : 0; } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(getClass().getSimpleName()); - builder.append("{name=").append(mName); - if (!mPermissions.isEmpty()) { - builder.append(", }"); - } else { - builder.append('}'); - } - return builder.toString(); - } - - private void addPermission(Permission permission) { - mPermissions.put(permission.getName(), permission); - } } - diff --git a/src/com/android/packageinstaller/permission/model/PermissionGroups.java b/src/com/android/packageinstaller/permission/model/PermissionGroups.java new file mode 100644 index 00000000..62477b88 --- /dev/null +++ b/src/com/android/packageinstaller/permission/model/PermissionGroups.java @@ -0,0 +1,222 @@ +/* + * 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.app.LoaderManager; +import android.app.LoaderManager.LoaderCallbacks; +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.content.Loader; +import android.content.pm.PackageInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.util.ArraySet; + +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.utils.Utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public final class PermissionGroups implements LoaderCallbacks> { + private final ArrayList mGroups = new ArrayList<>(); + private final Context mContext; + private final LoaderManager mLoaderManager; + private final PermissionsGroupsChangeCallback mCallback; + + public interface PermissionsGroupsChangeCallback { + public void onPermissionGroupsChanged(); + } + + public PermissionGroups(Context context, LoaderManager loaderManager, + PermissionsGroupsChangeCallback callback) { + mContext = context; + mLoaderManager = loaderManager; + mCallback = callback; + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new PermissionsLoader(mContext); + } + + @Override + public void onLoadFinished(Loader> loader, + List groups) { + if (mGroups.equals(groups)) { + return; + } + mGroups.clear(); + mGroups.addAll(groups); + mCallback.onPermissionGroupsChanged(); + } + + @Override + public void onLoaderReset(Loader> loader) { + mGroups.clear(); + mCallback.onPermissionGroupsChanged(); + } + + public void refresh() { + mLoaderManager.restartLoader(0, null, this); + mLoaderManager.getLoader(0).forceLoad(); + } + + public List getGroups() { + return mGroups; + } + + public PermissionGroup getGroup(String name) { + for (PermissionGroup group : mGroups) { + if (group.getName().equals(name)) { + return group; + } + } + return null; + } + + private static final class PermissionsLoader extends AsyncTaskLoader> { + + public PermissionsLoader(Context context) { + super(context); + } + + @Override + public List loadInBackground() { + List groups = new ArrayList<>(); + Set seenPermissions = new ArraySet<>(); + + + PackageManager packageManager = getContext().getPackageManager(); + List groupInfos = packageManager.getAllPermissionGroups(0); + + for (PermissionGroupInfo groupInfo : groupInfos) { + // Mare sure we respond to cancellation. + if (isLoadInBackgroundCanceled()) { + return Collections.emptyList(); + } + + // Get the permissions in this group. + final List groupPermissions; + try { + groupPermissions = packageManager.queryPermissionsByGroup(groupInfo.name, 0); + } catch (PackageManager.NameNotFoundException e) { + continue; + } + + boolean hasRuntimePermissions = false; + + // Cache seen permissions and see if group has runtime permissions. + for (PermissionInfo groupPermission : groupPermissions) { + seenPermissions.add(groupPermission.name); + if (groupPermission.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) { + hasRuntimePermissions = true; + } + } + + // No runtime permissions - not interesting for us. + if (!hasRuntimePermissions) { + continue; + } + + CharSequence label = loadItemInfoLabel(groupInfo); + Drawable icon = loadItemInfoIcon(groupInfo); + + // Create the group and add to the list. + PermissionGroup group = new PermissionGroup(groupInfo.name, + groupInfo.packageName, label, icon); + groups.add(group); + } + + + // Make sure we add groups for lone runtime permissions. + List installedPackages = getContext().getPackageManager() + .getInstalledPackages(PackageManager.GET_PERMISSIONS); + + + // We will filter out permissions that no package requests. + Set requestedPermissions = new ArraySet<>(); + for (PackageInfo installedPackage : installedPackages) { + if (installedPackage.requestedPermissions == null) { + break; + } + for (String requestedPermission : installedPackage.requestedPermissions) { + requestedPermissions.add(requestedPermission); + } + } + + for (PackageInfo installedPackage : installedPackages) { + if (installedPackage.permissions == null) { + continue; + } + + for (PermissionInfo permissionInfo : installedPackage.permissions) { + // If we have handled this permission, no more work to do. + if (!seenPermissions.add(permissionInfo.name)) { + continue; + } + + // We care only about runtime permissions. + if (permissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) { + continue; + } + + // If no app uses this permission, + if (!requestedPermissions.contains(permissionInfo.name)) { + continue; + } + + CharSequence label = loadItemInfoLabel(permissionInfo); + Drawable icon = loadItemInfoIcon(permissionInfo); + + // Create the group and add to the list. + PermissionGroup group = new PermissionGroup(permissionInfo.name, + permissionInfo.packageName, label, icon); + groups.add(group); + } + } + + Collections.sort(groups); + return groups; + } + + private CharSequence loadItemInfoLabel(PackageItemInfo itemInfo) { + CharSequence label = itemInfo.loadLabel(getContext().getPackageManager()); + if (label == null) { + label = itemInfo.name; + } + return label; + } + + private Drawable loadItemInfoIcon(PackageItemInfo itemInfo) { + final Drawable icon; + if (itemInfo.icon > 0) { + icon = Utils.loadDrawable(getContext().getPackageManager(), + itemInfo.packageName, itemInfo.icon); + } else { + icon = getContext().getDrawable(R.drawable.ic_perm_device_info); + } + return icon; + } + } +} diff --git a/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java index e1dd85a6..79e66246 100644 --- a/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java +++ b/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java @@ -38,6 +38,8 @@ import android.preference.SwitchPreference; import android.provider.Settings; import android.util.Log; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -47,8 +49,8 @@ import android.widget.TextView; import android.widget.Toast; import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.model.AppPermissionGroup; import com.android.packageinstaller.permission.model.AppPermissions; -import com.android.packageinstaller.permission.model.PermissionGroup; import com.android.packageinstaller.permission.utils.SafetyNetLogger; import com.android.packageinstaller.permission.utils.Utils; @@ -64,11 +66,12 @@ public final class AppPermissionsFragment extends SettingsWithHeader private static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton"; - private List mToggledGroups; + private List mToggledGroups; private AppPermissions mAppPermissions; private PreferenceScreen mExtraScreen; private boolean mHasConfirmedRevoke; + private boolean mShowLegacyPermissions; public static AppPermissionsFragment newInstance(String packageName) { AppPermissionsFragment instance = new AppPermissionsFragment(); @@ -98,9 +101,16 @@ public final class AppPermissionsFragment extends SettingsWithHeader @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case android.R.id.home: + case android.R.id.home: { getActivity().finish(); return true; + } + + case R.id.toggle_legacy_permissions: { + mShowLegacyPermissions = !mShowLegacyPermissions; + updatePermissionsUi(); + return true; + } } return super.onOptionsItemSelected(item); } @@ -124,6 +134,23 @@ public final class AppPermissionsFragment extends SettingsWithHeader public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); bindUi(); + updatePermissionsUi(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.toggle_legacy_permissions, menu); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + MenuItem item = menu.findItem(R.id.toggle_legacy_permissions); + if (!mShowLegacyPermissions) { + item.setTitle(R.string.show_legacy_permissions); + } else { + item.setTitle(R.string.hide_legacy_permissions); + } } private void bindUi() { @@ -162,7 +189,6 @@ public final class AppPermissionsFragment extends SettingsWithHeader breadcrumbView.setText(label); } - PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(activity); mAppPermissions = new AppPermissions(activity, packageInfo, null, new Runnable() { @Override public void run() { @@ -170,11 +196,26 @@ public final class AppPermissionsFragment extends SettingsWithHeader } }); + PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(activity); + setPreferenceScreen(screen); + } + + private void updatePermissionsUi() { + final Activity activity = getActivity(); + + if (activity == null) { + return; + } + + final PreferenceScreen screen = getPreferenceScreen(); + screen.removeAll(); + mExtraScreen = null; + final Preference extraPerms = new Preference(activity); extraPerms.setIcon(R.drawable.ic_toc); extraPerms.setTitle(R.string.additional_permissions); - for (PermissionGroup group : mAppPermissions.getPermissionGroups()) { + 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()) { @@ -188,14 +229,20 @@ public final class AppPermissionsFragment extends SettingsWithHeader continue; } + // Show legacy permissions only if the user chose that. + if (!mShowLegacyPermissions && !Utils.isModernPermissionGroup(group.getName())) { + continue; + } + SwitchPreference preference = new SwitchPreference(activity); preference.setOnPreferenceChangeListener(this); preference.setKey(group.getName()); - preference.setIcon(Utils.loadDrawable(pm, group.getIconPkg(), - group.getIconResId())); + preference.setIcon(Utils.loadDrawable(activity.getPackageManager(), + group.getIconPkg(), group.getIconResId())); preference.setTitle(group.getLabel()); preference.setPersistent(false); preference.setEnabled(!group.isPolicyFixed()); + if (group.getIconPkg().equals(OS_PKG)) { screen.addPreference(preference); } else { @@ -205,6 +252,7 @@ public final class AppPermissionsFragment extends SettingsWithHeader mExtraScreen.addPreference(preference); } } + if (mExtraScreen != null) { extraPerms.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override @@ -222,14 +270,12 @@ public final class AppPermissionsFragment extends SettingsWithHeader mExtraScreen.getPreferenceCount())); screen.addPreference(extraPerms); } - - setPreferenceScreen(screen); } @Override public boolean onPreferenceChange(final Preference preference, Object newValue) { String groupName = preference.getKey(); - final PermissionGroup group = mAppPermissions.getPermissionGroup(groupName); + final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName); if (group == null) { return false; @@ -269,7 +315,7 @@ public final class AppPermissionsFragment extends SettingsWithHeader logToggledGroups(); } - private void addToggledGroup(PermissionGroup group) { + private void addToggledGroup(AppPermissionGroup group) { if (mToggledGroups == null) { mToggledGroups = new ArrayList<>(); } @@ -304,7 +350,7 @@ public final class AppPermissionsFragment extends SettingsWithHeader Preference preference = screen.getPreference(i); if (preference instanceof SwitchPreference) { SwitchPreference switchPref = (SwitchPreference) preference; - PermissionGroup group = mAppPermissions + AppPermissionGroup group = mAppPermissions .getPermissionGroup(switchPref.getKey()); if (group != null) { switchPref.setChecked(group.areRuntimePermissionsGranted()); diff --git a/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java b/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java index b406db18..6f824ea0 100644 --- a/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java +++ b/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java @@ -34,9 +34,9 @@ import android.util.Log; import android.util.SparseArray; import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.model.AppPermissionGroup; import com.android.packageinstaller.permission.model.AppPermissions; import com.android.packageinstaller.permission.model.Permission; -import com.android.packageinstaller.permission.model.PermissionGroup; import com.android.packageinstaller.permission.utils.SafetyNetLogger; import java.util.ArrayList; @@ -100,7 +100,7 @@ public class GrantPermissionsActivity extends Activity implements } }); - for (PermissionGroup group : mAppPermissions.getPermissionGroups()) { + for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) { // We allow the user to choose only non-fixed permissions. A permission // is fixed either by device policy or the user denying with prejudice. if (!group.areRuntimePermissionsGranted() && @@ -201,7 +201,7 @@ public class GrantPermissionsActivity extends Activity implements } } - private void updateGrantResults(PermissionGroup group) { + private void updateGrantResults(AppPermissionGroup group) { for (Permission permission : group.getPermissions()) { if (permission.isGranted()) { final int index = ArrayUtils.getArrayIndex( @@ -307,7 +307,7 @@ public class GrantPermissionsActivity extends Activity implements } final int groupCount = mRequestGrantPermissionGroups.size(); - List groups = new ArrayList<>(groupCount); + List groups = new ArrayList<>(groupCount); for (int i = 0; i < groupCount; i++) { groups.add(mRequestGrantPermissionGroups.valueAt(i).mGroup); } @@ -320,10 +320,10 @@ public class GrantPermissionsActivity extends Activity implements public static final int STATE_ALLOWED = 1; public static final int STATE_DENIED = 2; - public final PermissionGroup mGroup; + public final AppPermissionGroup mGroup; public int mState = STATE_UNKNOWN; - public GroupState(PermissionGroup group) { + public GroupState(AppPermissionGroup group) { mGroup = group; } } diff --git a/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java b/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java index ef117a9c..2fc1a02b 100644 --- a/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java +++ b/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java @@ -31,26 +31,37 @@ public final class ManagePermissionsActivity extends Activity { Fragment fragment = null; String action = getIntent().getAction(); - if (Intent.ACTION_MANAGE_APP_PERMISSIONS.equals(action)) { - String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME); - if (packageName == null) { - Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PACKAGE_NAME"); - finish(); - return; - } - fragment = AppPermissionsFragment.newInstance(packageName); - } else if (Intent.ACTION_MANAGE_PERMISSION_APPS.equals(action)) { - String permissionName = getIntent().getStringExtra(Intent.EXTRA_PERMISSION_NAME); - if (permissionName == null) { - Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PERMISSION_NAME"); + + switch (action) { + case Intent.ACTION_MANAGE_PERMISSIONS: { + fragment = ManagePermissionsFragment.newInstance(); + } break; + + case Intent.ACTION_MANAGE_APP_PERMISSIONS: { + String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME); + if (packageName == null) { + Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PACKAGE_NAME"); + finish(); + return; + } + fragment = AppPermissionsFragment.newInstance(packageName); + } break; + + case Intent.ACTION_MANAGE_PERMISSION_APPS: { + String permissionName = getIntent().getStringExtra(Intent.EXTRA_PERMISSION_NAME); + if (permissionName == null) { + Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PERMISSION_NAME"); + finish(); + return; + } + fragment = PermissionAppsFragment.newInstance(permissionName); + } break; + + default: { + Log.w(LOG_TAG, "Unrecognized action " + action); finish(); return; } - fragment = PermissionAppsFragment.newInstance(permissionName); - } else { - Log.w(LOG_TAG, "Unrecognized action " + action); - finish(); - return; } getFragmentManager().beginTransaction().replace(android.R.id.content, fragment).commit(); diff --git a/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java new file mode 100644 index 00000000..5158b5e8 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java @@ -0,0 +1,216 @@ +/* + * 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.ui; + +import android.app.ActionBar; +import android.app.Activity; +import android.app.FragmentTransaction; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceFragment; +import android.preference.PreferenceScreen; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; + +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.model.PermissionGroup; +import com.android.packageinstaller.permission.model.PermissionGroups; +import com.android.packageinstaller.permission.utils.Utils; + +import java.util.List; + +public final class ManagePermissionsFragment extends PreferenceFragment + implements PermissionGroups.PermissionsGroupsChangeCallback, OnPreferenceClickListener { + private static final String LOG_TAG = "ManagePermissionsFragment"; + + private static final String OS_PKG = "android"; + + private PermissionGroups mPermissions; + + private PreferenceScreen mExtraScreen; + + private boolean mShowLegacyPermissions; + + public static ManagePermissionsFragment newInstance() { + return new ManagePermissionsFragment(); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setHasOptionsMenu(true); + final ActionBar ab = getActivity().getActionBar(); + if (ab != null) { + ab.setDisplayHomeAsUpEnabled(true); + } + mPermissions = new PermissionGroups(getActivity(), getLoaderManager(), this); + } + + @Override + public void onResume() { + super.onResume(); + mPermissions.refresh(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.toggle_legacy_permissions, menu); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + MenuItem item = menu.findItem(R.id.toggle_legacy_permissions); + if (!mShowLegacyPermissions) { + item.setTitle(R.string.show_legacy_permissions); + } else { + item.setTitle(R.string.hide_legacy_permissions); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: { + getActivity().finish(); + return true; + } + + case R.id.toggle_legacy_permissions: { + mShowLegacyPermissions = !mShowLegacyPermissions; + updatePermissionsUi(); + return true; + } + } + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + String key = preference.getKey(); + + PermissionGroup group = mPermissions.getGroup(key); + if (group == null) { + return false; + } + + Intent intent = new Intent(Intent.ACTION_MANAGE_PERMISSION_APPS) + .putExtra(Intent.EXTRA_PERMISSION_NAME, key); + try { + getActivity().startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.w(LOG_TAG, "No app to handle " + intent); + } + + return true; + } + + @Override + public void onPermissionGroupsChanged() { + updatePermissionsUi(); + } + + private void updatePermissionsUi() { + Activity activity = getActivity(); + + if (activity == null) { + return; + } + + List groups = mPermissions.getGroups(); + + PreferenceScreen screen = getPreferenceScreen(); + if (screen == null) { + screen = getPreferenceManager().createPreferenceScreen(activity); + setPreferenceScreen(screen); + } else { + screen.removeAll(); + if (mExtraScreen != null) { + mExtraScreen.removeAll(); + } + } + + for (PermissionGroup group : groups) { + // Show legacy permissions only if the user chose that. + if (!mShowLegacyPermissions && group.getDeclaringPackage().equals(OS_PKG) + && !Utils.isModernPermissionGroup(group.getName())) { + continue; + } + + Preference preference = new Preference(activity); + preference.setOnPreferenceClickListener(this); + preference.setKey(group.getName()); + preference.setIcon(group.getIcon()); + preference.setTitle(group.getLabel()); + preference.setPersistent(false); + + if (group.getDeclaringPackage().equals(OS_PKG)) { + screen.addPreference(preference); + } else { + if (mExtraScreen == null) { + mExtraScreen = getPreferenceManager().createPreferenceScreen(activity); + } + mExtraScreen.addPreference(preference); + } + } + + if (mExtraScreen != null && mExtraScreen.getPreferenceCount() > 0) { + Preference extraScreenPreference = new Preference(activity); + extraScreenPreference.setIcon(R.drawable.ic_toc); + extraScreenPreference.setTitle(R.string.additional_permissions); + extraScreenPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment(); + frag.setTargetFragment(ManagePermissionsFragment.this, 0); + FragmentTransaction ft = getFragmentManager().beginTransaction(); + ft.replace(android.R.id.content, frag); + ft.addToBackStack("AdditionalPerms"); + ft.commit(); + return true; + } + }); + extraScreenPreference.setSummary(getString(R.string.additional_permissions_more, + mExtraScreen.getPreferenceCount())); + screen.addPreference(extraScreenPreference); + } + } + + public static class AdditionalPermissionsFragment extends SettingsWithHeader { + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setPreferenceScreen(((ManagePermissionsFragment) getTargetFragment()).mExtraScreen); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Resources resources = getResources(); + Theme theme = getActivity().getTheme(); + setHeader(resources.getDrawable(R.drawable.ic_toc, theme), + getString(R.string.additional_permissions), null); + } + } +} diff --git a/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java b/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java index d1d7c87e..cbfcc528 100644 --- a/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java +++ b/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java @@ -37,10 +37,10 @@ import android.widget.ImageView; import android.widget.TextView; import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.model.AppPermissionGroup; import com.android.packageinstaller.permission.model.PermissionApps; import com.android.packageinstaller.permission.model.PermissionApps.Callback; import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp; -import com.android.packageinstaller.permission.model.PermissionGroup; import com.android.packageinstaller.permission.utils.SafetyNetLogger; import java.util.ArrayList; @@ -59,7 +59,7 @@ public final class PermissionAppsFragment extends SettingsWithHeader implements private PermissionApps mPermissionApps; - private ArrayMap mToggledGroups; + private ArrayMap mToggledGroups; private boolean mHasConfirmedRevoke; @Override @@ -136,6 +136,11 @@ public final class PermissionAppsFragment extends SettingsWithHeader implements @Override public void onPermissionsLoaded() { Context context = getActivity(); + + if (context == null) { + return; + } + PreferenceScreen preferences = getPreferenceScreen(); if (preferences == null) { preferences = getPreferenceManager().createPreferenceScreen(getActivity()); @@ -212,7 +217,7 @@ public final class PermissionAppsFragment extends SettingsWithHeader implements logToggledGroups(); } - private void addToggledGroup(String packageName, PermissionGroup group) { + private void addToggledGroup(String packageName, AppPermissionGroup group) { if (mToggledGroups == null) { mToggledGroups = new ArrayMap<>(); } @@ -229,7 +234,7 @@ public final class PermissionAppsFragment extends SettingsWithHeader implements final int groupCount = mToggledGroups.size(); for (int i = 0; i < groupCount; i++) { String packageName = mToggledGroups.keyAt(i); - List groups = new ArrayList<>(); + List groups = new ArrayList<>(); groups.add(mToggledGroups.valueAt(i)); SafetyNetLogger.logPermissionsToggled(packageName, groups); } diff --git a/src/com/android/packageinstaller/permission/utils/SafetyNetLogger.java b/src/com/android/packageinstaller/permission/utils/SafetyNetLogger.java index 0163b750..8280ba36 100644 --- a/src/com/android/packageinstaller/permission/utils/SafetyNetLogger.java +++ b/src/com/android/packageinstaller/permission/utils/SafetyNetLogger.java @@ -18,7 +18,7 @@ package com.android.packageinstaller.permission.utils; import android.content.pm.PackageInfo; import android.util.EventLog; -import com.android.packageinstaller.permission.model.PermissionGroup; +import com.android.packageinstaller.permission.model.AppPermissionGroup; import java.util.List; @@ -38,27 +38,27 @@ public final class SafetyNetLogger { } public static void logPermissionsRequested(PackageInfo packageInfo, - List groups) { + List groups) { EventLog.writeEvent(SNET_NET_EVENT_LOG_TAG, PERMISSIONS_REQUESTED, packageInfo.applicationInfo.uid, buildChangedGroupForPackageMessage( packageInfo.packageName, groups)); } - public static void logPermissionsToggled(String packageName, List groups) { + public static void logPermissionsToggled(String packageName, List groups) { EventLog.writeEvent(SNET_NET_EVENT_LOG_TAG, PERMISSIONS_TOGGLED, android.os.Process.myUid(), buildChangedGroupForPackageMessage( packageName, groups)); } private static String buildChangedGroupForPackageMessage(String packageName, - List groups) { + List groups) { StringBuilder builder = new StringBuilder(); builder.append(packageName).append(':'); final int groupCount = groups.size(); for (int i = 0; i < groupCount; i++) { - PermissionGroup group = groups.get(i); + AppPermissionGroup group = groups.get(i); if (i > 0) { builder.append(';'); } diff --git a/src/com/android/packageinstaller/permission/utils/Utils.java b/src/com/android/packageinstaller/permission/utils/Utils.java index 0dea8f34..7abb2627 100644 --- a/src/com/android/packageinstaller/permission/utils/Utils.java +++ b/src/com/android/packageinstaller/permission/utils/Utils.java @@ -16,6 +16,7 @@ package com.android.packageinstaller.permission.utils; +import android.Manifest; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -36,4 +37,23 @@ public class Utils { return null; } } + + 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: { + return true; + } + + default: { + return false; + } + } + } } -- cgit v1.2.3