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.tests.atomicinstall; 18 19 import static com.android.cts.install.lib.InstallUtils.assertStatusSuccess; 20 import static com.android.cts.install.lib.InstallUtils.getInstalledVersion; 21 import static com.android.cts.install.lib.InstallUtils.openPackageInstallerSession; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import static org.junit.Assert.fail; 26 27 import android.Manifest; 28 import android.content.pm.PackageInstaller; 29 30 import androidx.test.InstrumentationRegistry; 31 32 import com.android.compatibility.common.util.SystemUtil; 33 import com.android.cts.install.lib.Install; 34 import com.android.cts.install.lib.InstallUtils; 35 import com.android.cts.install.lib.LocalIntentSender; 36 import com.android.cts.install.lib.TestApp; 37 import com.android.cts.install.lib.Uninstall; 38 39 import org.junit.After; 40 import org.junit.Before; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 import org.junit.runners.JUnit4; 44 45 import java.util.function.Predicate; 46 import java.util.function.Supplier; 47 48 /** 49 * Tests for multi-package (a.k.a. atomic) installs. 50 */ 51 @RunWith(JUnit4.class) 52 public class AtomicInstallTest { 53 /** 54 * Time between repeated checks in {@link #retry}. 55 */ 56 private static final long RETRY_CHECK_INTERVAL_MILLIS = 500; 57 /** 58 * Maximum number of checks in {@link #retry} before a timeout occurs. 59 */ 60 private static final long RETRY_MAX_INTERVALS = 20; 61 62 public static final String TEST_APP_CORRUPT_FILENAME = "corrupt.apk"; 63 private static final TestApp CORRUPT_TESTAPP = new TestApp( 64 "corrupt", "com.corrupt", 1, false, TEST_APP_CORRUPT_FILENAME); 65 adoptShellPermissions()66 private void adoptShellPermissions() { 67 InstrumentationRegistry 68 .getInstrumentation() 69 .getUiAutomation() 70 .adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES, 71 Manifest.permission.DELETE_PACKAGES); 72 } 73 74 @Before setup()75 public void setup() throws Exception { 76 adoptShellPermissions(); 77 78 Uninstall.packages(TestApp.A, TestApp.B, TestApp.C); 79 } 80 81 @After dropShellPermissions()82 public void dropShellPermissions() { 83 InstrumentationRegistry 84 .getInstrumentation() 85 .getUiAutomation() 86 .dropShellPermissionIdentity(); 87 } 88 89 /** 90 * Cleans up sessions that are not committed during tests. 91 */ 92 @After cleanUpSessions()93 public void cleanUpSessions() { 94 InstallUtils.getPackageInstaller().getMySessions().forEach(info -> { 95 try { 96 InstallUtils.getPackageInstaller().abandonSession(info.getSessionId()); 97 } catch (Exception ignore) { 98 } 99 }); 100 } 101 retry(Supplier<T> supplier, Predicate<T> predicate, String message)102 private static <T> T retry(Supplier<T> supplier, Predicate<T> predicate, String message) 103 throws InterruptedException { 104 for (int i = 0; i < RETRY_MAX_INTERVALS; i++) { 105 T result = supplier.get(); 106 if (predicate.test(result)) { 107 return result; 108 } 109 Thread.sleep(RETRY_CHECK_INTERVAL_MILLIS); 110 } 111 throw new AssertionError(message); 112 } 113 114 /** 115 * Tests a completed session should be cleaned up. 116 */ 117 @Test testSessionCleanUp_Single()118 public void testSessionCleanUp_Single() throws Exception { 119 int sessionId = Install.single(TestApp.A1).commit(); 120 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1); 121 // The session is cleaned up asynchronously after install completed. 122 // Retry until the session no longer exists. 123 retry(() -> InstallUtils.getPackageInstaller().getSessionInfo(sessionId), 124 info -> info == null, 125 "Session " + sessionId + " not cleaned up"); 126 } 127 128 /** 129 * Tests a completed session should be cleaned up. 130 */ 131 @Test testSessionCleanUp_Multi()132 public void testSessionCleanUp_Multi() throws Exception { 133 int sessionId = Install.multi(TestApp.A1, TestApp.B1).commit(); 134 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1); 135 assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1); 136 // The session is cleaned up asynchronously after install completed. 137 // Retry until the session no longer exists. 138 retry(() -> InstallUtils.getPackageInstaller().getSessionInfo(sessionId), 139 info -> info == null, 140 "Session " + sessionId + " not cleaned up"); 141 } 142 143 @Test testInstallTwoApks()144 public void testInstallTwoApks() throws Exception { 145 Install.multi(TestApp.A1, TestApp.B1).commit(); 146 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1); 147 assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1); 148 } 149 150 /** 151 * Tests a removed child shouldn't be installed. 152 */ 153 @Test testRemoveChild()154 public void testRemoveChild() throws Exception { 155 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); 156 assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1); 157 assertThat(getInstalledVersion(TestApp.C)).isEqualTo(-1); 158 159 int parentId = Install.multi(TestApp.A1).createSession(); 160 int childBId = Install.single(TestApp.B1).createSession(); 161 int childCId = Install.single(TestApp.C1).createSession(); 162 try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) { 163 parent.addChildSessionId(childBId); 164 parent.addChildSessionId(childCId); 165 parent.removeChildSessionId(childBId); 166 LocalIntentSender sender = new LocalIntentSender(); 167 parent.commit(sender.getIntentSender()); 168 InstallUtils.assertStatusSuccess(sender.getResult()); 169 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1); 170 assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1); 171 assertThat(getInstalledVersion(TestApp.C)).isEqualTo(1); 172 } 173 } 174 175 @Test testInstallTwoApksDowngradeFail()176 public void testInstallTwoApksDowngradeFail() throws Exception { 177 Install.multi(TestApp.A2, TestApp.B1).commit(); 178 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2); 179 assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1); 180 181 InstallUtils.commitExpectingFailure(AssertionError.class, 182 "INSTALL_FAILED_VERSION_DOWNGRADE", Install.multi(TestApp.A1, TestApp.B1)); 183 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2); 184 assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1); 185 } 186 187 @Test testFailInconsistentMultiPackageCommit()188 public void testFailInconsistentMultiPackageCommit() throws Exception { 189 // Test inconsistency in staged settings 190 Install parentStaged = Install.multi(Install.single(TestApp.A1)).setStaged(); 191 Install childStaged = Install.multi(Install.single(TestApp.A1).setStaged()); 192 193 assertInconsistentStagedSettings(parentStaged); 194 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); 195 assertInconsistentStagedSettings(childStaged); 196 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); 197 198 // Test inconsistency in rollback settings 199 Install parentEnabledRollback = Install.multi(Install.single(TestApp.A1)) 200 .setEnableRollback(); 201 Install childEnabledRollback = Install.multi( 202 Install.single(TestApp.A1).setEnableRollback()); 203 204 assertInconsistentRollbackSettings(parentEnabledRollback); 205 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); 206 assertInconsistentRollbackSettings(childEnabledRollback); 207 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); 208 } 209 210 @Test testChildFailurePropagated()211 public void testChildFailurePropagated() throws Exception { 212 // Create a child session that "inherits" from a non-existent package. This 213 // causes the session commit to fail with a PackageManagerException. 214 Install childInstall = Install.single(TestApp.A1).setSessionMode( 215 PackageInstaller.SessionParams.MODE_INHERIT_EXISTING); 216 Install parentInstall = Install.multi(childInstall); 217 218 InstallUtils.commitExpectingFailure(AssertionError.class, "Missing existing base package", 219 parentInstall); 220 221 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); 222 } 223 224 @Test testEarlyFailureFailsAll()225 public void testEarlyFailureFailsAll() throws Exception { 226 InstallUtils.commitExpectingFailure(AssertionError.class, "Failed to parse", 227 Install.multi(TestApp.A1, TestApp.B1, CORRUPT_TESTAPP)); 228 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); 229 assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1); 230 } 231 232 @Test testInvalidStateScenario_MultiSessionCantBeApex()233 public void testInvalidStateScenario_MultiSessionCantBeApex() throws Exception { 234 try { 235 SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check true"); 236 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( 237 PackageInstaller.SessionParams.MODE_FULL_INSTALL); 238 params.setMultiPackage(); 239 params.setInstallAsApex(); 240 params.setStaged(); 241 try { 242 InstallUtils.getPackageInstaller().createSession(params); 243 fail("Should not be able to create a multi-session set as APEX!"); 244 } catch (Exception ignore) { 245 } 246 } finally { 247 SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check false"); 248 } 249 } 250 251 /** 252 * Tests a single-session can't have child. 253 */ 254 @Test testInvalidStateScenario_AddChildToSingleSessionShouldFail()255 public void testInvalidStateScenario_AddChildToSingleSessionShouldFail() throws Exception { 256 int parentId = Install.single(TestApp.A1).createSession(); 257 int childId = Install.single(TestApp.B1).createSession(); 258 try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) { 259 try { 260 parent.addChildSessionId(childId); 261 fail("Should not be able to add a child session to a single-session!"); 262 } catch (Exception ignore) { 263 } 264 } 265 } 266 267 /** 268 * Tests a multi-session can't be a child. 269 */ 270 @Test testInvalidStateScenario_MultiSessionAddedAsChildShouldFail()271 public void testInvalidStateScenario_MultiSessionAddedAsChildShouldFail() throws Exception { 272 int parentId = Install.multi(TestApp.A1).createSession(); 273 int childId = Install.multi(TestApp.B1).createSession(); 274 try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) { 275 try { 276 parent.addChildSessionId(childId); 277 fail("Should not be able to add a multi-session as a child!"); 278 } catch (Exception ignore) { 279 } 280 } 281 } 282 283 /** 284 * Tests a committed session can't add child. 285 */ 286 @Test testInvalidStateScenario_AddChildToCommittedSessionShouldFail()287 public void testInvalidStateScenario_AddChildToCommittedSessionShouldFail() throws Exception { 288 int parentId = Install.multi(TestApp.A1).createSession(); 289 int childId = Install.single(TestApp.B1).createSession(); 290 try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) { 291 LocalIntentSender sender = new LocalIntentSender(); 292 parent.commit(sender.getIntentSender()); 293 try { 294 parent.addChildSessionId(childId); 295 fail("Should not be able to add child to a committed session"); 296 } catch (Exception ignore) { 297 } 298 } 299 } 300 301 /** 302 * Tests a committed session can't remove child. 303 */ 304 @Test testInvalidStateScenario_RemoveChildFromCommittedSessionShouldFail()305 public void testInvalidStateScenario_RemoveChildFromCommittedSessionShouldFail() 306 throws Exception { 307 int parentId = Install.multi(TestApp.A1).createSession(); 308 int childId = Install.single(TestApp.B1).createSession(); 309 try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) { 310 parent.addChildSessionId(childId); 311 LocalIntentSender sender = new LocalIntentSender(); 312 parent.commit(sender.getIntentSender()); 313 try { 314 parent.removeChildSessionId(childId); 315 fail("Should not be able to remove child from a committed session"); 316 } catch (Exception ignore) { 317 } 318 } 319 } 320 321 /** 322 * Tests removing a child that is not its own should do nothing. 323 */ 324 @Test testInvalidStateScenario_RemoveWrongChildShouldDoNothing()325 public void testInvalidStateScenario_RemoveWrongChildShouldDoNothing() throws Exception { 326 int parent1Id = Install.multi(TestApp.A1).createSession(); 327 int parent2Id = Install.multi(TestApp.C1).createSession(); 328 int childId = Install.single(TestApp.B1).createSession(); 329 try (PackageInstaller.Session parent1 = openPackageInstallerSession(parent1Id); 330 PackageInstaller.Session parent2 = openPackageInstallerSession(parent2Id);) { 331 parent1.addChildSessionId(childId); 332 // Should do nothing since the child doesn't belong to parent2 333 parent2.removeChildSessionId(childId); 334 int currentParentId = 335 InstallUtils.getPackageInstaller().getSessionInfo(childId).getParentSessionId(); 336 // Check this child still belongs to parent1 337 assertThat(currentParentId).isEqualTo(parent1Id); 338 assertThat(parent1.getChildSessionIds()).asList().contains(childId); 339 assertThat(parent2.getChildSessionIds()).asList().doesNotContain(childId); 340 } 341 } 342 343 @Test testInvalidStateScenarios()344 public void testInvalidStateScenarios() throws Exception { 345 int parentSessionId = Install.multi(TestApp.A1, TestApp.B1).createSession(); 346 try (PackageInstaller.Session parentSession = 347 openPackageInstallerSession(parentSessionId)) { 348 for (int childSessionId : parentSession.getChildSessionIds()) { 349 try (PackageInstaller.Session childSession = 350 openPackageInstallerSession(childSessionId)) { 351 try { 352 LocalIntentSender sender = new LocalIntentSender(); 353 childSession.commit(sender.getIntentSender()); 354 fail("Should not be able to commit a child session!"); 355 } catch (IllegalStateException e) { 356 // ignore 357 } 358 try { 359 childSession.abandon(); 360 fail("Should not be able to abandon a child session!"); 361 } catch (IllegalStateException e) { 362 // ignore 363 } 364 } 365 } 366 int toAbandonSessionId = Install.single(TestApp.A1).createSession(); 367 try (PackageInstaller.Session toAbandonSession = 368 openPackageInstallerSession(toAbandonSessionId)) { 369 toAbandonSession.abandon(); 370 try { 371 parentSession.addChildSessionId(toAbandonSessionId); 372 fail("Should not be able to add abandoned child session!"); 373 } catch (RuntimeException e) { 374 // ignore 375 } 376 } 377 378 LocalIntentSender sender = new LocalIntentSender(); 379 parentSession.commit(sender.getIntentSender()); 380 assertStatusSuccess(sender.getResult()); 381 } 382 } 383 assertInconsistentStagedSettings(Install install)384 private static void assertInconsistentStagedSettings(Install install) { 385 assertInconsistentSettings("inconsistent staged settings", install); 386 } 387 assertInconsistentRollbackSettings(Install install)388 private static void assertInconsistentRollbackSettings(Install install) { 389 assertInconsistentSettings("inconsistent rollback settings", install); 390 } 391 assertInconsistentSettings(String failMessage, Install install)392 private static void assertInconsistentSettings(String failMessage, Install install) { 393 InstallUtils.commitExpectingFailure(IllegalStateException.class, failMessage, install); 394 } 395 } 396