1 /* 2 * Copyright (C) 2020 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.server.pm; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.assertThrows; 22 import static org.mockito.ArgumentMatchers.any; 23 import static org.mockito.ArgumentMatchers.anyInt; 24 import static org.mockito.ArgumentMatchers.anyString; 25 import static org.mockito.ArgumentMatchers.eq; 26 import static org.mockito.Mockito.doAnswer; 27 import static org.mockito.Mockito.doNothing; 28 import static org.mockito.Mockito.doReturn; 29 import static org.mockito.Mockito.never; 30 import static org.mockito.Mockito.spy; 31 import static org.mockito.Mockito.times; 32 import static org.mockito.Mockito.verify; 33 import static org.mockito.Mockito.when; 34 35 import android.apex.ApexInfo; 36 import android.apex.ApexSessionInfo; 37 import android.apex.ApexSessionParams; 38 import android.content.Context; 39 import android.content.pm.ApexStagedEvent; 40 import android.content.pm.IStagedApexObserver; 41 import android.content.pm.PackageInstaller; 42 import android.content.pm.PackageManager; 43 import android.content.pm.StagedApexInfo; 44 import android.os.SystemProperties; 45 import android.os.storage.IStorageManager; 46 import android.platform.test.annotations.Presubmit; 47 import android.util.IntArray; 48 import android.util.SparseArray; 49 50 import com.android.dx.mockito.inline.extended.ExtendedMockito; 51 import com.android.internal.content.InstallLocationUtils; 52 import com.android.internal.os.BackgroundThread; 53 import com.android.internal.util.Preconditions; 54 55 import org.junit.After; 56 import org.junit.Before; 57 import org.junit.Rule; 58 import org.junit.Test; 59 import org.junit.rules.TemporaryFolder; 60 import org.junit.runner.RunWith; 61 import org.junit.runners.JUnit4; 62 import org.mockito.ArgumentCaptor; 63 import org.mockito.Mock; 64 import org.mockito.Mockito; 65 import org.mockito.MockitoAnnotations; 66 import org.mockito.MockitoSession; 67 import org.mockito.invocation.InvocationOnMock; 68 import org.mockito.quality.Strictness; 69 import org.mockito.stubbing.Answer; 70 71 import java.io.File; 72 import java.util.ArrayList; 73 import java.util.Arrays; 74 import java.util.List; 75 import java.util.Map; 76 import java.util.concurrent.CompletableFuture; 77 import java.util.function.Predicate; 78 79 @Presubmit 80 @RunWith(JUnit4.class) 81 public class StagingManagerTest { 82 @Rule 83 public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); 84 85 @Mock private Context mContext; 86 @Mock private IStorageManager mStorageManager; 87 @Mock private ApexManager mApexManager; 88 @Mock private PackageManagerService mMockPackageManagerInternal; 89 90 private File mTmpDir; 91 private StagingManager mStagingManager; 92 93 private MockitoSession mMockitoSession; 94 95 @Before setUp()96 public void setUp() throws Exception { 97 MockitoAnnotations.initMocks(this); 98 when(mContext.getSystemService(eq(Context.POWER_SERVICE))).thenReturn(null); 99 100 mMockitoSession = ExtendedMockito.mockitoSession() 101 .strictness(Strictness.LENIENT) 102 .mockStatic(SystemProperties.class) 103 .mockStatic(InstallLocationUtils.class) 104 .startMocking(); 105 106 when(mStorageManager.supportsCheckpoint()).thenReturn(true); 107 when(mStorageManager.needsCheckpoint()).thenReturn(true); 108 when(InstallLocationUtils.getStorageManager()).thenReturn(mStorageManager); 109 110 when(SystemProperties.get(eq("ro.apex.updatable"))).thenReturn("true"); 111 when(SystemProperties.get(eq("ro.apex.updatable"), anyString())).thenReturn("true"); 112 113 mTmpDir = mTemporaryFolder.newFolder("StagingManagerTest"); 114 mStagingManager = new StagingManager(mContext, mApexManager); 115 } 116 117 @After tearDown()118 public void tearDown() throws Exception { 119 if (mMockitoSession != null) { 120 mMockitoSession.finishMocking(); 121 } 122 } 123 124 @Test restoreSessions_nonParentSession_throwsIAE()125 public void restoreSessions_nonParentSession_throwsIAE() throws Exception { 126 FakeStagedSession session = new FakeStagedSession(239); 127 session.setParentSessionId(1543); 128 129 assertThrows(IllegalArgumentException.class, 130 () -> mStagingManager.restoreSessions(Arrays.asList(session), false)); 131 } 132 133 @Test restoreSessions_nonCommittedSession_throwsIAE()134 public void restoreSessions_nonCommittedSession_throwsIAE() throws Exception { 135 FakeStagedSession session = new FakeStagedSession(239); 136 137 assertThrows(IllegalArgumentException.class, 138 () -> mStagingManager.restoreSessions(Arrays.asList(session), false)); 139 } 140 141 @Test restoreSessions_terminalSession_throwsIAE()142 public void restoreSessions_terminalSession_throwsIAE() throws Exception { 143 FakeStagedSession session = new FakeStagedSession(239); 144 session.setCommitted(true); 145 session.setSessionApplied(); 146 147 assertThrows(IllegalArgumentException.class, 148 () -> mStagingManager.restoreSessions(Arrays.asList(session), false)); 149 } 150 151 @Test restoreSessions_deviceUpgrading_failsAllSessions()152 public void restoreSessions_deviceUpgrading_failsAllSessions() throws Exception { 153 FakeStagedSession session1 = new FakeStagedSession(37); 154 session1.setCommitted(true); 155 FakeStagedSession session2 = new FakeStagedSession(57); 156 session2.setCommitted(true); 157 158 mStagingManager.restoreSessions(Arrays.asList(session1, session2), true); 159 160 assertThat(session1.getErrorCode()).isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); 161 assertThat(session1.getErrorMessage()).isEqualTo("Build fingerprint has changed"); 162 163 assertThat(session2.getErrorCode()).isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); 164 assertThat(session2.getErrorMessage()).isEqualTo("Build fingerprint has changed"); 165 } 166 167 @Test restoreSessions_multipleSessions_deviceWithoutFsCheckpointSupport_throwISE()168 public void restoreSessions_multipleSessions_deviceWithoutFsCheckpointSupport_throwISE() 169 throws Exception { 170 FakeStagedSession session1 = new FakeStagedSession(37); 171 session1.setCommitted(true); 172 FakeStagedSession session2 = new FakeStagedSession(57); 173 session2.setCommitted(true); 174 175 when(mStorageManager.supportsCheckpoint()).thenReturn(false); 176 177 assertThrows(IllegalStateException.class, 178 () -> mStagingManager.restoreSessions(Arrays.asList(session1, session2), false)); 179 } 180 181 @Test restoreSessions_handlesDestroyedAndNotReadySessions()182 public void restoreSessions_handlesDestroyedAndNotReadySessions() throws Exception { 183 FakeStagedSession destroyedApkSession = new FakeStagedSession(23); 184 destroyedApkSession.setCommitted(true); 185 destroyedApkSession.setDestroyed(true); 186 187 FakeStagedSession destroyedApexSession = new FakeStagedSession(37); 188 destroyedApexSession.setCommitted(true); 189 destroyedApexSession.setDestroyed(true); 190 destroyedApexSession.setIsApex(true); 191 192 FakeStagedSession nonReadyApkSession = new FakeStagedSession(57); 193 nonReadyApkSession.setCommitted(true); 194 195 FakeStagedSession nonReadyApexSession = new FakeStagedSession(73); 196 nonReadyApexSession.setCommitted(true); 197 nonReadyApexSession.setIsApex(true); 198 199 FakeStagedSession destroyedNonReadySession = new FakeStagedSession(101); 200 destroyedNonReadySession.setCommitted(true); 201 destroyedNonReadySession.setDestroyed(true); 202 203 FakeStagedSession regularApkSession = new FakeStagedSession(239); 204 regularApkSession.setCommitted(true); 205 regularApkSession.setSessionReady(); 206 207 List<StagingManager.StagedSession> sessions = new ArrayList<>(); 208 sessions.add(destroyedApkSession); 209 sessions.add(destroyedApexSession); 210 sessions.add(nonReadyApkSession); 211 sessions.add(nonReadyApexSession); 212 sessions.add(destroyedNonReadySession); 213 sessions.add(regularApkSession); 214 215 mStagingManager.restoreSessions(sessions, false); 216 217 assertThat(sessions).containsExactly(regularApkSession); 218 assertThat(destroyedApkSession.isDestroyed()).isTrue(); 219 assertThat(destroyedApexSession.isDestroyed()).isTrue(); 220 assertThat(destroyedNonReadySession.isDestroyed()).isTrue(); 221 222 mStagingManager.onBootCompletedBroadcastReceived(); 223 assertThat(nonReadyApkSession.hasVerificationStarted()).isTrue(); 224 assertThat(nonReadyApexSession.hasVerificationStarted()).isTrue(); 225 } 226 227 @Test restoreSessions_unknownApexSession_failsAllSessions()228 public void restoreSessions_unknownApexSession_failsAllSessions() throws Exception { 229 FakeStagedSession apkSession = new FakeStagedSession(239); 230 apkSession.setCommitted(true); 231 apkSession.setSessionReady(); 232 233 FakeStagedSession apexSession = new FakeStagedSession(1543); 234 apexSession.setCommitted(true); 235 apexSession.setIsApex(true); 236 apexSession.setSessionReady(); 237 238 List<StagingManager.StagedSession> sessions = new ArrayList<>(); 239 sessions.add(apkSession); 240 sessions.add(apexSession); 241 242 when(mApexManager.getSessions()).thenReturn(new SparseArray<>()); 243 mStagingManager.restoreSessions(sessions, false); 244 245 // Validate checkpoint wasn't aborted. 246 verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); 247 248 assertThat(apexSession.getErrorCode()) 249 .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); 250 assertThat(apexSession.getErrorMessage()).isEqualTo("apexd did not know anything about a " 251 + "staged session supposed to be activated"); 252 253 assertThat(apkSession.getErrorCode()) 254 .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); 255 assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); 256 } 257 258 @Test restoreSessions_failedApexSessions_failsAllSessions()259 public void restoreSessions_failedApexSessions_failsAllSessions() throws Exception { 260 FakeStagedSession apkSession = new FakeStagedSession(239); 261 apkSession.setCommitted(true); 262 apkSession.setSessionReady(); 263 264 FakeStagedSession apexSession1 = new FakeStagedSession(1543); 265 apexSession1.setCommitted(true); 266 apexSession1.setIsApex(true); 267 apexSession1.setSessionReady(); 268 269 FakeStagedSession apexSession2 = new FakeStagedSession(101); 270 apexSession2.setCommitted(true); 271 apexSession2.setIsApex(true); 272 apexSession2.setSessionReady(); 273 274 FakeStagedSession apexSession3 = new FakeStagedSession(57); 275 apexSession3.setCommitted(true); 276 apexSession3.setIsApex(true); 277 apexSession3.setSessionReady(); 278 279 ApexSessionInfo activationFailed = new ApexSessionInfo(); 280 activationFailed.sessionId = 1543; 281 activationFailed.isActivationFailed = true; 282 activationFailed.errorMessage = "Failed for test"; 283 284 ApexSessionInfo staged = new ApexSessionInfo(); 285 staged.sessionId = 101; 286 staged.isStaged = true; 287 288 SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>(); 289 apexdSessions.put(1543, activationFailed); 290 apexdSessions.put(101, staged); 291 when(mApexManager.getSessions()).thenReturn(apexdSessions); 292 293 List<StagingManager.StagedSession> sessions = new ArrayList<>(); 294 sessions.add(apkSession); 295 sessions.add(apexSession1); 296 sessions.add(apexSession2); 297 sessions.add(apexSession3); 298 299 mStagingManager.restoreSessions(sessions, false); 300 301 // Validate checkpoint wasn't aborted. 302 verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); 303 304 assertThat(apexSession1.getErrorCode()) 305 .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); 306 assertThat(apexSession1.getErrorMessage()).isEqualTo("APEX activation failed. " 307 + "Error: Failed for test"); 308 309 assertThat(apexSession2.getErrorCode()) 310 .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); 311 assertThat(apexSession2.getErrorMessage()).isEqualTo("Staged session 101 at boot didn't " 312 + "activate nor fail. Marking it as failed anyway."); 313 314 assertThat(apexSession3.getErrorCode()) 315 .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); 316 assertThat(apexSession3.getErrorMessage()).isEqualTo("apexd did not know anything about a " 317 + "staged session supposed to be activated"); 318 319 assertThat(apkSession.getErrorCode()) 320 .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); 321 assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); 322 } 323 324 @Test restoreSessions_stagedApexSession_failsAllSessions()325 public void restoreSessions_stagedApexSession_failsAllSessions() throws Exception { 326 FakeStagedSession apkSession = new FakeStagedSession(239); 327 apkSession.setCommitted(true); 328 apkSession.setSessionReady(); 329 330 FakeStagedSession apexSession = new FakeStagedSession(1543); 331 apexSession.setCommitted(true); 332 apexSession.setIsApex(true); 333 apexSession.setSessionReady(); 334 335 ApexSessionInfo staged = new ApexSessionInfo(); 336 staged.sessionId = 1543; 337 staged.isStaged = true; 338 339 SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>(); 340 apexdSessions.put(1543, staged); 341 when(mApexManager.getSessions()).thenReturn(apexdSessions); 342 343 List<StagingManager.StagedSession> sessions = new ArrayList<>(); 344 sessions.add(apkSession); 345 sessions.add(apexSession); 346 347 mStagingManager.restoreSessions(sessions, false); 348 349 // Validate checkpoint wasn't aborted. 350 verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); 351 352 assertThat(apexSession.getErrorCode()) 353 .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); 354 assertThat(apexSession.getErrorMessage()).isEqualTo("Staged session 1543 at boot didn't " 355 + "activate nor fail. Marking it as failed anyway."); 356 357 assertThat(apkSession.getErrorCode()) 358 .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); 359 assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); 360 } 361 362 @Test restoreSessions_failedAndActivatedApexSessions_abortsCheckpoint()363 public void restoreSessions_failedAndActivatedApexSessions_abortsCheckpoint() throws Exception { 364 FakeStagedSession apkSession = new FakeStagedSession(239); 365 apkSession.setCommitted(true); 366 apkSession.setSessionReady(); 367 368 FakeStagedSession apexSession1 = new FakeStagedSession(1543); 369 apexSession1.setCommitted(true); 370 apexSession1.setIsApex(true); 371 apexSession1.setSessionReady(); 372 373 FakeStagedSession apexSession2 = new FakeStagedSession(101); 374 apexSession2.setCommitted(true); 375 apexSession2.setIsApex(true); 376 apexSession2.setSessionReady(); 377 378 FakeStagedSession apexSession3 = new FakeStagedSession(57); 379 apexSession3.setCommitted(true); 380 apexSession3.setIsApex(true); 381 apexSession3.setSessionReady(); 382 383 FakeStagedSession apexSession4 = new FakeStagedSession(37); 384 apexSession4.setCommitted(true); 385 apexSession4.setIsApex(true); 386 apexSession4.setSessionReady(); 387 388 ApexSessionInfo activationFailed = new ApexSessionInfo(); 389 activationFailed.sessionId = 1543; 390 activationFailed.isActivationFailed = true; 391 392 ApexSessionInfo activated = new ApexSessionInfo(); 393 activated.sessionId = 101; 394 activated.isActivated = true; 395 396 ApexSessionInfo staged = new ApexSessionInfo(); 397 staged.sessionId = 57; 398 staged.isActivationFailed = true; 399 400 SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>(); 401 apexdSessions.put(1543, activationFailed); 402 apexdSessions.put(101, activated); 403 apexdSessions.put(57, staged); 404 when(mApexManager.getSessions()).thenReturn(apexdSessions); 405 406 List<StagingManager.StagedSession> sessions = new ArrayList<>(); 407 sessions.add(apkSession); 408 sessions.add(apexSession1); 409 sessions.add(apexSession2); 410 sessions.add(apexSession3); 411 sessions.add(apexSession4); 412 413 mStagingManager.restoreSessions(sessions, false); 414 415 // Validate checkpoint was aborted. 416 verify(mStorageManager, times(1)).abortChanges(eq("abort-staged-install"), eq(false)); 417 } 418 419 @Test restoreSessions_apexSessionInImpossibleState_failsAllSessions()420 public void restoreSessions_apexSessionInImpossibleState_failsAllSessions() throws Exception { 421 FakeStagedSession apkSession = new FakeStagedSession(239); 422 apkSession.setCommitted(true); 423 apkSession.setSessionReady(); 424 425 FakeStagedSession apexSession = new FakeStagedSession(1543); 426 apexSession.setCommitted(true); 427 apexSession.setIsApex(true); 428 apexSession.setSessionReady(); 429 430 ApexSessionInfo impossible = new ApexSessionInfo(); 431 impossible.sessionId = 1543; 432 433 SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>(); 434 apexdSessions.put(1543, impossible); 435 when(mApexManager.getSessions()).thenReturn(apexdSessions); 436 437 List<StagingManager.StagedSession> sessions = new ArrayList<>(); 438 sessions.add(apkSession); 439 sessions.add(apexSession); 440 441 mStagingManager.restoreSessions(sessions, false); 442 443 // Validate checkpoint wasn't aborted. 444 verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); 445 446 assertThat(apexSession.getErrorCode()) 447 .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); 448 assertThat(apexSession.getErrorMessage()).isEqualTo("Impossible state"); 449 450 assertThat(apkSession.getErrorCode()) 451 .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); 452 assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); 453 } 454 455 @Test getStagedApexInfos_validatePreConditions()456 public void getStagedApexInfos_validatePreConditions() throws Exception { 457 // Invalid session: null session 458 { 459 // Call and verify 460 IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, 461 () -> mStagingManager.getStagedApexInfos(null)); 462 assertThat(thrown).hasMessageThat().contains("Session is null"); 463 } 464 // Invalid session: has parent 465 { 466 FakeStagedSession session = new FakeStagedSession(241); 467 session.setParentSessionId(239); 468 session.setSessionReady(); 469 // Call and verify 470 IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, 471 () -> mStagingManager.getStagedApexInfos(session)); 472 assertThat(thrown).hasMessageThat().contains("241 session has parent"); 473 } 474 475 // Invalid session: does not contain apex 476 { 477 FakeStagedSession session = new FakeStagedSession(241); 478 session.setSessionReady(); 479 // Call and verify 480 IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, 481 () -> mStagingManager.getStagedApexInfos(session)); 482 assertThat(thrown).hasMessageThat().contains("241 session does not contain apex"); 483 } 484 // Invalid session: not ready 485 { 486 FakeStagedSession session = new FakeStagedSession(239); 487 session.setIsApex(true); 488 // Call and verify 489 Map<String, ApexInfo> result = mStagingManager.getStagedApexInfos(session); 490 assertThat(result).isEmpty(); 491 } 492 // Invalid session: destroyed 493 { 494 FakeStagedSession session = new FakeStagedSession(240); 495 session.setSessionReady(); 496 session.setIsApex(true); 497 session.setDestroyed(true); 498 // Call and verify 499 Map<String, ApexInfo> result = mStagingManager.getStagedApexInfos(session); 500 assertThat(result).isEmpty(); 501 } 502 } 503 getFakeApexInfo(List<String> moduleNames)504 private ApexInfo[] getFakeApexInfo(List<String> moduleNames) { 505 List<ApexInfo> result = new ArrayList<>(); 506 for (String moduleName : moduleNames) { 507 ApexInfo info = new ApexInfo(); 508 info.moduleName = moduleName; 509 result.add(info); 510 } 511 return result.toArray(new ApexInfo[0]); 512 } 513 514 @Test getStagedApexInfos_nonParentSession()515 public void getStagedApexInfos_nonParentSession() throws Exception { 516 FakeStagedSession validSession = new FakeStagedSession(239); 517 validSession.setIsApex(true); 518 validSession.setSessionReady(); 519 ApexInfo[] fakeApexInfos = getFakeApexInfo(Arrays.asList("module1")); 520 when(mApexManager.getStagedApexInfos(any())).thenReturn(fakeApexInfos); 521 522 // Call and verify 523 Map<String, ApexInfo> result = mStagingManager.getStagedApexInfos(validSession); 524 assertThat(result).containsExactly(fakeApexInfos[0].moduleName, fakeApexInfos[0]); 525 526 ArgumentCaptor<ApexSessionParams> argumentCaptor = 527 ArgumentCaptor.forClass(ApexSessionParams.class); 528 verify(mApexManager, times(1)).getStagedApexInfos(argumentCaptor.capture()); 529 ApexSessionParams params = argumentCaptor.getValue(); 530 assertThat(params.sessionId).isEqualTo(239); 531 } 532 533 @Test getStagedApexInfos_parentSession()534 public void getStagedApexInfos_parentSession() throws Exception { 535 FakeStagedSession childSession1 = new FakeStagedSession(201); 536 childSession1.setIsApex(true); 537 FakeStagedSession childSession2 = new FakeStagedSession(202); 538 childSession2.setIsApex(true); 539 FakeStagedSession nonApexChild = new FakeStagedSession(203); 540 FakeStagedSession parentSession = new FakeStagedSession(239, 541 Arrays.asList(childSession1, childSession2, nonApexChild)); 542 parentSession.setSessionReady(); 543 ApexInfo[] fakeApexInfos = getFakeApexInfo(Arrays.asList("module1", "module2")); 544 when(mApexManager.getStagedApexInfos(any())).thenReturn(fakeApexInfos); 545 546 // Call and verify 547 Map<String, ApexInfo> result = mStagingManager.getStagedApexInfos(parentSession); 548 assertThat(result).containsExactly(fakeApexInfos[0].moduleName, fakeApexInfos[0], 549 fakeApexInfos[1].moduleName, fakeApexInfos[1]); 550 551 ArgumentCaptor<ApexSessionParams> argumentCaptor = 552 ArgumentCaptor.forClass(ApexSessionParams.class); 553 verify(mApexManager, times(1)).getStagedApexInfos(argumentCaptor.capture()); 554 ApexSessionParams params = argumentCaptor.getValue(); 555 assertThat(params.sessionId).isEqualTo(239); 556 assertThat(params.childSessionIds).asList().containsExactly(201, 202); 557 } 558 559 @Test getStagedApexModuleNames_returnsStagedApexModules()560 public void getStagedApexModuleNames_returnsStagedApexModules() throws Exception { 561 FakeStagedSession validSession1 = new FakeStagedSession(239); 562 validSession1.setIsApex(true); 563 validSession1.setSessionReady(); 564 mStagingManager.createSession(validSession1); 565 566 FakeStagedSession childSession1 = new FakeStagedSession(123); 567 childSession1.setIsApex(true); 568 FakeStagedSession childSession2 = new FakeStagedSession(124); 569 childSession2.setIsApex(true); 570 FakeStagedSession nonApexChild = new FakeStagedSession(125); 571 FakeStagedSession parentSession = new FakeStagedSession(240, 572 Arrays.asList(childSession1, childSession2, nonApexChild)); 573 parentSession.setSessionReady(); 574 mStagingManager.createSession(parentSession); 575 576 mockApexManagerGetStagedApexInfoWithSessionId(); 577 578 List<String> result = mStagingManager.getStagedApexModuleNames(); 579 assertThat(result).containsExactly("239", "123", "124"); 580 verify(mApexManager, times(2)).getStagedApexInfos(any()); 581 } 582 583 // Make mApexManager return ApexInfo with same module name as the sessionId 584 // of the parameter that was passed into it mockApexManagerGetStagedApexInfoWithSessionId()585 private void mockApexManagerGetStagedApexInfoWithSessionId() { 586 when(mApexManager.getStagedApexInfos(any())).thenAnswer(new Answer<ApexInfo[]>() { 587 @Override 588 public ApexInfo[] answer(InvocationOnMock invocation) throws Throwable { 589 Object[] args = invocation.getArguments(); 590 ApexSessionParams params = (ApexSessionParams) args[0]; 591 IntArray sessionsToProcess = new IntArray(); 592 if (params.childSessionIds.length == 0) { 593 sessionsToProcess.add(params.sessionId); 594 } else { 595 sessionsToProcess.addAll(params.childSessionIds); 596 } 597 List<ApexInfo> result = new ArrayList<>(); 598 for (int session : sessionsToProcess.toArray()) { 599 ApexInfo info = new ApexInfo(); 600 info.moduleName = String.valueOf(session); 601 result.add(info); 602 } 603 return result.toArray(new ApexInfo[0]); 604 } 605 }); 606 } 607 608 @Test getStagedApexInfo()609 public void getStagedApexInfo() throws Exception { 610 FakeStagedSession validSession1 = new FakeStagedSession(239); 611 validSession1.setIsApex(true); 612 validSession1.setSessionReady(); 613 mStagingManager.createSession(validSession1); 614 ApexInfo[] fakeApexInfos = getFakeApexInfo(Arrays.asList("module1")); 615 when(mApexManager.getStagedApexInfos(any())).thenReturn(fakeApexInfos); 616 617 // Verify null is returned if module name is not found 618 StagedApexInfo result = mStagingManager.getStagedApexInfo("not found"); 619 assertThat(result).isNull(); 620 verify(mApexManager, times(1)).getStagedApexInfos(any()); 621 // Otherwise, the correct object is returned 622 result = mStagingManager.getStagedApexInfo("module1"); 623 assertThat(result.moduleName).isEqualTo(fakeApexInfos[0].moduleName); 624 assertThat(result.diskImagePath).isEqualTo(fakeApexInfos[0].modulePath); 625 assertThat(result.versionCode).isEqualTo(fakeApexInfos[0].versionCode); 626 assertThat(result.versionName).isEqualTo(fakeApexInfos[0].versionName); 627 verify(mApexManager, times(2)).getStagedApexInfos(any()); 628 } 629 630 @Test registeredStagedApexObserverIsNotifiedOnPreRebootVerificationCompletion()631 public void registeredStagedApexObserverIsNotifiedOnPreRebootVerificationCompletion() 632 throws Exception { 633 // Register observer 634 IStagedApexObserver observer = Mockito.mock(IStagedApexObserver.class); 635 mStagingManager.registerStagedApexObserver(observer); 636 637 // Create one staged session and trigger end of pre-reboot verification 638 { 639 FakeStagedSession session = new FakeStagedSession(239); 640 session.setIsApex(true); 641 session.setSessionReady(); 642 mockApexManagerGetStagedApexInfoWithSessionId(); 643 mStagingManager.commitSession(session); 644 645 assertThat(session.isSessionReady()).isTrue(); 646 ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass( 647 ApexStagedEvent.class); 648 verify(observer, times(1)).onApexStaged(argumentCaptor.capture()); 649 assertThat(argumentCaptor.getValue().stagedApexModuleNames).isEqualTo( 650 new String[]{"239"}); 651 } 652 653 // Create another staged session and verify observers are notified of union 654 { 655 Mockito.clearInvocations(observer); 656 FakeStagedSession session = new FakeStagedSession(240); 657 session.setIsApex(true); 658 session.setSessionReady(); 659 mStagingManager.commitSession(session); 660 661 assertThat(session.isSessionReady()).isTrue(); 662 ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass( 663 ApexStagedEvent.class); 664 verify(observer, times(1)).onApexStaged(argumentCaptor.capture()); 665 assertThat(argumentCaptor.getValue().stagedApexModuleNames).isEqualTo( 666 new String[]{"239", "240"}); 667 } 668 669 // Finally, verify that once unregistered, observer is not notified 670 mStagingManager.unregisterStagedApexObserver(observer); 671 { 672 Mockito.clearInvocations(observer); 673 FakeStagedSession session = new FakeStagedSession(241); 674 session.setIsApex(true); 675 session.setSessionReady(); 676 mStagingManager.commitSession(session); 677 678 assertThat(session.isSessionReady()).isTrue(); 679 verify(observer, never()).onApexStaged(any()); 680 } 681 } 682 683 @Test registeredStagedApexObserverIsNotifiedOnSessionAbandon()684 public void registeredStagedApexObserverIsNotifiedOnSessionAbandon() throws Exception { 685 // Register observer 686 IStagedApexObserver observer = Mockito.mock(IStagedApexObserver.class); 687 mStagingManager.registerStagedApexObserver(observer); 688 689 // Create a ready session and abandon it 690 FakeStagedSession session = new FakeStagedSession(239); 691 session.setIsApex(true); 692 session.setSessionReady(); 693 session.setDestroyed(true); 694 mStagingManager.createSession(session); 695 696 mStagingManager.abortCommittedSession(session); 697 698 assertThat(session.isSessionReady()).isTrue(); 699 ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass( 700 ApexStagedEvent.class); 701 verify(observer, times(1)).onApexStaged(argumentCaptor.capture()); 702 assertThat(argumentCaptor.getValue().stagedApexModuleNames).hasLength(0); 703 } 704 705 @Test stagedApexObserverIsOnlyCalledForApexSessions()706 public void stagedApexObserverIsOnlyCalledForApexSessions() throws Exception { 707 IStagedApexObserver observer = Mockito.mock(IStagedApexObserver.class); 708 mStagingManager.registerStagedApexObserver(observer); 709 710 // Trigger end of pre-reboot verification 711 FakeStagedSession session = new FakeStagedSession(239); 712 session.setSessionReady(); 713 mStagingManager.commitSession(session); 714 715 assertThat(session.isSessionReady()).isTrue(); 716 verify(observer, never()).onApexStaged(any()); 717 } 718 createSession(int sessionId, String packageName, long committedMillis)719 private StagingManager.StagedSession createSession(int sessionId, String packageName, 720 long committedMillis) { 721 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( 722 PackageInstaller.SessionParams.MODE_FULL_INSTALL); 723 params.isStaged = true; 724 725 InstallSource installSource = InstallSource.create("testInstallInitiator", 726 "testInstallOriginator", "testInstaller", 100, "testUpdateOwner", 727 "testAttributionTag", PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED); 728 729 PackageInstallerSession session = new PackageInstallerSession( 730 /* callback */ null, 731 /* context */ null, 732 /* pm */ mMockPackageManagerInternal, 733 /* sessionProvider */ null, 734 /* silentUpdatePolicy */ null, 735 /* looper */ BackgroundThread.getHandler().getLooper(), 736 /* stagingManager */ null, 737 /* sessionId */ sessionId, 738 /* userId */ 456, 739 /* installerUid */ -1, 740 /* installSource */ installSource, 741 /* sessionParams */ params, 742 /* createdMillis */ 0L, 743 /* committedMillis */ committedMillis, 744 /* stageDir */ mTmpDir, 745 /* stageCid */ null, 746 /* files */ null, 747 /* checksums */ null, 748 /* prepared */ true, 749 /* committed */ true, 750 /* destroyed */ false, 751 /* sealed */ false, // Setting to true would trigger some PM logic. 752 /* childSessionIds */ null, 753 /* parentSessionId */ -1, 754 /* isReady */ false, 755 /* isFailed */ false, 756 /* isApplied */false, 757 /* stagedSessionErrorCode */ PackageManager.INSTALL_UNKNOWN, 758 /* stagedSessionErrorMessage */ "no error", 759 /* preVerifiedDomains */ null); 760 761 StagingManager.StagedSession stagedSession = spy(session.mStagedSession); 762 doReturn(packageName).when(stagedSession).getPackageName(); 763 doAnswer(invocation -> { 764 Predicate<StagingManager.StagedSession> filter = invocation.getArgument(0); 765 return filter.test(stagedSession); 766 }).when(stagedSession).sessionContains(any()); 767 doNothing().when(stagedSession).setSessionFailed(anyInt(), anyString()); 768 return stagedSession; 769 } 770 771 private static final class FakeStagedSession implements StagingManager.StagedSession { 772 private final int mSessionId; 773 private boolean mIsApex = false; 774 private boolean mIsCommitted = false; 775 private boolean mIsReady = false; 776 private boolean mIsApplied = false; 777 private boolean mIsFailed = false; 778 private int mErrorCode = -1; 779 private String mErrorMessage; 780 private boolean mIsDestroyed = false; 781 private int mParentSessionId = -1; 782 private String mPackageName; 783 private boolean mIsAbandonded = false; 784 private boolean mVerificationStarted = false; 785 private final List<StagingManager.StagedSession> mChildSessions; 786 FakeStagedSession(int sessionId)787 private FakeStagedSession(int sessionId) { 788 mSessionId = sessionId; 789 mChildSessions = new ArrayList<>(); 790 } 791 FakeStagedSession(int sessionId, List<StagingManager.StagedSession> childSessions)792 private FakeStagedSession(int sessionId, List<StagingManager.StagedSession> childSessions) { 793 mSessionId = sessionId; 794 mChildSessions = childSessions; 795 } 796 setParentSessionId(int parentSessionId)797 private void setParentSessionId(int parentSessionId) { 798 mParentSessionId = parentSessionId; 799 } 800 setCommitted(boolean isCommitted)801 private void setCommitted(boolean isCommitted) { 802 mIsCommitted = isCommitted; 803 } 804 setIsApex(boolean isApex)805 private void setIsApex(boolean isApex) { 806 mIsApex = isApex; 807 } 808 setDestroyed(boolean isDestroyed)809 private void setDestroyed(boolean isDestroyed) { 810 mIsDestroyed = isDestroyed; 811 } 812 setPackageName(String packageName)813 private void setPackageName(String packageName) { 814 mPackageName = packageName; 815 } 816 isAbandonded()817 private boolean isAbandonded() { 818 return mIsAbandonded; 819 } 820 hasVerificationStarted()821 private boolean hasVerificationStarted() { 822 return mVerificationStarted; 823 } 824 addChildSession(FakeStagedSession session)825 private FakeStagedSession addChildSession(FakeStagedSession session) { 826 mChildSessions.add(session); 827 session.setParentSessionId(sessionId()); 828 return this; 829 } 830 getErrorCode()831 private int getErrorCode() { 832 return mErrorCode; 833 } 834 getErrorMessage()835 private String getErrorMessage() { 836 return mErrorMessage; 837 } 838 839 @Override isMultiPackage()840 public boolean isMultiPackage() { 841 return !mChildSessions.isEmpty(); 842 } 843 844 @Override isApexSession()845 public boolean isApexSession() { 846 return mIsApex; 847 } 848 849 @Override isCommitted()850 public boolean isCommitted() { 851 return mIsCommitted; 852 } 853 854 @Override isInTerminalState()855 public boolean isInTerminalState() { 856 return isSessionApplied() || isSessionFailed(); 857 } 858 859 @Override isDestroyed()860 public boolean isDestroyed() { 861 return mIsDestroyed; 862 } 863 864 @Override isSessionReady()865 public boolean isSessionReady() { 866 return mIsReady; 867 } 868 869 @Override isSessionApplied()870 public boolean isSessionApplied() { 871 return mIsApplied; 872 } 873 874 @Override isSessionFailed()875 public boolean isSessionFailed() { 876 return mIsFailed; 877 } 878 879 @Override getChildSessions()880 public List<StagingManager.StagedSession> getChildSessions() { 881 return mChildSessions; 882 } 883 884 @Override getPackageName()885 public String getPackageName() { 886 return mPackageName; 887 } 888 889 @Override getParentSessionId()890 public int getParentSessionId() { 891 return mParentSessionId; 892 } 893 894 @Override sessionId()895 public int sessionId() { 896 return mSessionId; 897 } 898 899 @Override sessionParams()900 public PackageInstaller.SessionParams sessionParams() { 901 throw new UnsupportedOperationException(); 902 } 903 904 @Override sessionContains(Predicate<StagingManager.StagedSession> filter)905 public boolean sessionContains(Predicate<StagingManager.StagedSession> filter) { 906 return filter.test(this); 907 } 908 909 @Override containsApkSession()910 public boolean containsApkSession() { 911 Preconditions.checkState(!hasParentSessionId(), "Child session"); 912 if (!isMultiPackage()) { 913 return !isApexSession(); 914 } 915 for (StagingManager.StagedSession session : mChildSessions) { 916 if (!session.isApexSession()) { 917 return true; 918 } 919 } 920 return false; 921 } 922 923 @Override containsApexSession()924 public boolean containsApexSession() { 925 Preconditions.checkState(!hasParentSessionId(), "Child session"); 926 if (!isMultiPackage()) { 927 return isApexSession(); 928 } 929 for (StagingManager.StagedSession session : mChildSessions) { 930 if (session.isApexSession()) { 931 return true; 932 } 933 } 934 return false; 935 } 936 937 @Override setSessionReady()938 public void setSessionReady() { 939 mIsReady = true; 940 } 941 942 @Override setSessionFailed(int errorCode, String errorMessage)943 public void setSessionFailed(int errorCode, String errorMessage) { 944 Preconditions.checkState(!mIsApplied, "Already marked as applied"); 945 mIsFailed = true; 946 mErrorCode = errorCode; 947 mErrorMessage = errorMessage; 948 } 949 950 @Override setSessionApplied()951 public void setSessionApplied() { 952 Preconditions.checkState(!mIsFailed, "Already marked as failed"); 953 mIsApplied = true; 954 } 955 956 @Override installSession()957 public CompletableFuture<Void> installSession() { 958 throw new UnsupportedOperationException(); 959 } 960 961 @Override hasParentSessionId()962 public boolean hasParentSessionId() { 963 return mParentSessionId != -1; 964 } 965 966 @Override getCommittedMillis()967 public long getCommittedMillis() { 968 throw new UnsupportedOperationException(); 969 } 970 971 @Override abandon()972 public void abandon() { 973 mIsAbandonded = true; 974 } 975 976 @Override verifySession()977 public void verifySession() { 978 mVerificationStarted = true; 979 } 980 } 981 } 982