Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fe58e6f7 authored by Samiul Islam's avatar Samiul Islam
Browse files

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

The set of staged APEX is changed whenever:
- a session containing APEX succesfully passes pre-reboot verification
  and gets marked ready
- a staged session gets abandoned

Bug: 187444679
Test: atest StagingManagerTest
Change-Id: I5c5bb4523cdab46e22fe3c8e2373289d8619c10e
Merged-In: I5c5bb4523cdab46e22fe3c8e2373289d8619c10e
(cherry picked from commit 9e7de2a0)
parent 9f80efac
Loading
Loading
Loading
Loading
+41 −3
Original line number Diff line number Diff line
@@ -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<PackageParser2> 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<Integer> mSuccessfulStagedSessionIds = new ArrayList<>();

    @GuardedBy("mStagedApexObservers")
    private final List<IStagedApexObserver> 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<StagedSession> 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;
+115 −9
Original line number Diff line number Diff line
@@ -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);

        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
    // of the parameter that was passed into it
    private void mockApexManagerGetStagedApexInfoWithSessionId() {
        when(mApexManager.getStagedApexInfos(any())).thenAnswer(new Answer<ApexInfo[]>() {
            @Override
            public ApexInfo[] answer(InvocationOnMock invocation) throws Throwable {
@@ -669,10 +681,6 @@ public class StagingManagerTest {
                return result.toArray(new ApexInfo[0]);
            }
        });

        List<String> 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<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,
            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() {