• 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.ApexInfoList;
22 import android.apex.ApexSessionInfo;
23 import android.apex.ApexSessionParams;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.IIntentReceiver;
27 import android.content.IIntentSender;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.IntentSender;
31 import android.content.pm.ApplicationInfo;
32 import android.content.pm.PackageInfo;
33 import android.content.pm.PackageInstaller;
34 import android.content.pm.PackageInstaller.SessionInfo;
35 import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode;
36 import android.content.pm.PackageManager;
37 import android.content.pm.PackageManagerInternal;
38 import android.content.pm.PackageParser.PackageParserException;
39 import android.content.pm.PackageParser.SigningDetails;
40 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
41 import android.content.pm.parsing.PackageInfoWithoutStateUtils;
42 import android.content.rollback.RollbackInfo;
43 import android.content.rollback.RollbackManager;
44 import android.os.Bundle;
45 import android.os.Handler;
46 import android.os.IBinder;
47 import android.os.Looper;
48 import android.os.Message;
49 import android.os.PowerManager;
50 import android.os.RemoteException;
51 import android.os.SystemProperties;
52 import android.os.Trace;
53 import android.os.UserHandle;
54 import android.text.TextUtils;
55 import android.util.ArraySet;
56 import android.util.IntArray;
57 import android.util.Slog;
58 import android.util.SparseArray;
59 import android.util.TimingsTraceLog;
60 import android.util.apk.ApkSignatureVerifier;
61 
62 import com.android.internal.annotations.GuardedBy;
63 import com.android.internal.annotations.VisibleForTesting;
64 import com.android.internal.content.PackageHelper;
65 import com.android.internal.os.BackgroundThread;
66 import com.android.internal.util.Preconditions;
67 import com.android.server.LocalServices;
68 import com.android.server.SystemService;
69 import com.android.server.SystemServiceManager;
70 import com.android.server.pm.parsing.PackageParser2;
71 import com.android.server.pm.parsing.pkg.AndroidPackage;
72 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
73 import com.android.server.pm.parsing.pkg.ParsedPackage;
74 import com.android.server.rollback.RollbackManagerInternal;
75 import com.android.server.rollback.WatchdogRollbackLogger;
76 
77 import java.io.BufferedReader;
78 import java.io.BufferedWriter;
79 import java.io.File;
80 import java.io.FileReader;
81 import java.io.FileWriter;
82 import java.util.ArrayList;
83 import java.util.List;
84 import java.util.Set;
85 import java.util.concurrent.LinkedBlockingQueue;
86 import java.util.concurrent.TimeUnit;
87 import java.util.function.Predicate;
88 import java.util.function.Supplier;
89 
90 /**
91  * This class handles staged install sessions, i.e. install sessions that require packages to
92  * be installed only after a reboot.
93  */
94 public class StagingManager {
95 
96     private static final String TAG = "StagingManager";
97 
98     private final ApexManager mApexManager;
99     private final PowerManager mPowerManager;
100     private final Context mContext;
101     private final PreRebootVerificationHandler mPreRebootVerificationHandler;
102     private final Supplier<PackageParser2> mPackageParserSupplier;
103 
104     private final File mFailureReasonFile = new File("/metadata/staged-install/failure_reason.txt");
105     private String mFailureReason;
106 
107     @GuardedBy("mStagedSessions")
108     private final SparseArray<StagedSession> mStagedSessions = new SparseArray<>();
109 
110     @GuardedBy("mFailedPackageNames")
111     private final List<String> mFailedPackageNames = new ArrayList<>();
112     private String mNativeFailureReason;
113 
114     @GuardedBy("mSuccessfulStagedSessionIds")
115     private final List<Integer> mSuccessfulStagedSessionIds = new ArrayList<>();
116 
117     interface StagedSession {
isMultiPackage()118         boolean isMultiPackage();
isApexSession()119         boolean isApexSession();
isCommitted()120         boolean isCommitted();
isInTerminalState()121         boolean isInTerminalState();
isDestroyed()122         boolean isDestroyed();
isSessionReady()123         boolean isSessionReady();
isSessionApplied()124         boolean isSessionApplied();
isSessionFailed()125         boolean isSessionFailed();
getChildSessions()126         List<StagedSession> getChildSessions();
getPackageName()127         String getPackageName();
getParentSessionId()128         int getParentSessionId();
sessionId()129         int sessionId();
sessionParams()130         PackageInstaller.SessionParams sessionParams();
sessionContains(Predicate<StagedSession> filter)131         boolean sessionContains(Predicate<StagedSession> filter);
containsApkSession()132         boolean containsApkSession();
containsApexSession()133         boolean containsApexSession();
setSessionReady()134         void setSessionReady();
setSessionFailed(@tagedSessionErrorCode int errorCode, String errorMessage)135         void setSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage);
setSessionApplied()136         void setSessionApplied();
installSession(IntentSender statusReceiver)137         void installSession(IntentSender statusReceiver);
hasParentSessionId()138         boolean hasParentSessionId();
getCommittedMillis()139         long getCommittedMillis();
abandon()140         void abandon();
notifyStartPreRebootVerification()141         boolean notifyStartPreRebootVerification();
notifyEndPreRebootVerification()142         void notifyEndPreRebootVerification();
verifySession()143         void verifySession();
144     }
145 
StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier)146     StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier) {
147         this(context, packageParserSupplier, ApexManager.getInstance());
148     }
149 
150     @VisibleForTesting
StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier, ApexManager apexManager)151     StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier,
152             ApexManager apexManager) {
153         mContext = context;
154         mPackageParserSupplier = packageParserSupplier;
155 
156         mApexManager = apexManager;
157         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
158         mPreRebootVerificationHandler = new PreRebootVerificationHandler(
159                 BackgroundThread.get().getLooper());
160 
161         if (mFailureReasonFile.exists()) {
162             try (BufferedReader reader = new BufferedReader(new FileReader(mFailureReasonFile))) {
163                 mFailureReason = reader.readLine();
164             } catch (Exception ignore) { }
165         }
166     }
167 
168     /**
169      This class manages lifecycle events for StagingManager.
170      */
171     public static final class Lifecycle extends SystemService {
172         private static StagingManager sStagingManager;
173 
Lifecycle(Context context)174         public Lifecycle(Context context) {
175             super(context);
176         }
177 
startService(StagingManager stagingManager)178         void startService(StagingManager stagingManager) {
179             sStagingManager = stagingManager;
180             LocalServices.getService(SystemServiceManager.class).startService(this);
181         }
182 
183         @Override
onStart()184         public void onStart() {
185             // no-op
186         }
187 
188         @Override
onBootPhase(int phase)189         public void onBootPhase(int phase) {
190             if (phase == SystemService.PHASE_BOOT_COMPLETED && sStagingManager != null) {
191                 sStagingManager.markStagedSessionsAsSuccessful();
192                 sStagingManager.markBootCompleted();
193             }
194         }
195     }
196 
markBootCompleted()197     private void markBootCompleted() {
198         mApexManager.markBootCompleted();
199     }
200 
201     /**
202      * Validates the signature used to sign the container of the new apex package
203      *
204      * @param newApexPkg The new apex package that is being installed
205      */
validateApexSignature(PackageInfo newApexPkg)206     private void validateApexSignature(PackageInfo newApexPkg)
207             throws PackageManagerException {
208         // Get signing details of the new package
209         final String apexPath = newApexPkg.applicationInfo.sourceDir;
210         final String packageName = newApexPkg.packageName;
211         int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
212                 newApexPkg.applicationInfo.targetSdkVersion);
213 
214         final SigningDetails newSigningDetails;
215         try {
216             newSigningDetails = ApkSignatureVerifier.verify(apexPath, minSignatureScheme);
217         } catch (PackageParserException e) {
218             throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
219                     "Failed to parse APEX package " + apexPath + " : " + e, e);
220         }
221 
222         // Get signing details of the existing package
223         final PackageInfo existingApexPkg = mApexManager.getPackageInfo(packageName,
224                 ApexManager.MATCH_ACTIVE_PACKAGE);
225         if (existingApexPkg == null) {
226             // This should never happen, because submitSessionToApexService ensures that no new
227             // apexes were installed.
228             throw new IllegalStateException("Unknown apex package " + packageName);
229         }
230 
231         final SigningDetails existingSigningDetails;
232         try {
233             existingSigningDetails = ApkSignatureVerifier.verify(
234                 existingApexPkg.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
235         } catch (PackageParserException e) {
236             throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
237                     "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir
238                             + " : " + e, e);
239         }
240 
241         // Verify signing details for upgrade
242         if (newSigningDetails.checkCapability(existingSigningDetails,
243                 SigningDetails.CertCapabilities.INSTALLED_DATA)
244                 || existingSigningDetails.checkCapability(newSigningDetails,
245                 SigningDetails.CertCapabilities.ROLLBACK)) {
246             return;
247         }
248 
249         throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
250                 "APK-container signature of APEX package " + packageName + " with version "
251                         + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not"
252                         + " compatible with the one currently installed on device");
253     }
254 
submitSessionToApexService(@onNull StagedSession session, int rollbackId)255     private List<PackageInfo> submitSessionToApexService(@NonNull StagedSession session,
256             int rollbackId) throws PackageManagerException {
257         final IntArray childSessionIds = new IntArray();
258         if (session.isMultiPackage()) {
259             for (StagedSession s : session.getChildSessions()) {
260                 if (s.isApexSession()) {
261                     childSessionIds.add(s.sessionId());
262                 }
263             }
264         }
265         ApexSessionParams apexSessionParams = new ApexSessionParams();
266         apexSessionParams.sessionId = session.sessionId();
267         apexSessionParams.childSessionIds = childSessionIds.toArray();
268         if (session.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
269             apexSessionParams.isRollback = true;
270             apexSessionParams.rollbackId = rollbackId;
271         } else {
272             if (rollbackId != -1) {
273                 apexSessionParams.hasRollbackEnabled = true;
274                 apexSessionParams.rollbackId = rollbackId;
275             }
276         }
277         // submitStagedSession will throw a PackageManagerException if apexd verification fails,
278         // which will be propagated to populate stagedSessionErrorMessage of this session.
279         final ApexInfoList apexInfoList = mApexManager.submitStagedSession(apexSessionParams);
280         final List<PackageInfo> result = new ArrayList<>();
281         final List<String> apexPackageNames = new ArrayList<>();
282         for (ApexInfo apexInfo : apexInfoList.apexInfos) {
283             final PackageInfo packageInfo;
284             final int flags = PackageManager.GET_META_DATA;
285             try (PackageParser2 packageParser = mPackageParserSupplier.get()) {
286                 File apexFile = new File(apexInfo.modulePath);
287                 final ParsedPackage parsedPackage = packageParser.parsePackage(
288                         apexFile, flags, false);
289                 packageInfo = PackageInfoWithoutStateUtils.generate(parsedPackage, apexInfo, flags);
290                 if (packageInfo == null) {
291                     throw new PackageManagerException(
292                             SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
293                             "Unable to generate package info: " + apexInfo.modulePath);
294                 }
295             } catch (PackageParserException e) {
296                 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
297                         "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e);
298             }
299             final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName,
300                     ApexManager.MATCH_ACTIVE_PACKAGE);
301             if (activePackage == null) {
302                 Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName);
303                 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
304                         "It is forbidden to install new APEX packages.");
305             }
306             checkRequiredVersionCode(session, activePackage);
307             checkDowngrade(session, activePackage, packageInfo);
308             result.add(packageInfo);
309             apexPackageNames.add(packageInfo.packageName);
310         }
311         Slog.d(TAG, "Session " + session.sessionId() + " has following APEX packages: "
312                 + apexPackageNames);
313         return result;
314     }
315 
retrieveRollbackIdForCommitSession(int sessionId)316     private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException {
317         RollbackManager rm = mContext.getSystemService(RollbackManager.class);
318 
319         final List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks();
320         for (int i = 0, size = rollbacks.size(); i < size; i++) {
321             final RollbackInfo rollback = rollbacks.get(i);
322             if (rollback.getCommittedSessionId() == sessionId) {
323                 return rollback.getRollbackId();
324             }
325         }
326         throw new PackageManagerException(
327                 "Could not find rollback id for commit session: " + sessionId);
328     }
329 
checkRequiredVersionCode(final StagedSession session, final PackageInfo activePackage)330     private void checkRequiredVersionCode(final StagedSession session,
331             final PackageInfo activePackage) throws PackageManagerException {
332         if (session.sessionParams().requiredInstalledVersionCode
333                 == PackageManager.VERSION_CODE_HIGHEST) {
334             return;
335         }
336         final long activeVersion = activePackage.applicationInfo.longVersionCode;
337         if (activeVersion != session.sessionParams().requiredInstalledVersionCode) {
338             throw new PackageManagerException(
339                     SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
340                     "Installed version of APEX package " + activePackage.packageName
341                             + " does not match required. Active version: " + activeVersion
342                             + " required: " + session.sessionParams().requiredInstalledVersionCode);
343         }
344     }
345 
checkDowngrade(final StagedSession session, final PackageInfo activePackage, final PackageInfo newPackage)346     private void checkDowngrade(final StagedSession session,
347             final PackageInfo activePackage, final PackageInfo newPackage)
348             throws PackageManagerException {
349         final long activeVersion = activePackage.applicationInfo.longVersionCode;
350         final long newVersionCode = newPackage.applicationInfo.longVersionCode;
351         final boolean isAppDebuggable = (activePackage.applicationInfo.flags
352                 & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
353         final boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
354                 session.sessionParams().installFlags, isAppDebuggable);
355         if (activeVersion > newVersionCode && !allowsDowngrade) {
356             throw new PackageManagerException(
357                     SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
358                     "Downgrade of APEX package " + newPackage.packageName
359                             + " is not allowed. Active version: " + activeVersion
360                             + " attempted: " + newVersionCode);
361         }
362     }
363 
364     // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
abortCheckpoint(String failureReason, boolean supportsCheckpoint, boolean needsCheckpoint)365     private void abortCheckpoint(String failureReason, boolean supportsCheckpoint,
366             boolean needsCheckpoint) {
367         Slog.e(TAG, failureReason);
368         try {
369             if (supportsCheckpoint && needsCheckpoint) {
370                 // Store failure reason for next reboot
371                 try (BufferedWriter writer =
372                              new BufferedWriter(new FileWriter(mFailureReasonFile))) {
373                     writer.write(failureReason);
374                 } catch (Exception e) {
375                     Slog.w(TAG, "Failed to save failure reason: ", e);
376                 }
377 
378                 // Only revert apex sessions if device supports updating apex
379                 if (mApexManager.isApexSupported()) {
380                     mApexManager.revertActiveSessions();
381                 }
382 
383                 PackageHelper.getStorageManager().abortChanges(
384                         "abort-staged-install", false /*retry*/);
385             }
386         } catch (Exception e) {
387             Slog.wtf(TAG, "Failed to abort checkpoint", e);
388             // Only revert apex sessions if device supports updating apex
389             if (mApexManager.isApexSupported()) {
390                 mApexManager.revertActiveSessions();
391             }
392             mPowerManager.reboot(null);
393         }
394     }
395 
396     /**
397      * Utility function for extracting apex sessions out of multi-package/single session.
398      */
extractApexSessions(StagedSession session)399     private List<StagedSession> extractApexSessions(StagedSession session) {
400         List<StagedSession> apexSessions = new ArrayList<>();
401         if (session.isMultiPackage()) {
402             for (StagedSession s : session.getChildSessions()) {
403                 if (s.containsApexSession()) {
404                     apexSessions.add(s);
405                 }
406             }
407         } else {
408             apexSessions.add(session);
409         }
410         return apexSessions;
411     }
412 
413     /**
414      * Checks if all apk-in-apex were installed without errors for all of the apex sessions. Throws
415      * error for any apk-in-apex failed to install.
416      *
417      * @throws PackageManagerException if any apk-in-apex failed to install
418      */
checkInstallationOfApkInApexSuccessful(StagedSession session)419     private void checkInstallationOfApkInApexSuccessful(StagedSession session)
420             throws PackageManagerException {
421         final List<StagedSession> apexSessions = extractApexSessions(session);
422         if (apexSessions.isEmpty()) {
423             return;
424         }
425 
426         for (StagedSession apexSession : apexSessions) {
427             String packageName = apexSession.getPackageName();
428             String errorMsg = mApexManager.getApkInApexInstallError(packageName);
429             if (errorMsg != null) {
430                 throw new PackageManagerException(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
431                         "Failed to install apk-in-apex of " + packageName + " : " + errorMsg);
432             }
433         }
434     }
435 
436     /**
437      * Perform snapshot and restore as required both for APEXes themselves and for apks in APEX.
438      * Apks inside apex are not installed using apk-install flow. They are scanned from the system
439      * directory directly by PackageManager, as such, RollbackManager need to handle their data
440      * separately here.
441      */
snapshotAndRestoreForApexSession(StagedSession session)442     private void snapshotAndRestoreForApexSession(StagedSession session) {
443         boolean doSnapshotOrRestore =
444                 (session.sessionParams().installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
445                 || session.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK;
446         if (!doSnapshotOrRestore) {
447             return;
448         }
449 
450         // Find all the apex sessions that needs processing
451         final List<StagedSession> apexSessions = extractApexSessions(session);
452         if (apexSessions.isEmpty()) {
453             return;
454         }
455 
456         final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
457         final int[] allUsers = um.getUserIds();
458         RollbackManagerInternal rm = LocalServices.getService(RollbackManagerInternal.class);
459 
460         for (int i = 0, sessionsSize = apexSessions.size(); i < sessionsSize; i++) {
461             final String packageName = apexSessions.get(i).getPackageName();
462             // Perform any snapshots or restores for the APEX itself
463             snapshotAndRestoreApexUserData(packageName, allUsers, rm);
464 
465             // Process the apks inside the APEX
466             final List<String> apksInApex = mApexManager.getApksInApex(packageName);
467             for (int j = 0, apksSize = apksInApex.size(); j < apksSize; j++) {
468                 snapshotAndRestoreApkInApexUserData(apksInApex.get(j), allUsers, rm);
469             }
470         }
471     }
472 
snapshotAndRestoreApexUserData( String packageName, int[] allUsers, RollbackManagerInternal rm)473     private void snapshotAndRestoreApexUserData(
474             String packageName, int[] allUsers, RollbackManagerInternal rm) {
475         // appId, ceDataInode, and seInfo are not needed for APEXes
476         rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(allUsers), 0, 0,
477                 null, 0 /*token*/);
478     }
479 
snapshotAndRestoreApkInApexUserData( String packageName, int[] allUsers, RollbackManagerInternal rm)480     private void snapshotAndRestoreApkInApexUserData(
481             String packageName, int[] allUsers, RollbackManagerInternal rm) {
482         PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class);
483         AndroidPackage pkg = mPmi.getPackage(packageName);
484         if (pkg == null) {
485             Slog.e(TAG, "Could not find package: " + packageName
486                     + "for snapshotting/restoring user data.");
487             return;
488         }
489 
490         int appId = -1;
491         long ceDataInode = -1;
492         final PackageSetting ps = mPmi.getPackageSetting(packageName);
493         if (ps != null) {
494             appId = ps.appId;
495             ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM);
496             // NOTE: We ignore the user specified in the InstallParam because we know this is
497             // an update, and hence need to restore data for all installed users.
498             final int[] installedUsers = ps.queryInstalledUsers(allUsers, true);
499 
500             final String seInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
501             rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers),
502                     appId, ceDataInode, seInfo, 0 /*token*/);
503         }
504     }
505 
506     /**
507      *  Prepares for the logging of apexd reverts by storing the native failure reason if necessary,
508      *  and adding the package name of the session which apexd reverted to the list of reverted
509      *  session package names.
510      *  Logging needs to wait until the ACTION_BOOT_COMPLETED broadcast is sent.
511      */
prepareForLoggingApexdRevert(@onNull StagedSession session, @NonNull String nativeFailureReason)512     private void prepareForLoggingApexdRevert(@NonNull StagedSession session,
513             @NonNull String nativeFailureReason) {
514         synchronized (mFailedPackageNames) {
515             mNativeFailureReason = nativeFailureReason;
516             if (session.getPackageName() != null) {
517                 mFailedPackageNames.add(session.getPackageName());
518             }
519         }
520     }
521 
resumeSession(@onNull StagedSession session, boolean supportsCheckpoint, boolean needsCheckpoint)522     private void resumeSession(@NonNull StagedSession session, boolean supportsCheckpoint,
523             boolean needsCheckpoint) throws PackageManagerException {
524         Slog.d(TAG, "Resuming session " + session.sessionId());
525 
526         final boolean hasApex = session.containsApexSession();
527 
528         // Before we resume session, we check if revert is needed or not. Typically, we enter file-
529         // system checkpoint mode when we reboot first time in order to install staged sessions. We
530         // want to install staged sessions in this mode as rebooting now will revert user data. If
531         // something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will
532         // have no effect on user data, so mark the sessions as failed instead.
533         // If checkpoint is supported, then we only resume sessions if we are in checkpointing mode.
534         // If not, we fail all sessions.
535         if (supportsCheckpoint && !needsCheckpoint) {
536             String revertMsg = "Reverting back to safe state. Marking " + session.sessionId()
537                     + " as failed.";
538             final String reasonForRevert = getReasonForRevert();
539             if (!TextUtils.isEmpty(reasonForRevert)) {
540                 revertMsg += " Reason for revert: " + reasonForRevert;
541             }
542             Slog.d(TAG, revertMsg);
543             session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg);
544             return;
545         }
546 
547         // Handle apk and apk-in-apex installation
548         if (hasApex) {
549             checkInstallationOfApkInApexSuccessful(session);
550             checkDuplicateApkInApex(session);
551             snapshotAndRestoreForApexSession(session);
552             Slog.i(TAG, "APEX packages in session " + session.sessionId()
553                     + " were successfully activated. Proceeding with APK packages, if any");
554         }
555         // The APEX part of the session is activated, proceed with the installation of APKs.
556         Slog.d(TAG, "Installing APK packages in session " + session.sessionId());
557         TimingsTraceLog t = new TimingsTraceLog(
558                 "StagingManagerTiming", Trace.TRACE_TAG_PACKAGE_MANAGER);
559         t.traceBegin("installApksInSession");
560         installApksInSession(session);
561         t.traceEnd();
562 
563         Slog.d(TAG, "Marking session " + session.sessionId() + " as applied");
564         session.setSessionApplied();
565         if (hasApex) {
566             if (supportsCheckpoint) {
567                 // Store the session ID, which will be marked as successful by ApexManager upon
568                 // boot completion.
569                 synchronized (mSuccessfulStagedSessionIds) {
570                     mSuccessfulStagedSessionIds.add(session.sessionId());
571                 }
572             } else {
573                 // Mark sessions as successful immediately on non-checkpointing devices.
574                 mApexManager.markStagedSessionSuccessful(session.sessionId());
575             }
576         }
577     }
578 
onInstallationFailure(StagedSession session, PackageManagerException e, boolean supportsCheckpoint, boolean needsCheckpoint)579     void onInstallationFailure(StagedSession session, PackageManagerException e,
580             boolean supportsCheckpoint, boolean needsCheckpoint) {
581         session.setSessionFailed(e.error, e.getMessage());
582         abortCheckpoint("Failed to install sessionId: " + session.sessionId()
583                 + " Error: " + e.getMessage(), supportsCheckpoint, needsCheckpoint);
584 
585         // If checkpoint is not supported, we have to handle failure for one staged session.
586         if (!session.containsApexSession()) {
587             return;
588         }
589 
590         if (!mApexManager.revertActiveSessions()) {
591             Slog.e(TAG, "Failed to abort APEXd session");
592         } else {
593             Slog.e(TAG,
594                     "Successfully aborted apexd session. Rebooting device in order to revert "
595                             + "to the previous state of APEXd.");
596             mPowerManager.reboot(null);
597         }
598     }
599 
getReasonForRevert()600     private String getReasonForRevert() {
601         if (!TextUtils.isEmpty(mFailureReason)) {
602             return mFailureReason;
603         }
604         if (!TextUtils.isEmpty(mNativeFailureReason)) {
605             return "Session reverted due to crashing native process: " + mNativeFailureReason;
606         }
607         return "";
608     }
609 
610     /**
611      * Throws a PackageManagerException if there are duplicate packages in apk and apk-in-apex.
612      */
checkDuplicateApkInApex(@onNull StagedSession session)613     private void checkDuplicateApkInApex(@NonNull StagedSession session)
614             throws PackageManagerException {
615         if (!session.isMultiPackage()) {
616             return;
617         }
618         final Set<String> apkNames = new ArraySet<>();
619         for (StagedSession s : session.getChildSessions()) {
620             if (!s.isApexSession()) {
621                 apkNames.add(s.getPackageName());
622             }
623         }
624         final List<StagedSession> apexSessions = extractApexSessions(session);
625         for (StagedSession apexSession : apexSessions) {
626             String packageName = apexSession.getPackageName();
627             for (String apkInApex : mApexManager.getApksInApex(packageName)) {
628                 if (!apkNames.add(apkInApex)) {
629                     throw new PackageManagerException(
630                             SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
631                             "Package: " + packageName + " in session: "
632                                     + apexSession.sessionId() + " has duplicate apk-in-apex: "
633                                     + apkInApex, null);
634 
635                 }
636             }
637         }
638     }
639 
installApksInSession(StagedSession session)640     private void installApksInSession(StagedSession session)
641             throws PackageManagerException {
642         if (!session.containsApkSession()) {
643             return;
644         }
645 
646         final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
647         session.installSession(receiver.getIntentSender());
648         final Intent result = receiver.getResult();
649         final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
650                 PackageInstaller.STATUS_FAILURE);
651         if (status != PackageInstaller.STATUS_SUCCESS) {
652             final String errorMessage = result.getStringExtra(
653                     PackageInstaller.EXTRA_STATUS_MESSAGE);
654             Slog.e(TAG, "Failure to install APK staged session "
655                     + session.sessionId() + " [" + errorMessage + "]");
656             throw new PackageManagerException(
657                     SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage);
658         }
659     }
660 
commitSession(@onNull StagedSession session)661     void commitSession(@NonNull StagedSession session) {
662         // Store this parent session which will be used to check overlapping later
663         createSession(session);
664         mPreRebootVerificationHandler.startPreRebootVerification(session);
665     }
666 
getSessionIdForParentOrSelf(StagedSession session)667     private int getSessionIdForParentOrSelf(StagedSession session) {
668         return session.hasParentSessionId() ? session.getParentSessionId() : session.sessionId();
669     }
670 
getParentSessionOrSelf(StagedSession session)671     private StagedSession getParentSessionOrSelf(StagedSession session) {
672         return session.hasParentSessionId()
673                 ? getStagedSession(session.getParentSessionId())
674                 : session;
675     }
676 
isRollback(StagedSession session)677     private boolean isRollback(StagedSession session) {
678         final StagedSession root = getParentSessionOrSelf(session);
679         return root.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK;
680     }
681 
682     /**
683      * <p> Check if the session provided is non-overlapping with the active staged sessions.
684      *
685      * <p> A session is non-overlapping if it meets one of the following conditions: </p>
686      * <ul>
687      *     <li>It is a parent session</li>
688      *     <li>It is already one of the active sessions</li>
689      *     <li>Its package name is not same as any of the active sessions</li>
690      * </ul>
691      * @throws PackageManagerException if session fails the check
692      */
693     @VisibleForTesting
checkNonOverlappingWithStagedSessions(@onNull StagedSession session)694     void checkNonOverlappingWithStagedSessions(@NonNull StagedSession session)
695             throws PackageManagerException {
696         if (session.isMultiPackage()) {
697             // We cannot say a parent session overlaps until we process its children
698             return;
699         }
700 
701         String packageName = session.getPackageName();
702         if (packageName == null) {
703             throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
704                     "Cannot stage session " + session.sessionId() + " with package name null");
705         }
706 
707         boolean supportsCheckpoint;
708         try {
709             supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
710         } catch (RemoteException e) {
711             throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
712                     "Can't query fs-checkpoint status : " + e);
713         }
714 
715         final boolean isRollback = isRollback(session);
716 
717         synchronized (mStagedSessions) {
718             for (int i = 0; i < mStagedSessions.size(); i++) {
719                 final StagedSession stagedSession = mStagedSessions.valueAt(i);
720                 if (stagedSession.hasParentSessionId() || !stagedSession.isCommitted()
721                         || stagedSession.isInTerminalState()
722                         || stagedSession.isDestroyed()) {
723                     continue;
724                 }
725 
726                 if (stagedSession.getCommittedMillis() > session.getCommittedMillis()) {
727                     // Ignore sessions that are committed after the provided session. When there are
728                     // overlaps between sessions, we will fail the one committed later instead of
729                     // the earlier one.
730                     continue;
731                 }
732 
733                 // From here on, stagedSession is a parent active staged session
734 
735                 // Check if session is one of the active sessions
736                 if (getSessionIdForParentOrSelf(session) == stagedSession.sessionId()) {
737                     Slog.w(TAG, "Session " + session.sessionId() + " is already staged");
738                     continue;
739                 }
740 
741                 if (isRollback && !isRollback(stagedSession)) {
742                     // If the new session is a rollback, then it gets priority. The existing
743                     // session is failed to reduce risk and avoid an SDK extension dependency
744                     // violation.
745                     final StagedSession root = stagedSession;
746                     if (!ensureActiveApexSessionIsAborted(root)) {
747                         Slog.e(TAG, "Failed to abort apex session " + root.sessionId());
748                         // Safe to ignore active apex session abort failure since session
749                         // will be marked failed on next step and staging directory for session
750                         // will be deleted.
751                     }
752                     root.setSessionFailed(
753                             SessionInfo.STAGED_SESSION_CONFLICT,
754                             "Session was failed by rollback session: " + session.sessionId());
755                     Slog.i(TAG, "Session " + root.sessionId() + " is marked failed due to "
756                             + "rollback session: " + session.sessionId());
757                 } else if (stagedSession.sessionContains(
758                         s -> s.getPackageName().equals(packageName))) {
759                     // New session cannot have same package name as one of the active sessions
760                     throw new PackageManagerException(
761                             SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
762                             "Package: " + session.getPackageName() + " in session: "
763                                     + session.sessionId()
764                                     + " has been staged already by session: "
765                                     + stagedSession.sessionId(), null);
766                 }
767 
768                 // Staging multiple root sessions is not allowed if device doesn't support
769                 // checkpoint. If session and stagedSession do not have common ancestor, they are
770                 // from two different root sessions.
771                 if (!supportsCheckpoint) {
772                     throw new PackageManagerException(
773                             SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
774                             "Cannot stage multiple sessions without checkpoint support", null);
775                 }
776             }
777         }
778     }
779 
780     /**
781      * Returns id of a committed and non-finalized stated session that contains same
782      * {@code packageName}, or {@code -1} if no sessions have this {@code packageName} staged.
783      */
getSessionIdByPackageName(@onNull String packageName)784     int getSessionIdByPackageName(@NonNull String packageName) {
785         synchronized (mStagedSessions) {
786             for (int i = 0; i < mStagedSessions.size(); i++) {
787                 StagedSession stagedSession = mStagedSessions.valueAt(i);
788                 if (!stagedSession.isCommitted() || stagedSession.isDestroyed()
789                         || stagedSession.isInTerminalState()) {
790                     continue;
791                 }
792                 if (stagedSession.getPackageName().equals(packageName)) {
793                     return stagedSession.sessionId();
794                 }
795             }
796         }
797         return -1;
798     }
799 
800     @VisibleForTesting
createSession(@onNull StagedSession sessionInfo)801     void createSession(@NonNull StagedSession sessionInfo) {
802         synchronized (mStagedSessions) {
803             mStagedSessions.append(sessionInfo.sessionId(), sessionInfo);
804         }
805     }
806 
abortSession(@onNull StagedSession session)807     void abortSession(@NonNull StagedSession session) {
808         synchronized (mStagedSessions) {
809             mStagedSessions.remove(session.sessionId());
810         }
811     }
812 
813     /**
814      * <p>Abort committed staged session
815      */
abortCommittedSession(@onNull StagedSession session)816     void abortCommittedSession(@NonNull StagedSession session) {
817         int sessionId = session.sessionId();
818         if (session.isInTerminalState()) {
819             Slog.w(TAG, "Cannot abort session in final state: " + sessionId);
820             return;
821         }
822         if (!session.isDestroyed()) {
823             throw new IllegalStateException("Committed session must be destroyed before aborting it"
824                     + " from StagingManager");
825         }
826         if (getStagedSession(sessionId) == null) {
827             Slog.w(TAG, "Session " + sessionId + " has been abandoned already");
828             return;
829         }
830 
831         // A session could be marked ready once its pre-reboot verification ends
832         if (session.isSessionReady()) {
833             if (!ensureActiveApexSessionIsAborted(session)) {
834                 // Failed to ensure apex session is aborted, so it can still be staged. We can still
835                 // safely cleanup the staged session since pre-reboot verification is complete.
836                 // Also, cleaning up the stageDir prevents the apex from being activated.
837                 Slog.e(TAG, "Failed to abort apex session " + session.sessionId());
838             }
839         }
840 
841         // Session was successfully aborted from apexd (if required) and pre-reboot verification
842         // is also complete. It is now safe to clean up the session from system.
843         abortSession(session);
844     }
845 
846     /**
847      * Ensure that there is no active apex session staged in apexd for the given session.
848      *
849      * @return returns true if it is ensured that there is no active apex session, otherwise false
850      */
ensureActiveApexSessionIsAborted(StagedSession session)851     private boolean ensureActiveApexSessionIsAborted(StagedSession session) {
852         if (!session.containsApexSession()) {
853             return true;
854         }
855         final ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId());
856         if (apexSession == null || isApexSessionFinalized(apexSession)) {
857             return true;
858         }
859         return mApexManager.abortStagedSession(session.sessionId());
860     }
861 
isApexSessionFinalized(ApexSessionInfo session)862     private boolean isApexSessionFinalized(ApexSessionInfo session) {
863         /* checking if the session is in a final state, i.e., not active anymore */
864         return session.isUnknown || session.isActivationFailed || session.isSuccess
865                 || session.isReverted;
866     }
867 
isApexSessionFailed(ApexSessionInfo apexSessionInfo)868     private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) {
869         // isRevertInProgress is included to cover the scenario, when a device is rebooted
870         // during the revert, and apexd fails to resume the revert after reboot.
871         return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown
872                 || apexSessionInfo.isReverted || apexSessionInfo.isRevertInProgress
873                 || apexSessionInfo.isRevertFailed;
874     }
875 
handleNonReadyAndDestroyedSessions(List<StagedSession> sessions)876     private void handleNonReadyAndDestroyedSessions(List<StagedSession> sessions) {
877         int j = sessions.size();
878         for (int i = 0; i < j; ) {
879             // Maintain following invariant:
880             //  * elements at positions [0, i) should be kept
881             //  * elements at positions [j, n) should be remove.
882             //  * n = sessions.size()
883             StagedSession session = sessions.get(i);
884             if (session.isDestroyed()) {
885                 // Device rebooted before abandoned session was cleaned up.
886                 session.abandon();
887                 StagedSession session2 = sessions.set(j - 1, session);
888                 sessions.set(i, session2);
889                 j--;
890             } else if (!session.isSessionReady()) {
891                 // The framework got restarted before the pre-reboot verification could complete,
892                 // restart the verification.
893                 mPreRebootVerificationHandler.startPreRebootVerification(session);
894                 StagedSession session2 = sessions.set(j - 1, session);
895                 sessions.set(i, session2);
896                 j--;
897             } else {
898                 i++;
899             }
900         }
901         // Delete last j elements.
902         sessions.subList(j, sessions.size()).clear();
903     }
904 
restoreSessions(@onNull List<StagedSession> sessions, boolean isDeviceUpgrading)905     void restoreSessions(@NonNull List<StagedSession> sessions, boolean isDeviceUpgrading) {
906         TimingsTraceLog t = new TimingsTraceLog(
907                 "StagingManagerTiming", Trace.TRACE_TAG_PACKAGE_MANAGER);
908         t.traceBegin("restoreSessions");
909 
910         // Do not resume sessions if boot completed already
911         if (SystemProperties.getBoolean("sys.boot_completed", false)) {
912             return;
913         }
914 
915         for (int i = 0; i < sessions.size(); i++) {
916             StagedSession session = sessions.get(i);
917             // Quick check that PackageInstallerService gave us sessions we expected.
918             Preconditions.checkArgument(!session.hasParentSessionId(),
919                     session.sessionId() + " is a child session");
920             Preconditions.checkArgument(session.isCommitted(),
921                     session.sessionId() + " is not committed");
922             Preconditions.checkArgument(!session.isInTerminalState(),
923                     session.sessionId() + " is in terminal state");
924             // Store this parent session which will be used to check overlapping later
925             createSession(session);
926         }
927 
928         if (isDeviceUpgrading) {
929             // TODO(ioffe): check that corresponding apex sessions are failed.
930             // The preconditions used during pre-reboot verification might have changed when device
931             // is upgrading. Fail all the sessions and exit early.
932             for (int i = 0; i < sessions.size(); i++) {
933                 StagedSession session = sessions.get(i);
934                 session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
935                         "Build fingerprint has changed");
936             }
937             return;
938         }
939 
940         boolean needsCheckpoint = false;
941         boolean supportsCheckpoint = false;
942         try {
943             supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
944             needsCheckpoint = PackageHelper.getStorageManager().needsCheckpoint();
945         } catch (RemoteException e) {
946             // This means that vold has crashed, and device is in a bad state.
947             throw new IllegalStateException("Failed to get checkpoint status", e);
948         }
949 
950         if (sessions.size() > 1 && !supportsCheckpoint) {
951             throw new IllegalStateException("Detected multiple staged sessions on a device without "
952                     + "fs-checkpoint support");
953         }
954 
955         // Do a set of quick checks before resuming individual sessions:
956         //   1. Schedule a pre-reboot verification for non-ready sessions.
957         //   2. Abandon destroyed sessions.
958         handleNonReadyAndDestroyedSessions(sessions); // mutates |sessions|
959 
960         //   3. Check state of apex sessions is consistent. All non-applied sessions will be marked
961         //      as failed.
962         final SparseArray<ApexSessionInfo> apexSessions = mApexManager.getSessions();
963         boolean hasFailedApexSession = false;
964         boolean hasAppliedApexSession = false;
965         for (int i = 0; i < sessions.size(); i++) {
966             StagedSession session = sessions.get(i);
967             if (!session.containsApexSession()) {
968                 // At this point we are only interested in apex sessions.
969                 continue;
970             }
971             final ApexSessionInfo apexSession = apexSessions.get(session.sessionId());
972             if (apexSession == null || apexSession.isUnknown) {
973                 hasFailedApexSession = true;
974                 session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "apexd did "
975                         + "not know anything about a staged session supposed to be activated");
976                 continue;
977             } else if (isApexSessionFailed(apexSession)) {
978                 hasFailedApexSession = true;
979                 if (!TextUtils.isEmpty(apexSession.crashingNativeProcess)) {
980                     prepareForLoggingApexdRevert(session, apexSession.crashingNativeProcess);
981                 }
982                 String errorMsg = "APEX activation failed.";
983                 final String reasonForRevert = getReasonForRevert();
984                 if (!TextUtils.isEmpty(reasonForRevert)) {
985                     errorMsg += " Reason: " + reasonForRevert;
986                 } else if (!TextUtils.isEmpty(apexSession.errorMessage)) {
987                     errorMsg += " Error: " + apexSession.errorMessage;
988                 }
989                 Slog.d(TAG, errorMsg);
990                 session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
991                 continue;
992             } else if (apexSession.isActivated || apexSession.isSuccess) {
993                 hasAppliedApexSession = true;
994                 continue;
995             } else if (apexSession.isStaged) {
996                 // Apexd did not apply the session for some unknown reason. There is no guarantee
997                 // that apexd will install it next time. Safer to proactively mark it as failed.
998                 hasFailedApexSession = true;
999                 session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
1000                         "Staged session " + session.sessionId() + " at boot didn't activate nor "
1001                         + "fail. Marking it as failed anyway.");
1002             } else {
1003                 Slog.w(TAG, "Apex session " + session.sessionId() + " is in impossible state");
1004                 hasFailedApexSession = true;
1005                 session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
1006                         "Impossible state");
1007             }
1008         }
1009 
1010         if (hasAppliedApexSession && hasFailedApexSession) {
1011             abortCheckpoint("Found both applied and failed apex sessions", supportsCheckpoint,
1012                     needsCheckpoint);
1013             return;
1014         }
1015 
1016         if (hasFailedApexSession) {
1017             // Either of those means that we failed at least one apex session, hence we should fail
1018             // all other sessions.
1019             for (int i = 0; i < sessions.size(); i++) {
1020                 StagedSession session = sessions.get(i);
1021                 if (session.isSessionFailed()) {
1022                     // Session has been already failed in the loop above.
1023                     continue;
1024                 }
1025                 session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
1026                         "Another apex session failed");
1027             }
1028             return;
1029         }
1030 
1031         // Time to resume sessions.
1032         for (int i = 0; i < sessions.size(); i++) {
1033             StagedSession session = sessions.get(i);
1034             try {
1035                 resumeSession(session, supportsCheckpoint, needsCheckpoint);
1036             } catch (PackageManagerException e) {
1037                 onInstallationFailure(session, e, supportsCheckpoint, needsCheckpoint);
1038             } catch (Exception e) {
1039                 Slog.e(TAG, "Staged install failed due to unhandled exception", e);
1040                 onInstallationFailure(session, new PackageManagerException(
1041                         SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
1042                         "Staged install failed due to unhandled exception: " + e),
1043                         supportsCheckpoint, needsCheckpoint);
1044             }
1045         }
1046         t.traceEnd();
1047     }
1048 
logFailedApexSessionsIfNecessary()1049     private void logFailedApexSessionsIfNecessary() {
1050         synchronized (mFailedPackageNames) {
1051             if (!mFailedPackageNames.isEmpty()) {
1052                 WatchdogRollbackLogger.logApexdRevert(mContext,
1053                         mFailedPackageNames, mNativeFailureReason);
1054             }
1055         }
1056     }
1057 
markStagedSessionsAsSuccessful()1058     private void markStagedSessionsAsSuccessful() {
1059         synchronized (mSuccessfulStagedSessionIds) {
1060             for (int i = 0; i < mSuccessfulStagedSessionIds.size(); i++) {
1061                 mApexManager.markStagedSessionSuccessful(mSuccessfulStagedSessionIds.get(i));
1062             }
1063         }
1064     }
1065 
systemReady()1066     void systemReady() {
1067         new Lifecycle(mContext).startService(this);
1068         // Register the receiver of boot completed intent for staging manager.
1069         mContext.registerReceiver(new BroadcastReceiver() {
1070             @Override
1071             public void onReceive(Context ctx, Intent intent) {
1072                 onBootCompletedBroadcastReceived();
1073                 ctx.unregisterReceiver(this);
1074             }
1075         }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
1076 
1077         mFailureReasonFile.delete();
1078     }
1079 
1080     @VisibleForTesting
onBootCompletedBroadcastReceived()1081     void onBootCompletedBroadcastReceived() {
1082         mPreRebootVerificationHandler.readyToStart();
1083         BackgroundThread.getExecutor().execute(() -> logFailedApexSessionsIfNecessary());
1084     }
1085 
1086     private static class LocalIntentReceiverSync {
1087         private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
1088 
1089         private final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
1090             @Override
1091             public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
1092                     IIntentReceiver finishedReceiver, String requiredPermission,
1093                     Bundle options) {
1094                 try {
1095                     mResult.offer(intent, 5, TimeUnit.SECONDS);
1096                 } catch (InterruptedException e) {
1097                     throw new RuntimeException(e);
1098                 }
1099             }
1100         };
1101 
getIntentSender()1102         public IntentSender getIntentSender() {
1103             return new IntentSender((IIntentSender) mLocalSender);
1104         }
1105 
getResult()1106         public Intent getResult() {
1107             try {
1108                 return mResult.take();
1109             } catch (InterruptedException e) {
1110                 throw new RuntimeException(e);
1111             }
1112         }
1113     }
1114 
getStagedSession(int sessionId)1115     private StagedSession getStagedSession(int sessionId) {
1116         StagedSession session;
1117         synchronized (mStagedSessions) {
1118             session = mStagedSessions.get(sessionId);
1119         }
1120         return session;
1121     }
1122 
1123     // TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all
1124     //  verification logic is extracted out of StagingManager into PMS, we can remove
1125     //  this.
notifyVerificationComplete(StagedSession session)1126     void notifyVerificationComplete(StagedSession session) {
1127         mPreRebootVerificationHandler.onPreRebootVerificationComplete(session);
1128     }
1129 
1130     // TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all
1131     //  verification logic is extracted out of StagingManager into PMS, we can remove
1132     //  this.
notifyPreRebootVerification_Apk_Complete(@onNull StagedSession session)1133     void notifyPreRebootVerification_Apk_Complete(@NonNull StagedSession session) {
1134         mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(session);
1135     }
1136 
1137     private final class PreRebootVerificationHandler extends Handler {
1138         // Hold sessions before handler gets ready to do the verification.
1139         private List<StagedSession> mPendingSessions;
1140         private boolean mIsReady;
1141 
PreRebootVerificationHandler(Looper looper)1142         PreRebootVerificationHandler(Looper looper) {
1143             super(looper);
1144         }
1145 
1146         /**
1147          * Handler for states of pre reboot verification. The states are arranged linearly (shown
1148          * below) with each state either calling the next state, or calling some other method that
1149          * eventually calls the next state.
1150          *
1151          * <p><ul>
1152          *     <li>MSG_PRE_REBOOT_VERIFICATION_START</li>
1153          *     <li>MSG_PRE_REBOOT_VERIFICATION_APEX</li>
1154          *     <li>MSG_PRE_REBOOT_VERIFICATION_APK</li>
1155          *     <li>MSG_PRE_REBOOT_VERIFICATION_END</li>
1156          * </ul></p>
1157          *
1158          * Details about each of state can be found in corresponding handler of node.
1159          */
1160         private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1;
1161         private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2;
1162         private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3;
1163         private static final int MSG_PRE_REBOOT_VERIFICATION_END = 4;
1164 
1165         @Override
handleMessage(Message msg)1166         public void handleMessage(Message msg) {
1167             final int sessionId = msg.arg1;
1168             final int rollbackId = msg.arg2;
1169             final StagedSession session = (StagedSession) msg.obj;
1170             if (session.isDestroyed() || session.isSessionFailed()) {
1171                 // No point in running verification on a destroyed/failed session
1172                 onPreRebootVerificationComplete(session);
1173                 return;
1174             }
1175             try {
1176                 switch (msg.what) {
1177                     case MSG_PRE_REBOOT_VERIFICATION_START:
1178                         handlePreRebootVerification_Start(session);
1179                         break;
1180                     case MSG_PRE_REBOOT_VERIFICATION_APEX:
1181                         handlePreRebootVerification_Apex(session, rollbackId);
1182                         break;
1183                     case MSG_PRE_REBOOT_VERIFICATION_APK:
1184                         handlePreRebootVerification_Apk(session);
1185                         break;
1186                     case MSG_PRE_REBOOT_VERIFICATION_END:
1187                         handlePreRebootVerification_End(session);
1188                         break;
1189                 }
1190             } catch (Exception e) {
1191                 Slog.e(TAG, "Pre-reboot verification failed due to unhandled exception", e);
1192                 onPreRebootVerificationFailure(session,
1193                         SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
1194                         "Pre-reboot verification failed due to unhandled exception: " + e);
1195             }
1196         }
1197 
1198         // Notify the handler that system is ready, and reschedule the pre-reboot verifications.
readyToStart()1199         private synchronized void readyToStart() {
1200             mIsReady = true;
1201             if (mPendingSessions != null) {
1202                 for (int i = 0; i < mPendingSessions.size(); i++) {
1203                     StagedSession session = mPendingSessions.get(i);
1204                     startPreRebootVerification(session);
1205                 }
1206                 mPendingSessions = null;
1207             }
1208         }
1209 
1210         // Method for starting the pre-reboot verification
startPreRebootVerification( @onNull StagedSession session)1211         private synchronized void startPreRebootVerification(
1212                 @NonNull StagedSession session) {
1213             if (!mIsReady) {
1214                 if (mPendingSessions == null) {
1215                     mPendingSessions = new ArrayList<>();
1216                 }
1217                 mPendingSessions.add(session);
1218                 return;
1219             }
1220 
1221             if (session.notifyStartPreRebootVerification()) {
1222                 int sessionId = session.sessionId();
1223                 Slog.d(TAG, "Starting preRebootVerification for session " + sessionId);
1224                 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, -1, session)
1225                         .sendToTarget();
1226             }
1227         }
1228 
onPreRebootVerificationFailure(StagedSession session, @SessionInfo.StagedSessionErrorCode int errorCode, String errorMessage)1229         private void onPreRebootVerificationFailure(StagedSession session,
1230                 @SessionInfo.StagedSessionErrorCode int errorCode, String errorMessage) {
1231             if (!ensureActiveApexSessionIsAborted(session)) {
1232                 Slog.e(TAG, "Failed to abort apex session " + session.sessionId());
1233                 // Safe to ignore active apex session abortion failure since session will be marked
1234                 // failed on next step and staging directory for session will be deleted.
1235             }
1236             session.setSessionFailed(errorCode, errorMessage);
1237             onPreRebootVerificationComplete(session);
1238         }
1239 
1240         // Things to do when pre-reboot verification completes for a particular sessionId
onPreRebootVerificationComplete(StagedSession session)1241         private void onPreRebootVerificationComplete(StagedSession session) {
1242             int sessionId = session.sessionId();
1243             Slog.d(TAG, "Stopping preRebootVerification for session " + sessionId);
1244             session.notifyEndPreRebootVerification();
1245         }
1246 
notifyPreRebootVerification_Start_Complete( @onNull StagedSession session, int rollbackId)1247         private void notifyPreRebootVerification_Start_Complete(
1248                 @NonNull StagedSession session, int rollbackId) {
1249             obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, session.sessionId(), rollbackId,
1250                     session).sendToTarget();
1251         }
1252 
notifyPreRebootVerification_Apex_Complete( @onNull StagedSession session)1253         private void notifyPreRebootVerification_Apex_Complete(
1254                 @NonNull StagedSession session) {
1255             obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, session.sessionId(), -1, session)
1256                     .sendToTarget();
1257         }
1258 
notifyPreRebootVerification_Apk_Complete( @onNull StagedSession session)1259         private void notifyPreRebootVerification_Apk_Complete(
1260                 @NonNull StagedSession session) {
1261             obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, session.sessionId(), -1, session)
1262                     .sendToTarget();
1263         }
1264 
1265         /**
1266          * A placeholder state for starting the pre reboot verification.
1267          *
1268          * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification
1269          */
handlePreRebootVerification_Start(@onNull StagedSession session)1270         private void handlePreRebootVerification_Start(@NonNull StagedSession session) {
1271             try {
1272                 if (session.isMultiPackage()) {
1273                     for (StagedSession s : session.getChildSessions()) {
1274                         checkNonOverlappingWithStagedSessions(s);
1275                     }
1276                 } else {
1277                     checkNonOverlappingWithStagedSessions(session);
1278                 }
1279             } catch (PackageManagerException e) {
1280                 onPreRebootVerificationFailure(session, e.error, e.getMessage());
1281                 return;
1282             }
1283 
1284             int rollbackId = -1;
1285             if ((session.sessionParams().installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK)
1286                     != 0) {
1287                 // If rollback is enabled for this session, we call through to the RollbackManager
1288                 // with the list of sessions it must enable rollback for. Note that
1289                 // notifyStagedSession is a synchronous operation.
1290                 final RollbackManagerInternal rm =
1291                         LocalServices.getService(RollbackManagerInternal.class);
1292                 try {
1293                     // NOTE: To stay consistent with the non-staged install flow, we don't fail the
1294                     // entire install if rollbacks can't be enabled.
1295                     rollbackId = rm.notifyStagedSession(session.sessionId());
1296                 } catch (RuntimeException re) {
1297                     Slog.e(TAG, "Failed to notifyStagedSession for session: "
1298                             + session.sessionId(), re);
1299                 }
1300             } else if (session.sessionParams().installReason
1301                     == PackageManager.INSTALL_REASON_ROLLBACK) {
1302                 try {
1303                     rollbackId = retrieveRollbackIdForCommitSession(session.sessionId());
1304                 } catch (PackageManagerException e) {
1305                     onPreRebootVerificationFailure(session, e.error, e.getMessage());
1306                     return;
1307                 }
1308             }
1309 
1310             notifyPreRebootVerification_Start_Complete(session, rollbackId);
1311         }
1312 
1313         /**
1314          * Pre-reboot verification state for apex files:
1315          *
1316          * <p><ul>
1317          *     <li>submits session to apex service</li>
1318          *     <li>validates signatures of apex files</li>
1319          * </ul></p>
1320          */
handlePreRebootVerification_Apex( @onNull StagedSession session, int rollbackId)1321         private void handlePreRebootVerification_Apex(
1322                 @NonNull StagedSession session, int rollbackId) {
1323             final boolean hasApex = session.containsApexSession();
1324 
1325             // APEX checks. For single-package sessions, check if they contain an APEX. For
1326             // multi-package sessions, find all the child sessions that contain an APEX.
1327             if (hasApex) {
1328                 final List<PackageInfo> apexPackages;
1329                 try {
1330                     apexPackages = submitSessionToApexService(session, rollbackId);
1331                     for (int i = 0, size = apexPackages.size(); i < size; i++) {
1332                         validateApexSignature(apexPackages.get(i));
1333                     }
1334                 } catch (PackageManagerException e) {
1335                     onPreRebootVerificationFailure(session, e.error, e.getMessage());
1336                     return;
1337                 }
1338 
1339                 final PackageManagerInternal packageManagerInternal =
1340                         LocalServices.getService(PackageManagerInternal.class);
1341                 packageManagerInternal.pruneCachedApksInApex(apexPackages);
1342             }
1343 
1344             notifyPreRebootVerification_Apex_Complete(session);
1345         }
1346 
1347         /**
1348          * Pre-reboot verification state for apk files. Session is sent to
1349          * {@link PackageManagerService} for verification and it notifies back the result via
1350          * {@link #notifyPreRebootVerification_Apk_Complete}
1351          */
handlePreRebootVerification_Apk(@onNull StagedSession session)1352         private void handlePreRebootVerification_Apk(@NonNull StagedSession session) {
1353             if (!session.containsApkSession()) {
1354                 notifyPreRebootVerification_Apk_Complete(session);
1355                 return;
1356             }
1357             session.verifySession();
1358         }
1359 
1360         /**
1361          * Pre-reboot verification state for wrapping up:
1362          * <p><ul>
1363          *     <li>enables rollback if required</li>
1364          *     <li>marks session as ready</li>
1365          * </ul></p>
1366          */
handlePreRebootVerification_End(@onNull StagedSession session)1367         private void handlePreRebootVerification_End(@NonNull StagedSession session) {
1368             // Before marking the session as ready, start checkpoint service if available
1369             try {
1370                 if (PackageHelper.getStorageManager().supportsCheckpoint()) {
1371                     PackageHelper.getStorageManager().startCheckpoint(2);
1372                 }
1373             } catch (Exception e) {
1374                 // Failed to get hold of StorageManager
1375                 Slog.e(TAG, "Failed to get hold of StorageManager", e);
1376                 onPreRebootVerificationFailure(session, SessionInfo.STAGED_SESSION_UNKNOWN,
1377                         "Failed to get hold of StorageManager");
1378                 return;
1379             }
1380 
1381             // Stop pre-reboot verification before marking session ready. From this point on, if we
1382             // abandon the session then it will be cleaned up immediately. If session is abandoned
1383             // after this point, then even if for some reason system tries to install the session
1384             // or activate its apex, there won't be any files to work with as they will be cleaned
1385             // up by the system as part of abandonment. If session is abandoned before this point,
1386             // then the session is already destroyed and cannot be marked ready anymore.
1387             onPreRebootVerificationComplete(session);
1388 
1389             // Proactively mark session as ready before calling apexd. Although this call order
1390             // looks counter-intuitive, this is the easiest way to ensure that session won't end up
1391             // in the inconsistent state:
1392             //  - If device gets rebooted right before call to apexd, then apexd will never activate
1393             //      apex files of this staged session. This will result in StagingManager failing
1394             //      the session.
1395             // On the other hand, if the order of the calls was inverted (first call apexd, then
1396             // mark session as ready), then if a device gets rebooted right after the call to apexd,
1397             // only apex part of the train will be applied, leaving device in an inconsistent state.
1398             Slog.d(TAG, "Marking session " + session.sessionId() + " as ready");
1399             session.setSessionReady();
1400             if (session.isSessionReady()) {
1401                 final boolean hasApex = session.containsApexSession();
1402                 if (hasApex) {
1403                     try {
1404                         mApexManager.markStagedSessionReady(session.sessionId());
1405                     } catch (PackageManagerException e) {
1406                         session.setSessionFailed(e.error, e.getMessage());
1407                         return;
1408                     }
1409                 }
1410             }
1411         }
1412     }
1413 }
1414