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 throw new AssertionError("PENDING USER ACTION"); 146 } else if (status != PackageInstaller.STATUS_SUCCESS) { 147 String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); 148 throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message); 149 } 150 } 151 152 /** 153 * Asserts that {@code result} intent has a failure status. 154 */ assertStatusFailure(Intent result)155 public static void assertStatusFailure(Intent result) { 156 // Pass SUCCESS as default to ensure that this doesn't accidentally pass 157 int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, 158 PackageInstaller.STATUS_SUCCESS); 159 switch (status) { 160 case -2: // PackageInstaller.STATUS_PENDING_STREAMING 161 case PackageInstaller.STATUS_PENDING_USER_ACTION: 162 throw new AssertionError("PENDING " + status); 163 case PackageInstaller.STATUS_SUCCESS: 164 throw new AssertionError("INCORRECT SUCCESS "); 165 case PackageInstaller.STATUS_FAILURE: 166 case PackageInstaller.STATUS_FAILURE_BLOCKED: 167 case PackageInstaller.STATUS_FAILURE_ABORTED: 168 case PackageInstaller.STATUS_FAILURE_INVALID: 169 case PackageInstaller.STATUS_FAILURE_CONFLICT: 170 case PackageInstaller.STATUS_FAILURE_STORAGE: 171 case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE: 172 case PackageInstaller.STATUS_FAILURE_TIMEOUT: 173 break; 174 default: 175 String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); 176 throw new AssertionError(message == null ? "UNKNOWN STATUS" : message); 177 } 178 } 179 180 /** 181 * Commits {@link Install} but expects to fail. 182 * 183 * @param expectedThrowableClass class or superclass of the expected throwable. 184 * 185 */ commitExpectingFailure(Class expectedThrowableClass, String expectedFailMessage, Install install)186 public static void commitExpectingFailure(Class expectedThrowableClass, 187 String expectedFailMessage, Install install) { 188 assertThrows(expectedThrowableClass, expectedFailMessage, () -> install.commit()); 189 } 190 191 /** 192 * Mutates {@code installFlags} field of {@code params} by adding {@code 193 * additionalInstallFlags} to it. 194 */ 195 @VisibleForTesting mutateInstallFlags(PackageInstaller.SessionParams params, int additionalInstallFlags)196 public static void mutateInstallFlags(PackageInstaller.SessionParams params, 197 int additionalInstallFlags) { 198 final Class<?> clazz = params.getClass(); 199 Field installFlagsField; 200 try { 201 installFlagsField = clazz.getDeclaredField("installFlags"); 202 } catch (NoSuchFieldException e) { 203 throw new AssertionError("Unable to reflect over SessionParams.installFlags", e); 204 } 205 206 try { 207 int flags = installFlagsField.getInt(params); 208 flags |= additionalInstallFlags; 209 installFlagsField.setAccessible(true); 210 installFlagsField.setInt(params, flags); 211 } catch (IllegalAccessException e) { 212 throw new AssertionError("Unable to reflect over SessionParams.installFlags", e); 213 } 214 } 215 216 private static final String NO_RESPONSE = "NO RESPONSE"; 217 218 /** 219 * Calls into the test app to process user data. 220 * Asserts if the user data could not be processed or was version 221 * incompatible with the previously processed user data. 222 */ processUserData(String packageName)223 public static void processUserData(String packageName) { 224 Intent intent = new Intent(); 225 intent.setComponent(new ComponentName(packageName, CLASS_NAME_PROCESS_BROADCAST)); 226 intent.setAction("PROCESS_USER_DATA"); 227 Context context = InstrumentationRegistry.getTargetContext(); 228 229 HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread"); 230 handlerThread.start(); 231 232 // It can sometimes take a while after rollback before the app will 233 // receive this broadcast, so try a few times in a loop. 234 String result = NO_RESPONSE; 235 for (int i = 0; i < NUM_MAX_POLLS; ++i) { 236 BlockingQueue<String> resultQueue = new LinkedBlockingQueue<>(); 237 context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { 238 @Override 239 public void onReceive(Context context, Intent intent) { 240 if (getResultCode() == 1) { 241 resultQueue.add("OK"); 242 } else { 243 // If the test app doesn't receive the broadcast or 244 // fails to set the result data, then getResultData 245 // here returns the initial NO_RESPONSE data passed to 246 // the sendOrderedBroadcast call. 247 resultQueue.add(getResultData()); 248 } 249 } 250 }, new Handler(handlerThread.getLooper()), 0, NO_RESPONSE, null); 251 252 try { 253 result = resultQueue.take(); 254 if (!result.equals(NO_RESPONSE)) { 255 break; 256 } 257 Thread.sleep(POLL_WAIT_TIME_MILLIS); 258 } catch (InterruptedException e) { 259 throw new AssertionError(e); 260 } 261 } 262 263 assertThat(result).isEqualTo("OK"); 264 } 265 266 /** 267 * Retrieves the app's user data version from userdata.txt. 268 * @return -1 if userdata.txt doesn't exist or -2 if the app doesn't handle the broadcast which 269 * could happen when the app crashes or doesn't start at all. 270 */ getUserDataVersion(String packageName)271 public static int getUserDataVersion(String packageName) { 272 Intent intent = new Intent(); 273 intent.setComponent(new ComponentName(packageName, CLASS_NAME_PROCESS_BROADCAST)); 274 intent.setAction("GET_USER_DATA_VERSION"); 275 Context context = InstrumentationRegistry.getTargetContext(); 276 277 HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread"); 278 handlerThread.start(); 279 280 // The response code returned when the broadcast is not received by the app or when the app 281 // crashes during handling the broadcast. We will retry when this code is returned. 282 final int noResponse = -2; 283 // It can sometimes take a while after rollback before the app will 284 // receive this broadcast, so try a few times in a loop. 285 BlockingQueue<Integer> resultQueue = new LinkedBlockingQueue<>(); 286 for (int i = 0; i < NUM_MAX_POLLS; ++i) { 287 context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { 288 @Override 289 public void onReceive(Context context, Intent intent) { 290 resultQueue.add(getResultCode()); 291 } 292 }, new Handler(handlerThread.getLooper()), noResponse, null, null); 293 294 try { 295 int userDataVersion = resultQueue.take(); 296 if (userDataVersion != noResponse) { 297 return userDataVersion; 298 } 299 Thread.sleep(POLL_WAIT_TIME_MILLIS); 300 } catch (InterruptedException e) { 301 throw new AssertionError(e); 302 } 303 } 304 305 return noResponse; 306 } 307 sendBroadcastAndWait(Intent intent)308 private static void sendBroadcastAndWait(Intent intent) throws Exception { 309 var handlerThread = new HandlerThread("TestHandlerThread"); 310 handlerThread.start(); 311 312 var latch = new CountDownLatch(1); 313 var context = InstrumentationRegistry.getTargetContext(); 314 context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { 315 @Override 316 public void onReceive(Context context, Intent intent) { 317 latch.countDown(); 318 } 319 }, new Handler(handlerThread.getLooper()), 0, null, null); 320 latch.await(); 321 } 322 323 /** 324 * Tells the app to request audio focus. 325 */ requestAudioFocus(String packageName)326 public static void requestAudioFocus(String packageName) throws Exception { 327 Intent intent = new Intent(); 328 intent.setComponent(new ComponentName(packageName, CLASS_NAME_PROCESS_BROADCAST)); 329 intent.setAction("REQUEST_AUDIO_FOCUS"); 330 sendBroadcastAndWait(intent); 331 } 332 333 /** 334 * Tells the app to abandon audio focus. 335 */ abandonAudioFocus(String packageName)336 public static void abandonAudioFocus(String packageName) throws Exception { 337 Intent intent = new Intent(); 338 intent.setComponent(new ComponentName(packageName, CLASS_NAME_PROCESS_BROADCAST)); 339 intent.setAction("ABANDON_AUDIO_FOCUS"); 340 sendBroadcastAndWait(intent); 341 } 342 343 /** 344 * Checks whether the given package is installed on /system and was not updated. 345 */ isSystemAppWithoutUpdate(String packageName)346 static boolean isSystemAppWithoutUpdate(String packageName) { 347 PackageInfo pi = getPackageInfo(packageName); 348 if (pi == null) { 349 return false; 350 } else { 351 return ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) 352 && ((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0); 353 } 354 } 355 356 /** 357 * Checks whether a given package is installed for only the given user, from a list of users. 358 * @param packageName the package to check 359 * @param userIdToCheck the user id of the user to check 360 * @param userIds a list of user ids to check 361 * @return {@code true} if the package is only installed for the given user, 362 * {@code false} otherwise. 363 */ isOnlyInstalledForUser(String packageName, int userIdToCheck, List<Integer> userIds)364 public static boolean isOnlyInstalledForUser(String packageName, int userIdToCheck, 365 List<Integer> userIds) { 366 Context context = InstrumentationRegistry.getTargetContext(); 367 PackageManager pm = context.getPackageManager(); 368 for (int userId: userIds) { 369 List<PackageInfo> installedPackages; 370 if (userId != userIdToCheck) { 371 installedPackages = pm.getInstalledPackagesAsUser( 372 PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX), 373 userId); 374 for (PackageInfo pi : installedPackages) { 375 if (pi.packageName.equals(packageName)) { 376 return false; 377 } 378 } 379 } 380 } 381 return true; 382 } 383 384 /** 385 * Returns the session by session Id, or null if no session is found. 386 */ getStagedSessionInfo(int sessionId)387 public static PackageInstaller.SessionInfo getStagedSessionInfo(int sessionId) { 388 PackageInstaller packageInstaller = getPackageInstaller(); 389 for (PackageInstaller.SessionInfo session : packageInstaller.getStagedSessions()) { 390 if (session.getSessionId() == sessionId) { 391 return session; 392 } 393 } 394 return null; 395 } 396 397 /** 398 * Assert that the given staged session is abandoned. The method assumes that the given session 399 * is staged. 400 * @param sessionId of the staged session 401 */ assertStagedSessionIsAbandoned(int sessionId)402 public static void assertStagedSessionIsAbandoned(int sessionId) { 403 assertThat(getStagedSessionInfo(sessionId)).isNull(); 404 } 405 406 /** 407 * A functional interface representing an operation that takes no arguments, 408 * returns no arguments and might throw a {@link Throwable} of any kind. 409 */ 410 @FunctionalInterface 411 private interface Operation { 412 /** 413 * This is the method that gets called for any object that implements this interface. 414 */ run()415 void run() throws Throwable; 416 } 417 418 /** 419 * Runs {@link Operation} and expects a {@link Throwable} of the given class to be thrown. 420 * 421 * @param expectedThrowableClass class or superclass of the expected throwable. 422 */ assertThrows(Class expectedThrowableClass, String expectedFailMessage, Operation operation)423 private static void assertThrows(Class expectedThrowableClass, String expectedFailMessage, 424 Operation operation) { 425 try { 426 operation.run(); 427 } catch (Throwable expected) { 428 assertThat(expectedThrowableClass.isAssignableFrom(expected.getClass())).isTrue(); 429 assertThat(expected.getMessage()).containsMatch(expectedFailMessage); 430 return; 431 } 432 fail("Operation was expected to fail!"); 433 } 434 } 435