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

Commit 225b7123 authored by Kevin Han's avatar Kevin Han
Browse files

Move inflation-related NEM tests to their own file

In a gambit to improve organization and make other improvements to
NotificationEntryManagerTest, this CL moves the inflation-related tests
to a separate file, leaving more focused unit tests in
NotificationEntryManagerTest.

In the process of doing this, I've also tried to make net positive
changes to how the inflation test works. The summary of changes below.

The Good
* We no longer modify NotificationContentInflater behavior for tests by
telling it to enter "synchronous" mode which probably means we no
longer have to maintain a test-specific path of logic in that class
* We no longer depend on a countdown latch attached as a listener to
know when inflation is done, so listeners breaking shouldn't break all
the inflation-related tests.
* We no longer have the possibility of the test blocking forever from a
TestableLooper#processMessages waiting for a message forever (we just
throw a runtime exception now if it times out)
* Hopefully less flaky since we have tighter control/knowledge on what
messages we produce/wait on.

The Bad
* We now have to use the main thread looper. This is because using
NotificationContentInflater "for real" uses a lot of AsyncTasks which
HAVE to post back to main thread. Since we split the tests though, this
is only needed for inflation-related tests which arguably should be on
main thread.

The Ugly
* The test is more fragile for async/main-thread-related changes as we
depend on knowing the exact number of main thread callbacks to know when
inflation is done. I've tried to mitigate this as much as possible by
having very explicit documentation, but this is clearly less than ideal.

Test: atest SystemUITests
Change-Id: Iff5413cc48167e182f9fdfa0606790c0d4071154
parent 5263c11a
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -482,6 +482,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
     * once per notification as the packageInfo can't technically change for a notification row.
     */
    private void cacheIsSystemNotification() {
        //TODO: This probably shouldn't be in ExpandableNotificationRow
        if (mEntry != null && mEntry.mIsSystemNotification == null) {
            if (mSystemNotificationAsyncTask.getStatus() == AsyncTask.Status.PENDING) {
                // Run async task once, only if it hasn't already been executed. Note this is
+17 −181
Original line number Diff line number Diff line
@@ -52,73 +52,55 @@ import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArraySet;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;

import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import com.android.systemui.statusbar.notification.row.NotificationEntryManagerInflationTest;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.util.time.FakeSystemClock;

import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * Unit tests for {@link NotificationEntryManager}. This test will not test any interactions with
 * inflation. Instead, for functional inflation tests, see
 * {@link NotificationEntryManagerInflationTest}.
 */
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper()
@@ -129,17 +111,11 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
    @Mock private NotificationPresenter mPresenter;
    @Mock private KeyguardEnvironment mEnvironment;
    @Mock private ExpandableNotificationRow mRow;
    @Mock private NotificationListContainer mListContainer;
    @Mock private NotificationEntryListener mEntryListener;
    @Mock private NotificationRemoveInterceptor mRemoveInterceptor;
    @Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback;
    @Mock private HeadsUpManager mHeadsUpManager;
    @Mock private RankingMap mRankingMap;
    @Mock private RemoteInputController mRemoteInputController;
    @Mock private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
    @Mock private NotificationGroupManager mGroupManager;
    @Mock private NotificationGutsManager mGutsManager;
    @Mock private NotificationRemoteInputManager mRemoteInputManager;
    @Mock private DeviceProvisionedController mDeviceProvisionedController;
    @Mock private RowInflaterTask mAsyncInflationTask;
@@ -147,18 +123,12 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
    @Mock private FeatureFlags mFeatureFlags;
    @Mock private LeakDetector mLeakDetector;
    @Mock private NotificationMediaManager mNotificationMediaManager;
    @Mock private ExpandableNotificationRowComponent.Builder
            mExpandableNotificationRowComponentBuilder;
    @Mock private ExpandableNotificationRowComponent mExpandableNotificationRowComponent;
    @Mock private FalsingManager mFalsingManager;
    @Mock private KeyguardBypassController mKeyguardBypassController;
    @Mock private StatusBarStateController mStatusBarStateController;
    @Mock private NotificationRowBinder mNotificationRowBinder;

    private int mId;
    private NotificationEntry mEntry;
    private StatusBarNotification mSbn;
    private TestableNotificationEntryManager mEntryManager;
    private CountDownLatch mCountDownLatch;
    private NotificationEntryManager mEntryManager;

    private void setUserSentiment(String key, int sentiment) {
        doAnswer(invocationOnMock -> {
@@ -202,41 +172,16 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
        MockitoAnnotations.initMocks(this);
        mDependency.injectMockDependency(SmartReplyController.class);

        mCountDownLatch = new CountDownLatch(1);

        allowTestableLooperAsMainThread();
        mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                Handler.createAsync(TestableLooper.get(this).getLooper()));
        when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
        when(mListContainer.getViewParentForNotification(any())).thenReturn(
                new FrameLayout(mContext));

        mEntry = createNotification();
        mSbn = mEntry.getSbn();

        mEntry.expandedIcon = mock(StatusBarIconView.class);

        RowContentBindStage bindStage = mock(RowContentBindStage.class);
        when(bindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
        NotificationRowBinderImpl notificationRowBinder =
                new NotificationRowBinderImpl(mContext,
                        new NotificationMessagingUtil(mContext),
                        mRemoteInputManager,
                        mLockscreenUserManager,
                        mock(NotifBindPipeline.class),
                        bindStage,
                        true, /* allowLongPress */
                        mKeyguardBypassController,
                        mStatusBarStateController,
                        mGroupManager,
                        mGutsManager,
                        mNotificationInterruptionStateProvider,
                        RowInflaterTask::new,
                        mExpandableNotificationRowComponentBuilder);

        when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
        when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
        mEntryManager = new TestableNotificationEntryManager(
        mEntryManager = new NotificationEntryManager(
                mLogger,
                mGroupManager,
                new NotificationRankingManager(
@@ -250,7 +195,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
                        mock(HighPriorityProvider.class)),
                mEnvironment,
                mFeatureFlags,
                () -> notificationRowBinder,
                () -> mNotificationRowBinder,
                () -> mRemoteInputManager,
                mLeakDetector,
                mock(ForegroundServiceDismissalFeatureController.class)
@@ -259,152 +204,45 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
        mEntryManager.addNotificationEntryListener(mEntryListener);
        mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor);

        notificationRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback);
        notificationRowBinder.setInflationCallback(mEntryManager);
        notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class));

        setUserSentiment(
                mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);

        ArgumentCaptor<ExpandableNotificationRow> viewCaptor =
                ArgumentCaptor.forClass(ExpandableNotificationRow.class);
        when(mExpandableNotificationRowComponentBuilder
                .expandableNotificationRow(viewCaptor.capture()))
                .thenReturn(mExpandableNotificationRowComponentBuilder);
        when(mExpandableNotificationRowComponentBuilder
                .notificationEntry(any()))
                .thenReturn(mExpandableNotificationRowComponentBuilder);
        when(mExpandableNotificationRowComponentBuilder
                .onDismissRunnable(any()))
                .thenReturn(mExpandableNotificationRowComponentBuilder);
        when(mExpandableNotificationRowComponentBuilder
                .inflationCallback(any()))
                .thenReturn(mExpandableNotificationRowComponentBuilder);
        when(mExpandableNotificationRowComponentBuilder
                .rowContentBindStage(any()))
                .thenReturn(mExpandableNotificationRowComponentBuilder);
        when(mExpandableNotificationRowComponentBuilder
                .onExpandClickListener(any()))
                .thenReturn(mExpandableNotificationRowComponentBuilder);

        when(mExpandableNotificationRowComponentBuilder.build())
                .thenReturn(mExpandableNotificationRowComponent);
        when(mExpandableNotificationRowComponent.getExpandableNotificationRowController())
                .thenAnswer((Answer<ExpandableNotificationRowController>) invocation ->
                        new ExpandableNotificationRowController(
                                viewCaptor.getValue(),
                                mock(ActivatableNotificationViewController.class),
                                mNotificationMediaManager,
                                mock(PluginManager.class),
                                new FakeSystemClock(),
                                "FOOBAR", "FOOBAR",
                                mKeyguardBypassController,
                                mGroupManager,
                                bindStage,
                                mock(NotificationLogger.class),
                                mHeadsUpManager,
                                mPresenter,
                                mStatusBarStateController,
                                mEntryManager,
                                mGutsManager,
                                true,
                                null,
                                mFalsingManager
                        ));
    }

    @After
    public void tearDown() {
        // CLEAN UP inflation tasks so they don't callback in a future test
        mEntry.abortTask();
        setUserSentiment(mSbn.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);
    }

    // TODO: These tests are closer to functional tests and we should move them to their own file.
    // and also strip some of the verifies that make the test too complex
    @Test
    @Ignore
    public void testAddNotification() throws Exception {
        TestableLooper.get(this).processAllMessages();

        doAnswer(invocation -> {
            mCountDownLatch.countDown();
            return null;
        }).when(mBindCallback).onBindRow(any(), any(), any(), any());

        // Post on main thread, otherwise we will be stuck waiting here for the inflation finished
        // callback forever, since it won't execute until the tests ends.
    public void testAddNotification_setsUserSentiment() {
        mEntryManager.addNotification(mSbn, mRankingMap);
        TestableLooper.get(this).processMessages(1);
        assertTrue(mCountDownLatch.await(10, TimeUnit.SECONDS));
        assertTrue(mEntryManager.getCountDownLatch().await(10, TimeUnit.SECONDS));

        // Check that no inflation error occurred.
        verify(mEntryListener, never()).onInflationError(any(), any());

        // Row inflation:
        ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
                NotificationEntry.class);
        verify(mBindCallback).onBindRow(entryCaptor.capture(), any(), eq(mSbn), any());
        verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
        NotificationEntry entry = entryCaptor.getValue();
        verify(mRemoteInputManager).bindRow(entry.getRow());

        // Row content inflation:
        verify(mEntryListener).onNotificationAdded(entry);
        verify(mPresenter).updateNotificationViews();

        assertEquals(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()), entry);
        assertNotNull(entry.getRow());
        assertEquals(mEntry.getUserSentiment(),
                Ranking.USER_SENTIMENT_NEUTRAL);
        assertEquals(entry.getUserSentiment(), Ranking.USER_SENTIMENT_NEUTRAL);
    }

    @Test
    @Ignore
    public void testUpdateNotification() throws Exception {
        TestableLooper.get(this).processAllMessages();

    public void testUpdateNotification_updatesUserSentiment() {
        mEntryManager.addActiveNotificationForTest(mEntry);

        setUserSentiment(
                mEntry.getKey(), Ranking.USER_SENTIMENT_NEGATIVE);

        mEntryManager.updateNotification(mSbn, mRankingMap);
        TestableLooper.get(this).processMessages(1);
        // Wait for content update.
        assertTrue(mEntryManager.getCountDownLatch().await(10, TimeUnit.SECONDS));

        verify(mEntryListener, never()).onInflationError(any(), any());

        verify(mEntryListener).onPreEntryUpdated(mEntry);
        verify(mPresenter).updateNotificationViews();
        verify(mEntryListener).onPostEntryUpdated(mEntry);

        assertNotNull(mEntry.getRow());
        assertEquals(Ranking.USER_SENTIMENT_NEGATIVE,
                mEntry.getUserSentiment());
        assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, mEntry.getUserSentiment());
    }

    @Test
    @Ignore
    public void testUpdateNotification_prePostEntryOrder() throws Exception {
        TestableLooper.get(this).processAllMessages();

        mEntryManager.addActiveNotificationForTest(mEntry);

        mEntryManager.updateNotification(mSbn, mRankingMap);
        TestableLooper.get(this).processMessages(1);
        // Wait for content update.
        assertTrue(mEntryManager.getCountDownLatch().await(10, TimeUnit.SECONDS));

        verify(mEntryListener, never()).onInflationError(any(), any());

        // Ensure that update callbacks happen in correct order
        InOrder order = inOrder(mEntryListener, mPresenter, mEntryListener);
        order.verify(mEntryListener).onPreEntryUpdated(mEntry);
        order.verify(mPresenter).updateNotificationViews();
        order.verify(mEntryListener).onPostEntryUpdated(mEntry);

        assertNotNull(mEntry.getRow());
    }

    @Test
@@ -414,8 +252,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase {

        mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);

        verify(mEntryListener, never()).onInflationError(any(), any());

        verify(mPresenter).updateNotificationViews();
        verify(mEntryListener).onEntryRemoved(
                eq(mEntry), any(), eq(false) /* removedByUser */);
+385 −0

File added.

Preview size limit exceeded, changes collapsed.