• 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.annotation.Nullable;
21 import android.apex.ApexInfo;
22 import android.apex.ApexInfoList;
23 import android.apex.ApexSessionInfo;
24 import android.content.Context;
25 import android.content.IIntentReceiver;
26 import android.content.IIntentSender;
27 import android.content.Intent;
28 import android.content.IntentSender;
29 import android.content.pm.PackageInfo;
30 import android.content.pm.PackageInstaller;
31 import android.content.pm.PackageInstaller.SessionInfo;
32 import android.content.pm.PackageManager;
33 import android.content.pm.PackageParser.PackageParserException;
34 import android.content.pm.PackageParser.SigningDetails;
35 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
36 import android.content.pm.ParceledListSlice;
37 import android.content.pm.Signature;
38 import android.content.rollback.IRollbackManager;
39 import android.os.Bundle;
40 import android.os.Handler;
41 import android.os.IBinder;
42 import android.os.ParcelFileDescriptor;
43 import android.os.PowerManager;
44 import android.os.RemoteException;
45 import android.os.ServiceManager;
46 import android.util.Slog;
47 import android.util.SparseArray;
48 import android.util.apk.ApkSignatureVerifier;
49 
50 import com.android.internal.annotations.GuardedBy;
51 import com.android.internal.os.BackgroundThread;
52 
53 import java.io.File;
54 import java.io.IOException;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.List;
58 import java.util.concurrent.LinkedBlockingQueue;
59 import java.util.concurrent.TimeUnit;
60 import java.util.function.Predicate;
61 import java.util.stream.Collectors;
62 
63 /**
64  * This class handles staged install sessions, i.e. install sessions that require packages to
65  * be installed only after a reboot.
66  */
67 public class StagingManager {
68 
69     private static final String TAG = "StagingManager";
70 
71     private final PackageInstallerService mPi;
72     private final ApexManager mApexManager;
73     private final PowerManager mPowerManager;
74     private final Handler mBgHandler;
75 
76     @GuardedBy("mStagedSessions")
77     private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
78 
StagingManager(PackageInstallerService pi, ApexManager am, Context context)79     StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
80         mPi = pi;
81         mApexManager = am;
82         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
83         mBgHandler = BackgroundThread.getHandler();
84     }
85 
updateStoredSession(@onNull PackageInstallerSession sessionInfo)86     private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) {
87         synchronized (mStagedSessions) {
88             PackageInstallerSession storedSession = mStagedSessions.get(sessionInfo.sessionId);
89             // storedSession might be null if a call to abortSession was made before the session
90             // is updated.
91             if (storedSession != null) {
92                 mStagedSessions.put(sessionInfo.sessionId, sessionInfo);
93             }
94         }
95     }
96 
getSessions(int callingUid)97     ParceledListSlice<PackageInstaller.SessionInfo> getSessions(int callingUid) {
98         final List<PackageInstaller.SessionInfo> result = new ArrayList<>();
99         synchronized (mStagedSessions) {
100             for (int i = 0; i < mStagedSessions.size(); i++) {
101                 final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
102                 result.add(stagedSession.generateInfoForCaller(false /*icon*/, callingUid));
103             }
104         }
105         return new ParceledListSlice<>(result);
106     }
107 
validateApexSignature(String apexPath, String packageName)108     private boolean validateApexSignature(String apexPath, String packageName) {
109         final SigningDetails signingDetails;
110         try {
111             signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
112         } catch (PackageParserException e) {
113             Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e);
114             return false;
115         }
116 
117         final PackageInfo packageInfo = mApexManager.getPackageInfoForApexName(packageName);
118 
119         if (packageInfo == null) {
120             // Don't allow installation of new APEX.
121             Slog.e(TAG, "Attempted to install a new apex " + packageName + ". Rejecting");
122             return false;
123         }
124 
125         final SigningDetails existingSigningDetails;
126         try {
127             existingSigningDetails = ApkSignatureVerifier.verify(
128                 packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
129         } catch (PackageParserException e) {
130             Slog.e(TAG, "Unable to parse APEX package: "
131                     + packageInfo.applicationInfo.sourceDir, e);
132             return false;
133         }
134 
135         // Now that we have both sets of signatures, demand that they're an exact match.
136         if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) {
137             return true;
138         }
139 
140         return false;
141     }
142 
submitSessionToApexService(@onNull PackageInstallerSession session, List<PackageInstallerSession> childSessions, ApexInfoList apexInfoList)143     private boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
144                                                List<PackageInstallerSession> childSessions,
145                                                ApexInfoList apexInfoList) {
146         boolean submittedToApexd = mApexManager.submitStagedSession(
147                 session.sessionId,
148                 childSessions != null
149                         ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
150                         new int[]{},
151                 apexInfoList);
152         if (!submittedToApexd) {
153             session.setStagedSessionFailed(
154                     SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
155                     "APEX staging failed, check logcat messages from apexd for more details.");
156             return false;
157         }
158         for (ApexInfo newPackage : apexInfoList.apexInfos) {
159             PackageInfo activePackage = mApexManager.getPackageInfoForApexName(
160                     newPackage.packageName);
161             if (activePackage == null) {
162                 continue;
163             }
164             long activeVersion = activePackage.applicationInfo.longVersionCode;
165             if (session.params.requiredInstalledVersionCode
166                     != PackageManager.VERSION_CODE_HIGHEST) {
167                 if (activeVersion != session.params.requiredInstalledVersionCode) {
168                     session.setStagedSessionFailed(
169                             SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
170                             "Installed version of APEX package " + newPackage.packageName
171                             + " does not match required. Active version: " + activeVersion
172                             + " required: " + session.params.requiredInstalledVersionCode);
173 
174                     if (!mApexManager.abortActiveSession()) {
175                         Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
176                     }
177                     return false;
178                 }
179             }
180 
181             boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
182                     session.params.installFlags, activePackage.applicationInfo.flags);
183             if (activeVersion > newPackage.versionCode && !allowsDowngrade) {
184                 session.setStagedSessionFailed(
185                         SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
186                         "Downgrade of APEX package " + newPackage.packageName
187                                 + " is not allowed. Active version: " + activeVersion
188                                 + " attempted: " + newPackage.versionCode);
189 
190                 if (!mApexManager.abortActiveSession()) {
191                     Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
192                 }
193                 return false;
194             }
195         }
196         return true;
197     }
198 
isApexSession(@onNull PackageInstallerSession session)199     private static boolean isApexSession(@NonNull PackageInstallerSession session) {
200         return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
201     }
202 
preRebootVerification(@onNull PackageInstallerSession session)203     private void preRebootVerification(@NonNull PackageInstallerSession session) {
204         boolean success = true;
205 
206         final ApexInfoList apexInfoList = new ApexInfoList();
207         // APEX checks. For single-package sessions, check if they contain an APEX. For
208         // multi-package sessions, find all the child sessions that contain an APEX.
209         if (!session.isMultiPackage()
210                 && isApexSession(session)) {
211             success = submitSessionToApexService(session, null, apexInfoList);
212 
213         } else if (session.isMultiPackage()) {
214             List<PackageInstallerSession> childSessions =
215                     Arrays.stream(session.getChildSessionIds())
216                             // Retrieve cached sessions matching ids.
217                             .mapToObj(i -> mStagedSessions.get(i))
218                             // Filter only the ones containing APEX.
219                             .filter(childSession -> isApexSession(childSession))
220                             .collect(Collectors.toList());
221             if (!childSessions.isEmpty()) {
222                 success = submitSessionToApexService(session, childSessions, apexInfoList);
223             } // else this is a staged multi-package session with no APEX files.
224         }
225 
226         if (!success) {
227             // submitSessionToApexService will populate error.
228             return;
229         }
230 
231         if (sessionContainsApk(session)) {
232             if (!installApksInSession(session, /* preReboot */ true)) {
233                 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
234                         "APK verification failed. Check logcat messages for "
235                                 + "more information.");
236                 // TODO(b/118865310): abort the session on apexd.
237                 return;
238             }
239         }
240 
241         if (apexInfoList.apexInfos != null && apexInfoList.apexInfos.length > 0) {
242             // For APEXes, we validate the signature here before we mark the session as ready,
243             // so we fail the session early if there is a signature mismatch. For APKs, the
244             // signature verification will be done by the package manager at the point at which
245             // it applies the staged install.
246             for (ApexInfo apexPackage : apexInfoList.apexInfos) {
247                 if (!validateApexSignature(apexPackage.packagePath,
248                         apexPackage.packageName)) {
249                     session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
250                             "APK-container signature verification failed for package "
251                                     + apexPackage.packageName + ". Signature of file "
252                                     + apexPackage.packagePath + " does not match the signature of "
253                                     + " the package already installed.");
254                     // TODO(b/118865310): abort the session on apexd.
255                     return;
256                 }
257             }
258         }
259 
260         if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
261             // If rollback is enabled for this session, we call through to the RollbackManager
262             // with the list of sessions it must enable rollback for. Note that notifyStagedSession
263             // is a synchronous operation.
264             final IRollbackManager rm = IRollbackManager.Stub.asInterface(
265                     ServiceManager.getService(Context.ROLLBACK_SERVICE));
266             try {
267                 // NOTE: To stay consistent with the non-staged install flow, we don't fail the
268                 // entire install if rollbacks can't be enabled.
269                 if (!rm.notifyStagedSession(session.sessionId)) {
270                     Slog.e(TAG, "Unable to enable rollback for session: " + session.sessionId);
271                 }
272             } catch (RemoteException re) {
273                 // Cannot happen, the rollback manager is in the same process.
274             }
275         }
276 
277         session.setStagedSessionReady();
278         if (sessionContainsApex(session)
279                 && !mApexManager.markStagedSessionReady(session.sessionId)) {
280             session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
281                             "APEX staging failed, check logcat messages from apexd for more "
282                             + "details.");
283         }
284     }
285 
286 
sessionContains(@onNull PackageInstallerSession session, Predicate<PackageInstallerSession> filter)287     private boolean sessionContains(@NonNull PackageInstallerSession session,
288                                     Predicate<PackageInstallerSession> filter) {
289         if (!session.isMultiPackage()) {
290             return filter.test(session);
291         }
292         synchronized (mStagedSessions) {
293             return !(Arrays.stream(session.getChildSessionIds())
294                     // Retrieve cached sessions matching ids.
295                     .mapToObj(i -> mStagedSessions.get(i))
296                     // Filter only the ones containing APEX.
297                     .filter(childSession -> filter.test(childSession))
298                     .collect(Collectors.toList())
299                     .isEmpty());
300         }
301     }
302 
sessionContainsApex(@onNull PackageInstallerSession session)303     private boolean sessionContainsApex(@NonNull PackageInstallerSession session) {
304         return sessionContains(session, (s) -> isApexSession(s));
305     }
306 
sessionContainsApk(@onNull PackageInstallerSession session)307     private boolean sessionContainsApk(@NonNull PackageInstallerSession session) {
308         return sessionContains(session, (s) -> !isApexSession(s));
309     }
310 
resumeSession(@onNull PackageInstallerSession session)311     private void resumeSession(@NonNull PackageInstallerSession session) {
312         boolean hasApex = sessionContainsApex(session);
313         if (hasApex) {
314             // Check with apexservice whether the apex packages have been activated.
315             ApexSessionInfo apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId);
316             if (apexSessionInfo == null) {
317                 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
318                         "apexd did not know anything about a staged session supposed to be"
319                         + "activated");
320                 return;
321             }
322             if (isApexSessionFailed(apexSessionInfo)) {
323                 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
324                         "APEX activation failed. Check logcat messages from apexd for "
325                                 + "more information.");
326                 return;
327             }
328             if (apexSessionInfo.isVerified) {
329                 // Session has been previously submitted to apexd, but didn't complete all the
330                 // pre-reboot verification, perhaps because the device rebooted in the meantime.
331                 // Greedily re-trigger the pre-reboot verification.
332                 Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to be "
333                         + "verified, resuming pre-reboot verification");
334                 mBgHandler.post(() -> preRebootVerification(session));
335                 return;
336             }
337             if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
338                 // In all the remaining cases apexd will try to apply the session again at next
339                 // boot. Nothing to do here for now.
340                 Slog.w(TAG, "Staged session " + session.sessionId + " scheduled to be applied "
341                         + "at boot didn't activate nor fail. This usually means that apexd will "
342                         + "retry at next reboot.");
343                 return;
344             }
345         }
346         // The APEX part of the session is activated, proceed with the installation of APKs.
347         if (!installApksInSession(session, /* preReboot */ false)) {
348             session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
349                     "Staged installation of APKs failed. Check logcat messages for"
350                         + "more information.");
351 
352             if (!hasApex) {
353                 return;
354             }
355 
356             if (!mApexManager.abortActiveSession()) {
357                 Slog.e(TAG, "Failed to abort APEXd session");
358             } else {
359                 Slog.e(TAG,
360                         "Successfully aborted apexd session. Rebooting device in order to revert "
361                                 + "to the previous state of APEXd.");
362                 mPowerManager.reboot(null);
363             }
364             return;
365         }
366 
367         session.setStagedSessionApplied();
368         if (hasApex) {
369             mApexManager.markStagedSessionSuccessful(session.sessionId);
370         }
371     }
372 
findAPKsInDir(File stageDir)373     private List<String> findAPKsInDir(File stageDir) {
374         List<String> ret = new ArrayList<>();
375         if (stageDir != null && stageDir.exists()) {
376             for (File file : stageDir.listFiles()) {
377                 if (file.getAbsolutePath().toLowerCase().endsWith(".apk")) {
378                     ret.add(file.getAbsolutePath());
379                 }
380             }
381         }
382         return ret;
383     }
384 
createAndWriteApkSession( @onNull PackageInstallerSession originalSession, boolean preReboot)385     private PackageInstallerSession createAndWriteApkSession(
386             @NonNull PackageInstallerSession originalSession, boolean preReboot) {
387         if (originalSession.stageDir == null) {
388             Slog.wtf(TAG, "Attempting to install a staged APK session with no staging dir");
389             return null;
390         }
391         List<String> apkFilePaths = findAPKsInDir(originalSession.stageDir);
392         if (apkFilePaths.isEmpty()) {
393             Slog.w(TAG, "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
394             return null;
395         }
396 
397         PackageInstaller.SessionParams params = originalSession.params.copy();
398         params.isStaged = false;
399         params.installFlags |= PackageManager.INSTALL_STAGED;
400         // TODO(b/129744602): use the userid from the original session.
401         if (preReboot) {
402             params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
403             params.installFlags |= PackageManager.INSTALL_DRY_RUN;
404         } else {
405             params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
406         }
407         int apkSessionId = mPi.createSession(
408                 params, originalSession.getInstallerPackageName(),
409                 0 /* UserHandle.SYSTEM */);
410         PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
411 
412         try {
413             apkSession.open();
414             for (String apkFilePath : apkFilePaths) {
415                 File apkFile = new File(apkFilePath);
416                 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
417                         ParcelFileDescriptor.MODE_READ_ONLY);
418                 long sizeBytes = pfd.getStatSize();
419                 if (sizeBytes < 0) {
420                     Slog.e(TAG, "Unable to get size of: " + apkFilePath);
421                     return null;
422                 }
423                 apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
424             }
425         } catch (IOException e) {
426             Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
427             return null;
428         }
429         return apkSession;
430     }
431 
commitApkSession(@onNull PackageInstallerSession apkSession, int originalSessionId, boolean preReboot)432     private boolean commitApkSession(@NonNull PackageInstallerSession apkSession,
433                                      int originalSessionId, boolean preReboot) {
434 
435         if (!preReboot) {
436             if ((apkSession.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
437                 // If rollback is available for this session, notify the rollback
438                 // manager of the apk session so it can properly enable rollback.
439                 final IRollbackManager rm = IRollbackManager.Stub.asInterface(
440                         ServiceManager.getService(Context.ROLLBACK_SERVICE));
441                 try {
442                     rm.notifyStagedApkSession(originalSessionId, apkSession.sessionId);
443                 } catch (RemoteException re) {
444                     // Cannot happen, the rollback manager is in the same process.
445                 }
446             }
447         }
448 
449         final LocalIntentReceiver receiver = new LocalIntentReceiver();
450         apkSession.commit(receiver.getIntentSender(), false);
451         final Intent result = receiver.getResult();
452         final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
453                 PackageInstaller.STATUS_FAILURE);
454         if (status == PackageInstaller.STATUS_SUCCESS) {
455             return true;
456         }
457         Slog.e(TAG, "Failure to install APK staged session " + originalSessionId + " ["
458                 + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
459         return false;
460     }
461 
installApksInSession(@onNull PackageInstallerSession session, boolean preReboot)462     private boolean installApksInSession(@NonNull PackageInstallerSession session,
463                                          boolean preReboot) {
464         if (!session.isMultiPackage() && !isApexSession(session)) {
465             // APK single-packaged staged session. Do a regular install.
466             PackageInstallerSession apkSession = createAndWriteApkSession(session, preReboot);
467             if (apkSession == null) {
468                 return false;
469             }
470             return commitApkSession(apkSession, session.sessionId, preReboot);
471         } else if (session.isMultiPackage()) {
472             // For multi-package staged sessions containing APKs, we identify which child sessions
473             // contain an APK, and with those then create a new multi-package group of sessions,
474             // carrying over all the session parameters and unmarking them as staged. On commit the
475             // sessions will be installed atomically.
476             List<PackageInstallerSession> childSessions;
477             synchronized (mStagedSessions) {
478                 childSessions =
479                         Arrays.stream(session.getChildSessionIds())
480                                 // Retrieve cached sessions matching ids.
481                                 .mapToObj(i -> mStagedSessions.get(i))
482                                 // Filter only the ones containing APKs.s
483                                 .filter(childSession -> !isApexSession(childSession))
484                                 .collect(Collectors.toList());
485             }
486             if (childSessions.isEmpty()) {
487                 // APEX-only multi-package staged session, nothing to do.
488                 return true;
489             }
490             PackageInstaller.SessionParams params = session.params.copy();
491             params.isStaged = false;
492             if (preReboot) {
493                 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
494             }
495             // TODO(b/129744602): use the userid from the original session.
496             int apkParentSessionId = mPi.createSession(
497                     params, session.getInstallerPackageName(),
498                     0 /* UserHandle.SYSTEM */);
499             PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
500             try {
501                 apkParentSession.open();
502             } catch (IOException e) {
503                 Slog.e(TAG, "Unable to prepare multi-package session for staged session "
504                         + session.sessionId);
505                 return false;
506             }
507 
508             for (PackageInstallerSession sessionToClone : childSessions) {
509                 PackageInstallerSession apkChildSession =
510                         createAndWriteApkSession(sessionToClone, preReboot);
511                 if (apkChildSession == null) {
512                     return false;
513                 }
514                 try {
515                     apkParentSession.addChildSessionId(apkChildSession.sessionId);
516                 } catch (IllegalStateException e) {
517                     Slog.e(TAG, "Failed to add a child session for installing the APK files", e);
518                     return false;
519                 }
520             }
521             return commitApkSession(apkParentSession, session.sessionId, preReboot);
522         }
523         // APEX single-package staged session, nothing to do.
524         return true;
525     }
526 
commitSession(@onNull PackageInstallerSession session)527     void commitSession(@NonNull PackageInstallerSession session) {
528         updateStoredSession(session);
529         mBgHandler.post(() -> preRebootVerification(session));
530     }
531 
532     @Nullable
getActiveSession()533     PackageInstallerSession getActiveSession() {
534         synchronized (mStagedSessions) {
535             for (int i = 0; i < mStagedSessions.size(); i++) {
536                 final PackageInstallerSession session = mStagedSessions.valueAt(i);
537                 if (!session.isCommitted()) {
538                     continue;
539                 }
540                 if (session.hasParentSessionId()) {
541                     // Staging manager will finalize only parent session. Ignore child sessions
542                     // picking the active.
543                     continue;
544                 }
545                 if (!session.isStagedSessionApplied() && !session.isStagedSessionFailed()) {
546                     return session;
547                 }
548             }
549         }
550         return null;
551     }
552 
createSession(@onNull PackageInstallerSession sessionInfo)553     void createSession(@NonNull PackageInstallerSession sessionInfo) {
554         synchronized (mStagedSessions) {
555             mStagedSessions.append(sessionInfo.sessionId, sessionInfo);
556         }
557     }
558 
abortSession(@onNull PackageInstallerSession session)559     void abortSession(@NonNull PackageInstallerSession session) {
560         synchronized (mStagedSessions) {
561             mStagedSessions.remove(session.sessionId);
562         }
563     }
564 
abortCommittedSession(@onNull PackageInstallerSession session)565     void abortCommittedSession(@NonNull PackageInstallerSession session) {
566         if (session.isStagedSessionApplied()) {
567             Slog.w(TAG, "Cannot abort applied session : " + session.sessionId);
568             return;
569         }
570         abortSession(session);
571 
572         boolean hasApex = sessionContainsApex(session);
573         if (hasApex) {
574             ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId);
575             if (apexSession == null || isApexSessionFinalized(apexSession)) {
576                 Slog.w(TAG,
577                         "Cannot abort session because it is not active or APEXD is not reachable");
578                 return;
579             }
580             mApexManager.abortActiveSession();
581         }
582     }
583 
isApexSessionFinalized(ApexSessionInfo session)584     private boolean isApexSessionFinalized(ApexSessionInfo session) {
585         /* checking if the session is in a final state, i.e., not active anymore */
586         return session.isUnknown || session.isActivationFailed || session.isSuccess
587                 || session.isRolledBack;
588     }
589 
isApexSessionFailed(ApexSessionInfo apexSessionInfo)590     private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) {
591         // isRollbackInProgress is included to cover the scenario, when a device is rebooted in
592         // during the rollback, and apexd fails to resume the rollback after reboot.
593         return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown
594                 || apexSessionInfo.isRolledBack || apexSessionInfo.isRollbackInProgress
595                 || apexSessionInfo.isRollbackFailed;
596     }
597 
598     @GuardedBy("mStagedSessions")
isMultiPackageSessionComplete(@onNull PackageInstallerSession session)599     private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) {
600         // This method assumes that the argument is either a parent session of a multi-package
601         // i.e. isMultiPackage() returns true, or that it is a child session, i.e.
602         // hasParentSessionId() returns true.
603         if (session.isMultiPackage()) {
604             // Parent session of a multi-package group. Check that we restored all the children.
605             for (int childSession : session.getChildSessionIds()) {
606                 if (mStagedSessions.get(childSession) == null) {
607                     return false;
608                 }
609             }
610             return true;
611         }
612         if (session.hasParentSessionId()) {
613             PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId());
614             if (parent == null) {
615                 return false;
616             }
617             return isMultiPackageSessionComplete(parent);
618         }
619         Slog.wtf(TAG, "Attempting to restore an invalid multi-package session.");
620         return false;
621     }
622 
restoreSession(@onNull PackageInstallerSession session)623     void restoreSession(@NonNull PackageInstallerSession session) {
624         PackageInstallerSession sessionToResume = session;
625         synchronized (mStagedSessions) {
626             mStagedSessions.append(session.sessionId, session);
627             // For multi-package sessions, we don't know in which order they will be restored. We
628             // need to wait until we have restored all the session in a group before restoring them.
629             if (session.isMultiPackage() || session.hasParentSessionId()) {
630                 if (!isMultiPackageSessionComplete(session)) {
631                     // Still haven't recovered all sessions of the group, return.
632                     return;
633                 }
634                 // Group recovered, find the parent if necessary and resume the installation.
635                 if (session.hasParentSessionId()) {
636                     sessionToResume = mStagedSessions.get(session.getParentSessionId());
637                 }
638             }
639         }
640         checkStateAndResume(sessionToResume);
641     }
642 
checkStateAndResume(@onNull PackageInstallerSession session)643     private void checkStateAndResume(@NonNull PackageInstallerSession session) {
644         if (!session.isCommitted()) {
645             // Session hasn't been committed yet, ignore.
646             return;
647         }
648         // Check the state of the session and decide what to do next.
649         if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) {
650             // Final states, nothing to do.
651             return;
652         }
653         if (!session.isStagedSessionReady()) {
654             // The framework got restarted before the pre-reboot verification could complete,
655             // restart the verification.
656             mBgHandler.post(() -> preRebootVerification(session));
657         } else {
658             // Session had already being marked ready. Start the checks to verify if there is any
659             // follow-up work.
660             resumeSession(session);
661         }
662     }
663 
664     private static class LocalIntentReceiver {
665         private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
666 
667         private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
668             @Override
669             public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
670                              IIntentReceiver finishedReceiver, String requiredPermission,
671                              Bundle options) {
672                 try {
673                     mResult.offer(intent, 5, TimeUnit.SECONDS);
674                 } catch (InterruptedException e) {
675                     throw new RuntimeException(e);
676                 }
677             }
678         };
679 
getIntentSender()680         public IntentSender getIntentSender() {
681             return new IntentSender((IIntentSender) mLocalSender);
682         }
683 
getResult()684         public Intent getResult() {
685             try {
686                 return mResult.take();
687             } catch (InterruptedException e) {
688                 throw new RuntimeException(e);
689             }
690         }
691     }
692 }
693