1 /* 2 * Copyright (C) 2021 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.bedstead.nene.permissions; 18 19 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 20 21 import android.app.UiAutomation; 22 import android.content.Context; 23 import android.content.pm.PackageManager; 24 import android.content.pm.PermissionInfo; 25 import android.os.Build; 26 import android.util.Log; 27 28 import com.android.bedstead.nene.TestApis; 29 import com.android.bedstead.nene.exceptions.NeneException; 30 import com.android.bedstead.nene.packages.Package; 31 import com.android.bedstead.nene.packages.PackageReference; 32 import com.android.bedstead.nene.users.UserReference; 33 import com.android.bedstead.nene.utils.ShellCommandUtils; 34 import com.android.bedstead.nene.utils.Versions; 35 36 import java.util.ArrayList; 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Set; 40 41 /** Permission manager for tests. */ 42 public class Permissions { 43 44 public static final String MANAGE_PROFILE_AND_DEVICE_OWNERS = 45 "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"; 46 public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; 47 public static final String NOTIFY_PENDING_SYSTEM_UPDATE = 48 "android.permission.NOTIFY_PENDING_SYSTEM_UPDATE"; 49 50 private static final String LOG_TAG = "Permissions"; 51 52 private List<PermissionContextImpl> mPermissionContexts = new ArrayList<>(); 53 private static final TestApis sTestApis = new TestApis(); 54 private static final Context sContext = sTestApis.context().instrumentedContext(); 55 private static final PackageManager sPackageManager = sContext.getPackageManager(); 56 private static final PackageReference sInstrumentedPackage = 57 sTestApis.packages().find(sContext.getPackageName()); 58 private static final UserReference sUser = sTestApis.users().instrumented(); 59 private static final Package sShellPackage = 60 sTestApis.packages().find("com.android.shell").resolve(); 61 private static final boolean SUPPORTS_ADOPT_SHELL_PERMISSIONS = 62 Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q; 63 64 // Permissions is a singleton as permission state must be application wide 65 public static final Permissions sInstance = new Permissions(); 66 67 private Set<String> mExistingPermissions; 68 Permissions()69 private Permissions() { 70 71 } 72 73 /** 74 * Enter a {@link PermissionContext} where the given permissions are granted. 75 * 76 * <p>If the permissions cannot be granted, and are not already granted, an exception will be 77 * thrown. 78 * 79 * <p>Recommended usage: 80 * {@code 81 * 82 * try (PermissionContext p = mTestApis.permissions().withPermission(PERMISSION1, PERMISSION2) { 83 * // Code which needs the permissions goes here 84 * } 85 * } 86 */ withPermission(String... permissions)87 public PermissionContextImpl withPermission(String... permissions) { 88 if (mPermissionContexts.isEmpty()) { 89 recordExistingPermissions(); 90 } 91 92 PermissionContextImpl permissionContext = new PermissionContextImpl(this); 93 mPermissionContexts.add(permissionContext); 94 95 permissionContext.withPermission(permissions); 96 97 return permissionContext; 98 } 99 100 /** 101 * Enter a {@link PermissionContext} where the given permissions are not granted. 102 * 103 * <p>If the permissions cannot be denied, and are not already denied, an exception will be 104 * thrown. 105 * 106 * <p>Recommended usage: 107 * {@code 108 * 109 * try (PermissionContext p = 110 * mTestApis.permissions().withoutPermission(PERMISSION1, PERMISSION2) { 111 * // Code which needs the permissions goes here 112 * } 113 */ withoutPermission(String... permissions)114 public PermissionContextImpl withoutPermission(String... permissions) { 115 if (mPermissionContexts.isEmpty()) { 116 recordExistingPermissions(); 117 } 118 119 PermissionContextImpl permissionContext = new PermissionContextImpl(this); 120 mPermissionContexts.add(permissionContext); 121 122 permissionContext.withoutPermission(permissions); 123 124 return permissionContext; 125 } 126 undoPermission(PermissionContext permissionContext)127 void undoPermission(PermissionContext permissionContext) { 128 mPermissionContexts.remove(permissionContext); 129 applyPermissions(); 130 } 131 applyPermissions()132 void applyPermissions() { 133 if (mPermissionContexts.isEmpty()) { 134 restoreExistingPermissions(); 135 return; 136 } 137 138 Package resolvedInstrumentedPackage = sInstrumentedPackage.resolve(); 139 140 if (SUPPORTS_ADOPT_SHELL_PERMISSIONS) { 141 ShellCommandUtils.uiAutomation().dropShellPermissionIdentity(); 142 } 143 Set<String> grantedPermissions = new HashSet<>(); 144 Set<String> deniedPermissions = new HashSet<>(); 145 146 for (PermissionContextImpl permissionContext : mPermissionContexts) { 147 for (String permission : permissionContext.grantedPermissions()) { 148 grantedPermissions.add(permission); 149 deniedPermissions.remove(permission); 150 } 151 152 for (String permission : permissionContext.deniedPermissions()) { 153 grantedPermissions.remove(permission); 154 deniedPermissions.add(permission); 155 } 156 } 157 158 Log.d(LOG_TAG, "Applying permissions granting: " 159 + grantedPermissions + " denying: " + deniedPermissions); 160 161 // We first try to use shell permissions, because they can be revoked/etc. much more easily 162 163 Set<String> adoptedShellPermissions = new HashSet<>(); 164 165 for (String permission : grantedPermissions) { 166 Log.d(LOG_TAG , "Trying to grant " + permission); 167 if (resolvedInstrumentedPackage.grantedPermissions(sUser).contains(permission)) { 168 // Already granted, can skip 169 Log.d(LOG_TAG, permission + " already granted at runtime"); 170 } else if (resolvedInstrumentedPackage.requestedPermissions().contains(permission) 171 && sContext.checkSelfPermission(permission) == PERMISSION_GRANTED) { 172 // Already granted, can skip 173 Log.d(LOG_TAG, permission + " already granted from manifest"); 174 } else if (SUPPORTS_ADOPT_SHELL_PERMISSIONS 175 && sShellPackage.requestedPermissions().contains(permission)) { 176 adoptedShellPermissions.add(permission); 177 Log.d(LOG_TAG, "will adopt " + permission); 178 } else if (canGrantPermission(permission)) { 179 Log.d(LOG_TAG, "Granting " + permission); 180 sInstrumentedPackage.grantPermission(sUser, permission); 181 } else { 182 Log.d(LOG_TAG, "Can not grant " + permission); 183 removePermissionContextsUntilCanApply(); 184 throw new NeneException("PermissionContext requires granting " 185 + permission + " but cannot."); 186 } 187 } 188 189 for (String permission : deniedPermissions) { 190 Log.d(LOG_TAG , "Trying to deny " + permission); 191 if (!resolvedInstrumentedPackage.grantedPermissions(sUser).contains(permission)) { 192 // Already denied, can skip 193 Log.d(LOG_TAG, permission + " already denied"); 194 } else if (SUPPORTS_ADOPT_SHELL_PERMISSIONS 195 && !sShellPackage.requestedPermissions().contains(permission)) { 196 adoptedShellPermissions.add(permission); 197 Log.d(LOG_TAG, "will adopt " + permission); 198 } else { // We can't deny a permission to ourselves 199 Log.d(LOG_TAG, "Can not deny " + permission); 200 removePermissionContextsUntilCanApply(); 201 throw new NeneException("PermissionContext requires denying " 202 + permission + " but cannot."); 203 } 204 } 205 206 if (!adoptedShellPermissions.isEmpty()) { 207 Log.d(LOG_TAG, "Adopting " + adoptedShellPermissions); 208 ShellCommandUtils.uiAutomation().adoptShellPermissionIdentity( 209 adoptedShellPermissions.toArray(new String[0])); 210 } 211 } 212 clearPermissions()213 void clearPermissions() { 214 mPermissionContexts.clear(); 215 applyPermissions(); 216 } 217 removePermissionContextsUntilCanApply()218 private void removePermissionContextsUntilCanApply() { 219 try { 220 mPermissionContexts.remove(mPermissionContexts.size() - 1); 221 applyPermissions(); 222 } catch (NeneException e) { 223 // Suppress NeneException here as we may get a few as we pop through the stack 224 } 225 } 226 canGrantPermission(String permission)227 private boolean canGrantPermission(String permission) { 228 try { 229 PermissionInfo p = sPackageManager.getPermissionInfo(permission, /* flags= */ 0); 230 if ((p.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) > 0) { 231 return true; 232 } 233 if ((p.protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) > 0) { 234 return true; 235 } 236 237 return false; 238 } catch (PackageManager.NameNotFoundException e) { 239 return false; 240 } 241 } 242 recordExistingPermissions()243 private void recordExistingPermissions() { 244 if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) { 245 return; 246 } 247 248 mExistingPermissions = ShellCommandUtils.uiAutomation().getAdoptedShellPermissions(); 249 } 250 restoreExistingPermissions()251 private void restoreExistingPermissions() { 252 if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) { 253 return; 254 } 255 256 if (mExistingPermissions.isEmpty()) { 257 ShellCommandUtils.uiAutomation().dropShellPermissionIdentity(); 258 } else if (mExistingPermissions == UiAutomation.ALL_PERMISSIONS) { 259 ShellCommandUtils.uiAutomation().adoptShellPermissionIdentity(); 260 } else { 261 ShellCommandUtils.uiAutomation().adoptShellPermissionIdentity( 262 mExistingPermissions.toArray(new String[0])); 263 } 264 265 mExistingPermissions = null; 266 } 267 } 268