1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar;
18 
19 import static junit.framework.Assert.assertEquals;
20 import static junit.framework.Assert.assertFalse;
21 import static junit.framework.Assert.assertTrue;
22 
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.verify;
25 import static org.mockito.Mockito.when;
26 
27 import android.app.Notification;
28 import android.content.Context;
29 import android.os.Handler;
30 import android.os.Looper;
31 import android.os.SystemClock;
32 import android.os.UserHandle;
33 import android.service.notification.NotificationListenerService;
34 import android.testing.AndroidTestingRunner;
35 import android.testing.TestableLooper;
36 
37 import androidx.annotation.NonNull;
38 import androidx.test.filters.SmallTest;
39 
40 import com.android.systemui.SysuiTestCase;
41 import com.android.systemui.dump.DumpManager;
42 import com.android.systemui.flags.FeatureFlags;
43 import com.android.systemui.plugins.statusbar.StatusBarStateController;
44 import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.RemoteInputActiveExtender;
45 import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.RemoteInputHistoryExtender;
46 import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.SmartReplyHistoryExtender;
47 import com.android.systemui.statusbar.notification.NotificationEntryManager;
48 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
49 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
50 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
51 import com.android.systemui.statusbar.phone.StatusBar;
52 import com.android.systemui.statusbar.policy.RemoteInputUriController;
53 
54 import com.google.android.collect.Sets;
55 
56 import org.junit.Before;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 import org.mockito.Mock;
60 import org.mockito.MockitoAnnotations;
61 
62 import java.util.Optional;
63 
64 import dagger.Lazy;
65 
66 @SmallTest
67 @RunWith(AndroidTestingRunner.class)
68 @TestableLooper.RunWithLooper
69 public class NotificationRemoteInputManagerTest extends SysuiTestCase {
70     private static final String TEST_PACKAGE_NAME = "test";
71     private static final int TEST_UID = 0;
72 
73     @Mock private NotificationPresenter mPresenter;
74     @Mock private RemoteInputController.Delegate mDelegate;
75     @Mock private NotificationRemoteInputManager.Callback mCallback;
76     @Mock private RemoteInputController mController;
77     @Mock private SmartReplyController mSmartReplyController;
78     @Mock private NotificationListenerService.RankingMap mRanking;
79     @Mock private ExpandableNotificationRow mRow;
80     @Mock private StatusBarStateController mStateController;
81     @Mock private RemoteInputUriController mRemoteInputUriController;
82     @Mock private NotificationClickNotifier mClickNotifier;
83 
84     // Dependency mocks:
85     @Mock private NotificationEntryManager mEntryManager;
86     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
87 
88     private TestableNotificationRemoteInputManager mRemoteInputManager;
89     private NotificationEntry mEntry;
90     private RemoteInputHistoryExtender mRemoteInputHistoryExtender;
91     private SmartReplyHistoryExtender mSmartReplyHistoryExtender;
92     private RemoteInputActiveExtender mRemoteInputActiveExtender;
93     private TestableNotificationRemoteInputManager.FakeLegacyRemoteInputLifetimeExtender
94             mLegacyRemoteInputLifetimeExtender;
95 
96     @Before
setUp()97     public void setUp() {
98         MockitoAnnotations.initMocks(this);
99 
100         mRemoteInputManager = new TestableNotificationRemoteInputManager(mContext,
101                 mock(FeatureFlags.class),
102                 mLockscreenUserManager,
103                 mSmartReplyController,
104                 mEntryManager,
105                 mock(RemoteInputNotificationRebuilder.class),
106                 () -> Optional.of(mock(StatusBar.class)),
107                 mStateController,
108                 Handler.createAsync(Looper.myLooper()),
109                 mRemoteInputUriController,
110                 mClickNotifier,
111                 mock(ActionClickLogger.class),
112                 mock(DumpManager.class));
113         mEntry = new NotificationEntryBuilder()
114                 .setPkg(TEST_PACKAGE_NAME)
115                 .setOpPkg(TEST_PACKAGE_NAME)
116                 .setUid(TEST_UID)
117                 .setNotification(new Notification())
118                 .setUser(UserHandle.CURRENT)
119                 .build();
120         mEntry.setRow(mRow);
121 
122         mRemoteInputManager.setUpWithPresenterForTest(mCallback,
123                 mDelegate, mController);
124         for (NotificationLifetimeExtender extender : mRemoteInputManager.getLifetimeExtenders()) {
125             extender.setCallback(
126                     mock(NotificationLifetimeExtender.NotificationSafeToRemoveCallback.class));
127         }
128     }
129 
130     @Test
testPerformOnRemoveNotification()131     public void testPerformOnRemoveNotification() {
132         when(mController.isRemoteInputActive(mEntry)).thenReturn(true);
133         mRemoteInputManager.onPerformRemoveNotification(mEntry, mEntry.getKey());
134 
135         assertFalse(mEntry.mRemoteEditImeVisible);
136         verify(mController).removeRemoteInput(mEntry, null);
137     }
138 
139     @Test
testShouldExtendLifetime_remoteInputActive()140     public void testShouldExtendLifetime_remoteInputActive() {
141         when(mController.isRemoteInputActive(mEntry)).thenReturn(true);
142 
143         assertTrue(mRemoteInputManager.isRemoteInputActive(mEntry));
144         assertTrue(mRemoteInputActiveExtender.shouldExtendLifetime(mEntry));
145     }
146 
147     @Test
testShouldExtendLifetime_isSpinning()148     public void testShouldExtendLifetime_isSpinning() {
149         NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY = true;
150         when(mController.isSpinning(mEntry.getKey())).thenReturn(true);
151 
152         assertTrue(mRemoteInputManager.shouldKeepForRemoteInputHistory(mEntry));
153         assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry));
154     }
155 
156     @Test
testShouldExtendLifetime_recentRemoteInput()157     public void testShouldExtendLifetime_recentRemoteInput() {
158         NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY = true;
159         mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime();
160 
161         assertTrue(mRemoteInputManager.shouldKeepForRemoteInputHistory(mEntry));
162         assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry));
163     }
164 
165     @Test
testShouldExtendLifetime_smartReplySending()166     public void testShouldExtendLifetime_smartReplySending() {
167         NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY = true;
168         when(mSmartReplyController.isSendingSmartReply(mEntry.getKey())).thenReturn(true);
169 
170         assertTrue(mRemoteInputManager.shouldKeepForSmartReplyHistory(mEntry));
171         assertTrue(mSmartReplyHistoryExtender.shouldExtendLifetime(mEntry));
172     }
173 
174     @Test
testNotificationWithRemoteInputActiveIsRemovedOnCollapse()175     public void testNotificationWithRemoteInputActiveIsRemovedOnCollapse() {
176         mRemoteInputActiveExtender.setShouldManageLifetime(mEntry, true /* shouldManage */);
177 
178         assertEquals(mLegacyRemoteInputLifetimeExtender.getEntriesKeptForRemoteInputActive(),
179                 Sets.newArraySet(mEntry));
180 
181         mRemoteInputManager.onPanelCollapsed();
182 
183         assertTrue(
184                 mLegacyRemoteInputLifetimeExtender.getEntriesKeptForRemoteInputActive().isEmpty());
185     }
186 
187     private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
188 
TestableNotificationRemoteInputManager( Context context, FeatureFlags featureFlags, NotificationLockscreenUserManager lockscreenUserManager, SmartReplyController smartReplyController, NotificationEntryManager notificationEntryManager, RemoteInputNotificationRebuilder rebuilder, Lazy<Optional<StatusBar>> statusBarOptionalLazy, StatusBarStateController statusBarStateController, Handler mainHandler, RemoteInputUriController remoteInputUriController, NotificationClickNotifier clickNotifier, ActionClickLogger actionClickLogger, DumpManager dumpManager)189         TestableNotificationRemoteInputManager(
190                 Context context,
191                 FeatureFlags featureFlags,
192                 NotificationLockscreenUserManager lockscreenUserManager,
193                 SmartReplyController smartReplyController,
194                 NotificationEntryManager notificationEntryManager,
195                 RemoteInputNotificationRebuilder rebuilder,
196                 Lazy<Optional<StatusBar>> statusBarOptionalLazy,
197                 StatusBarStateController statusBarStateController,
198                 Handler mainHandler,
199                 RemoteInputUriController remoteInputUriController,
200                 NotificationClickNotifier clickNotifier,
201                 ActionClickLogger actionClickLogger,
202                 DumpManager dumpManager) {
203             super(
204                     context,
205                     featureFlags,
206                     lockscreenUserManager,
207                     smartReplyController,
208                     notificationEntryManager,
209                     rebuilder,
210                     statusBarOptionalLazy,
211                     statusBarStateController,
212                     mainHandler,
213                     remoteInputUriController,
214                     clickNotifier,
215                     actionClickLogger,
216                     dumpManager);
217         }
218 
setUpWithPresenterForTest(Callback callback, RemoteInputController.Delegate delegate, RemoteInputController controller)219         public void setUpWithPresenterForTest(Callback callback,
220                 RemoteInputController.Delegate delegate,
221                 RemoteInputController controller) {
222             super.setUpWithCallback(callback, delegate);
223             mRemoteInputController = controller;
224         }
225 
226         @NonNull
227         @Override
createLegacyRemoteInputLifetimeExtender( Handler mainHandler, NotificationEntryManager notificationEntryManager, SmartReplyController smartReplyController)228         protected LegacyRemoteInputLifetimeExtender createLegacyRemoteInputLifetimeExtender(
229                 Handler mainHandler,
230                 NotificationEntryManager notificationEntryManager,
231                 SmartReplyController smartReplyController) {
232             mLegacyRemoteInputLifetimeExtender = new FakeLegacyRemoteInputLifetimeExtender();
233             return mLegacyRemoteInputLifetimeExtender;
234         }
235 
236         class FakeLegacyRemoteInputLifetimeExtender extends LegacyRemoteInputLifetimeExtender {
237 
238             @Override
addLifetimeExtenders()239             protected void addLifetimeExtenders() {
240                 mRemoteInputActiveExtender = new RemoteInputActiveExtender();
241                 mRemoteInputHistoryExtender = new RemoteInputHistoryExtender();
242                 mSmartReplyHistoryExtender = new SmartReplyHistoryExtender();
243                 mLifetimeExtenders.add(mRemoteInputHistoryExtender);
244                 mLifetimeExtenders.add(mSmartReplyHistoryExtender);
245                 mLifetimeExtenders.add(mRemoteInputActiveExtender);
246             }
247         }
248 
249     }
250 }
251