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.content.BroadcastReceiver; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 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 35 import androidx.test.InstrumentationRegistry; 36 37 import com.google.common.annotations.VisibleForTesting; 38 39 import java.io.IOException; 40 import java.lang.reflect.Field; 41 import java.util.concurrent.BlockingQueue; 42 import java.util.concurrent.LinkedBlockingQueue; 43 44 /** 45 * Utilities to facilitate installation in tests. 46 */ 47 public class InstallUtils { 48 /** 49 * Adopts the given shell permissions. 50 */ adoptShellPermissionIdentity(String... permissions)51 public static void adoptShellPermissionIdentity(String... permissions) { 52 InstrumentationRegistry 53 .getInstrumentation() 54 .getUiAutomation() 55 .adoptShellPermissionIdentity(permissions); 56 } 57 58 /** 59 * Drops all shell permissions. 60 */ dropShellPermissionIdentity()61 public static void dropShellPermissionIdentity() { 62 InstrumentationRegistry 63 .getInstrumentation() 64 .getUiAutomation() 65 .dropShellPermissionIdentity(); 66 } 67 /** 68 * Returns the version of the given package installed on device. 69 * Returns -1 if the package is not currently installed. 70 */ getInstalledVersion(String packageName)71 public static long getInstalledVersion(String packageName) { 72 Context context = InstrumentationRegistry.getContext(); 73 PackageManager pm = context.getPackageManager(); 74 try { 75 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); 76 return info.getLongVersionCode(); 77 } catch (PackageManager.NameNotFoundException e) { 78 return -1; 79 } 80 } 81 82 /** 83 * Waits for the given session to be marked as ready. 84 * Throws an assertion if the session fails. 85 */ waitForSessionReady(int sessionId)86 public static void waitForSessionReady(int sessionId) { 87 BlockingQueue<PackageInstaller.SessionInfo> sessionStatus = new LinkedBlockingQueue<>(); 88 BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() { 89 @Override 90 public void onReceive(Context context, Intent intent) { 91 PackageInstaller.SessionInfo info = 92 intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION); 93 if (info != null && info.getSessionId() == sessionId) { 94 if (info.isStagedSessionReady() || info.isStagedSessionFailed()) { 95 try { 96 sessionStatus.put(info); 97 } catch (InterruptedException e) { 98 throw new AssertionError(e); 99 } 100 } 101 } 102 } 103 }; 104 IntentFilter sessionUpdatedFilter = 105 new IntentFilter(PackageInstaller.ACTION_SESSION_UPDATED); 106 107 Context context = InstrumentationRegistry.getContext(); 108 context.registerReceiver(sessionUpdatedReceiver, sessionUpdatedFilter); 109 110 PackageInstaller installer = getPackageInstaller(); 111 PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId); 112 113 try { 114 if (info.isStagedSessionReady() || info.isStagedSessionFailed()) { 115 sessionStatus.put(info); 116 } 117 118 info = sessionStatus.take(); 119 context.unregisterReceiver(sessionUpdatedReceiver); 120 if (info.isStagedSessionFailed()) { 121 throw new AssertionError(info.getStagedSessionErrorMessage()); 122 } 123 } catch (InterruptedException e) { 124 throw new AssertionError(e); 125 } 126 } 127 128 /** 129 * Returns the info for the given package name. 130 */ getPackageInfo(String packageName)131 public static PackageInfo getPackageInfo(String packageName) { 132 Context context = InstrumentationRegistry.getContext(); 133 PackageManager pm = context.getPackageManager(); 134 try { 135 return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); 136 } catch (PackageManager.NameNotFoundException e) { 137 return null; 138 } 139 } 140 141 /** 142 * Returns the PackageInstaller instance of the current {@code Context} 143 */ getPackageInstaller()144 public static PackageInstaller getPackageInstaller() { 145 return InstrumentationRegistry.getContext().getPackageManager().getPackageInstaller(); 146 } 147 148 /** 149 * Returns an existing session to actively perform work. 150 * {@see PackageInstaller#openSession} 151 */ openPackageInstallerSession(int sessionId)152 public static PackageInstaller.Session openPackageInstallerSession(int sessionId) 153 throws IOException { 154 return getPackageInstaller().openSession(sessionId); 155 } 156 157 /** 158 * Asserts that {@code result} intent has a success status. 159 */ assertStatusSuccess(Intent result)160 public static void assertStatusSuccess(Intent result) { 161 int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, 162 PackageInstaller.STATUS_FAILURE); 163 if (status == -1) { 164 throw new AssertionError("PENDING USER ACTION"); 165 } else if (status > 0) { 166 String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); 167 throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message); 168 } 169 } 170 171 /** 172 * Commits {@link Install} but expects to fail. 173 * 174 * @param expectedThrowableClass class or superclass of the expected throwable. 175 * 176 */ commitExpectingFailure(Class expectedThrowableClass, String expectedFailMessage, Install install)177 public static void commitExpectingFailure(Class expectedThrowableClass, 178 String expectedFailMessage, Install install) { 179 assertThrows(expectedThrowableClass, expectedFailMessage, () -> install.commit()); 180 } 181 182 /** 183 * Mutates {@code installFlags} field of {@code params} by adding {@code 184 * additionalInstallFlags} to it. 185 */ 186 @VisibleForTesting mutateInstallFlags(PackageInstaller.SessionParams params, int additionalInstallFlags)187 public static void mutateInstallFlags(PackageInstaller.SessionParams params, 188 int additionalInstallFlags) { 189 final Class<?> clazz = params.getClass(); 190 Field installFlagsField; 191 try { 192 installFlagsField = clazz.getDeclaredField("installFlags"); 193 } catch (NoSuchFieldException e) { 194 throw new AssertionError("Unable to reflect over SessionParams.installFlags", e); 195 } 196 197 try { 198 int flags = installFlagsField.getInt(params); 199 flags |= additionalInstallFlags; 200 installFlagsField.setAccessible(true); 201 installFlagsField.setInt(params, flags); 202 } catch (IllegalAccessException e) { 203 throw new AssertionError("Unable to reflect over SessionParams.installFlags", e); 204 } 205 } 206 207 private static final String NO_RESPONSE = "NO RESPONSE"; 208 209 /** 210 * Calls into the test app to process user data. 211 * Asserts if the user data could not be processed or was version 212 * incompatible with the previously processed user data. 213 */ processUserData(String packageName)214 public static void processUserData(String packageName) { 215 Intent intent = new Intent(); 216 intent.setComponent(new ComponentName(packageName, 217 "com.android.cts.install.lib.testapp.ProcessUserData")); 218 Context context = InstrumentationRegistry.getContext(); 219 220 HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread"); 221 handlerThread.start(); 222 223 // It can sometimes take a while after rollback before the app will 224 // receive this broadcast, so try a few times in a loop. 225 String result = NO_RESPONSE; 226 for (int i = 0; result.equals(NO_RESPONSE) && i < 5; ++i) { 227 BlockingQueue<String> resultQueue = new LinkedBlockingQueue<>(); 228 context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { 229 @Override 230 public void onReceive(Context context, Intent intent) { 231 if (getResultCode() == 1) { 232 resultQueue.add("OK"); 233 } else { 234 // If the test app doesn't receive the broadcast or 235 // fails to set the result data, then getResultData 236 // here returns the initial NO_RESPONSE data passed to 237 // the sendOrderedBroadcast call. 238 resultQueue.add(getResultData()); 239 } 240 } 241 }, new Handler(handlerThread.getLooper()), 0, NO_RESPONSE, null); 242 243 try { 244 result = resultQueue.take(); 245 } catch (InterruptedException e) { 246 throw new AssertionError(e); 247 } 248 } 249 250 assertThat(result).isEqualTo("OK"); 251 } 252 253 /** 254 * Checks whether the given package is installed on /system and was not updated. 255 */ isSystemAppWithoutUpdate(String packageName)256 static boolean isSystemAppWithoutUpdate(String packageName) { 257 PackageInfo pi = getPackageInfo(packageName); 258 if (pi == null) { 259 return false; 260 } else { 261 return ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) 262 && ((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0); 263 } 264 } 265 266 /** 267 * A functional interface representing an operation that takes no arguments, 268 * returns no arguments and might throw a {@link Throwable} of any kind. 269 */ 270 @FunctionalInterface 271 private interface Operation { 272 /** 273 * This is the method that gets called for any object that implements this interface. 274 */ run()275 void run() throws Throwable; 276 } 277 278 /** 279 * Runs {@link Operation} and expects a {@link Throwable} of the given class to be thrown. 280 * 281 * @param expectedThrowableClass class or superclass of the expected throwable. 282 */ assertThrows(Class expectedThrowableClass, String expectedFailMessage, Operation operation)283 private static void assertThrows(Class expectedThrowableClass, String expectedFailMessage, 284 Operation operation) { 285 try { 286 operation.run(); 287 } catch (Throwable expected) { 288 assertThat(expectedThrowableClass.isAssignableFrom(expected.getClass())).isTrue(); 289 assertThat(expected.getMessage()).containsMatch(expectedFailMessage); 290 return; 291 } 292 fail("Operation was expected to fail!"); 293 } 294 } 295