summaryrefslogtreecommitdiffstats
path: root/src/com/android/packageinstaller/permission/model/PermissionGroup.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/packageinstaller/permission/model/PermissionGroup.java')
-rw-r--r--src/com/android/packageinstaller/permission/model/PermissionGroup.java380
1 files changed, 380 insertions, 0 deletions
diff --git a/src/com/android/packageinstaller/permission/model/PermissionGroup.java b/src/com/android/packageinstaller/permission/model/PermissionGroup.java
new file mode 100644
index 00000000..9a5291d8
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/model/PermissionGroup.java
@@ -0,0 +1,380 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+public final class PermissionGroup implements Comparable<PermissionGroup> {
+ 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 AppOpsManager mAppOps;
+ private final ActivityManager mActivityManager;
+
+ private final PackageInfo mPackageInfo;
+ private final String mName;
+ private final CharSequence mLabel;
+ private final ArrayMap<String, Permission> 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<PermissionInfo> 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<PermissionInfo> 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;
+ final boolean appOpAllowed;
+
+ if (group.mAppSupportsRuntimePermissions) {
+ appOp = AppOpsManager.OP_NONE;
+ appOpAllowed = false;
+ } else {
+ appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)
+ ? AppOpsManager.permissionToOpCode(requestedPermissionInfo.name)
+ : AppOpsManager.OP_NONE;
+ appOpAllowed = appOp != AppOpsManager.OP_NONE
+ && context.getSystemService(AppOpsManager.class).checkOp(appOp,
+ packageInfo.applicationInfo.uid, packageInfo.packageName)
+ == AppOpsManager.MODE_ALLOWED;
+ }
+
+ Permission permission = new Permission(requestedPermission, granted,
+ appOp, appOpAllowed);
+ group.addPermission(permission);
+ }
+
+ return group;
+ }
+
+ private PermissionGroup(Context context, PackageInfo packageInfo, String name,
+ CharSequence label, String iconPkg, int iconResId) {
+ mContext = context;
+ mPackageInfo = packageInfo;
+ mAppSupportsRuntimePermissions = packageInfo.applicationInfo
+ .targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
+ mAppOps = context.getSystemService(AppOpsManager.class);
+ mActivityManager = context.getSystemService(ActivityManager.class);
+ mName = name;
+ mLabel = label;
+ mIconPkg = iconPkg;
+ mIconResId = iconResId;
+ }
+
+ public PackageInfo getApp() {
+ return mPackageInfo;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ 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() {
+ 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) {
+ if (!permission.isGranted()) {
+ mContext.getPackageManager().grantPermission(mPackageInfo.packageName,
+ permission.getName(), new UserHandle(mContext.getUserId()));
+ permission.setGranted(true);
+ }
+ } 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.getAppOp() == AppOpsManager.OP_NONE) {
+ continue;
+ }
+
+ if (!permission.isAppOpAllowed()) {
+ // 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) {
+ String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+ for (String packageName : packageNames) {
+ mAppOps.setMode(permission.getAppOp(), uid, packageName,
+ AppOpsManager.MODE_ALLOWED);
+ }
+ } else {
+ mAppOps.setMode(permission.getAppOp(), uid, mPackageInfo.packageName,
+ AppOpsManager.MODE_ALLOWED);
+ }
+
+ // 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);
+
+ permission.setAppOpAllowed(true);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public boolean revokeRuntimePermissions() {
+ 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) {
+ if (permission.isGranted()) {
+ mContext.getPackageManager().revokePermission(mPackageInfo.packageName,
+ permission.getName(), new UserHandle(mContext.getUserId()));
+ permission.setGranted(false);
+ }
+ } 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.getAppOp() == AppOpsManager.OP_NONE) {
+ continue;
+ }
+
+ if (permission.isAppOpAllowed()) {
+ // 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 = mContext.getPackageManager().getPackagesForUid(uid);
+ for (String packageName : packageNames) {
+ mAppOps.setMode(permission.getAppOp(), uid,
+ packageName, AppOpsManager.MODE_IGNORED);
+ }
+ } else {
+ mAppOps.setMode(permission.getAppOp(), uid,
+ mPackageInfo.packageName, AppOpsManager.MODE_IGNORED);
+ }
+
+ // 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);
+
+ permission.setAppOpAllowed(false);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public List<Permission> getPermissions() {
+ return new ArrayList<>(mPermissions.values());
+ }
+
+ @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;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null) {
+ return false;
+ }
+
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ PermissionGroup other = (PermissionGroup) 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(", <has permissions>}");
+ } else {
+ builder.append('}');
+ }
+ return builder.toString();
+ }
+
+ void addPermission(Permission permission) {
+ mPermissions.put(permission.getName(), permission);
+ }
+}
+