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