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