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.rollback.lib; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import android.app.ActivityManager; 22 import android.app.AlarmManager; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.VersionedPackage; 28 import android.content.rollback.PackageRollbackInfo; 29 import android.content.rollback.RollbackInfo; 30 import android.content.rollback.RollbackManager; 31 import android.util.Log; 32 33 import androidx.test.InstrumentationRegistry; 34 35 import com.android.cts.install.lib.InstallUtils; 36 import com.android.cts.install.lib.LocalIntentSender; 37 import com.android.cts.install.lib.TestApp; 38 39 import java.io.IOException; 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.Objects; 43 import java.util.concurrent.CountDownLatch; 44 import java.util.function.Predicate; 45 import java.util.function.Supplier; 46 47 /** 48 * Utilities to facilitate testing rollbacks. 49 */ 50 public class RollbackUtils { 51 52 private static final String TAG = "RollbackTest"; 53 54 /** 55 * Time between repeated checks in {@link #retry}. 56 */ 57 private static final long RETRY_CHECK_INTERVAL_MILLIS = 500; 58 59 /** 60 * Maximum number of checks in {@link #retry} before a timeout occurs. 61 */ 62 private static final long RETRY_MAX_INTERVALS = 20; 63 64 65 /** 66 * Gets the RollbackManager for the instrumentation context. 67 */ getRollbackManager()68 public static RollbackManager getRollbackManager() { 69 Context context = InstrumentationRegistry.getContext(); 70 RollbackManager rm = (RollbackManager) context.getSystemService(Context.ROLLBACK_SERVICE); 71 if (rm == null) { 72 throw new AssertionError("Failed to get RollbackManager"); 73 } 74 return rm; 75 } 76 77 /** 78 * Returns a rollback for the given rollback Id, if found. Otherwise, returns null. 79 */ getRollbackById(List<RollbackInfo> rollbacks, int rollbackId)80 private static RollbackInfo getRollbackById(List<RollbackInfo> rollbacks, int rollbackId) { 81 for (RollbackInfo rollback :rollbacks) { 82 if (rollback.getRollbackId() == rollbackId) { 83 return rollback; 84 } 85 } 86 return null; 87 } 88 89 /** 90 * Returns an available rollback for the given package name. Returns null 91 * if there are no available rollbacks, and throws an assertion if there 92 * is more than one. 93 */ getAvailableRollback(String packageName)94 public static RollbackInfo getAvailableRollback(String packageName) { 95 RollbackManager rm = getRollbackManager(); 96 return getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), packageName); 97 } 98 99 /** 100 * Returns a recently committed rollback for the given package name. Returns null 101 * if there are no available rollbacks, and throws an assertion if there 102 * is more than one. 103 */ getCommittedRollback(String packageName)104 public static RollbackInfo getCommittedRollback(String packageName) { 105 RollbackManager rm = getRollbackManager(); 106 return getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), packageName); 107 } 108 109 /** 110 * Returns a recently committed rollback for the given rollback Id. 111 * Returns null if no committed rollback with a matching Id was found. 112 */ getCommittedRollbackById(int rollbackId)113 public static RollbackInfo getCommittedRollbackById(int rollbackId) { 114 RollbackManager rm = getRollbackManager(); 115 return getRollbackById(rm.getRecentlyCommittedRollbacks(), rollbackId); 116 } 117 118 /** 119 * Commit the given rollback. This method won't return until the committed session is made 120 * ready or failed. The caller is safe to immediately reboot the device right after the call. 121 * @throws AssertionError if the rollback fails. 122 */ rollback(int rollbackId, TestApp... causePackages)123 public static void rollback(int rollbackId, TestApp... causePackages) 124 throws InterruptedException { 125 List<VersionedPackage> causes = new ArrayList<>(); 126 for (TestApp cause : causePackages) { 127 causes.add(cause.getVersionedPackage()); 128 } 129 130 RollbackManager rm = getRollbackManager(); 131 LocalIntentSender sender = new LocalIntentSender(); 132 rm.commitRollback(rollbackId, causes, sender.getIntentSender()); 133 Intent result = sender.getResult(); 134 int status = result.getIntExtra(RollbackManager.EXTRA_STATUS, 135 RollbackManager.STATUS_FAILURE); 136 if (status != RollbackManager.STATUS_SUCCESS) { 137 String message = result.getStringExtra(RollbackManager.EXTRA_STATUS_MESSAGE); 138 throw new AssertionError(message); 139 } 140 141 RollbackInfo committed = getCommittedRollbackById(rollbackId); 142 if (committed.isStaged()) { 143 InstallUtils.waitForSessionReady(committed.getCommittedSessionId()); 144 } 145 } 146 147 /** 148 * Forwards the device clock time by {@code offsetMillis}. 149 */ forwardTimeBy(long offsetMillis)150 public static void forwardTimeBy(long offsetMillis) { 151 setTime(System.currentTimeMillis() + offsetMillis); 152 Log.i(TAG, "Forwarded time on device by " + offsetMillis + " millis"); 153 } 154 155 /** 156 * Returns the RollbackInfo with a given package in the list of rollbacks. 157 * Throws an assertion failure if there is more than one such rollback 158 * info. Returns null if there are no such rollback infos. 159 */ getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks, String packageName)160 public static RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks, 161 String packageName) { 162 RollbackInfo found = null; 163 for (RollbackInfo rollback : rollbacks) { 164 for (PackageRollbackInfo info : rollback.getPackages()) { 165 if (packageName.equals(info.getPackageName())) { 166 assertThat(found).isNull(); 167 found = rollback; 168 break; 169 } 170 } 171 } 172 return found; 173 } 174 175 /** 176 * Returns an available rollback matching the specified package name. If no such rollback is 177 * available, getAvailableRollbacks is called repeatedly until one becomes available. An 178 * assertion is raised if this does not occur after a certain number of checks. 179 */ waitForAvailableRollback(String packageName)180 public static RollbackInfo waitForAvailableRollback(String packageName) 181 throws InterruptedException { 182 return retry(() -> getAvailableRollback(packageName), 183 Objects::nonNull, "Rollback did not become available."); 184 } 185 186 /** 187 * If there is no available rollback matching the specified package name, this returns 188 * immediately. If such a rollback is available, getAvailableRollbacks is called repeatedly 189 * until it is no longer available. An assertion is raised if this does not occur after a 190 * certain number of checks. 191 */ waitForUnavailableRollback(String packageName)192 public static void waitForUnavailableRollback(String packageName) throws InterruptedException { 193 retry(() -> getAvailableRollback(packageName), Objects::isNull, 194 "Rollback did not become unavailable"); 195 } 196 hasRollbackInclude(List<RollbackInfo> rollbacks, String packageName)197 private static boolean hasRollbackInclude(List<RollbackInfo> rollbacks, String packageName) { 198 return rollbacks.stream().anyMatch( 199 ri -> ri.getPackages().stream().anyMatch( 200 pri -> packageName.equals(pri.getPackageName()))); 201 } 202 203 /** 204 * Retries until all rollbacks including {@code packageName} are gone. An assertion is raised if 205 * this does not occur after a certain number of checks. 206 */ waitForRollbackGone( Supplier<List<RollbackInfo>> supplier, String packageName)207 public static void waitForRollbackGone( 208 Supplier<List<RollbackInfo>> supplier, String packageName) throws InterruptedException { 209 retry(supplier, rollbacks -> !hasRollbackInclude(rollbacks, packageName), 210 "Rollback containing " + packageName + " did not go away"); 211 } 212 retry(Supplier<T> supplier, Predicate<T> predicate, String message)213 private static <T> T retry(Supplier<T> supplier, Predicate<T> predicate, String message) 214 throws InterruptedException { 215 for (int i = 0; i < RETRY_MAX_INTERVALS; i++) { 216 T result = supplier.get(); 217 if (predicate.test(result)) { 218 return result; 219 } 220 Thread.sleep(RETRY_CHECK_INTERVAL_MILLIS); 221 } 222 throw new AssertionError(message); 223 } 224 225 /** 226 * Send broadcast to crash {@code packageName} {@code count} times. If {@code count} is at least 227 * {@link PackageWatchdog#TRIGGER_FAILURE_COUNT}, watchdog crash detection will be triggered. 228 */ sendCrashBroadcast(String packageName, int count)229 public static void sendCrashBroadcast(String packageName, 230 int count) throws InterruptedException, IOException { 231 for (int i = 0; i < count; ++i) { 232 launchPackageForCrash(packageName); 233 } 234 } 235 setTime(long millis)236 private static void setTime(long millis) { 237 Context context = InstrumentationRegistry.getContext(); 238 AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 239 am.setTime(millis); 240 } 241 242 /** 243 * Launches {@code packageName} with {@link Intent#ACTION_MAIN} and 244 * waits for a CRASH broadcast from the launched app. 245 */ launchPackageForCrash(String packageName)246 private static void launchPackageForCrash(String packageName) 247 throws InterruptedException, IOException { 248 // Force stop the package before launching it to make sure it isn't 249 // stuck in a non-launchable state. And wait a second afterwards to 250 // avoid interfering with when we launch the app. 251 Log.i(TAG, "Force stopping " + packageName); 252 Context context = InstrumentationRegistry.getContext(); 253 ActivityManager am = context.getSystemService(ActivityManager.class); 254 am.forceStopPackage(packageName); 255 Thread.sleep(1000); 256 257 // Register a receiver to listen for the CRASH broadcast. 258 CountDownLatch latch = new CountDownLatch(1); 259 IntentFilter crashFilter = new IntentFilter(); 260 crashFilter.addAction("com.android.tests.rollback.CRASH"); 261 crashFilter.addCategory(Intent.CATEGORY_DEFAULT); 262 BroadcastReceiver crashReceiver = new BroadcastReceiver() { 263 @Override 264 public void onReceive(Context context, Intent intent) { 265 Log.i(TAG, "Received CRASH broadcast from " + packageName); 266 latch.countDown(); 267 } 268 }; 269 context.registerReceiver(crashReceiver, crashFilter); 270 271 // Launch the app. 272 Intent intent = new Intent(Intent.ACTION_MAIN); 273 intent.setPackage(packageName); 274 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 275 intent.addCategory(Intent.CATEGORY_LAUNCHER); 276 Log.i(TAG, "Launching " + packageName + " with " + intent); 277 context.startActivity(intent); 278 279 Log.i(TAG, "Waiting for CRASH broadcast from " + packageName); 280 latch.await(); 281 282 context.unregisterReceiver(crashReceiver); 283 284 // Sleep long enough for packagewatchdog to be notified of crash 285 Thread.sleep(1000); 286 } 287 } 288 289