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 import java.util.Set; 31 32 /** 33 * An app that requests permissions. 34 * 35 * <p>Allows to query all permission groups of the app and which permission belongs to which group. 36 */ 37 public final class AppPermissions { 38 /** 39 * All permission groups the app requests. Background permission groups are attached to their 40 * foreground groups. 41 */ 42 private final ArrayList<AppPermissionGroup> mGroups = new ArrayList<>(); 43 44 /** Cache: group name -> group */ 45 private final ArrayMap<String, AppPermissionGroup> mGroupNameToGroup = new ArrayMap<>(); 46 47 /** Cache: permission name -> group. Might point to background group */ 48 private final ArrayMap<String, AppPermissionGroup> mPermissionNameToGroup = new ArrayMap<>(); 49 50 private final Context mContext; 51 52 private final CharSequence mAppLabel; 53 54 private final Runnable mOnErrorCallback; 55 56 private final boolean mSortGroups; 57 58 /** Do not actually commit changes to the platform until {@link #persistChanges} is called */ 59 private final boolean mDelayChanges; 60 61 private PackageInfo mPackageInfo; 62 AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups, Runnable onErrorCallback)63 public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups, 64 Runnable onErrorCallback) { 65 this(context, packageInfo, sortGroups, false, onErrorCallback); 66 } 67 AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups, boolean delayChanges, Runnable onErrorCallback)68 public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups, 69 boolean delayChanges, Runnable onErrorCallback) { 70 mContext = context; 71 mPackageInfo = packageInfo; 72 mAppLabel = Utils.getAppLabel(packageInfo.applicationInfo, context); 73 mSortGroups = sortGroups; 74 mDelayChanges = delayChanges; 75 mOnErrorCallback = onErrorCallback; 76 loadPermissionGroups(); 77 } 78 getPackageInfo()79 public PackageInfo getPackageInfo() { 80 return mPackageInfo; 81 } 82 refresh()83 public void refresh() { 84 loadPackageInfo(); 85 loadPermissionGroups(); 86 } 87 getAppLabel()88 public CharSequence getAppLabel() { 89 return mAppLabel; 90 } 91 getPermissionGroup(String name)92 public AppPermissionGroup getPermissionGroup(String name) { 93 return mGroupNameToGroup.get(name); 94 } 95 getPermissionGroups()96 public List<AppPermissionGroup> getPermissionGroups() { 97 return mGroups; 98 } 99 isReviewRequired()100 public boolean isReviewRequired() { 101 final int groupCount = mGroups.size(); 102 for (int i = 0; i < groupCount; i++) { 103 AppPermissionGroup group = mGroups.get(i); 104 if (group.isReviewRequired()) { 105 return true; 106 } 107 } 108 return false; 109 } 110 loadPackageInfo()111 private void loadPackageInfo() { 112 try { 113 mPackageInfo = mContext.createPackageContextAsUser(mPackageInfo.packageName, 0, 114 UserHandle.getUserHandleForUid(mPackageInfo.applicationInfo.uid)) 115 .getPackageManager().getPackageInfo(mPackageInfo.packageName, 116 PackageManager.GET_PERMISSIONS); 117 } catch (PackageManager.NameNotFoundException e) { 118 if (mOnErrorCallback != null) { 119 mOnErrorCallback.run(); 120 } 121 } 122 } 123 124 /** 125 * Add all individual permissions of the {@code group} to the {@link #mPermissionNameToGroup} 126 * lookup table. 127 * 128 * @param group The group of permissions to add 129 */ addAllPermissions(AppPermissionGroup group)130 private void addAllPermissions(AppPermissionGroup group) { 131 ArrayList<Permission> perms = group.getPermissions(); 132 133 int numPerms = perms.size(); 134 for (int permNum = 0; permNum < numPerms; permNum++) { 135 mPermissionNameToGroup.put(perms.get(permNum).getName(), group); 136 } 137 } 138 loadPermissionGroups()139 private void loadPermissionGroups() { 140 mGroups.clear(); 141 mGroupNameToGroup.clear(); 142 mPermissionNameToGroup.clear(); 143 144 if (mPackageInfo.requestedPermissions != null) { 145 for (String requestedPerm : mPackageInfo.requestedPermissions) { 146 if (getGroupForPermission(requestedPerm) == null) { 147 AppPermissionGroup group = AppPermissionGroup.create(mContext, mPackageInfo, 148 requestedPerm, mDelayChanges); 149 if (group == null) { 150 continue; 151 } 152 153 mGroups.add(group); 154 mGroupNameToGroup.put(group.getName(), group); 155 156 addAllPermissions(group); 157 158 AppPermissionGroup backgroundGroup = group.getBackgroundPermissions(); 159 if (backgroundGroup != null) { 160 addAllPermissions(backgroundGroup); 161 } 162 } 163 } 164 165 if (mSortGroups) { 166 Collections.sort(mGroups); 167 } 168 } 169 } 170 171 /** 172 * Find the group a permission belongs to. 173 * 174 * <p>The group found might be a background group. 175 * 176 * @param permission The name of the permission 177 * 178 * @return The group the permission belongs to 179 */ getGroupForPermission(String permission)180 public AppPermissionGroup getGroupForPermission(String permission) { 181 return mPermissionNameToGroup.get(permission); 182 } 183 184 /** 185 * If the changes to the permission groups were delayed, persist them now. 186 * 187 * @param mayKillBecauseOfAppOpsChange If the app may be killed if app ops change. If this is 188 * set to {@code false} the caller has to make sure to kill 189 * the app if needed. 190 */ persistChanges(boolean mayKillBecauseOfAppOpsChange)191 public void persistChanges(boolean mayKillBecauseOfAppOpsChange) { 192 persistChanges(mayKillBecauseOfAppOpsChange, null); 193 } 194 195 /** 196 * If the changes to the permission groups were delayed, persist them now. 197 * 198 * @param mayKillBecauseOfAppOpsChange If the app may be killed if app ops change. If this is 199 * set to {@code false} the caller has to make sure to kill 200 * the app if needed. 201 * @param filterPermissions If provided, only persist state for the given permissions 202 */ persistChanges(boolean mayKillBecauseOfAppOpsChange, Set<String> filterPermissions)203 public void persistChanges(boolean mayKillBecauseOfAppOpsChange, 204 Set<String> filterPermissions) { 205 if (mDelayChanges) { 206 int numGroups = mGroups.size(); 207 208 for (int i = 0; i < numGroups; i++) { 209 AppPermissionGroup group = mGroups.get(i); 210 group.persistChanges(mayKillBecauseOfAppOpsChange, null, filterPermissions); 211 212 AppPermissionGroup backgroundGroup = group.getBackgroundPermissions(); 213 if (backgroundGroup != null) { 214 backgroundGroup.persistChanges(mayKillBecauseOfAppOpsChange, null, 215 filterPermissions); 216 } 217 } 218 } 219 } 220 } 221