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