1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.permissioncontroller.permission.model; 18 19 import android.content.Context; 20 import android.content.pm.PackageInfo; 21 import android.content.pm.PackageManager; 22 import android.os.UserHandle; 23 import android.util.ArrayMap; 24 25 import com.android.permissioncontroller.permission.utils.Utils; 26 27 import java.util.ArrayList; 28 import java.util.Collections; 29 import java.util.List; 30 31 /** 32 * An app that requests permissions. 33 * 34 * <p>Allows to query all permission groups of the app and which permission belongs to which group. 35 */ 36 public final class AppPermissions { 37 /** 38 * All permission groups the app requests. Background permission groups are attached to their 39 * foreground groups. 40 */ 41 private final ArrayList<AppPermissionGroup> mGroups = new ArrayList<>(); 42 43 /** Cache: group name -> group */ 44 private final ArrayMap<String, AppPermissionGroup> mGroupNameToGroup = new ArrayMap<>(); 45 46 /** Cache: permission name -> group. Might point to background group */ 47 private final ArrayMap<String, AppPermissionGroup> mPermissionNameToGroup = new ArrayMap<>(); 48 49 private final Context mContext; 50 51 private final CharSequence mAppLabel; 52 53 private final Runnable mOnErrorCallback; 54 55 private final boolean mSortGroups; 56 57 /** Do not actually commit changes to the platform until {@link #persistChanges} is called */ 58 private final boolean mDelayChanges; 59 60 private PackageInfo mPackageInfo; 61 AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups, Runnable onErrorCallback)62 public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups, 63 Runnable onErrorCallback) { 64 this(context, packageInfo, sortGroups, false, onErrorCallback); 65 } 66 AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups, boolean delayChanges, Runnable onErrorCallback)67 public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups, 68 boolean delayChanges, Runnable onErrorCallback) { 69 mContext = context; 70 mPackageInfo = packageInfo; 71 mAppLabel = Utils.getAppLabel(packageInfo.applicationInfo, context); 72 mSortGroups = sortGroups; 73 mDelayChanges = delayChanges; 74 mOnErrorCallback = onErrorCallback; 75 loadPermissionGroups(); 76 } 77 getPackageInfo()78 public PackageInfo getPackageInfo() { 79 return mPackageInfo; 80 } 81 refresh()82 public void refresh() { 83 loadPackageInfo(); 84 loadPermissionGroups(); 85 } 86 getAppLabel()87 public CharSequence getAppLabel() { 88 return mAppLabel; 89 } 90 getPermissionGroup(String name)91 public AppPermissionGroup getPermissionGroup(String name) { 92 return mGroupNameToGroup.get(name); 93 } 94 getPermissionGroups()95 public List<AppPermissionGroup> getPermissionGroups() { 96 return mGroups; 97 } 98 isReviewRequired()99 public boolean isReviewRequired() { 100 final int groupCount = mGroups.size(); 101 for (int i = 0; i < groupCount; i++) { 102 AppPermissionGroup group = mGroups.get(i); 103 if (group.isReviewRequired()) { 104 return true; 105 } 106 } 107 return false; 108 } 109 loadPackageInfo()110 private void loadPackageInfo() { 111 try { 112 mPackageInfo = mContext.createPackageContextAsUser(mPackageInfo.packageName, 0, 113 UserHandle.getUserHandleForUid(mPackageInfo.applicationInfo.uid)) 114 .getPackageManager().getPackageInfo(mPackageInfo.packageName, 115 PackageManager.GET_PERMISSIONS); 116 } catch (PackageManager.NameNotFoundException e) { 117 if (mOnErrorCallback != null) { 118 mOnErrorCallback.run(); 119 } 120 } 121 } 122 123 /** 124 * Add all individual permissions of the {@code group} to the {@link #mPermissionNameToGroup} 125 * lookup table. 126 * 127 * @param group The group of permissions to add 128 */ addAllPermissions(AppPermissionGroup group)129 private void addAllPermissions(AppPermissionGroup group) { 130 ArrayList<Permission> perms = group.getPermissions(); 131 132 int numPerms = perms.size(); 133 for (int permNum = 0; permNum < numPerms; permNum++) { 134 mPermissionNameToGroup.put(perms.get(permNum).getName(), group); 135 } 136 } 137 loadPermissionGroups()138 private void loadPermissionGroups() { 139 mGroups.clear(); 140 mGroupNameToGroup.clear(); 141 mPermissionNameToGroup.clear(); 142 143 if (mPackageInfo.requestedPermissions != null) { 144 for (String requestedPerm : mPackageInfo.requestedPermissions) { 145 if (getGroupForPermission(requestedPerm) == null) { 146 AppPermissionGroup group = AppPermissionGroup.create(mContext, mPackageInfo, 147 requestedPerm, mDelayChanges); 148 if (group == null) { 149 continue; 150 } 151 152 mGroups.add(group); 153 mGroupNameToGroup.put(group.getName(), group); 154 155 addAllPermissions(group); 156 157 AppPermissionGroup backgroundGroup = group.getBackgroundPermissions(); 158 if (backgroundGroup != null) { 159 addAllPermissions(backgroundGroup); 160 } 161 } 162 } 163 164 if (mSortGroups) { 165 Collections.sort(mGroups); 166 } 167 } 168 } 169 170 /** 171 * Find the group a permission belongs to. 172 * 173 * <p>The group found might be a background group. 174 * 175 * @param permission The name of the permission 176 * 177 * @return The group the permission belongs to 178 */ getGroupForPermission(String permission)179 public AppPermissionGroup getGroupForPermission(String permission) { 180 return mPermissionNameToGroup.get(permission); 181 } 182 183 /** 184 * If the changes to the permission groups were delayed, persist them now. 185 * 186 * @param mayKillBecauseOfAppOpsChange If the app may be killed if app ops change. If this is 187 * set to {@code false} the caller has to make sure to kill 188 * the app if needed. 189 */ persistChanges(boolean mayKillBecauseOfAppOpsChange)190 public void persistChanges(boolean mayKillBecauseOfAppOpsChange) { 191 if (mDelayChanges) { 192 int numGroups = mGroups.size(); 193 194 for (int i = 0; i < numGroups; i++) { 195 AppPermissionGroup group = mGroups.get(i); 196 group.persistChanges(mayKillBecauseOfAppOpsChange); 197 198 AppPermissionGroup backgroundGroup = group.getBackgroundPermissions(); 199 if (backgroundGroup != null) { 200 backgroundGroup.persistChanges(mayKillBecauseOfAppOpsChange); 201 } 202 } 203 } 204 } 205 } 206