Merge changes I34d7ee3b,I99337ea2,I5c190938,If79073a9,Ie3343681 into qt-qpr1-dev
* changes: Reschedule the pre-reboot verification after boot completed. Prevent sessions from resuming once boot is completed Prevent extra sessions owned by staged install from living across restarts Prevent exceptions during staged install from crashing system server Prevent exceptions in pre-reboot verification from crashing system server
This commit is contained in:
committed by
Android (Google) Code Review
commit
71a7e46d2a
@@ -218,6 +218,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
|
|||||||
|
|
||||||
public void systemReady() {
|
public void systemReady() {
|
||||||
mAppOps = mContext.getSystemService(AppOpsManager.class);
|
mAppOps = mContext.getSystemService(AppOpsManager.class);
|
||||||
|
mStagingManager.systemReady();
|
||||||
|
|
||||||
synchronized (mSessions) {
|
synchronized (mSessions) {
|
||||||
readSessionsLocked();
|
readSessionsLocked();
|
||||||
@@ -380,6 +381,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
|
|||||||
Slog.w(TAG, "Abandoning old session created at "
|
Slog.w(TAG, "Abandoning old session created at "
|
||||||
+ session.createdMillis);
|
+ session.createdMillis);
|
||||||
valid = false;
|
valid = false;
|
||||||
|
} else if (isExtraSessionForStagedInstall(session)) {
|
||||||
|
valid = false;
|
||||||
} else {
|
} else {
|
||||||
valid = true;
|
valid = true;
|
||||||
}
|
}
|
||||||
@@ -410,6 +413,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extra sessions are created during staged install on temporary basis. They should not be
|
||||||
|
// allowed to live across system server restart.
|
||||||
|
private boolean isExtraSessionForStagedInstall(PackageInstallerSession session) {
|
||||||
|
return (session.params.installFlags & PackageManager.INSTALL_DRY_RUN) != 0
|
||||||
|
|| (session.params.installFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
@GuardedBy("mSessions")
|
@GuardedBy("mSessions")
|
||||||
private void addHistoricalSessionLocked(PackageInstallerSession session) {
|
private void addHistoricalSessionLocked(PackageInstallerSession session) {
|
||||||
CharArrayWriter writer = new CharArrayWriter();
|
CharArrayWriter writer = new CharArrayWriter();
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ import android.annotation.Nullable;
|
|||||||
import android.apex.ApexInfo;
|
import android.apex.ApexInfo;
|
||||||
import android.apex.ApexInfoList;
|
import android.apex.ApexInfoList;
|
||||||
import android.apex.ApexSessionInfo;
|
import android.apex.ApexSessionInfo;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.IIntentReceiver;
|
import android.content.IIntentReceiver;
|
||||||
import android.content.IIntentSender;
|
import android.content.IIntentSender;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.content.IntentSender;
|
import android.content.IntentSender;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageInstaller;
|
import android.content.pm.PackageInstaller;
|
||||||
@@ -44,6 +46,7 @@ import android.os.ParcelableException;
|
|||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.ServiceManager;
|
import android.os.ServiceManager;
|
||||||
|
import android.os.SystemProperties;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.util.apk.ApkSignatureVerifier;
|
import android.util.apk.ApkSignatureVerifier;
|
||||||
@@ -72,12 +75,16 @@ public class StagingManager {
|
|||||||
private final PackageInstallerService mPi;
|
private final PackageInstallerService mPi;
|
||||||
private final ApexManager mApexManager;
|
private final ApexManager mApexManager;
|
||||||
private final PowerManager mPowerManager;
|
private final PowerManager mPowerManager;
|
||||||
|
private final Context mContext;
|
||||||
private final Handler mBgHandler;
|
private final Handler mBgHandler;
|
||||||
|
private PackageInstallerSession mPendingSession;
|
||||||
|
private boolean mIsReady;
|
||||||
|
|
||||||
@GuardedBy("mStagedSessions")
|
@GuardedBy("mStagedSessions")
|
||||||
private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
|
private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
|
||||||
|
|
||||||
StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
|
StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
|
||||||
|
mContext = context;
|
||||||
mPi = pi;
|
mPi = pi;
|
||||||
mApexManager = am;
|
mApexManager = am;
|
||||||
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
@@ -202,85 +209,97 @@ public class StagingManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void preRebootVerification(@NonNull PackageInstallerSession session) {
|
private void preRebootVerification(@NonNull PackageInstallerSession session) {
|
||||||
boolean success = true;
|
try {
|
||||||
|
if (!mIsReady) {
|
||||||
final ApexInfoList apexInfoList = new ApexInfoList();
|
mPendingSession = session;
|
||||||
// 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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (apexInfoList.apexInfos != null && apexInfoList.apexInfos.length > 0) {
|
boolean success = true;
|
||||||
// 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
|
final ApexInfoList apexInfoList = new ApexInfoList();
|
||||||
// signature verification will be done by the package manager at the point at which
|
// APEX checks. For single-package sessions, check if they contain an APEX. For
|
||||||
// it applies the staged install.
|
// multi-package sessions, find all the child sessions that contain an APEX.
|
||||||
for (ApexInfo apexPackage : apexInfoList.apexInfos) {
|
if (!session.isMultiPackage()
|
||||||
if (!validateApexSignature(apexPackage.packagePath,
|
&& isApexSession(session)) {
|
||||||
apexPackage.packageName)) {
|
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,
|
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||||
"APK-container signature verification failed for package "
|
"APK verification failed. Check logcat messages for "
|
||||||
+ apexPackage.packageName + ". Signature of file "
|
+ "more information.");
|
||||||
+ apexPackage.packagePath + " does not match the signature of "
|
|
||||||
+ " the package already installed.");
|
|
||||||
// TODO(b/118865310): abort the session on apexd.
|
// TODO(b/118865310): abort the session on apexd.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
|
if (apexInfoList.apexInfos != null && apexInfoList.apexInfos.length > 0) {
|
||||||
// If rollback is enabled for this session, we call through to the RollbackManager
|
// For APEXes, we validate the signature here before we mark the session as ready,
|
||||||
// with the list of sessions it must enable rollback for. Note that notifyStagedSession
|
// so we fail the session early if there is a signature mismatch. For APKs, the
|
||||||
// is a synchronous operation.
|
// signature verification will be done by the package manager at the point at which
|
||||||
final IRollbackManager rm = IRollbackManager.Stub.asInterface(
|
// it applies the staged install.
|
||||||
ServiceManager.getService(Context.ROLLBACK_SERVICE));
|
for (ApexInfo apexPackage : apexInfoList.apexInfos) {
|
||||||
try {
|
if (!validateApexSignature(apexPackage.packagePath,
|
||||||
// NOTE: To stay consistent with the non-staged install flow, we don't fail the
|
apexPackage.packageName)) {
|
||||||
// entire install if rollbacks can't be enabled.
|
session.setStagedSessionFailed(
|
||||||
if (!rm.notifyStagedSession(session.sessionId)) {
|
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||||
Slog.e(TAG, "Unable to enable rollback for session: " + session.sessionId);
|
"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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (RemoteException re) {
|
|
||||||
// Cannot happen, the rollback manager is in the same process.
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
session.setStagedSessionReady();
|
if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
|
||||||
if (sessionContainsApex(session)
|
// If rollback is enabled for this session, we call through to the RollbackManager
|
||||||
&& !mApexManager.markStagedSessionReady(session.sessionId)) {
|
// 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,
|
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||||
"APEX staging failed, check logcat messages from apexd for more "
|
"Pre-reboot verification failed due to unhandled exception: " + e);
|
||||||
+ "details.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,22 +365,9 @@ public class StagingManager {
|
|||||||
}
|
}
|
||||||
// The APEX part of the session is activated, proceed with the installation of APKs.
|
// The APEX part of the session is activated, proceed with the installation of APKs.
|
||||||
if (!installApksInSession(session, /* preReboot */ false)) {
|
if (!installApksInSession(session, /* preReboot */ false)) {
|
||||||
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
|
onInstallationFailure(session, new PackageManagerException(
|
||||||
"Staged installation of APKs failed. Check logcat messages for"
|
SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "Staged installation of APKs "
|
||||||
+ "more information.");
|
+ "failed. Check logcat messages for more information."));
|
||||||
|
|
||||||
if (!hasApex) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,6 +377,21 @@ public class StagingManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
private List<String> findAPKsInDir(File stageDir) {
|
||||||
List<String> ret = new ArrayList<>();
|
List<String> ret = new ArrayList<>();
|
||||||
if (stageDir != null && stageDir.exists()) {
|
if (stageDir != null && stageDir.exists()) {
|
||||||
@@ -648,6 +669,11 @@ public class StagingManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkStateAndResume(@NonNull PackageInstallerSession session) {
|
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()) {
|
if (!session.isCommitted()) {
|
||||||
// Session hasn't been committed yet, ignore.
|
// Session hasn't been committed yet, ignore.
|
||||||
return;
|
return;
|
||||||
@@ -664,7 +690,37 @@ public class StagingManager {
|
|||||||
} else {
|
} else {
|
||||||
// Session had already being marked ready. Start the checks to verify if there is any
|
// Session had already being marked ready. Start the checks to verify if there is any
|
||||||
// follow-up work.
|
// follow-up work.
|
||||||
resumeSession(session);
|
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;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user