1 /* 2 * Copyright (C) 2019 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.server.policy; 18 19 import static android.Manifest.permission.READ_EXTERNAL_STORAGE; 20 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; 21 import static android.app.AppOpsManager.MODE_ALLOWED; 22 import static android.app.AppOpsManager.MODE_DEFAULT; 23 import static android.app.AppOpsManager.MODE_IGNORED; 24 import static android.app.AppOpsManager.OP_LEGACY_STORAGE; 25 import static android.app.AppOpsManager.OP_NONE; 26 import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; 27 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; 28 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; 29 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; 30 31 import static java.lang.Integer.min; 32 33 import android.annotation.NonNull; 34 import android.annotation.Nullable; 35 import android.app.AppOpsManager; 36 import android.content.Context; 37 import android.content.pm.ApplicationInfo; 38 import android.content.pm.PackageManager; 39 import android.os.Build; 40 import android.os.UserHandle; 41 42 /** 43 * The behavior of soft restricted permissions is different for each permission. This class collects 44 * the policies in one place. 45 * 46 * This is the twin of 47 * {@link com.android.packageinstaller.permission.utils.SoftRestrictedPermissionPolicy} 48 */ 49 public abstract class SoftRestrictedPermissionPolicy { 50 private static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT = 51 FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT 52 | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT 53 | FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; 54 55 private static final SoftRestrictedPermissionPolicy DUMMY_POLICY = 56 new SoftRestrictedPermissionPolicy() { 57 @Override 58 public int resolveAppOp() { 59 return OP_NONE; 60 } 61 62 @Override 63 public int getDesiredOpMode() { 64 return MODE_DEFAULT; 65 } 66 67 @Override 68 public boolean shouldSetAppOpIfNotDefault() { 69 return false; 70 } 71 72 @Override 73 public boolean canBeGranted() { 74 return true; 75 } 76 }; 77 78 /** 79 * TargetSDK is per package. To make sure two apps int the same shared UID do not fight over 80 * what to set, always compute the combined targetSDK. 81 * 82 * @param context A context 83 * @param appInfo The app that is changed 84 * @param user The user the app belongs to 85 * 86 * @return The minimum targetSDK of all apps sharing the uid of the app 87 */ getMinimumTargetSDK(@onNull Context context, @NonNull ApplicationInfo appInfo, @NonNull UserHandle user)88 private static int getMinimumTargetSDK(@NonNull Context context, 89 @NonNull ApplicationInfo appInfo, @NonNull UserHandle user) { 90 PackageManager pm = context.getPackageManager(); 91 92 int minimumTargetSDK = appInfo.targetSdkVersion; 93 94 String[] uidPkgs = pm.getPackagesForUid(appInfo.uid); 95 if (uidPkgs != null) { 96 for (String uidPkg : uidPkgs) { 97 if (!uidPkg.equals(appInfo.packageName)) { 98 ApplicationInfo uidPkgInfo; 99 try { 100 uidPkgInfo = pm.getApplicationInfoAsUser(uidPkg, 0, user); 101 } catch (PackageManager.NameNotFoundException e) { 102 continue; 103 } 104 105 minimumTargetSDK = min(minimumTargetSDK, uidPkgInfo.targetSdkVersion); 106 } 107 } 108 } 109 110 return minimumTargetSDK; 111 } 112 113 /** 114 * Get the policy for a soft restricted permission. 115 * 116 * @param context A context to use 117 * @param appInfo The application the permission belongs to. Can be {@code null}, but then 118 * only {@link #resolveAppOp} will work. 119 * @param user The user the app belongs to. Can be {@code null}, but then only 120 * {@link #resolveAppOp} will work. 121 * @param permission The name of the permission 122 * 123 * @return The policy for this permission 124 */ forPermission(@onNull Context context, @Nullable ApplicationInfo appInfo, @Nullable UserHandle user, @NonNull String permission)125 public static @NonNull SoftRestrictedPermissionPolicy forPermission(@NonNull Context context, 126 @Nullable ApplicationInfo appInfo, @Nullable UserHandle user, 127 @NonNull String permission) { 128 switch (permission) { 129 // Storage uses a special app op to decide the mount state and supports soft restriction 130 // where the restricted state allows the permission but only for accessing the medial 131 // collections. 132 case READ_EXTERNAL_STORAGE: { 133 final int flags; 134 final boolean applyRestriction; 135 final boolean isWhiteListed; 136 final boolean hasRequestedLegacyExternalStorage; 137 final int targetSDK; 138 139 if (appInfo != null) { 140 PackageManager pm = context.getPackageManager(); 141 flags = pm.getPermissionFlags(permission, appInfo.packageName, user); 142 applyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; 143 isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; 144 targetSDK = getMinimumTargetSDK(context, appInfo, user); 145 146 boolean hasAnyRequestedLegacyExternalStorage = 147 appInfo.hasRequestedLegacyExternalStorage(); 148 149 // hasRequestedLegacyExternalStorage is per package. To make sure two apps in 150 // the same shared UID do not fight over what to set, always compute the 151 // combined hasRequestedLegacyExternalStorage 152 String[] uidPkgs = pm.getPackagesForUid(appInfo.uid); 153 if (uidPkgs != null) { 154 for (String uidPkg : uidPkgs) { 155 if (!uidPkg.equals(appInfo.packageName)) { 156 ApplicationInfo uidPkgInfo; 157 try { 158 uidPkgInfo = pm.getApplicationInfoAsUser(uidPkg, 0, user); 159 } catch (PackageManager.NameNotFoundException e) { 160 continue; 161 } 162 163 hasAnyRequestedLegacyExternalStorage |= 164 uidPkgInfo.hasRequestedLegacyExternalStorage(); 165 } 166 } 167 } 168 169 hasRequestedLegacyExternalStorage = hasAnyRequestedLegacyExternalStorage; 170 } else { 171 flags = 0; 172 applyRestriction = false; 173 isWhiteListed = false; 174 hasRequestedLegacyExternalStorage = false; 175 targetSDK = 0; 176 } 177 178 return new SoftRestrictedPermissionPolicy() { 179 @Override 180 public int resolveAppOp() { 181 return OP_LEGACY_STORAGE; 182 } 183 184 @Override 185 public int getDesiredOpMode() { 186 if (applyRestriction) { 187 return MODE_DEFAULT; 188 } else if (hasRequestedLegacyExternalStorage) { 189 return MODE_ALLOWED; 190 } else { 191 return MODE_IGNORED; 192 } 193 } 194 195 @Override 196 public boolean shouldSetAppOpIfNotDefault() { 197 // Do not switch from allowed -> ignored as this would mean to retroactively 198 // turn on isolated storage. This will make the app loose all its files. 199 return getDesiredOpMode() != MODE_IGNORED; 200 } 201 202 @Override 203 public boolean canBeGranted() { 204 if (isWhiteListed || targetSDK >= Build.VERSION_CODES.Q) { 205 return true; 206 } else { 207 return false; 208 } 209 } 210 }; 211 } 212 case WRITE_EXTERNAL_STORAGE: { 213 final boolean isWhiteListed; 214 final int targetSDK; 215 216 if (appInfo != null) { 217 final int flags = context.getPackageManager().getPermissionFlags(permission, 218 appInfo.packageName, user); 219 isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; 220 targetSDK = getMinimumTargetSDK(context, appInfo, user); 221 } else { 222 isWhiteListed = false; 223 targetSDK = 0; 224 } 225 226 return new SoftRestrictedPermissionPolicy() { 227 @Override 228 public int resolveAppOp() { 229 return OP_NONE; 230 } 231 232 @Override 233 public int getDesiredOpMode() { 234 return MODE_DEFAULT; 235 } 236 237 @Override 238 public boolean shouldSetAppOpIfNotDefault() { 239 return false; 240 } 241 242 @Override 243 public boolean canBeGranted() { 244 return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q; 245 } 246 }; 247 } 248 default: 249 return DUMMY_POLICY; 250 } 251 } 252 253 /** 254 * @return An app op to be changed based on the state of the permission or 255 * {@link AppOpsManager#OP_NONE} if not app-op should be set. 256 */ 257 public abstract int resolveAppOp(); 258 259 /** 260 * @return The mode the {@link #resolveAppOp() app op} should be in. 261 */ 262 public abstract @AppOpsManager.Mode int getDesiredOpMode(); 263 264 /** 265 * @return If the {@link #resolveAppOp() app op} should be set even if the app-op is currently 266 * not {@link AppOpsManager#MODE_DEFAULT}. 267 */ 268 public abstract boolean shouldSetAppOpIfNotDefault(); 269 270 /** 271 * @return If the permission can be granted 272 */ 273 public abstract boolean canBeGranted(); 274 } 275