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