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