blob: 2210ff86133c6086e2d5d7aa741fff776ec80c0c [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.ParceledListSlice;
import android.content.pm.Signature;
import android.content.rollback.IRollbackManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.util.Slog;
import android.util.SparseArray;
import android.util.apk.ApkSignatureVerifier;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* This class handles staged install sessions, i.e. install sessions that require packages to
* be installed only after a reboot.
*/
public class StagingManager {
private static final String TAG = "StagingManager";
private final PackageInstallerService mPi;
private final ApexManager mApexManager;
private final PowerManager mPowerManager;
private final Context mContext;
private final Handler mBgHandler;
private PackageInstallerSession mPendingSession;
private boolean mIsReady;
@GuardedBy("mStagedSessions")
private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
mContext = context;
mPi = pi;
mApexManager = am;
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mBgHandler = BackgroundThread.getHandler();
}
private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) {
synchronized (mStagedSessions) {
PackageInstallerSession storedSession = mStagedSessions.get(sessionInfo.sessionId);
// storedSession might be null if a call to abortSession was made before the session
// is updated.
if (storedSession != null) {
mStagedSessions.put(sessionInfo.sessionId, sessionInfo);
}
}
}
ParceledListSlice<PackageInstaller.SessionInfo> getSessions(int callingUid) {
final List<PackageInstaller.SessionInfo> result = new ArrayList<>();
synchronized (mStagedSessions) {
for (int i = 0; i < mStagedSessions.size(); i++) {
final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
result.add(stagedSession.generateInfoForCaller(false /*icon*/, callingUid));
}
}
return new ParceledListSlice<>(result);
}
private boolean validateApexSignature(String apexPath, String packageName) {
final SigningDetails signingDetails;
try {
signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
} catch (PackageParserException e) {
Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e);
return false;
}
final PackageInfo packageInfo = mApexManager.getPackageInfoForApexName(packageName);
if (packageInfo == null) {
// Don't allow installation of new APEX.
Slog.e(TAG, "Attempted to install a new apex " + packageName + ". Rejecting");
return false;
}
final SigningDetails existingSigningDetails;
try {
existingSigningDetails = ApkSignatureVerifier.verify(
packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
} catch (PackageParserException e) {
Slog.e(TAG, "Unable to parse APEX package: "
+ packageInfo.applicationInfo.sourceDir, e);
return false;
}
// Now that we have both sets of signatures, demand that they're an exact match.
if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) {
return true;
}
return false;
}
private boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
List<PackageInstallerSession> childSessions,
ApexInfoList apexInfoList) {
boolean submittedToApexd = mApexManager.submitStagedSession(
session.sessionId,
childSessions != null
? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
new int[]{},
apexInfoList);
if (!submittedToApexd) {
session.setStagedSessionFailed(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"APEX staging failed, check logcat messages from apexd for more details.");
return false;
}
for (ApexInfo newPackage : apexInfoList.apexInfos) {
PackageInfo activePackage = mApexManager.getPackageInfoForApexName(
newPackage.packageName);
if (activePackage == null) {
continue;
}
long activeVersion = activePackage.applicationInfo.longVersionCode;
if (session.params.requiredInstalledVersionCode
!= PackageManager.VERSION_CODE_HIGHEST) {
if (activeVersion != session.params.requiredInstalledVersionCode) {
session.setStagedSessionFailed(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Installed version of APEX package " + newPackage.packageName
+ " does not match required. Active version: " + activeVersion
+ " required: " + session.params.requiredInstalledVersionCode);
if (!mApexManager.abortActiveSession()) {
Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
}
return false;
}
}
boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
session.params.installFlags, activePackage.applicationInfo.flags);
if (activeVersion > newPackage.versionCode && !allowsDowngrade) {
session.setStagedSessionFailed(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Downgrade of APEX package " + newPackage.packageName
+ " is not allowed. Active version: " + activeVersion
+ " attempted: " + newPackage.versionCode);
if (!mApexManager.abortActiveSession()) {
Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
}
return false;
}
}
return true;
}
private static boolean isApexSession(@NonNull PackageInstallerSession session) {
return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
}
private void preRebootVerification(@NonNull PackageInstallerSession session) {
try {
if (!mIsReady) {
mPendingSession = session;
return;
}
boolean success = true;
final ApexInfoList apexInfoList = new ApexInfoList();
// APEX checks. For single-package sessions, check if they contain an APEX. For
// multi-package sessions, find all the child sessions that contain an APEX.
if (!session.isMultiPackage()
&& isApexSession(session)) {
success = submitSessionToApexService(session, null, apexInfoList);
} else if (session.isMultiPackage()) {
List<PackageInstallerSession> childSessions =
Arrays.stream(session.getChildSessionIds())
// Retrieve cached sessions matching ids.
.mapToObj(i -> mStagedSessions.get(i))
// Filter only the ones containing APEX.
.filter(childSession -> isApexSession(childSession))
.collect(Collectors.toList());
if (!childSessions.isEmpty()) {
success = submitSessionToApexService(session, childSessions, apexInfoList);
} // else this is a staged multi-package session with no APEX files.
}
if (!success) {
// submitSessionToApexService will populate error.
return;
}
if (sessionContainsApk(session)) {
if (!installApksInSession(session, /* preReboot */ true)) {
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"APK verification failed. Check logcat messages for "
+ "more information.");
// TODO(b/118865310): abort the session on apexd.
return;
}
}
if (apexInfoList.apexInfos != null && apexInfoList.apexInfos.length > 0) {
// For APEXes, we validate the signature here before we mark the session as ready,
// so we fail the session early if there is a signature mismatch. For APKs, the
// signature verification will be done by the package manager at the point at which
// it applies the staged install.
for (ApexInfo apexPackage : apexInfoList.apexInfos) {
if (!validateApexSignature(apexPackage.packagePath,
apexPackage.packageName)) {
session.setStagedSessionFailed(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"APK-container signature verification failed for package "
+ apexPackage.packageName + ". Signature of file "
+ apexPackage.packagePath + " does not match the signature"
+ " of the package already installed.");
// TODO(b/118865310): abort the session on apexd.
return;
}
}
}
if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
// If rollback is enabled for this session, we call through to the RollbackManager
// with the list of sessions it must enable rollback for. Note that
// notifyStagedSession is a synchronous operation.
final IRollbackManager rm = IRollbackManager.Stub.asInterface(
ServiceManager.getService(Context.ROLLBACK_SERVICE));
try {
// NOTE: To stay consistent with the non-staged install flow, we don't fail the
// entire install if rollbacks can't be enabled.
if (!rm.notifyStagedSession(session.sessionId)) {
Slog.e(TAG, "Unable to enable rollback for session: " + session.sessionId);
}
} catch (RemoteException re) {
// Cannot happen, the rollback manager is in the same process.
}
}
session.setStagedSessionReady();
if (sessionContainsApex(session)
&& !mApexManager.markStagedSessionReady(session.sessionId)) {
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"APEX staging failed, check logcat messages from apexd for more "
+ "details.");
}
} catch (Exception e) {
Slog.e(TAG, "Pre-reboot verification failed due to unhandled exception", e);
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Pre-reboot verification failed due to unhandled exception: " + e);
}
}
private boolean sessionContains(@NonNull PackageInstallerSession session,
Predicate<PackageInstallerSession> filter) {
if (!session.isMultiPackage()) {
return filter.test(session);
}
synchronized (mStagedSessions) {
return !(Arrays.stream(session.getChildSessionIds())
// Retrieve cached sessions matching ids.
.mapToObj(i -> mStagedSessions.get(i))
// Filter only the ones containing APEX.
.filter(childSession -> filter.test(childSession))
.collect(Collectors.toList())
.isEmpty());
}
}
private boolean sessionContainsApex(@NonNull PackageInstallerSession session) {
return sessionContains(session, (s) -> isApexSession(s));
}
private boolean sessionContainsApk(@NonNull PackageInstallerSession session) {
return sessionContains(session, (s) -> !isApexSession(s));
}
private void resumeSession(@NonNull PackageInstallerSession session) {
boolean hasApex = sessionContainsApex(session);
if (hasApex) {
// Check with apexservice whether the apex packages have been activated.
ApexSessionInfo apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId);
if (apexSessionInfo == null) {
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
"apexd did not know anything about a staged session supposed to be"
+ "activated");
return;
}
if (isApexSessionFailed(apexSessionInfo)) {
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
"APEX activation failed. Check logcat messages from apexd for "
+ "more information.");
return;
}
if (apexSessionInfo.isVerified) {
// Session has been previously submitted to apexd, but didn't complete all the
// pre-reboot verification, perhaps because the device rebooted in the meantime.
// Greedily re-trigger the pre-reboot verification.
Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to be "
+ "verified, resuming pre-reboot verification");
mBgHandler.post(() -> preRebootVerification(session));
return;
}
if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
// In all the remaining cases apexd will try to apply the session again at next
// boot. Nothing to do here for now.
Slog.w(TAG, "Staged session " + session.sessionId + " scheduled to be applied "
+ "at boot didn't activate nor fail. This usually means that apexd will "
+ "retry at next reboot.");
return;
}
}
// The APEX part of the session is activated, proceed with the installation of APKs.
if (!installApksInSession(session, /* preReboot */ false)) {
onInstallationFailure(session, new PackageManagerException(
SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "Staged installation of APKs "
+ "failed. Check logcat messages for more information."));
return;
}
session.setStagedSessionApplied();
if (hasApex) {
mApexManager.markStagedSessionSuccessful(session.sessionId);
}
}
void onInstallationFailure(PackageInstallerSession session, PackageManagerException e) {
session.setStagedSessionFailed(e.error, e.getMessage());
if (!sessionContainsApex(session)) {
return;
}
if (!mApexManager.abortActiveSession()) {
Slog.e(TAG, "Failed to abort APEXd session");
} else {
Slog.e(TAG,
"Successfully aborted apexd session. Rebooting device in order to revert "
+ "to the previous state of APEXd.");
mPowerManager.reboot(null);
}
}
private List<String> findAPKsInDir(File stageDir) {
List<String> ret = new ArrayList<>();
if (stageDir != null && stageDir.exists()) {
for (File file : stageDir.listFiles()) {
if (file.getAbsolutePath().toLowerCase().endsWith(".apk")) {
ret.add(file.getAbsolutePath());
}
}
}
return ret;
}
private PackageInstallerSession createAndWriteApkSession(
@NonNull PackageInstallerSession originalSession, boolean preReboot) {
if (originalSession.stageDir == null) {
Slog.wtf(TAG, "Attempting to install a staged APK session with no staging dir");
return null;
}
List<String> apkFilePaths = findAPKsInDir(originalSession.stageDir);
if (apkFilePaths.isEmpty()) {
Slog.w(TAG, "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
return null;
}
PackageInstaller.SessionParams params = originalSession.params.copy();
params.isStaged = false;
params.installFlags |= PackageManager.INSTALL_STAGED;
// TODO(b/129744602): use the userid from the original session.
if (preReboot) {
params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
params.installFlags |= PackageManager.INSTALL_DRY_RUN;
} else {
params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
}
try {
int apkSessionId = mPi.createSession(
params, originalSession.getInstallerPackageName(),
0 /* UserHandle.SYSTEM */);
PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
apkSession.open();
for (String apkFilePath : apkFilePaths) {
File apkFile = new File(apkFilePath);
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
ParcelFileDescriptor.MODE_READ_ONLY);
long sizeBytes = (pfd == null) ? -1 : pfd.getStatSize();
if (sizeBytes < 0) {
Slog.e(TAG, "Unable to get size of: " + apkFilePath);
return null;
}
apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
}
return apkSession;
} catch (IOException | ParcelableException e) {
Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
return null;
}
}
private boolean commitApkSession(@NonNull PackageInstallerSession apkSession,
int originalSessionId, boolean preReboot) {
if (!preReboot) {
if ((apkSession.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
// If rollback is available for this session, notify the rollback
// manager of the apk session so it can properly enable rollback.
final IRollbackManager rm = IRollbackManager.Stub.asInterface(
ServiceManager.getService(Context.ROLLBACK_SERVICE));
try {
rm.notifyStagedApkSession(originalSessionId, apkSession.sessionId);
} catch (RemoteException re) {
// Cannot happen, the rollback manager is in the same process.
}
}
}
final LocalIntentReceiver receiver = new LocalIntentReceiver();
apkSession.commit(receiver.getIntentSender(), false);
final Intent result = receiver.getResult();
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status == PackageInstaller.STATUS_SUCCESS) {
return true;
}
Slog.e(TAG, "Failure to install APK staged session " + originalSessionId + " ["
+ result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
return false;
}
private boolean installApksInSession(@NonNull PackageInstallerSession session,
boolean preReboot) {
if (!session.isMultiPackage() && !isApexSession(session)) {
// APK single-packaged staged session. Do a regular install.
PackageInstallerSession apkSession = createAndWriteApkSession(session, preReboot);
if (apkSession == null) {
return false;
}
return commitApkSession(apkSession, session.sessionId, preReboot);
} else if (session.isMultiPackage()) {
// For multi-package staged sessions containing APKs, we identify which child sessions
// contain an APK, and with those then create a new multi-package group of sessions,
// carrying over all the session parameters and unmarking them as staged. On commit the
// sessions will be installed atomically.
List<PackageInstallerSession> childSessions;
synchronized (mStagedSessions) {
childSessions =
Arrays.stream(session.getChildSessionIds())
// Retrieve cached sessions matching ids.
.mapToObj(i -> mStagedSessions.get(i))
// Filter only the ones containing APKs.s
.filter(childSession -> !isApexSession(childSession))
.collect(Collectors.toList());
}
if (childSessions.isEmpty()) {
// APEX-only multi-package staged session, nothing to do.
return true;
}
PackageInstaller.SessionParams params = session.params.copy();
params.isStaged = false;
if (preReboot) {
params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
}
// TODO(b/129744602): use the userid from the original session.
int apkParentSessionId = mPi.createSession(
params, session.getInstallerPackageName(),
0 /* UserHandle.SYSTEM */);
PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
try {
apkParentSession.open();
} catch (IOException e) {
Slog.e(TAG, "Unable to prepare multi-package session for staged session "
+ session.sessionId);
return false;
}
for (PackageInstallerSession sessionToClone : childSessions) {
PackageInstallerSession apkChildSession =
createAndWriteApkSession(sessionToClone, preReboot);
if (apkChildSession == null) {
return false;
}
try {
apkParentSession.addChildSessionId(apkChildSession.sessionId);
} catch (IllegalStateException e) {
Slog.e(TAG, "Failed to add a child session for installing the APK files", e);
return false;
}
}
return commitApkSession(apkParentSession, session.sessionId, preReboot);
}
// APEX single-package staged session, nothing to do.
return true;
}
void commitSession(@NonNull PackageInstallerSession session) {
updateStoredSession(session);
mBgHandler.post(() -> preRebootVerification(session));
}
@Nullable
PackageInstallerSession getActiveSession() {
synchronized (mStagedSessions) {
for (int i = 0; i < mStagedSessions.size(); i++) {
final PackageInstallerSession session = mStagedSessions.valueAt(i);
if (!session.isCommitted()) {
continue;
}
if (session.hasParentSessionId()) {
// Staging manager will finalize only parent session. Ignore child sessions
// picking the active.
continue;
}
if (!session.isStagedSessionApplied() && !session.isStagedSessionFailed()) {
return session;
}
}
}
return null;
}
void createSession(@NonNull PackageInstallerSession sessionInfo) {
synchronized (mStagedSessions) {
mStagedSessions.append(sessionInfo.sessionId, sessionInfo);
}
}
void abortSession(@NonNull PackageInstallerSession session) {
synchronized (mStagedSessions) {
mStagedSessions.remove(session.sessionId);
}
}
void abortCommittedSession(@NonNull PackageInstallerSession session) {
if (session.isStagedSessionApplied()) {
Slog.w(TAG, "Cannot abort applied session : " + session.sessionId);
return;
}
abortSession(session);
boolean hasApex = sessionContainsApex(session);
if (hasApex) {
ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId);
if (apexSession == null || isApexSessionFinalized(apexSession)) {
Slog.w(TAG,
"Cannot abort session because it is not active or APEXD is not reachable");
return;
}
mApexManager.abortActiveSession();
}
}
private boolean isApexSessionFinalized(ApexSessionInfo session) {
/* checking if the session is in a final state, i.e., not active anymore */
return session.isUnknown || session.isActivationFailed || session.isSuccess
|| session.isRolledBack;
}
private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) {
// isRollbackInProgress is included to cover the scenario, when a device is rebooted in
// during the rollback, and apexd fails to resume the rollback after reboot.
return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown
|| apexSessionInfo.isRolledBack || apexSessionInfo.isRollbackInProgress
|| apexSessionInfo.isRollbackFailed;
}
@GuardedBy("mStagedSessions")
private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) {
// This method assumes that the argument is either a parent session of a multi-package
// i.e. isMultiPackage() returns true, or that it is a child session, i.e.
// hasParentSessionId() returns true.
if (session.isMultiPackage()) {
// Parent session of a multi-package group. Check that we restored all the children.
for (int childSession : session.getChildSessionIds()) {
if (mStagedSessions.get(childSession) == null) {
return false;
}
}
return true;
}
if (session.hasParentSessionId()) {
PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId());
if (parent == null) {
return false;
}
return isMultiPackageSessionComplete(parent);
}
Slog.wtf(TAG, "Attempting to restore an invalid multi-package session.");
return false;
}
void restoreSession(@NonNull PackageInstallerSession session, boolean isDeviceUpgrading) {
PackageInstallerSession sessionToResume = session;
synchronized (mStagedSessions) {
mStagedSessions.append(session.sessionId, session);
// For multi-package sessions, we don't know in which order they will be restored. We
// need to wait until we have restored all the session in a group before restoring them.
if (session.isMultiPackage() || session.hasParentSessionId()) {
if (!isMultiPackageSessionComplete(session)) {
// Still haven't recovered all sessions of the group, return.
return;
}
// Group recovered, find the parent if necessary and resume the installation.
if (session.hasParentSessionId()) {
sessionToResume = mStagedSessions.get(session.getParentSessionId());
}
}
}
// The preconditions used during pre-reboot verification might have changed when device
// is upgrading. Updated staged sessions to activation failed before we resume the session.
if (isDeviceUpgrading && !sessionToResume.isStagedAndInTerminalState()) {
sessionToResume.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
"Build fingerprint has changed");
return;
}
checkStateAndResume(sessionToResume);
}
private void checkStateAndResume(@NonNull PackageInstallerSession session) {
// Do not resume session if boot completed already
if (SystemProperties.getBoolean("sys.boot_completed", false)) {
return;
}
if (!session.isCommitted()) {
// Session hasn't been committed yet, ignore.
return;
}
// Check the state of the session and decide what to do next.
if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) {
// Final states, nothing to do.
return;
}
if (!session.isStagedSessionReady()) {
// The framework got restarted before the pre-reboot verification could complete,
// restart the verification.
mBgHandler.post(() -> preRebootVerification(session));
} else {
// Session had already being marked ready. Start the checks to verify if there is any
// follow-up work.
try {
resumeSession(session);
} catch (Exception e) {
Slog.e(TAG, "Staged install failed due to unhandled exception", e);
onInstallationFailure(session, new PackageManagerException(
SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
"Staged install failed due to unhandled exception: " + e));
}
}
}
void systemReady() {
// Register the receiver of boot completed intent for staging manager.
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context ctx, Intent intent) {
readyToStart();
ctx.unregisterReceiver(this);
}
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
// Notify the handler that system is ready, and reschedule the pre-reboot verifications.
private synchronized void readyToStart() {
mIsReady = true;
if (mPendingSession != null) {
mBgHandler.post(() -> {
preRebootVerification(mPendingSession);
mPendingSession = null;
});
}
}
private static class LocalIntentReceiver {
private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
IIntentReceiver finishedReceiver, String requiredPermission,
Bundle options) {
try {
mResult.offer(intent, 5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
public IntentSender getIntentSender() {
return new IntentSender((IIntentSender) mLocalSender);
}
public Intent getResult() {
try {
return mResult.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}