diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index ce40d95189ceb..ff7c54a8d5796 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -29,7 +29,9 @@ import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.pm.ApexStagedEvent; import android.content.pm.ApplicationInfo; +import android.content.pm.IStagedApexObserver; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; @@ -102,7 +104,8 @@ public class StagingManager { private final ApexManager mApexManager; private final PowerManager mPowerManager; private final Context mContext; - private final PreRebootVerificationHandler mPreRebootVerificationHandler; + @VisibleForTesting + final PreRebootVerificationHandler mPreRebootVerificationHandler; private final Supplier mPackageParserSupplier; private final File mFailureReasonFile = new File("/metadata/staged-install/failure_reason.txt"); @@ -118,6 +121,9 @@ public class StagingManager { @GuardedBy("mSuccessfulStagedSessionIds") private final List mSuccessfulStagedSessionIds = new ArrayList<>(); + @GuardedBy("mStagedApexObservers") + private final List mStagedApexObservers = new ArrayList<>(); + interface StagedSession { boolean isMultiPackage(); boolean isApexSession(); @@ -202,6 +208,18 @@ public class StagingManager { 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 * @@ -840,6 +858,9 @@ public class StagingManager { // Also, cleaning up the stageDir prevents the apex from being activated. 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 @@ -1218,7 +1239,22 @@ public class StagingManager { 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. private List mPendingSessions; 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_APEX = 2; 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 public void handleMessage(Message msg) { @@ -1486,6 +1523,7 @@ public class StagingManager { if (hasApex) { try { mApexManager.markStagedSessionReady(session.sessionId()); + notifyStagedApexObservers(); } catch (PackageManagerException e) { session.setSessionFailed(e.error, e.getMessage()); return; diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java index 8dca89d76f20d..c68294f1fa803 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java @@ -35,9 +35,12 @@ 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; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode; +import android.os.Message; import android.os.SystemProperties; import android.os.storage.IStorageManager; import android.platform.test.annotations.Presubmit; @@ -58,6 +61,7 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; import org.mockito.invocation.InvocationOnMock; @@ -647,8 +651,16 @@ public class StagingManagerTest { parentSession.setSessionReady(); mStagingManager.createSession(parentSession); - // Make mApexManager return ApexInfo with same module name as the sessionId - // of the parameter that was passed into it + mockApexManagerGetStagedApexInfoWithSessionId(); + + List 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 + // of the parameter that was passed into it + private void mockApexManagerGetStagedApexInfoWithSessionId() { when(mApexManager.getStagedApexInfos(any())).thenAnswer(new Answer() { @Override public ApexInfo[] answer(InvocationOnMock invocation) throws Throwable { @@ -669,10 +681,6 @@ public class StagingManagerTest { return result.toArray(new ApexInfo[0]); } }); - - List result = mStagingManager.getStagedApexModuleNames(); - assertThat(result).containsExactly("239", "123", "124"); - verify(mApexManager, times(2)).getStagedApexInfos(any()); } @Test @@ -694,6 +702,106 @@ public class StagingManagerTest { 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 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 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 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, long committedMillis) { PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( @@ -956,9 +1064,7 @@ public class StagingManagerTest { } @Override - public void notifyEndPreRebootVerification() { - throw new UnsupportedOperationException(); - } + public void notifyEndPreRebootVerification() {} @Override public void verifySession() {