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.packages; 18 19 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; 20 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; 21 import static android.content.pm.PermissionInfo.PROTECTION_FLAG_DEVELOPMENT; 22 23 import static com.google.common.truth.Truth.assertWithMessage; 24 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.PackageManager; 28 import android.content.pm.PermissionInfo; 29 30 import androidx.annotation.Nullable; 31 32 import com.android.bedstead.nene.TestApis; 33 import com.android.bedstead.nene.annotations.Experimental; 34 import com.android.bedstead.nene.exceptions.AdbException; 35 import com.android.bedstead.nene.exceptions.NeneException; 36 import com.android.bedstead.nene.permissions.PermissionContext; 37 import com.android.bedstead.nene.users.UserReference; 38 import com.android.bedstead.nene.utils.ShellCommand; 39 import com.android.compatibility.common.util.BlockingBroadcastReceiver; 40 41 import java.io.File; 42 import java.util.Arrays; 43 import java.util.Set; 44 import java.util.stream.Collectors; 45 46 /** 47 * A representation of a package on device which may or may not exist. 48 * 49 * <p>To resolve the package into a {@link Package}, see {@link #resolve()}. 50 */ 51 public abstract class PackageReference { 52 private final TestApis mTestApis; 53 private final String mPackageName; 54 55 private static final int PIDS_PER_USER_ID = 100000; 56 57 private final PackageManager mPackageManager; 58 PackageReference(TestApis testApis, String packageName)59 PackageReference(TestApis testApis, String packageName) { 60 mTestApis = testApis; 61 mPackageManager = mTestApis.context().instrumentedContext().getPackageManager(); 62 mPackageName = packageName; 63 } 64 65 /** Return the package's name. */ packageName()66 public String packageName() { 67 return mPackageName; 68 } 69 70 /** 71 * Get the current state of the {@link Package} from the device, or {@code null} if the package 72 * does not exist. 73 */ 74 @Nullable resolve()75 public Package resolve() { 76 return mTestApis.packages().fetchPackage(mPackageName); 77 } 78 79 /** 80 * Install the package on the given user. 81 * 82 * <p>If you wish to install a package which is not already installed on another user, see 83 * {@link Packages#install(UserReference, File)}. 84 */ install(UserReference user)85 public PackageReference install(UserReference user) { 86 if (user == null) { 87 throw new NullPointerException(); 88 } 89 try { 90 // Expected output "Package X installed for user: Y" 91 ShellCommand.builderForUser(user, "cmd package install-existing") 92 .addOperand(mPackageName) 93 .validate( 94 (output) -> output.contains("installed for user")) 95 .execute(); 96 return this; 97 } catch (AdbException e) { 98 throw new NeneException("Could not install-existing package " + this, e); 99 } 100 } 101 102 /** 103 * Uninstall the package for all users. 104 * 105 * <p>The package will no longer {@link #resolve()}. 106 */ uninstallFromAllUsers()107 public PackageReference uninstallFromAllUsers() { 108 Package pkg = resolve(); 109 if (pkg == null) { 110 return this; 111 } 112 113 for (UserReference user : pkg.installedOnUsers()) { 114 pkg.uninstall(user); 115 } 116 117 return this; 118 } 119 120 /** 121 * Uninstall the package for the given user. 122 * 123 * <p>If this is the last user which has this package installed, then the package will no 124 * longer {@link #resolve()}. 125 * 126 * <p>If the package is not installed for the given user, nothing will happen. 127 */ uninstall(UserReference user)128 public PackageReference uninstall(UserReference user) { 129 if (user == null) { 130 throw new NullPointerException(); 131 } 132 133 IntentFilter packageRemovedIntentFilter = 134 new IntentFilter(Intent.ACTION_PACKAGE_REMOVED); 135 packageRemovedIntentFilter.addDataScheme("package"); 136 137 BlockingBroadcastReceiver broadcastReceiver = BlockingBroadcastReceiver.create( 138 mTestApis.context().androidContextAsUser(user), 139 packageRemovedIntentFilter); 140 141 try { 142 try (PermissionContext p = mTestApis.permissions().withPermission( 143 INTERACT_ACROSS_USERS_FULL)) { 144 broadcastReceiver.register(); 145 } 146 147 // Expected output "Success" 148 String output = ShellCommand.builderForUser(user, "pm uninstall") 149 .addOperand(mPackageName) 150 .validate((o) -> { 151 o = o.toUpperCase(); 152 return o.startsWith("SUCCESS") || o.contains("NOT INSTALLED FOR"); 153 }) 154 .execute(); 155 156 if (output.toUpperCase().startsWith("SUCCESS")) { 157 broadcastReceiver.awaitForBroadcastOrFail(); 158 } 159 160 return this; 161 } catch (AdbException e) { 162 throw new NeneException("Could not uninstall package " + this, e); 163 } finally { 164 broadcastReceiver.unregisterQuietly(); 165 } 166 } 167 168 /** 169 * Enable this package for the given {@link UserReference}. 170 */ 171 @Experimental enable(UserReference user)172 public PackageReference enable(UserReference user) { 173 try { 174 ShellCommand.builderForUser(user, "pm enable") 175 .addOperand(mPackageName) 176 .validate(o -> o.contains("new state")) 177 .execute(); 178 } catch (AdbException e) { 179 throw new NeneException("Error enabling package " + this + " for user " + user, e); 180 } 181 return this; 182 } 183 184 /** 185 * Enable this package on the instrumented user. 186 */ 187 @Experimental enable()188 public PackageReference enable() { 189 return enable(mTestApis.users().instrumented()); 190 } 191 192 /** 193 * Disable this package for the given {@link UserReference}. 194 */ 195 @Experimental disable(UserReference user)196 public PackageReference disable(UserReference user) { 197 try { 198 ShellCommand.builderForUser(user, "pm disable") 199 .addOperand(mPackageName) 200 .validate(o -> o.contains("new state")) 201 .execute(); 202 } catch (AdbException e) { 203 throw new NeneException("Error disabling package " + this + " for user " + user, e); 204 } 205 return this; 206 } 207 208 /** 209 * Disable this package on the instrumented user. 210 */ 211 @Experimental disable()212 public PackageReference disable() { 213 return disable(mTestApis.users().instrumented()); 214 } 215 216 /** 217 * Get a reference to the given {@code componentName} within this package. 218 * 219 * <p>This does not guarantee that the component exists. 220 */ 221 @Experimental component(String componentName)222 public ComponentReference component(String componentName) { 223 return new ComponentReference(mTestApis, this, componentName); 224 } 225 226 /** 227 * Grant a permission for the package on the given user. 228 * 229 * <p>The package must be installed on the user, must request the given permission, and the 230 * permission must be a runtime permission. 231 */ grantPermission(UserReference user, String permission)232 public PackageReference grantPermission(UserReference user, String permission) { 233 // There is no readable output upon failure so we need to check ourselves 234 checkCanGrantOrRevokePermission(user, permission); 235 236 try { 237 ShellCommand.builderForUser(user, "pm grant") 238 .addOperand(packageName()) 239 .addOperand(permission) 240 .allowEmptyOutput(true) 241 .validate(String::isEmpty) 242 .execute(); 243 244 assertWithMessage("Error granting permission " + permission 245 + " to package " + this + " on user " + user 246 + ". Command appeared successful but not set.") 247 .that(resolve().grantedPermissions(user)).contains(permission); 248 249 return this; 250 } catch (AdbException e) { 251 throw new NeneException("Error granting permission " + permission + " to package " 252 + this + " on user " + user, e); 253 } 254 } 255 256 /** 257 * Deny a permission for the package on the given user. 258 * 259 * <p>The package must be installed on the user, must request the given permission, and the 260 * permission must be a runtime permission. 261 * 262 * <p>You can not deny permissions for the current package on the current user. 263 */ denyPermission(UserReference user, String permission)264 public PackageReference denyPermission(UserReference user, String permission) { 265 // There is no readable output upon failure so we need to check ourselves 266 checkCanGrantOrRevokePermission(user, permission); 267 268 if (packageName().equals(mTestApis.context().instrumentedContext().getPackageName()) 269 && user.equals(mTestApis.users().instrumented())) { 270 Package resolved = resolve(); 271 if (!resolved.grantedPermissions(user).contains(permission)) { 272 return this; // Already denied 273 } 274 throw new NeneException("Cannot deny permission from current package"); 275 } 276 277 try { 278 ShellCommand.builderForUser(user, "pm revoke") 279 .addOperand(packageName()) 280 .addOperand(permission) 281 .allowEmptyOutput(true) 282 .validate(String::isEmpty) 283 .execute(); 284 285 assertWithMessage("Error denying permission " + permission 286 + " to package " + this + " on user " + user 287 + ". Command appeared successful but not set.") 288 .that(resolve().grantedPermissions(user)).doesNotContain(permission); 289 290 return this; 291 } catch (AdbException e) { 292 throw new NeneException("Error denying permission " + permission + " to package " 293 + this + " on user " + user, e); 294 } 295 } 296 checkCanGrantOrRevokePermission(UserReference user, String permission)297 private void checkCanGrantOrRevokePermission(UserReference user, String permission) { 298 Package resolved = resolve(); 299 if (resolved == null || !resolved.installedOnUsers().contains(user)) { 300 throw new NeneException("Attempting to grant " + permission + " to " + this 301 + " on user " + user + ". But it is not installed"); 302 } 303 304 try { 305 PermissionInfo permissionInfo = 306 mPackageManager.getPermissionInfo(permission, /* flags= */ 0); 307 308 if (!protectionIsDangerous(permissionInfo.protectionLevel) 309 && !protectionIsDevelopment(permissionInfo.protectionLevel)) { 310 throw new NeneException("Cannot grant non-runtime permission " 311 + permission + ", protection level is " + permissionInfo.protectionLevel); 312 } 313 314 if (!resolved.requestedPermissions().contains(permission)) { 315 throw new NeneException("Cannot grant permission " 316 + permission + " which was not requested by package " + packageName()); 317 } 318 } catch (PackageManager.NameNotFoundException e) { 319 throw new NeneException("Permission does not exist: " + permission); 320 } 321 } 322 protectionIsDangerous(int protectionLevel)323 private boolean protectionIsDangerous(int protectionLevel) { 324 return (protectionLevel & PROTECTION_DANGEROUS) != 0; 325 } 326 protectionIsDevelopment(int protectionLevel)327 private boolean protectionIsDevelopment(int protectionLevel) { 328 return (protectionLevel & PROTECTION_FLAG_DEVELOPMENT) != 0; 329 } 330 331 @Experimental runningProcesses()332 public Set<ProcessReference> runningProcesses() { 333 // TODO(scottjonathan): See if this can be remade using 334 // ActivityManager#getRunningappProcesses 335 try { 336 return ShellCommand.builder("ps") 337 .addOperand("-A") 338 .addOperand("-n") 339 .executeAndParseOutput(o -> parsePsOutput(o).stream() 340 .filter(p -> p.mPackageName.equals(mPackageName)) 341 .map(p -> new ProcessReference(this, p.mPid, mTestApis.users().find(p.mUserId)))) 342 .collect(Collectors.toSet()); 343 } catch (AdbException e) { 344 throw new NeneException("Error getting running processes ", e); 345 } 346 } 347 parsePsOutput(String psOutput)348 private Set<ProcessInfo> parsePsOutput(String psOutput) { 349 return Arrays.stream(psOutput.split("\n")) 350 .skip(1) // Skip the title line 351 .map(s -> s.split("\\s+")) 352 .map(m -> new ProcessInfo( 353 m[8], Integer.parseInt(m[1]), Integer.parseInt(m[0]) / PIDS_PER_USER_ID)) 354 .collect(Collectors.toSet()); 355 } 356 357 private static final class ProcessInfo { 358 public final String mPackageName; 359 public final int mPid; 360 public final int mUserId; 361 ProcessInfo(String packageName, int pid, int userId)362 public ProcessInfo(String packageName, int pid, int userId) { 363 if (packageName == null) { 364 throw new NullPointerException(); 365 } 366 mPackageName = packageName; 367 mPid = pid; 368 mUserId = userId; 369 } 370 371 @Override toString()372 public String toString() { 373 return "ProcessInfo{packageName=" + mPackageName + ", pid=" 374 + mPid + ", userId=" + mUserId + "}"; 375 } 376 } 377 378 @Experimental 379 @Nullable runningProcess(UserReference user)380 public ProcessReference runningProcess(UserReference user) { 381 return runningProcesses().stream().filter( 382 i -> i.user().equals(user)) 383 .findAny() 384 .orElse(null); 385 } 386 387 388 @Override hashCode()389 public int hashCode() { 390 return mPackageName.hashCode(); 391 } 392 393 @Override equals(Object obj)394 public boolean equals(Object obj) { 395 if (!(obj instanceof PackageReference)) { 396 return false; 397 } 398 399 PackageReference other = (PackageReference) obj; 400 return other.mPackageName.equals(mPackageName); 401 } 402 403 @Override toString()404 public String toString() { 405 StringBuilder stringBuilder = new StringBuilder("PackageReference{"); 406 stringBuilder.append("packageName=" + mPackageName); 407 stringBuilder.append("}"); 408 return stringBuilder.toString(); 409 } 410 } 411