Notify StagedApexObservers when there is a change in set of staged APEX am: fe58e6f7d1

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15638981

Change-Id: I6adf21dcaca6f1baf6fe172d44204d6fd3205468
This commit is contained in:
Samiul Islam
2021-08-26 22:41:35 +00:00
committed by Automerger Merge Worker
2 changed files with 156 additions and 12 deletions

View File

@@ -29,7 +29,9 @@ import android.content.IIntentSender;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.IntentSender; import android.content.IntentSender;
import android.content.pm.ApexStagedEvent;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.IStagedApexObserver;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionInfo;
@@ -102,7 +104,8 @@ public class StagingManager {
private final ApexManager mApexManager; private final ApexManager mApexManager;
private final PowerManager mPowerManager; private final PowerManager mPowerManager;
private final Context mContext; private final Context mContext;
private final PreRebootVerificationHandler mPreRebootVerificationHandler; @VisibleForTesting
final PreRebootVerificationHandler mPreRebootVerificationHandler;
private final Supplier<PackageParser2> mPackageParserSupplier; private final Supplier<PackageParser2> mPackageParserSupplier;
private final File mFailureReasonFile = new File("/metadata/staged-install/failure_reason.txt"); private final File mFailureReasonFile = new File("/metadata/staged-install/failure_reason.txt");
@@ -118,6 +121,9 @@ public class StagingManager {
@GuardedBy("mSuccessfulStagedSessionIds") @GuardedBy("mSuccessfulStagedSessionIds")
private final List<Integer> mSuccessfulStagedSessionIds = new ArrayList<>(); private final List<Integer> mSuccessfulStagedSessionIds = new ArrayList<>();
@GuardedBy("mStagedApexObservers")
private final List<IStagedApexObserver> mStagedApexObservers = new ArrayList<>();
interface StagedSession { interface StagedSession {
boolean isMultiPackage(); boolean isMultiPackage();
boolean isApexSession(); boolean isApexSession();
@@ -202,6 +208,18 @@ public class StagingManager {
mApexManager.markBootCompleted(); mApexManager.markBootCompleted();
} }
void registerStagedApexObserver(IStagedApexObserver observer) {
synchronized (mStagedApexObservers) {
mStagedApexObservers.add(observer);
}
}
void unregisterStagedApexObserver(IStagedApexObserver observer) {
synchronized (mStagedApexObservers) {
mStagedApexObservers.remove(observer);
}
}
/** /**
* Validates the signature used to sign the container of the new apex package * Validates the signature used to sign the container of the new apex package
* *
@@ -840,6 +858,9 @@ public class StagingManager {
// Also, cleaning up the stageDir prevents the apex from being activated. // Also, cleaning up the stageDir prevents the apex from being activated.
Slog.e(TAG, "Failed to abort apex session " + session.sessionId()); Slog.e(TAG, "Failed to abort apex session " + session.sessionId());
} }
if (session.containsApexSession()) {
notifyStagedApexObservers();
}
} }
// Session was successfully aborted from apexd (if required) and pre-reboot verification // Session was successfully aborted from apexd (if required) and pre-reboot verification
@@ -1218,7 +1239,22 @@ public class StagingManager {
return null; return null;
} }
private final class PreRebootVerificationHandler extends Handler { private void notifyStagedApexObservers() {
synchronized (mStagedApexObservers) {
for (IStagedApexObserver observer : mStagedApexObservers) {
ApexStagedEvent event = new ApexStagedEvent();
event.stagedApexModuleNames = getStagedApexModuleNames().toArray(new String[0]);
try {
observer.onApexStaged(event);
} catch (RemoteException re) {
Slog.w(TAG, "Failed to contact the observer " + re.getMessage());
}
}
}
}
@VisibleForTesting
final class PreRebootVerificationHandler extends Handler {
// Hold sessions before handler gets ready to do the verification. // Hold sessions before handler gets ready to do the verification.
private List<StagedSession> mPendingSessions; private List<StagedSession> mPendingSessions;
private boolean mIsReady; private boolean mIsReady;
@@ -1244,7 +1280,8 @@ public class StagingManager {
private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1; private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1;
private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2; private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2;
private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3; private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3;
private static final int MSG_PRE_REBOOT_VERIFICATION_END = 4; @VisibleForTesting
static final int MSG_PRE_REBOOT_VERIFICATION_END = 4;
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
@@ -1486,6 +1523,7 @@ public class StagingManager {
if (hasApex) { if (hasApex) {
try { try {
mApexManager.markStagedSessionReady(session.sessionId()); mApexManager.markStagedSessionReady(session.sessionId());
notifyStagedApexObservers();
} catch (PackageManagerException e) { } catch (PackageManagerException e) {
session.setSessionFailed(e.error, e.getMessage()); session.setSessionFailed(e.error, e.getMessage());
return; return;

View File

@@ -35,9 +35,12 @@ import android.apex.ApexSessionInfo;
import android.apex.ApexSessionParams; import android.apex.ApexSessionParams;
import android.content.Context; import android.content.Context;
import android.content.IntentSender; import android.content.IntentSender;
import android.content.pm.ApexStagedEvent;
import android.content.pm.IStagedApexObserver;
import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode; import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode;
import android.os.Message;
import android.os.SystemProperties; import android.os.SystemProperties;
import android.os.storage.IStorageManager; import android.os.storage.IStorageManager;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
@@ -58,6 +61,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4; import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession; import org.mockito.MockitoSession;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
@@ -647,8 +651,16 @@ public class StagingManagerTest {
parentSession.setSessionReady(); parentSession.setSessionReady();
mStagingManager.createSession(parentSession); mStagingManager.createSession(parentSession);
mockApexManagerGetStagedApexInfoWithSessionId();
List<String> result = mStagingManager.getStagedApexModuleNames();
assertThat(result).containsExactly("239", "123", "124");
verify(mApexManager, times(2)).getStagedApexInfos(any());
}
// Make mApexManager return ApexInfo with same module name as the sessionId // Make mApexManager return ApexInfo with same module name as the sessionId
// of the parameter that was passed into it // of the parameter that was passed into it
private void mockApexManagerGetStagedApexInfoWithSessionId() {
when(mApexManager.getStagedApexInfos(any())).thenAnswer(new Answer<ApexInfo[]>() { when(mApexManager.getStagedApexInfos(any())).thenAnswer(new Answer<ApexInfo[]>() {
@Override @Override
public ApexInfo[] answer(InvocationOnMock invocation) throws Throwable { public ApexInfo[] answer(InvocationOnMock invocation) throws Throwable {
@@ -669,10 +681,6 @@ public class StagingManagerTest {
return result.toArray(new ApexInfo[0]); return result.toArray(new ApexInfo[0]);
} }
}); });
List<String> result = mStagingManager.getStagedApexModuleNames();
assertThat(result).containsExactly("239", "123", "124");
verify(mApexManager, times(2)).getStagedApexInfos(any());
} }
@Test @Test
@@ -694,6 +702,106 @@ public class StagingManagerTest {
verify(mApexManager, times(2)).getStagedApexInfos(any()); verify(mApexManager, times(2)).getStagedApexInfos(any());
} }
@Test
public void registeredStagedApexObserverIsNotifiedOnPreRebootVerificationCompletion()
throws Exception {
// Register observer
IStagedApexObserver observer = Mockito.mock(IStagedApexObserver.class);
mStagingManager.registerStagedApexObserver(observer);
// Create one staged session and trigger end of pre-reboot verification
{
FakeStagedSession session = new FakeStagedSession(239);
session.setIsApex(true);
mStagingManager.createSession(session);
mockApexManagerGetStagedApexInfoWithSessionId();
triggerEndOfPreRebootVerification(session);
assertThat(session.isSessionReady()).isTrue();
ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass(
ApexStagedEvent.class);
verify(observer, times(1)).onApexStaged(argumentCaptor.capture());
assertThat(argumentCaptor.getValue().stagedApexModuleNames).isEqualTo(
new String[]{"239"});
}
// Create another staged session and verify observers are notified of union
{
Mockito.clearInvocations(observer);
FakeStagedSession session = new FakeStagedSession(240);
session.setIsApex(true);
mStagingManager.createSession(session);
triggerEndOfPreRebootVerification(session);
assertThat(session.isSessionReady()).isTrue();
ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass(
ApexStagedEvent.class);
verify(observer, times(1)).onApexStaged(argumentCaptor.capture());
assertThat(argumentCaptor.getValue().stagedApexModuleNames).isEqualTo(
new String[]{"239", "240"});
}
// Finally, verify that once unregistered, observer is not notified
mStagingManager.unregisterStagedApexObserver(observer);
{
Mockito.clearInvocations(observer);
FakeStagedSession session = new FakeStagedSession(241);
session.setIsApex(true);
mStagingManager.createSession(session);
triggerEndOfPreRebootVerification(session);
assertThat(session.isSessionReady()).isTrue();
verify(observer, never()).onApexStaged(any());
}
}
@Test
public void registeredStagedApexObserverIsNotifiedOnSessionAbandon() throws Exception {
// Register observer
IStagedApexObserver observer = Mockito.mock(IStagedApexObserver.class);
mStagingManager.registerStagedApexObserver(observer);
// Create a ready session and abandon it
FakeStagedSession session = new FakeStagedSession(239);
session.setIsApex(true);
session.setSessionReady();
session.setDestroyed(true);
mStagingManager.createSession(session);
mStagingManager.abortCommittedSession(session);
assertThat(session.isSessionReady()).isTrue();
ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass(
ApexStagedEvent.class);
verify(observer, times(1)).onApexStaged(argumentCaptor.capture());
assertThat(argumentCaptor.getValue().stagedApexModuleNames).hasLength(0);
}
@Test
public void stagedApexObserverIsOnlyCalledForApexSessions() throws Exception {
IStagedApexObserver observer = Mockito.mock(IStagedApexObserver.class);
mStagingManager.registerStagedApexObserver(observer);
// Trigger end of pre-reboot verification
FakeStagedSession session = new FakeStagedSession(239);
mStagingManager.createSession(session);
triggerEndOfPreRebootVerification(session);
assertThat(session.isSessionReady()).isTrue();
verify(observer, never()).onApexStaged(any());
}
private void triggerEndOfPreRebootVerification(StagingManager.StagedSession session) {
StagingManager.PreRebootVerificationHandler handler =
mStagingManager.mPreRebootVerificationHandler;
Message msg = handler.obtainMessage(
handler.MSG_PRE_REBOOT_VERIFICATION_END, session.sessionId(), -1, session);
handler.handleMessage(msg);
}
private StagingManager.StagedSession createSession(int sessionId, String packageName, private StagingManager.StagedSession createSession(int sessionId, String packageName,
long committedMillis) { long committedMillis) {
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
@@ -956,9 +1064,7 @@ public class StagingManagerTest {
} }
@Override @Override
public void notifyEndPreRebootVerification() { public void notifyEndPreRebootVerification() {}
throw new UnsupportedOperationException();
}
@Override @Override
public void verifySession() { public void verifySession() {