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.cts.install.lib; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.fail; 22 23 import android.app.UiAutomation; 24 import android.content.BroadcastReceiver; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.PackageInfo; 30 import android.content.pm.PackageInstaller; 31 import android.content.pm.PackageManager; 32 import android.os.Handler; 33 import android.os.HandlerThread; 34 import android.os.SystemClock; 35 36 import androidx.test.InstrumentationRegistry; 37 38 import com.android.modules.utils.build.SdkLevel; 39 40 import com.google.common.annotations.VisibleForTesting; 41 42 import java.io.IOException; 43 import java.lang.reflect.Field; 44 import java.util.List; 45 import java.util.concurrent.BlockingQueue; 46 import java.util.concurrent.CountDownLatch; 47 import java.util.concurrent.LinkedBlockingQueue; 48 49 /** 50 * Utilities to facilitate installation in tests. 51 */ 52 public class InstallUtils { 53 private static final int NUM_MAX_POLLS = 5; 54 private static final int POLL_WAIT_TIME_MILLIS = 200; 55 private static final long GET_UIAUTOMATION_TIMEOUT_MS = 60000; 56 private static final String CLASS_NAME_PROCESS_BROADCAST = 57 "com.android.cts.install.lib.testapp.ProcessBroadcast"; 58 getUiAutomation()59 private static UiAutomation getUiAutomation() { 60 final long start = SystemClock.uptimeMillis(); 61 while (SystemClock.uptimeMillis() - start < GET_UIAUTOMATION_TIMEOUT_MS) { 62 UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 63 if (ui != null) { 64 return ui; 65 } 66 } 67 throw new AssertionError("Failed to get UiAutomation"); 68 } 69 70 /** 71 * Adopts the given shell permissions. 72 */ adoptShellPermissionIdentity(String... permissions)73 public static void adoptShellPermissionIdentity(String... permissions) { 74 getUiAutomation().adoptShellPermissionIdentity(permissions); 75 } 76 77 /** 78 * Drops all shell permissions. 79 */ dropShellPermissionIdentity()80 public static void dropShellPermissionIdentity() { 81 getUiAutomation().dropShellPermissionIdentity(); 82 } 83 /** 84 * Returns the version of the given package installed on device. 85 * Returns -1 if the package is not currently installed. 86 */ getInstalledVersion(String packageName)87 public static long getInstalledVersion(String packageName) { 88 Context context = InstrumentationRegistry.getTargetContext(); 89 PackageManager pm = context.getPackageManager(); 90 try { 91 if (SdkLevel.isAtLeastT()) { 92 PackageInfo info = pm.getPackageInfo(packageName, 93 PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX)); 94 return info.getLongVersionCode(); 95 } else { 96 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); 97 return info.getLongVersionCode(); 98 } 99 } catch (PackageManager.NameNotFoundException e) { 100 return -1; 101 } 102 } 103 104 /** 105 * Returns the info for the given package name. 106 */ getPackageInfo(String packageName)107 public static PackageInfo getPackageInfo(String packageName) { 108 Context context = InstrumentationRegistry.getTargetContext(); 109 PackageManager pm = context.getPackageManager(); 110 try { 111 if (SdkLevel.isAtLeastT()) { 112 return pm.getPackageInfo(packageName, 113 PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX)); 114 } else { 115 return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); 116 } 117 } catch (PackageManager.NameNotFoundException e) { 118 return null; 119 } 120 } 121 122 /** 123 * Returns the PackageInstaller instance of the current {@code Context} 124 */ getPackageInstaller()125 public static PackageInstaller getPackageInstaller() { 126 return InstrumentationRegistry.getTargetContext().getPackageManager().getPackageInstaller(); 127 } 128 129 /** 130 * Returns an existing session to actively perform work. 131 * {@see PackageInstaller#openSession} 132 */ openPackageInstallerSession(int sessionId)133 public static PackageInstaller.Session openPackageInstallerSession(int sessionId) 134 throws IOException { 135 return getPackageInstaller().openSession(sessionId); 136 } 137 138 /** 139 * Asserts that {@code result} intent has a success status. 140 */ assertStatusSuccess(Intent result)141 public static void assertStatusSuccess(Intent result) { 142 int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, 143 PackageInstaller.STATUS_FAILURE); 144 if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) { 145 Intent intent = result.getParcelableExtra(Intent.EXTRA_INTENT); 146 throw new AssertionError("PENDING USER ACTION: " + intent); 147 } else if (status != PackageInstaller.STATUS_SUCCESS) { 148 String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); 149 throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message); 150 } 151 } 152 153 /** 154 * Asserts that {@code result} intent has a failure status. 155 */ assertStatusFailure(Intent result)156 public static void assertStatusFailure(Intent result) { 157 // Pass SUCCESS as default to ensure that this doesn't accidentally pass 158 int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, 159 PackageInstaller.STATUS_SUCCESS); 160 switch (status) { 161 case -2: // PackageInstaller.STATUS_PENDING_STREAMING 162 case PackageInstaller.STATUS_PENDING_USER_ACTION: 163 throw new AssertionError("PENDING " + status); 164 case PackageInstaller.STATUS_SUCCESS: 165 throw new AssertionError("INCORRECT SUCCESS "); 166 case PackageInstaller.STATUS_FAILURE: 167 case PackageInstaller.STATUS_FAILURE_BLOCKED: 168 case PackageInstaller.STATUS_FAILURE_ABORTED: 169 case PackageInstaller.STATUS_FAILURE_INVALID: 170 case PackageInstaller.STATUS_FAILURE_CONFLICT: 171 case PackageInstaller.STATUS_FAILURE_STORAGE: 172 case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE: 173 case PackageInstaller.STATUS_FAILURE_TIMEOUT: 174 break; 175 default: 176 String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); 177 throw new AssertionError(message == null ? "UNKNOWN STATUS" : message); 178 } 179 } 180 181 /** 182 * Commits {@link Install} but expects to fail. 183 * 184 * @param expectedThrowableClass class or superclass of the expected throwable. 185 * 186 */ commitExpectingFailure(Class expectedThrowableClass, String expectedFailMessage, Install install)187 public static void commitExpectingFailure(Class expectedThrowableClass, 188 String expectedFailMessage, Install install) { 189 assertThrows(expectedThrowableClass, expectedFailMessage, () -> install.commit()); 190 } 191 192 /** 193 * Mutates {@code installFlags} field of {@code params} by adding {@code 194 * additionalInstallFlags} to it. 195 */ 196 @VisibleForTesting mutateInstallFlags(PackageInstaller.SessionParams params, int additionalInstallFlags)197 public static void mutateInstallFlags(PackageInstaller.SessionParams params, 198 int additionalInstallFlags) { 199 final Class<?> clazz = params.getClass(); 200 Field installFlagsField; 201 try { 202 installFlagsField = clazz.getDeclaredField("installFlags"); 203 } catch (NoSuchFieldException e) { 204 throw new AssertionError("Unable to reflect over SessionParams.installFlags", e); 205 } 206 207 try { 208 int flags = installFlagsField.getInt(params); 209 flags |= additionalInstallFlags; 210 installFlagsField.setAccessible(true); 211 installFlagsField.setInt(params, flags); 212 } catch (IllegalAccessException e) { 213 throw new AssertionError("Unable to reflect over SessionParams.installFlags", e); 214 } 215 } 216 217 private static final String NO_RESPONSE = "NO RESPONSE"; 218 219 /** 220 * Calls into the test app to process user data. 221 * Asserts if the user data could not be processed or was version 222 * incompatible with the previously processed user data. 223 */ processUserData(String packageName)224 public static void processUserData(String packageName) { 225 Intent intent = new Intent(); 226 intent.setComponent(new ComponentName(packageName, CLASS_NAME_PROCESS_BROADCAST)); 227 intent.setAction("PROCESS_USER_DATA"); 228 Context context = InstrumentationRegistry.getTargetContext(); 229 230 HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread"); 231 handlerThread.start(); 232 233 // It can sometimes take a while after rollback before the app will 234 // receive this broadcast, so try a few times in a loop. 235 String result = NO_RESPONSE; 236 for (int i = 0; i < NUM_MAX_POLLS; ++i) { 237 BlockingQueue<String> resultQueue = new LinkedBlockingQueue<>(); 238 context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { 239 @Override 240 public void onReceive(Context context, Intent intent) { 241 if (getResultCode() == 1) { 242 resultQueue.add("OK"); 243 } else { 244 // If the test app doesn't receive the broadcast or 245 // fails to set the result data, then getResultData 246 // here returns the initial NO_RESPONSE data passed to 247 // the sendOrderedBroadcast call. 248 resultQueue.add(getResultData()); 249 } 250 } 251 }, new Handler(handlerThread.getLooper()), 0, NO_RESPONSE, null); 252 253 try { 254 result = resultQueue.take(); 255 if (!result.equals(NO_RESPONSE)) { 256 break; 257 } 258 Thread.sleep(POLL_WAIT_TIME_MILLIS); 259 } catch (InterruptedException e) { 260 throw new AssertionError(e); 261 } 262 } 263 264 assertThat(result).isEqualTo("OK"); 265 } 266 267 /** 268 * Retrieves the app's user data version from userdata.txt. 269 * @return -1 if userdata.txt doesn't exist or -2 if the app doesn't handle the broadcast which 270 * could happen when the app crashes or doesn't start at all. 271 */ getUserDataVersion(String packageName)272 public static int getUserDataVersion(String packageName) { 273 Intent intent = new Intent(); 274 intent.setComponent(new ComponentName(packageName, CLASS_NAME_PROCESS_BROADCAST)); 275 intent.setAction("GET_USER_DATA_VERSION"); 276 Context context = InstrumentationRegistry.getTargetContext(); 277 278 HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread"); 279 handlerThread.start(); 280 281 // The response code returned when the broadcast is not received by the app or when the app 282 // crashes during handling the broadcast. We will retry when this code is returned. 283 final int noResponse = -2; 284 // It can sometimes take a while after rollback before the app will 285 // receive this broadcast, so try a few times in a loop. 286 BlockingQueue<Integer> resultQueue = new LinkedBlockingQueue<>(); 287 for (int i = 0; i < NUM_MAX_POLLS; ++i) { 288 context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { 289 @Override 290 public void onReceive(Context context, Intent intent) { 291 resultQueue.add(getResultCode()); 292 } 293 }, new Handler(handlerThread.getLooper()), noResponse, null, null); 294 295 try { 296 int userDataVersion = resultQueue.take(); 297 if (userDataVersion != noResponse) { 298 return userDataVersion; 299 } 300 Thread.sleep(POLL_WAIT_TIME_MILLIS); 301 } catch (InterruptedException e) { 302 throw new AssertionError(e); 303 } 304 } 305 306 return noResponse; 307 } 308 sendBroadcastAndWait(Intent intent)309 private static void sendBroadcastAndWait(Intent intent) throws Exception { 310 var handlerThread = new HandlerThread("TestHandlerThread"); 311 handlerThread.start(); 312 313 var latch = new CountDownLatch(1); 314 var context = InstrumentationRegistry.getTargetContext(); 315 context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { 316 @Override 317 public void onReceive(Context context, Intent intent) { 318 latch.countDown(); 319 } 320 }, new Handler(handlerThread.getLooper()), 0, null, null); 321 latch.await(); 322 } 323 324 /** 325 * Tells the app to request audio focus. 326 */ requestAudioFocus(String packageName)327 public static void requestAudioFocus(String packageName) throws Exception { 328 Intent intent = new Intent(); 329 intent.setComponent(new ComponentName(packageName, CLASS_NAME_PROCESS_BROADCAST)); 330 intent.setAction("REQUEST_AUDIO_FOCUS"); 331 sendBroadcastAndWait(intent); 332 } 333 334 /** 335 * Tells the app to abandon audio focus. 336 */ abandonAudioFocus(String packageName)337 public static void abandonAudioFocus(String packageName) throws Exception { 338 Intent intent = new Intent(); 339 intent.setComponent(new ComponentName(packageName, CLASS_NAME_PROCESS_BROADCAST)); 340 intent.setAction("ABANDON_AUDIO_FOCUS"); 341 sendBroadcastAndWait(intent); 342 } 343 344 /** 345 * Checks whether the given package is installed on /system and was not updated. 346 */ isSystemAppWithoutUpdate(String packageName)347 static boolean isSystemAppWithoutUpdate(String packageName) { 348 PackageInfo pi = getPackageInfo(packageName); 349 if (pi == null) { 350 return false; 351 } else { 352 return ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) 353 && ((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0); 354 } 355 } 356 357 /** 358 * Checks whether a given package is installed for only the given user, from a list of users. 359 * @param packageName the package to check 360 * @param userIdToCheck the user id of the user to check 361 * @param userIds a list of user ids to check 362 * @return {@code true} if the package is only installed for the given user, 363 * {@code false} otherwise. 364 */ isOnlyInstalledForUser(String packageName, int userIdToCheck, List<Integer> userIds)365 public static boolean isOnlyInstalledForUser(String packageName, int userIdToCheck, 366 List<Integer> userIds) { 367 Context context = InstrumentationRegistry.getTargetContext(); 368 PackageManager pm = context.getPackageManager(); 369 for (int userId: userIds) { 370 List<PackageInfo> installedPackages; 371 if (userId != userIdToCheck) { 372 installedPackages = pm.getInstalledPackagesAsUser( 373 PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX), 374 userId); 375 for (PackageInfo pi : installedPackages) { 376 if (pi.packageName.equals(packageName)) { 377 return false; 378 } 379 } 380 } 381 } 382 return true; 383 } 384 385 /** 386 * Returns the session by session Id, or null if no session is found. 387 */ getStagedSessionInfo(int sessionId)388 public static PackageInstaller.SessionInfo getStagedSessionInfo(int sessionId) { 389 PackageInstaller packageInstaller = getPackageInstaller(); 390 for (PackageInstaller.SessionInfo session : packageInstaller.getStagedSessions()) { 391 if (session.getSessionId() == sessionId) { 392 return session; 393 } 394 } 395 return null; 396 } 397 398 /** 399 * Assert that the given staged session is abandoned. The method assumes that the given session 400 * is staged. 401 * @param sessionId of the staged session 402 */ assertStagedSessionIsAbandoned(int sessionId)403 public static void assertStagedSessionIsAbandoned(int sessionId) { 404 assertThat(getStagedSessionInfo(sessionId)).isNull(); 405 } 406 407 /** 408 * A functional interface representing an operation that takes no arguments, 409 * returns no arguments and might throw a {@link Throwable} of any kind. 410 */ 411 @FunctionalInterface 412 private interface Operation { 413 /** 414 * This is the method that gets called for any object that implements this interface. 415 */ run()416 void run() throws Throwable; 417 } 418 419 /** 420 * Runs {@link Operation} and expects a {@link Throwable} of the given class to be thrown. 421 * 422 * @param expectedThrowableClass class or superclass of the expected throwable. 423 */ assertThrows(Class expectedThrowableClass, String expectedFailMessage, Operation operation)424 private static void assertThrows(Class expectedThrowableClass, String expectedFailMessage, 425 Operation operation) { 426 try { 427 operation.run(); 428 } catch (Throwable expected) { 429 assertThat(expectedThrowableClass.isAssignableFrom(expected.getClass())).isTrue(); 430 assertThat(expected.getMessage()).containsMatch(expectedFailMessage); 431 return; 432 } 433 fail("Operation was expected to fail!"); 434 } 435 } 436