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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
));
|
||||
setUserSentiment(mSbn.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// CLEAN UP inflation tasks so they don't callback in a future test
|
||||
mEntry.abortTask();
|
||||
}
|
||||
|
||||
// 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 */);
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar.notification.row;
|
||||
|
||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.os.Handler;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.NotificationListenerService.Ranking;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.util.NotificationMessagingUtil;
|
||||
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.NotificationLockscreenUserManager;
|
||||
import com.android.systemui.statusbar.NotificationMediaManager;
|
||||
import com.android.systemui.statusbar.NotificationPresenter;
|
||||
import com.android.systemui.statusbar.NotificationRemoteInputManager;
|
||||
import com.android.systemui.statusbar.SbnBuilder;
|
||||
import com.android.systemui.statusbar.SmartReplyController;
|
||||
import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
|
||||
import com.android.systemui.statusbar.notification.NotificationClicker;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
|
||||
import com.android.systemui.statusbar.notification.NotificationFilter;
|
||||
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
|
||||
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
|
||||
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
|
||||
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.NotificationRowContentBinder.InflationFlag;
|
||||
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
|
||||
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
|
||||
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.HeadsUpManager;
|
||||
import com.android.systemui.statusbar.policy.SmartReplyConstants;
|
||||
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.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
/**
|
||||
* Functional tests for notification inflation from {@link NotificationEntryManager}.
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
public class NotificationEntryManagerInflationTest extends SysuiTestCase {
|
||||
|
||||
private static final String TEST_TITLE = "Title";
|
||||
private static final String TEST_TEXT = "Text";
|
||||
private static final long TIMEOUT_TIME = 10000;
|
||||
private static final Runnable TIMEOUT_RUNNABLE = () -> {
|
||||
throw new RuntimeException("Timed out waiting to inflate");
|
||||
};
|
||||
|
||||
@Mock private NotificationPresenter mPresenter;
|
||||
@Mock private NotificationEntryManager.KeyguardEnvironment mEnvironment;
|
||||
@Mock private NotificationListContainer mListContainer;
|
||||
@Mock private NotificationEntryListener mEntryListener;
|
||||
@Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback;
|
||||
@Mock private HeadsUpManager mHeadsUpManager;
|
||||
@Mock private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
|
||||
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
|
||||
@Mock private NotificationGutsManager mGutsManager;
|
||||
@Mock private NotificationRemoteInputManager mRemoteInputManager;
|
||||
@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 NotificationGroupManager mGroupManager;
|
||||
@Mock private FeatureFlags mFeatureFlags;
|
||||
@Mock private LeakDetector mLeakDetector;
|
||||
|
||||
@Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
|
||||
@Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
|
||||
|
||||
private StatusBarNotification mSbn;
|
||||
private NotificationListenerService.RankingMap mRankingMap;
|
||||
private NotificationEntryManager mEntryManager;
|
||||
private NotificationRowBinderImpl mRowBinder;
|
||||
private Handler mHandler;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mDependency.injectMockDependency(SmartReplyController.class);
|
||||
|
||||
mHandler = Handler.createAsync(TestableLooper.get(this).getLooper());
|
||||
|
||||
Notification notification = new Notification.Builder(mContext)
|
||||
.setSmallIcon(R.drawable.ic_person)
|
||||
.setContentTitle(TEST_TITLE)
|
||||
.setContentText(TEST_TEXT)
|
||||
.build();
|
||||
mSbn = new SbnBuilder()
|
||||
.setNotification(notification)
|
||||
.build();
|
||||
|
||||
when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
|
||||
when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
|
||||
|
||||
mEntryManager = new NotificationEntryManager(
|
||||
mock(NotificationEntryManagerLogger.class),
|
||||
mGroupManager,
|
||||
new NotificationRankingManager(
|
||||
() -> mock(NotificationMediaManager.class),
|
||||
mGroupManager,
|
||||
mHeadsUpManager,
|
||||
mock(NotificationFilter.class),
|
||||
mock(NotificationEntryManagerLogger.class),
|
||||
mock(NotificationSectionsFeatureManager.class),
|
||||
mock(PeopleNotificationIdentifier.class),
|
||||
mock(HighPriorityProvider.class)),
|
||||
mEnvironment,
|
||||
mFeatureFlags,
|
||||
() -> mRowBinder,
|
||||
() -> mRemoteInputManager,
|
||||
mLeakDetector,
|
||||
mock(ForegroundServiceDismissalFeatureController.class)
|
||||
);
|
||||
|
||||
NotifRemoteViewCache cache = new NotifRemoteViewCacheImpl(mEntryManager);
|
||||
NotifBindPipeline pipeline = new NotifBindPipeline(
|
||||
mEntryManager,
|
||||
mock(NotifBindPipelineLogger.class));
|
||||
NotificationContentInflater binder = new NotificationContentInflater(
|
||||
cache,
|
||||
mRemoteInputManager,
|
||||
() -> mock(SmartReplyConstants.class),
|
||||
() -> mock(SmartReplyController.class));
|
||||
RowContentBindStage stage = new RowContentBindStage(
|
||||
binder,
|
||||
mock(NotifInflationErrorManager.class),
|
||||
mock(RowContentBindStageLogger.class));
|
||||
pipeline.setStage(stage);
|
||||
|
||||
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,
|
||||
stage,
|
||||
mock(NotificationLogger.class),
|
||||
mHeadsUpManager,
|
||||
mPresenter,
|
||||
mStatusBarStateController,
|
||||
mEntryManager,
|
||||
mGutsManager,
|
||||
true,
|
||||
null,
|
||||
mFalsingManager
|
||||
));
|
||||
|
||||
when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
|
||||
.thenReturn(mNotificationRowComponentBuilder);
|
||||
when(mNotificationRowComponentBuilder.build()).thenReturn(
|
||||
() -> mActivatableNotificationViewController);
|
||||
|
||||
mRowBinder = new NotificationRowBinderImpl(
|
||||
mContext,
|
||||
new NotificationMessagingUtil(mContext),
|
||||
mRemoteInputManager,
|
||||
mLockscreenUserManager,
|
||||
pipeline,
|
||||
stage,
|
||||
true, /* allowLongPress */
|
||||
mock(KeyguardBypassController.class),
|
||||
mock(StatusBarStateController.class),
|
||||
mGroupManager,
|
||||
mGutsManager,
|
||||
mNotificationInterruptionStateProvider,
|
||||
RowInflaterTask::new,
|
||||
mExpandableNotificationRowComponentBuilder);
|
||||
|
||||
mEntryManager.setUpWithPresenter(mPresenter);
|
||||
mEntryManager.addNotificationEntryListener(mEntryListener);
|
||||
|
||||
mRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback);
|
||||
mRowBinder.setInflationCallback(mEntryManager);
|
||||
mRowBinder.setNotificationClicker(mock(NotificationClicker.class));
|
||||
|
||||
Ranking ranking = new Ranking();
|
||||
ranking.populate(
|
||||
mSbn.getKey(),
|
||||
0,
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
IMPORTANCE_DEFAULT,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
Ranking.USER_SENTIMENT_NEUTRAL,
|
||||
false,
|
||||
-1,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
mRankingMap = new NotificationListenerService.RankingMap(new Ranking[] {ranking});
|
||||
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
// Don't leave anything on main thread
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddNotification() {
|
||||
// WHEN a notification is added
|
||||
mEntryManager.addNotification(mSbn, mRankingMap);
|
||||
ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
|
||||
NotificationEntry.class);
|
||||
verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
|
||||
NotificationEntry entry = entryCaptor.getValue();
|
||||
|
||||
// Wait for inflation
|
||||
// row inflation, system notification, remote views, contracted view
|
||||
waitForMessages(4);
|
||||
|
||||
// THEN the notification has its row inflated
|
||||
assertNotNull(entry.getRow());
|
||||
assertNotNull(entry.getRow().getPrivateLayout().getContractedChild());
|
||||
|
||||
// THEN inflation callbacks are called
|
||||
verify(mBindCallback).onBindRow(eq(entry), any(), eq(mSbn), any());
|
||||
verify(mEntryListener, never()).onInflationError(any(), any());
|
||||
verify(mEntryListener).onEntryInflated(entry);
|
||||
verify(mEntryListener).onNotificationAdded(entry);
|
||||
|
||||
// THEN the notification is active
|
||||
assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
|
||||
|
||||
// THEN we update the presenter
|
||||
verify(mPresenter).updateNotificationViews();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateNotification() {
|
||||
// GIVEN a notification already added
|
||||
mEntryManager.addNotification(mSbn, mRankingMap);
|
||||
ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
|
||||
NotificationEntry.class);
|
||||
verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
|
||||
NotificationEntry entry = entryCaptor.getValue();
|
||||
waitForMessages(4);
|
||||
|
||||
Mockito.reset(mEntryListener);
|
||||
Mockito.reset(mPresenter);
|
||||
|
||||
// WHEN the notification is updated
|
||||
mEntryManager.updateNotification(mSbn, mRankingMap);
|
||||
|
||||
// Wait for inflation
|
||||
// remote views, contracted view
|
||||
waitForMessages(2);
|
||||
|
||||
// THEN the notification has its row and inflated
|
||||
assertNotNull(entry.getRow());
|
||||
|
||||
// THEN inflation callbacks are called
|
||||
verify(mEntryListener, never()).onInflationError(any(), any());
|
||||
verify(mEntryListener).onEntryReinflated(entry);
|
||||
|
||||
// THEN we update the presenter
|
||||
verify(mPresenter).updateNotificationViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a certain number of messages to finish before continuing, timing out if they never
|
||||
* occur.
|
||||
*
|
||||
* As part of the inflation pipeline, the main thread is forced to deal with several callbacks
|
||||
* due to the nature of the API used (generally because they're {@link android.os.AsyncTask}
|
||||
* callbacks). In order, these are
|
||||
*
|
||||
* 1) Callback after row inflation. See {@link RowInflaterTask}.
|
||||
* 2) Callback checking if row is system notification. See
|
||||
* {@link ExpandableNotificationRow#setEntry}
|
||||
* 3) Callback after remote views are created. See
|
||||
* {@link NotificationContentInflater.AsyncInflationTask}.
|
||||
* 4-6) Callback after each content view is inflated/rebound from remote view. See
|
||||
* {@link NotificationContentInflater#applyRemoteView} and {@link InflationFlag}.
|
||||
*
|
||||
* Depending on the test, only some of these will be necessary. For example, generally, not
|
||||
* every content view is inflated or the row may not be inflated if one already exists.
|
||||
*
|
||||
* Currently, the burden is on the developer to figure these out until we have a much more
|
||||
* test-friendly way of executing inflation logic (i.e. pass in an executor).
|
||||
*/
|
||||
private void waitForMessages(int numMessages) {
|
||||
mHandler.postDelayed(TIMEOUT_RUNNABLE, TIMEOUT_TIME);
|
||||
TestableLooper.get(this).processMessages(numMessages);
|
||||
mHandler.removeCallbacks(TIMEOUT_RUNNABLE);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user