Merge "Apply APPLIED/FAILED to non-staged sessions (8/n)"

This commit is contained in:
Chun-Wei Wang
2022-02-08 06:29:41 +00:00
committed by Android (Google) Code Review
4 changed files with 102 additions and 92 deletions

View File

@@ -1650,7 +1650,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
public void onSessionChanged(PackageInstallerSession session) {
session.markUpdated();
mSettingsWriteRequest.schedule();
if (mOkToSendBroadcasts && !session.isDestroyed()) {
// TODO(b/210359798): Remove the session.isStaged() check. Some apps assume this
// broadcast is sent by only staged sessions and call isStagedSessionApplied() without
// checking if it is a staged session or not and cause exception.
if (mOkToSendBroadcasts && !session.isDestroyed() && session.isStaged()) {
// we don't scrub the data here as this is sent only to the installer several
// privileged system packages
sendSessionUpdatedBroadcast(

View File

@@ -182,6 +182,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
@@ -549,18 +550,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
/**
* Installs apks of staged session while skipping the verification process for a committed
* and ready session.
*
* @return a CompletableFuture that will be completed when installation completes.
*/
@Override
public void installSession(IntentSender statusReceiver) {
public CompletableFuture<Void> installSession() {
assertCallerIsOwnerOrRootOrSystem();
assertNotChild("StagedSession#installSession");
Preconditions.checkArgument(isCommitted() && isSessionReady());
// Since staged sessions are installed during boot, the original reference to status
// receiver from the owner has already been lost. We can safely replace it with a
// status receiver from the system without effecting the flow.
updateRemoteStatusReceiver(statusReceiver);
install();
return install();
}
private void updateRemoteStatusReceiver(IntentSender remoteStatusReceiver) {
@@ -1694,15 +1692,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
if (isMultiPackage()) {
synchronized (mLock) {
final IntentSender childIntentSender =
new ChildStatusIntentReceiver(mChildSessions.clone(), statusReceiver)
.getIntentSender();
boolean sealFailed = false;
for (int i = mChildSessions.size() - 1; i >= 0; --i) {
// seal all children, regardless if any of them fail; we'll throw/return
// as appropriate once all children have been processed
if (!mChildSessions.valueAt(i)
.markAsSealed(childIntentSender, forTransfer)) {
if (!mChildSessions.valueAt(i).markAsSealed(null, forTransfer)) {
sealFailed = true;
}
}
@@ -1890,8 +1884,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
* This method may be called multiple times to update the status receiver validate caller
* permissions.
*/
private boolean markAsSealed(@NonNull IntentSender statusReceiver, boolean forTransfer) {
Objects.requireNonNull(statusReceiver);
private boolean markAsSealed(@Nullable IntentSender statusReceiver, boolean forTransfer) {
Preconditions.checkState(statusReceiver != null || hasParentSessionId(),
"statusReceiver can't be null for the root session");
assertCallerIsOwnerOrRoot();
synchronized (mLock) {
@@ -2191,7 +2186,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
@WorkerThread
private static boolean checkUserActionRequirement(PackageInstallerSession session) {
private static boolean checkUserActionRequirement(
PackageInstallerSession session, IntentSender target) {
if (session.isMultiPackage()) {
return false;
}
@@ -2200,7 +2196,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// TODO(b/159331446): Move this to makeSessionActiveForInstall and update javadoc
userActionRequirement = session.computeUserActionRequirement();
if (userActionRequirement == USER_ACTION_REQUIRED) {
session.sendPendingUserActionIntent();
session.sendPendingUserActionIntent(target);
return true;
}
@@ -2212,7 +2208,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (validatedTargetSdk != INVALID_TARGET_SDK_VERSION
&& validatedTargetSdk < Build.VERSION_CODES.Q) {
session.sendPendingUserActionIntent();
session.sendPendingUserActionIntent(target);
return true;
}
@@ -2221,7 +2217,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
session.getInstallerPackageName(), session.getPackageName())) {
// Fall back to the non-silent update if a repeated installation is invoked
// within the throttle time.
session.sendPendingUserActionIntent();
session.sendPendingUserActionIntent(target);
return true;
}
session.mSilentUpdatePolicy.track(session.getInstallerPackageName(),
@@ -2241,7 +2237,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private boolean sendPendingUserActionIntentIfNeeded() {
assertNotChild("PackageInstallerSession#sendPendingUserActionIntentIfNeeded");
return sessionContains(PackageInstallerSession::checkUserActionRequirement);
final IntentSender statusReceiver = getRemoteStatusReceiver();
return sessionContains(s -> checkUserActionRequirement(s, statusReceiver));
}
@WorkerThread
@@ -2439,53 +2436,69 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
});
}
private void install() {
try {
installNonStaged();
} catch (PackageManagerException e) {
final String completeMsg = ExceptionUtils.getCompleteMessage(e);
onSessionInstallationFailure(e.error, completeMsg);
}
/**
* Stages installs and do cleanup accordingly depending on whether the installation is
* successful or not.
*
* @return a future that will be completed when the whole process is completed.
*/
private CompletableFuture<Void> install() {
return installNonStaged().whenComplete((r, t) -> {
if (t == null) {
setSessionApplied();
dispatchSessionFinished(INSTALL_SUCCEEDED, "Session installed", null);
maybeFinishChildSessions(INSTALL_SUCCEEDED, "Session installed");
} else {
PackageManagerException e = (PackageManagerException) t.getCause();
setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED,
PackageManager.installStatusToString(e.error, e.getMessage()));
dispatchSessionFinished(e.error, e.getMessage(), null);
maybeFinishChildSessions(e.error, e.getMessage());
}
});
}
private void installNonStaged()
throws PackageManagerException {
final InstallParams installingSession = makeInstallParams();
if (installingSession == null) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
"Session should contain at least one apk session for installation");
}
if (isMultiPackage()) {
final List<PackageInstallerSession> childSessions = getChildSessions();
List<InstallParams> installingChildSessions = new ArrayList<>(childSessions.size());
boolean success = true;
PackageManagerException failure = null;
for (int i = 0; i < childSessions.size(); ++i) {
final PackageInstallerSession session = childSessions.get(i);
try {
final InstallParams installingChildSession =
session.makeInstallParams();
/**
* Stages sessions (including child sessions if any) for install.
*
* @return a future that will be completed when the whole session is completed (could be
* success or failure).
*/
private CompletableFuture<Void> installNonStaged() {
try {
List<CompletableFuture<Void>> futures = new ArrayList<>();
CompletableFuture<Void> future = new CompletableFuture<>();
futures.add(future);
final InstallParams installingSession = makeInstallParams(future);
if (isMultiPackage()) {
final List<PackageInstallerSession> childSessions = getChildSessions();
List<InstallParams> installingChildSessions = new ArrayList<>(childSessions.size());
for (int i = 0; i < childSessions.size(); ++i) {
final PackageInstallerSession session = childSessions.get(i);
future = new CompletableFuture<>();
futures.add(future);
final InstallParams installingChildSession = session.makeInstallParams(future);
if (installingChildSession != null) {
installingChildSessions.add(installingChildSession);
}
} catch (PackageManagerException e) {
failure = e;
success = false;
}
if (!installingChildSessions.isEmpty()) {
installingSession.installStage(installingChildSessions);
}
} else if (installingSession != null) {
installingSession.installStage();
}
if (!success) {
sendOnPackageInstalled(mContext, getRemoteStatusReceiver(), sessionId,
isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId, null,
failure.error, failure.getLocalizedMessage(), null);
return;
}
installingSession.installStage(installingChildSessions);
} else {
installingSession.installStage();
CompletableFuture<Void>[] arr = new CompletableFuture[futures.size()];
return CompletableFuture.allOf(futures.toArray(arr));
} catch (PackageManagerException e) {
CompletableFuture<Void> future = new CompletableFuture<>();
future.completeExceptionally(e);
return future;
}
}
private void sendPendingUserActionIntent() {
private void sendPendingUserActionIntent(IntentSender target) {
// User needs to confirm installation;
// give installer an intent they can use to involve
// user.
@@ -2493,7 +2506,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
intent.setPackage(mPm.getPackageInstallerPackageName());
intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
sendOnUserActionRequired(mContext, getRemoteStatusReceiver(), sessionId, intent);
sendOnUserActionRequired(mContext, target, sessionId, intent);
// Commit was keeping session marked as active until now; release
// that extra refcount so session appears idle.
@@ -2525,9 +2538,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
/**
* Stages this session for install and returns a
* {@link InstallParams} representing this new staged state.
*
* @param future a future that will be completed when this session is completed.
*/
@Nullable
private InstallParams makeInstallParams()
private InstallParams makeInstallParams(CompletableFuture<Void> future)
throws PackageManagerException {
synchronized (mLock) {
if (mDestroyed) {
@@ -2540,11 +2555,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
// Do not try to install staged apex session. Parent session will have at least one apk
// session.
if (!isMultiPackage() && isApexSession() && params.isStaged) {
dispatchSessionFinished(INSTALL_SUCCEEDED,
"Apex package should have been installed by apexd", null);
if (isMultiPackage()) {
// Always treat parent session as success for it has nothing to install
future.complete(null);
} else if (isApexSession() && params.isStaged) {
// Staged apex sessions have been handled by apexd
future.complete(null);
return null;
}
@@ -2557,12 +2573,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void onPackageInstalled(String basePackageName, int returnCode, String msg,
Bundle extras) {
if (!isStaged()) {
// We've reached point of no return; call into PMS to install the stage.
// Regardless of success or failure we always destroy session.
destroyInternal();
if (returnCode == INSTALL_SUCCEEDED) {
future.complete(null);
} else {
future.completeExceptionally(new PackageManagerException(returnCode, msg));
}
dispatchSessionFinished(returnCode, msg, extras);
}
};

View File

@@ -77,6 +77,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
@@ -131,7 +132,7 @@ public class StagingManager {
void setSessionReady();
void setSessionFailed(@SessionErrorCode int errorCode, String errorMessage);
void setSessionApplied();
void installSession(IntentSender statusReceiver);
CompletableFuture<Void> installSession();
boolean hasParentSessionId();
long getCommittedMillis();
void abandon();
@@ -417,8 +418,6 @@ public class StagingManager {
installApksInSession(session);
t.traceEnd();
Slog.d(TAG, "Marking session " + session.sessionId() + " as applied");
session.setSessionApplied();
if (hasApex) {
if (supportsCheckpoint) {
// Store the session ID, which will be marked as successful by ApexManager upon
@@ -494,24 +493,17 @@ public class StagingManager {
}
}
private void installApksInSession(StagedSession session)
throws PackageManagerException {
if (!session.containsApkSession()) {
return;
}
final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
session.installSession(receiver.getIntentSender());
final Intent result = receiver.getResult();
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status != PackageInstaller.STATUS_SUCCESS) {
final String errorMessage = result.getStringExtra(
PackageInstaller.EXTRA_STATUS_MESSAGE);
Slog.e(TAG, "Failure to install APK staged session "
+ session.sessionId() + " [" + errorMessage + "]");
throw new PackageManagerException(
SessionInfo.SESSION_ACTIVATION_FAILED, errorMessage);
private void installApksInSession(StagedSession session) throws PackageManagerException {
try {
// Blocking wait for installation to complete
session.installSession().get();
} catch (InterruptedException e) {
// Should be impossible
throw new RuntimeException(e);
} catch (ExecutionException ee) {
PackageManagerException e = (PackageManagerException) ee.getCause();
final String errorMsg = PackageManager.installStatusToString(e.error, e.getMessage());
throw new PackageManagerException(SessionInfo.SESSION_ACTIVATION_FAILED, errorMsg);
}
}

View File

@@ -36,7 +36,6 @@ import android.apex.ApexInfo;
import android.apex.ApexSessionInfo;
import android.apex.ApexSessionParams;
import android.content.Context;
import android.content.IntentSender;
import android.content.pm.ApexStagedEvent;
import android.content.pm.IStagedApexObserver;
import android.content.pm.PackageInstaller;
@@ -75,6 +74,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
@Presubmit
@@ -953,7 +953,7 @@ public class StagingManagerTest {
}
@Override
public void installSession(IntentSender statusReceiver) {
public CompletableFuture<Void> installSession() {
throw new UnsupportedOperationException();
}