• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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