• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 android.annotation.NonNull;
20 import android.apex.ApexInfo;
21 import android.apex.ApexSessionInfo;
22 import android.apex.ApexSessionParams;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.ApexStagedEvent;
28 import android.content.pm.IStagedApexObserver;
29 import android.content.pm.PackageInstaller;
30 import android.content.pm.PackageManager;
31 import android.content.pm.PackageManagerInternal;
32 import android.content.pm.StagedApexInfo;
33 import android.os.IBinder;
34 import android.os.PowerManager;
35 import android.os.RemoteException;
36 import android.os.SystemProperties;
37 import android.os.Trace;
38 import android.os.UserHandle;
39 import android.text.TextUtils;
40 import android.util.ArraySet;
41 import android.util.IntArray;
42 import android.util.Slog;
43 import android.util.SparseArray;
44 import android.util.TimingsTraceLog;
45 
46 import com.android.internal.annotations.GuardedBy;
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.internal.content.InstallLocationUtils;
49 import com.android.internal.os.BackgroundThread;
50 import com.android.internal.util.Preconditions;
51 import com.android.server.LocalServices;
52 import com.android.server.SystemService;
53 import com.android.server.SystemServiceManager;
54 import com.android.server.pm.pkg.AndroidPackage;
55 import com.android.server.pm.pkg.PackageStateInternal;
56 import com.android.server.pm.pkg.PackageStateUtils;
57 import com.android.server.rollback.ApexdRevertLogger;
58 import com.android.server.rollback.RollbackManagerInternal;
59 
60 import java.io.BufferedReader;
61 import java.io.BufferedWriter;
62 import java.io.File;
63 import java.io.FileReader;
64 import java.io.FileWriter;
65 import java.util.ArrayList;
66 import java.util.Arrays;
67 import java.util.Collections;
68 import java.util.List;
69 import java.util.Set;
70 import java.util.concurrent.CompletableFuture;
71 import java.util.concurrent.ExecutionException;
72 import java.util.function.Predicate;
73 
74 /**
75  * This class handles staged install sessions, i.e. install sessions that require packages to
76  * be installed only after a reboot.
77  */
78 public class StagingManager {
79 
80     private static final String TAG = "StagingManager";
81 
82     private final ApexManager mApexManager;
83     private final PowerManager mPowerManager;
84     private final Context mContext;
85 
86     private final File mFailureReasonFile = new File("/metadata/staged-install/failure_reason.txt");
87     private String mFailureReason;
88 
89     @GuardedBy("mStagedSessions")
90     private final SparseArray<StagedSession> mStagedSessions = new SparseArray<>();
91 
92     @GuardedBy("mFailedPackageNames")
93     private final List<String> mFailedPackageNames = new ArrayList<>();
94     private String mNativeFailureReason;
95 
96     @GuardedBy("mSuccessfulStagedSessionIds")
97     private final List<Integer> mSuccessfulStagedSessionIds = new ArrayList<>();
98 
99     @GuardedBy("mStagedApexObservers")
100     private final List<IStagedApexObserver> mStagedApexObservers = new ArrayList<>();
101 
102     private final CompletableFuture<Void> mBootCompleted = new CompletableFuture<>();
103 
104     interface StagedSession {
isMultiPackage()105         boolean isMultiPackage();
isApexSession()106         boolean isApexSession();
isCommitted()107         boolean isCommitted();
isInTerminalState()108         boolean isInTerminalState();
isDestroyed()109         boolean isDestroyed();
isSessionReady()110         boolean isSessionReady();
isSessionApplied()111         boolean isSessionApplied();
isSessionFailed()112         boolean isSessionFailed();
getChildSessions()113         List<StagedSession> getChildSessions();
getPackageName()114         String getPackageName();
getParentSessionId()115         int getParentSessionId();
sessionId()116         int sessionId();
sessionParams()117         PackageInstaller.SessionParams sessionParams();
sessionContains(Predicate<StagedSession> filter)118         boolean sessionContains(Predicate<StagedSession> filter);
containsApkSession()119         boolean containsApkSession();
containsApexSession()120         boolean containsApexSession();
setSessionReady()121         void setSessionReady();
setSessionFailed(int errorCode, String errorMessage)122         void setSessionFailed(int errorCode, String errorMessage);
setSessionApplied()123         void setSessionApplied();
installSession()124         CompletableFuture<Void> installSession();
hasParentSessionId()125         boolean hasParentSessionId();
getCommittedMillis()126         long getCommittedMillis();
abandon()127         void abandon();
verifySession()128         void verifySession();
129     }
130 
StagingManager(Context context)131     StagingManager(Context context) {
132         this(context, ApexManager.getInstance());
133     }
134 
135     @VisibleForTesting
StagingManager(Context context, ApexManager apexManager)136     StagingManager(Context context, ApexManager apexManager) {
137         mContext = context;
138 
139         mApexManager = apexManager;
140         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
141 
142         if (mFailureReasonFile.exists()) {
143             try (BufferedReader reader = new BufferedReader(new FileReader(mFailureReasonFile))) {
144                 mFailureReason = reader.readLine();
145             } catch (Exception ignore) { }
146         }
147     }
148 
149     /**
150      This class manages lifecycle events for StagingManager.
151      */
152     public static final class Lifecycle extends SystemService {
153         private static StagingManager sStagingManager;
154 
Lifecycle(Context context)155         public Lifecycle(Context context) {
156             super(context);
157         }
158 
startService(StagingManager stagingManager)159         void startService(StagingManager stagingManager) {
160             sStagingManager = stagingManager;
161             LocalServices.getService(SystemServiceManager.class).startService(this);
162         }
163 
164         @Override
onStart()165         public void onStart() {
166             // no-op
167         }
168 
169         @Override
onBootPhase(int phase)170         public void onBootPhase(int phase) {
171             if (phase == SystemService.PHASE_BOOT_COMPLETED && sStagingManager != null) {
172                 sStagingManager.markStagedSessionsAsSuccessful();
173                 sStagingManager.markBootCompleted();
174             }
175         }
176     }
177 
markBootCompleted()178     private void markBootCompleted() {
179         mApexManager.markBootCompleted();
180     }
181 
registerStagedApexObserver(IStagedApexObserver observer)182     void registerStagedApexObserver(IStagedApexObserver observer) {
183         if (observer == null) {
184             return;
185         }
186         if  (observer.asBinder() != null) {
187             try {
188                 observer.asBinder().linkToDeath(new IBinder.DeathRecipient() {
189                     @Override
190                     public void binderDied() {
191                         synchronized (mStagedApexObservers) {
192                             mStagedApexObservers.remove(observer);
193                         }
194                     }
195                 }, 0);
196             } catch (RemoteException re) {
197                 Slog.w(TAG, re.getMessage());
198             }
199         }
200         synchronized (mStagedApexObservers) {
201             mStagedApexObservers.add(observer);
202         }
203     }
204 
unregisterStagedApexObserver(IStagedApexObserver observer)205     void unregisterStagedApexObserver(IStagedApexObserver observer) {
206         synchronized (mStagedApexObservers) {
207             mStagedApexObservers.remove(observer);
208         }
209     }
210 
211     // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
abortCheckpoint(String failureReason, boolean supportsCheckpoint, boolean needsCheckpoint)212     private void abortCheckpoint(String failureReason, boolean supportsCheckpoint,
213             boolean needsCheckpoint) {
214         Slog.e(TAG, failureReason);
215         try {
216             if (supportsCheckpoint && needsCheckpoint) {
217                 // Store failure reason for next reboot
218                 try (BufferedWriter writer =
219                              new BufferedWriter(new FileWriter(mFailureReasonFile))) {
220                     writer.write(failureReason);
221                 } catch (Exception e) {
222                     Slog.w(TAG, "Failed to save failure reason: ", e);
223                 }
224 
225                 // Only revert apex sessions if device supports updating apex
226                 if (mApexManager.isApexSupported()) {
227                     mApexManager.revertActiveSessions();
228                 }
229 
230                 InstallLocationUtils.getStorageManager().abortChanges(
231                         "abort-staged-install", false /*retry*/);
232             }
233         } catch (Exception e) {
234             Slog.wtf(TAG, "Failed to abort checkpoint", e);
235             // Only revert apex sessions if device supports updating apex
236             if (mApexManager.isApexSupported()) {
237                 mApexManager.revertActiveSessions();
238             }
239             mPowerManager.reboot(null);
240         }
241     }
242 
243     /**
244      * Utility function for extracting apex sessions out of multi-package/single session.
245      */
extractApexSessions(StagedSession session)246     private List<StagedSession> extractApexSessions(StagedSession session) {
247         List<StagedSession> apexSessions = new ArrayList<>();
248         if (session.isMultiPackage()) {
249             for (StagedSession s : session.getChildSessions()) {
250                 if (s.containsApexSession()) {
251                     apexSessions.add(s);
252                 }
253             }
254         } else {
255             apexSessions.add(session);
256         }
257         return apexSessions;
258     }
259 
260     /**
261      * Checks if all apk-in-apex were installed without errors for all of the apex sessions. Throws
262      * error for any apk-in-apex failed to install.
263      *
264      * @throws PackageManagerException if any apk-in-apex failed to install
265      */
checkInstallationOfApkInApexSuccessful(StagedSession session)266     private void checkInstallationOfApkInApexSuccessful(StagedSession session)
267             throws PackageManagerException {
268         final List<StagedSession> apexSessions = extractApexSessions(session);
269         if (apexSessions.isEmpty()) {
270             return;
271         }
272 
273         for (StagedSession apexSession : apexSessions) {
274             String packageName = apexSession.getPackageName();
275             String errorMsg = mApexManager.getApkInApexInstallError(packageName);
276             if (errorMsg != null) {
277                 throw new PackageManagerException(PackageManager.INSTALL_ACTIVATION_FAILED,
278                         "Failed to install apk-in-apex of " + packageName + " : " + errorMsg);
279             }
280         }
281     }
282 
283     /**
284      * Perform snapshot and restore as required both for APEXes themselves and for apks in APEX.
285      * Apks inside apex are not installed using apk-install flow. They are scanned from the system
286      * directory directly by PackageManager, as such, RollbackManager need to handle their data
287      * separately here.
288      */
snapshotAndRestoreForApexSession(StagedSession session)289     private void snapshotAndRestoreForApexSession(StagedSession session) {
290         boolean doSnapshotOrRestore =
291                 (session.sessionParams().installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
292                 || session.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK;
293         if (!doSnapshotOrRestore) {
294             return;
295         }
296 
297         // Find all the apex sessions that needs processing
298         final List<StagedSession> apexSessions = extractApexSessions(session);
299         if (apexSessions.isEmpty()) {
300             return;
301         }
302 
303         final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
304         final int[] allUsers = um.getUserIds();
305         RollbackManagerInternal rm = LocalServices.getService(RollbackManagerInternal.class);
306 
307         for (int i = 0, sessionsSize = apexSessions.size(); i < sessionsSize; i++) {
308             final String packageName = apexSessions.get(i).getPackageName();
309             // Perform any snapshots or restores for the APEX itself
310             snapshotAndRestoreApexUserData(packageName, allUsers, rm);
311 
312             // Process the apks inside the APEX
313             final List<String> apksInApex = mApexManager.getApksInApex(packageName);
314             for (int j = 0, apksSize = apksInApex.size(); j < apksSize; j++) {
315                 snapshotAndRestoreApkInApexUserData(apksInApex.get(j), allUsers, rm);
316             }
317         }
318     }
319 
snapshotAndRestoreApexUserData( String packageName, int[] allUsers, RollbackManagerInternal rm)320     private void snapshotAndRestoreApexUserData(
321             String packageName, int[] allUsers, RollbackManagerInternal rm) {
322         // appId, ceDataInode, and seInfo are not needed for APEXes
323         rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(allUsers), 0, 0,
324                 null, 0 /*token*/);
325     }
326 
snapshotAndRestoreApkInApexUserData( String packageName, int[] allUsers, RollbackManagerInternal rm)327     private void snapshotAndRestoreApkInApexUserData(
328             String packageName, int[] allUsers, RollbackManagerInternal rm) {
329         PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class);
330         AndroidPackage pkg = mPmi.getPackage(packageName);
331         if (pkg == null) {
332             Slog.e(TAG, "Could not find package: " + packageName
333                     + "for snapshotting/restoring user data.");
334             return;
335         }
336 
337         int appId = -1;
338         long ceDataInode = -1;
339         final PackageStateInternal ps = mPmi.getPackageStateInternal(packageName);
340         if (ps != null) {
341             appId = ps.getAppId();
342             ceDataInode = ps.getUserStateOrDefault(UserHandle.USER_SYSTEM).getCeDataInode();
343             // NOTE: We ignore the user specified in the InstallParam because we know this is
344             // an update, and hence need to restore data for all installed users.
345             final int[] installedUsers = PackageStateUtils.queryInstalledUsers(ps, allUsers, true);
346 
347             final String seInfo = ps.getSeInfo();
348             rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers),
349                     appId, ceDataInode, seInfo, 0 /*token*/);
350         }
351     }
352 
353     /**
354      *  Prepares for the logging of apexd reverts by storing the native failure reason if necessary,
355      *  and adding the package name of the session which apexd reverted to the list of reverted
356      *  session package names.
357      *  Logging needs to wait until the ACTION_BOOT_COMPLETED broadcast is sent.
358      */
prepareForLoggingApexdRevert(@onNull StagedSession session, @NonNull String nativeFailureReason)359     private void prepareForLoggingApexdRevert(@NonNull StagedSession session,
360             @NonNull String nativeFailureReason) {
361         synchronized (mFailedPackageNames) {
362             mNativeFailureReason = nativeFailureReason;
363             if (session.getPackageName() != null) {
364                 mFailedPackageNames.add(session.getPackageName());
365             }
366         }
367     }
368 
resumeSession(@onNull StagedSession session, boolean supportsCheckpoint, boolean needsCheckpoint)369     private void resumeSession(@NonNull StagedSession session, boolean supportsCheckpoint,
370             boolean needsCheckpoint) throws PackageManagerException {
371         Slog.d(TAG, "Resuming session " + session.sessionId());
372 
373         final boolean hasApex = session.containsApexSession();
374 
375         // Before we resume session, we check if revert is needed or not. Typically, we enter file-
376         // system checkpoint mode when we reboot first time in order to install staged sessions. We
377         // want to install staged sessions in this mode as rebooting now will revert user data. If
378         // something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will
379         // have no effect on user data, so mark the sessions as failed instead.
380         // If checkpoint is supported, then we only resume sessions if we are in checkpointing mode.
381         // If not, we fail all sessions.
382         if (supportsCheckpoint && !needsCheckpoint) {
383             String revertMsg = "Reverting back to safe state. Marking " + session.sessionId()
384                     + " as failed.";
385             final String reasonForRevert = getReasonForRevert();
386             if (!TextUtils.isEmpty(reasonForRevert)) {
387                 revertMsg += " Reason for revert: " + reasonForRevert;
388             }
389             Slog.d(TAG, revertMsg);
390             session.setSessionFailed(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, revertMsg);
391             return;
392         }
393 
394         // Handle apk and apk-in-apex installation
395         if (hasApex) {
396             checkInstallationOfApkInApexSuccessful(session);
397             checkDuplicateApkInApex(session);
398             snapshotAndRestoreForApexSession(session);
399             Slog.i(TAG, "APEX packages in session " + session.sessionId()
400                     + " were successfully activated. Proceeding with APK packages, if any");
401         }
402         // The APEX part of the session is activated, proceed with the installation of APKs.
403         Slog.d(TAG, "Installing APK packages in session " + session.sessionId());
404         TimingsTraceLog t = new TimingsTraceLog(
405                 "StagingManagerTiming", Trace.TRACE_TAG_PACKAGE_MANAGER);
406         t.traceBegin("installApksInSession");
407         installApksInSession(session);
408         t.traceEnd();
409 
410         if (hasApex) {
411             if (supportsCheckpoint) {
412                 // Store the session ID, which will be marked as successful by ApexManager upon
413                 // boot completion.
414                 synchronized (mSuccessfulStagedSessionIds) {
415                     mSuccessfulStagedSessionIds.add(session.sessionId());
416                 }
417             } else {
418                 // Mark sessions as successful immediately on non-checkpointing devices.
419                 mApexManager.markStagedSessionSuccessful(session.sessionId());
420             }
421         }
422     }
423 
onInstallationFailure(StagedSession session, PackageManagerException e, boolean supportsCheckpoint, boolean needsCheckpoint)424     void onInstallationFailure(StagedSession session, PackageManagerException e,
425             boolean supportsCheckpoint, boolean needsCheckpoint) {
426         session.setSessionFailed(e.error, e.getMessage());
427         abortCheckpoint("Failed to install sessionId: " + session.sessionId()
428                 + " Error: " + e.getMessage(), supportsCheckpoint, needsCheckpoint);
429 
430         // If checkpoint is not supported, we have to handle failure for one staged session.
431         if (!session.containsApexSession()) {
432             return;
433         }
434 
435         if (!mApexManager.revertActiveSessions()) {
436             Slog.e(TAG, "Failed to abort APEXd session");
437         } else {
438             Slog.e(TAG,
439                     "Successfully aborted apexd session. Rebooting device in order to revert "
440                             + "to the previous state of APEXd.");
441             mPowerManager.reboot(null);
442         }
443     }
444 
getReasonForRevert()445     private String getReasonForRevert() {
446         if (!TextUtils.isEmpty(mFailureReason)) {
447             return mFailureReason;
448         }
449         if (!TextUtils.isEmpty(mNativeFailureReason)) {
450             return "Session reverted due to crashing native process: " + mNativeFailureReason;
451         }
452         return "";
453     }
454 
455     /**
456      * Throws a PackageManagerException if there are duplicate packages in apk and apk-in-apex.
457      */
checkDuplicateApkInApex(@onNull StagedSession session)458     private void checkDuplicateApkInApex(@NonNull StagedSession session)
459             throws PackageManagerException {
460         if (!session.isMultiPackage()) {
461             return;
462         }
463         final Set<String> apkNames = new ArraySet<>();
464         for (StagedSession s : session.getChildSessions()) {
465             if (!s.isApexSession()) {
466                 apkNames.add(s.getPackageName());
467             }
468         }
469         final List<StagedSession> apexSessions = extractApexSessions(session);
470         for (StagedSession apexSession : apexSessions) {
471             String packageName = apexSession.getPackageName();
472             for (String apkInApex : mApexManager.getApksInApex(packageName)) {
473                 if (!apkNames.add(apkInApex)) {
474                     throw new PackageManagerException(
475                             PackageManager.INSTALL_ACTIVATION_FAILED,
476                             "Package: " + packageName + " in session: "
477                                     + apexSession.sessionId() + " has duplicate apk-in-apex: "
478                                     + apkInApex, null);
479 
480                 }
481             }
482         }
483     }
484 
installApksInSession(StagedSession session)485     private void installApksInSession(StagedSession session) throws PackageManagerException {
486         try {
487             // Blocking wait for installation to complete
488             session.installSession().get();
489         } catch (InterruptedException e) {
490             // Should be impossible
491             throw new RuntimeException(e);
492         } catch (ExecutionException ee) {
493             throw (PackageManagerException) ee.getCause();
494         }
495     }
496 
497     @VisibleForTesting
commitSession(@onNull StagedSession session)498     void commitSession(@NonNull StagedSession session) {
499         createSession(session);
500         handleCommittedSession(session);
501     }
502 
handleCommittedSession(@onNull StagedSession session)503     private void handleCommittedSession(@NonNull StagedSession session) {
504         if (session.isSessionReady() && session.containsApexSession()) {
505             notifyStagedApexObservers();
506         }
507     }
508 
509     @VisibleForTesting
createSession(@onNull StagedSession sessionInfo)510     void createSession(@NonNull StagedSession sessionInfo) {
511         synchronized (mStagedSessions) {
512             mStagedSessions.append(sessionInfo.sessionId(), sessionInfo);
513         }
514     }
515 
abortSession(@onNull StagedSession session)516     void abortSession(@NonNull StagedSession session) {
517         synchronized (mStagedSessions) {
518             mStagedSessions.remove(session.sessionId());
519         }
520     }
521 
522     /**
523      * <p>Abort committed staged session
524      */
abortCommittedSession(@onNull StagedSession session)525     void abortCommittedSession(@NonNull StagedSession session) {
526         int sessionId = session.sessionId();
527         if (session.isInTerminalState()) {
528             Slog.w(TAG, "Cannot abort session in final state: " + sessionId);
529             return;
530         }
531         if (!session.isDestroyed()) {
532             throw new IllegalStateException("Committed session must be destroyed before aborting it"
533                     + " from StagingManager");
534         }
535         if (getStagedSession(sessionId) == null) {
536             Slog.w(TAG, "Session " + sessionId + " has been abandoned already");
537             return;
538         }
539 
540         // A session could be marked ready once its pre-reboot verification ends
541         if (session.isSessionReady()) {
542             if (!ensureActiveApexSessionIsAborted(session)) {
543                 // Failed to ensure apex session is aborted, so it can still be staged. We can still
544                 // safely cleanup the staged session since pre-reboot verification is complete.
545                 // Also, cleaning up the stageDir prevents the apex from being activated.
546                 Slog.e(TAG, "Failed to abort apex session " + session.sessionId());
547             }
548             if (session.containsApexSession()) {
549                 notifyStagedApexObservers();
550             }
551         }
552 
553         // Session was successfully aborted from apexd (if required) and pre-reboot verification
554         // is also complete. It is now safe to clean up the session from system.
555         abortSession(session);
556     }
557 
558     /**
559      * Ensure that there is no active apex session staged in apexd for the given session.
560      *
561      * @return returns true if it is ensured that there is no active apex session, otherwise false
562      */
ensureActiveApexSessionIsAborted(StagedSession session)563     private boolean ensureActiveApexSessionIsAborted(StagedSession session) {
564         if (!session.containsApexSession()) {
565             return true;
566         }
567         final ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId());
568         if (apexSession == null || isApexSessionFinalized(apexSession)) {
569             return true;
570         }
571         return mApexManager.abortStagedSession(session.sessionId());
572     }
573 
isApexSessionFinalized(ApexSessionInfo session)574     private boolean isApexSessionFinalized(ApexSessionInfo session) {
575         /* checking if the session is in a final state, i.e., not active anymore */
576         return session.isUnknown || session.isActivationFailed || session.isSuccess
577                 || session.isReverted;
578     }
579 
isApexSessionFailed(ApexSessionInfo apexSessionInfo)580     private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) {
581         // isRevertInProgress is included to cover the scenario, when a device is rebooted
582         // during the revert, and apexd fails to resume the revert after reboot.
583         return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown
584                 || apexSessionInfo.isReverted || apexSessionInfo.isRevertInProgress
585                 || apexSessionInfo.isRevertFailed;
586     }
587 
handleNonReadyAndDestroyedSessions(List<StagedSession> sessions)588     private void handleNonReadyAndDestroyedSessions(List<StagedSession> sessions) {
589         int j = sessions.size();
590         for (int i = 0; i < j; ) {
591             // Maintain following invariant:
592             //  * elements at positions [0, i) should be kept
593             //  * elements at positions [j, n) should be remove.
594             //  * n = sessions.size()
595             StagedSession session = sessions.get(i);
596             if (session.isDestroyed()) {
597                 // Device rebooted before abandoned session was cleaned up.
598                 session.abandon();
599                 StagedSession session2 = sessions.set(j - 1, session);
600                 sessions.set(i, session2);
601                 j--;
602             } else if (!session.isSessionReady()) {
603                 // The framework got restarted before the pre-reboot verification could complete,
604                 // restart the verification.
605                 Slog.i(TAG, "Restart verification for session=" + session.sessionId());
606                 mBootCompleted.thenRun(() -> session.verifySession());
607                 StagedSession session2 = sessions.set(j - 1, session);
608                 sessions.set(i, session2);
609                 j--;
610             } else {
611                 i++;
612             }
613         }
614         // Delete last j elements.
615         sessions.subList(j, sessions.size()).clear();
616     }
617 
restoreSessions(@onNull List<StagedSession> sessions, boolean isDeviceUpgrading)618     void restoreSessions(@NonNull List<StagedSession> sessions, boolean isDeviceUpgrading) {
619         TimingsTraceLog t = new TimingsTraceLog(
620                 "StagingManagerTiming", Trace.TRACE_TAG_PACKAGE_MANAGER);
621         t.traceBegin("restoreSessions");
622 
623         // Do not resume sessions if boot completed already
624         if (SystemProperties.getBoolean("sys.boot_completed", false)) {
625             return;
626         }
627 
628         for (int i = 0; i < sessions.size(); i++) {
629             StagedSession session = sessions.get(i);
630             // Quick check that PackageInstallerService gave us sessions we expected.
631             Preconditions.checkArgument(!session.hasParentSessionId(),
632                     session.sessionId() + " is a child session");
633             Preconditions.checkArgument(session.isCommitted(),
634                     session.sessionId() + " is not committed");
635             Preconditions.checkArgument(!session.isInTerminalState(),
636                     session.sessionId() + " is in terminal state");
637             // Store this parent session which will be used to check overlapping later
638             createSession(session);
639         }
640 
641         if (isDeviceUpgrading) {
642             // TODO(ioffe): check that corresponding apex sessions are failed.
643             // The preconditions used during pre-reboot verification might have changed when device
644             // is upgrading. Fail all the sessions and exit early.
645             for (int i = 0; i < sessions.size(); i++) {
646                 StagedSession session = sessions.get(i);
647                 session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED,
648                         "Build fingerprint has changed");
649             }
650             return;
651         }
652 
653         boolean needsCheckpoint = false;
654         boolean supportsCheckpoint = false;
655         try {
656             supportsCheckpoint = InstallLocationUtils.getStorageManager().supportsCheckpoint();
657             needsCheckpoint = InstallLocationUtils.getStorageManager().needsCheckpoint();
658         } catch (RemoteException e) {
659             // This means that vold has crashed, and device is in a bad state.
660             throw new IllegalStateException("Failed to get checkpoint status", e);
661         }
662 
663         if (sessions.size() > 1 && !supportsCheckpoint) {
664             throw new IllegalStateException("Detected multiple staged sessions on a device without "
665                     + "fs-checkpoint support");
666         }
667 
668         // Do a set of quick checks before resuming individual sessions:
669         //   1. Schedule a pre-reboot verification for non-ready sessions.
670         //   2. Abandon destroyed sessions.
671         handleNonReadyAndDestroyedSessions(sessions); // mutates |sessions|
672 
673         //   3. Check state of apex sessions is consistent. All non-applied sessions will be marked
674         //      as failed.
675         final SparseArray<ApexSessionInfo> apexSessions = mApexManager.getSessions();
676         boolean hasFailedApexSession = false;
677         boolean hasAppliedApexSession = false;
678         for (int i = 0; i < sessions.size(); i++) {
679             StagedSession session = sessions.get(i);
680             if (!session.containsApexSession()) {
681                 // At this point we are only interested in apex sessions.
682                 continue;
683             }
684             final ApexSessionInfo apexSession = apexSessions.get(session.sessionId());
685             if (apexSession == null || apexSession.isUnknown) {
686                 hasFailedApexSession = true;
687                 session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, "apexd did "
688                         + "not know anything about a staged session supposed to be activated");
689                 continue;
690             } else if (isApexSessionFailed(apexSession)) {
691                 hasFailedApexSession = true;
692                 if (!TextUtils.isEmpty(apexSession.crashingNativeProcess)) {
693                     prepareForLoggingApexdRevert(session, apexSession.crashingNativeProcess);
694                 }
695                 String errorMsg = "APEX activation failed.";
696                 final String reasonForRevert = getReasonForRevert();
697                 if (!TextUtils.isEmpty(reasonForRevert)) {
698                     errorMsg += " Reason: " + reasonForRevert;
699                 } else if (!TextUtils.isEmpty(apexSession.errorMessage)) {
700                     errorMsg += " Error: " + apexSession.errorMessage;
701                 }
702                 Slog.d(TAG, errorMsg);
703                 session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, errorMsg);
704                 continue;
705             } else if (apexSession.isActivated || apexSession.isSuccess) {
706                 hasAppliedApexSession = true;
707                 continue;
708             } else if (apexSession.isStaged) {
709                 // Apexd did not apply the session for some unknown reason. There is no guarantee
710                 // that apexd will install it next time. Safer to proactively mark it as failed.
711                 hasFailedApexSession = true;
712                 session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED,
713                         "Staged session " + session.sessionId() + " at boot didn't activate nor "
714                         + "fail. Marking it as failed anyway.");
715             } else {
716                 Slog.w(TAG, "Apex session " + session.sessionId() + " is in impossible state");
717                 hasFailedApexSession = true;
718                 session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED,
719                         "Impossible state");
720             }
721         }
722 
723         if (hasAppliedApexSession && hasFailedApexSession) {
724             abortCheckpoint("Found both applied and failed apex sessions", supportsCheckpoint,
725                     needsCheckpoint);
726             return;
727         }
728 
729         if (hasFailedApexSession) {
730             // Either of those means that we failed at least one apex session, hence we should fail
731             // all other sessions.
732             for (int i = 0; i < sessions.size(); i++) {
733                 StagedSession session = sessions.get(i);
734                 if (session.isSessionFailed()) {
735                     // Session has been already failed in the loop above.
736                     continue;
737                 }
738                 session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED,
739                         "Another apex session failed");
740             }
741             return;
742         }
743 
744         // Time to resume sessions.
745         for (int i = 0; i < sessions.size(); i++) {
746             StagedSession session = sessions.get(i);
747             try {
748                 resumeSession(session, supportsCheckpoint, needsCheckpoint);
749             } catch (PackageManagerException e) {
750                 onInstallationFailure(session, e, supportsCheckpoint, needsCheckpoint);
751             } catch (Exception e) {
752                 Slog.e(TAG, "Staged install failed due to unhandled exception", e);
753                 onInstallationFailure(session, new PackageManagerException(
754                         PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
755                         "Staged install failed due to unhandled exception: " + e),
756                         supportsCheckpoint, needsCheckpoint);
757             }
758         }
759         t.traceEnd();
760     }
761 
logFailedApexSessionsIfNecessary()762     private void logFailedApexSessionsIfNecessary() {
763         synchronized (mFailedPackageNames) {
764             if (!mFailedPackageNames.isEmpty()) {
765                 ApexdRevertLogger.logApexdRevert(mContext,
766                         mFailedPackageNames, mNativeFailureReason);
767             }
768         }
769     }
770 
markStagedSessionsAsSuccessful()771     private void markStagedSessionsAsSuccessful() {
772         synchronized (mSuccessfulStagedSessionIds) {
773             for (int i = 0; i < mSuccessfulStagedSessionIds.size(); i++) {
774                 mApexManager.markStagedSessionSuccessful(mSuccessfulStagedSessionIds.get(i));
775             }
776         }
777     }
778 
systemReady()779     void systemReady() {
780         new Lifecycle(mContext).startService(this);
781         // Register the receiver of boot completed intent for staging manager.
782         mContext.registerReceiver(new BroadcastReceiver() {
783             @Override
784             public void onReceive(Context ctx, Intent intent) {
785                 onBootCompletedBroadcastReceived();
786                 ctx.unregisterReceiver(this);
787             }
788         }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
789 
790         mFailureReasonFile.delete();
791     }
792 
793     @VisibleForTesting
onBootCompletedBroadcastReceived()794     void onBootCompletedBroadcastReceived() {
795         mBootCompleted.complete(null);
796         BackgroundThread.getExecutor().execute(() -> logFailedApexSessionsIfNecessary());
797     }
798 
getStagedSession(int sessionId)799     private StagedSession getStagedSession(int sessionId) {
800         StagedSession session;
801         synchronized (mStagedSessions) {
802             session = mStagedSessions.get(sessionId);
803         }
804         return session;
805     }
806 
807     /**
808      * Returns ApexInfo about APEX contained inside the session.
809      *
810      * Returns an empty list if there is any error.
811      */
812     @VisibleForTesting
813     @NonNull
getStagedApexInfos(@onNull StagedSession session)814     List<ApexInfo> getStagedApexInfos(@NonNull StagedSession session) {
815         Preconditions.checkArgument(session != null, "Session is null");
816         Preconditions.checkArgument(!session.hasParentSessionId(),
817                 session.sessionId() + " session has parent session");
818         Preconditions.checkArgument(session.containsApexSession(),
819                 session.sessionId() + " session does not contain apex");
820 
821         // Even if caller calls this method on ready session, the session could be abandoned
822         // right after this method is called.
823         if (!session.isSessionReady() || session.isDestroyed()) {
824             return Collections.emptyList();
825         }
826 
827         ApexSessionParams params = new ApexSessionParams();
828         params.sessionId = session.sessionId();
829         final IntArray childSessionIds = new IntArray();
830         if (session.isMultiPackage()) {
831             for (StagedSession s : session.getChildSessions()) {
832                 if (s.isApexSession()) {
833                     childSessionIds.add(s.sessionId());
834                 }
835             }
836         }
837         params.childSessionIds = childSessionIds.toArray();
838         return Arrays.asList(mApexManager.getStagedApexInfos(params));
839     }
840 
841     /**
842      * Returns ApexInfo list about APEXes contained inside all staged sessions.
843      *
844      * Returns an empty list if there is any error.
845      */
846     @NonNull
getStagedApexInfos()847     List<StagedApexInfo> getStagedApexInfos() {
848         List<StagedApexInfo> result = new ArrayList<>();
849         synchronized (mStagedSessions) {
850             for (int i = 0; i < mStagedSessions.size(); i++) {
851                 final StagedSession session = mStagedSessions.valueAt(i);
852                 if (!session.isSessionReady() || session.isDestroyed()
853                         || session.hasParentSessionId() || !session.containsApexSession()) {
854                     continue;
855                 }
856                 getStagedApexInfos(session).stream().map(ai -> {
857                     StagedApexInfo info = new StagedApexInfo();
858                     info.moduleName = ai.moduleName;
859                     info.diskImagePath = ai.modulePath;
860                     info.versionCode = ai.versionCode;
861                     info.versionName = ai.versionName;
862                     info.hasClassPathJars = ai.hasClassPathJars;
863                     return info;
864                 }).forEach(result::add);
865             }
866         }
867         return result;
868     }
869 
notifyStagedApexObservers()870     private void notifyStagedApexObservers() {
871         synchronized (mStagedApexObservers) {
872             List<StagedApexInfo> stagedApexInfos = getStagedApexInfos();
873             ApexStagedEvent event = new ApexStagedEvent();
874             event.stagedApexInfos =
875                     stagedApexInfos.toArray(new StagedApexInfo[stagedApexInfos.size()]);
876             for (IStagedApexObserver observer : mStagedApexObservers) {
877                 try {
878                     observer.onApexStaged(event);
879                 } catch (RemoteException re) {
880                     Slog.w(TAG, "Failed to contact the observer " + re.getMessage());
881                 }
882             }
883         }
884     }
885 }
886