1 /*
2  * Copyright (C) 2019 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.server.people;
18 
19 import static android.app.people.ConversationStatus.ACTIVITY_GAME;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.ArgumentMatchers.eq;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.never;
28 import static org.mockito.Mockito.times;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when;
31 
32 import android.app.NotificationChannel;
33 import android.app.NotificationManager;
34 import android.app.people.ConversationChannel;
35 import android.app.people.ConversationStatus;
36 import android.app.people.IConversationListener;
37 import android.app.people.IPeopleManager;
38 import android.app.people.PeopleManager;
39 import android.app.prediction.AppPredictionContext;
40 import android.app.prediction.AppPredictionSessionId;
41 import android.app.prediction.AppTarget;
42 import android.app.prediction.IPredictionCallback;
43 import android.content.Context;
44 import android.content.pm.ParceledListSlice;
45 import android.content.pm.ShortcutInfo;
46 import android.os.Binder;
47 import android.os.Bundle;
48 import android.os.RemoteException;
49 import android.os.test.TestLooper;
50 import android.provider.DeviceConfig;
51 import android.testing.AndroidTestingRunner;
52 import android.testing.TestableContext;
53 import android.testing.TestableLooper;
54 
55 import androidx.test.InstrumentationRegistry;
56 
57 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
58 import com.android.server.LocalServices;
59 
60 import org.junit.After;
61 import org.junit.Before;
62 import org.junit.Rule;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 import org.mockito.ArgumentCaptor;
66 import org.mockito.Mock;
67 import org.mockito.MockitoAnnotations;
68 
69 import java.util.ArrayList;
70 import java.util.Arrays;
71 import java.util.List;
72 import java.util.function.Consumer;
73 
74 @RunWith(AndroidTestingRunner.class)
75 @TestableLooper.RunWithLooper
76 public final class PeopleServiceTest {
77     private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
78     private static final int APP_PREDICTION_TARGET_COUNT = 4;
79     private static final String TEST_PACKAGE_NAME = "com.example";
80     private static final int USER_ID = 0;
81     private static final String CONVERSATION_ID_1 = "12";
82     private static final String CONVERSATION_ID_2 = "123";
83 
84     private PeopleServiceInternal mServiceInternal;
85     private PeopleService.LocalService mLocalService;
86     private AppPredictionSessionId mSessionId;
87     private AppPredictionContext mPredictionContext;
88 
89     @Mock
90     private Context mMockContext;
91 
92     @Rule
93     public final TestableContext mContext =
94             new TestableContext(InstrumentationRegistry.getContext(), null);
95 
getContext()96     protected TestableContext getContext() {
97         return mContext;
98     }
99 
100     @Mock
101     private IPredictionCallback mCallback;
102     private TestableLooper mTestableLooper;
103     private final TestLooper mTestLooper = new TestLooper();
104 
105     private TestablePeopleService mPeopleService;
106     private IPeopleManager mIPeopleManager;
107     private PeopleManager mPeopleManager;
108 
109     @Before
setUp()110     public void setUp() {
111         MockitoAnnotations.initMocks(this);
112 
113         mPeopleService = new TestablePeopleService(mContext);
114         mTestableLooper = TestableLooper.get(this);
115         mIPeopleManager = ((IPeopleManager) mPeopleService.mService);
116         mPeopleManager = new PeopleManager(mContext, mIPeopleManager);
117         when(mMockContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
118         when(mCallback.asBinder()).thenReturn(new Binder());
119         PeopleService service = new PeopleService(mContext);
120         service.onStart(/* isForTesting= */ true);
121 
122         mServiceInternal = LocalServices.getService(PeopleServiceInternal.class);
123         mLocalService = (PeopleService.LocalService) mServiceInternal;
124 
125         mSessionId = new AppPredictionSessionId("abc", USER_ID);
126         mPredictionContext = new AppPredictionContext.Builder(mMockContext)
127                 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
128                 .setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT)
129                 .setExtras(new Bundle())
130                 .build();
131         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
132                 SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
133                 Boolean.toString(false),
134                 true /* makeDefault*/);
135     }
136 
137     @After
tearDown()138     public void tearDown() {
139         LocalServices.removeServiceForTest(PeopleServiceInternal.class);
140     }
141 
142     @Test
testRegisterCallbacks()143     public void testRegisterCallbacks() throws RemoteException {
144         mServiceInternal.onCreatePredictionSession(mPredictionContext, mSessionId);
145 
146         SessionInfo sessionInfo = mLocalService.getSessionInfo(mSessionId);
147 
148         mServiceInternal.registerPredictionUpdates(mSessionId, mCallback);
149 
150         Consumer<List<AppTarget>> updatePredictionMethod =
151                 sessionInfo.getPredictor().getUpdatePredictionsMethod();
152         updatePredictionMethod.accept(new ArrayList<>());
153         updatePredictionMethod.accept(new ArrayList<>());
154 
155         verify(mCallback, times(2)).onResult(any(ParceledListSlice.class));
156 
157         mServiceInternal.unregisterPredictionUpdates(mSessionId, mCallback);
158 
159         updatePredictionMethod.accept(new ArrayList<>());
160 
161         // After the un-registration, the callback should no longer be called.
162         verify(mCallback, times(2)).onResult(any(ParceledListSlice.class));
163 
164         mServiceInternal.onDestroyPredictionSession(mSessionId);
165     }
166 
167     @Test
testRegisterConversationListener()168     public void testRegisterConversationListener() throws Exception {
169         assertEquals(0,
170                 mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
171 
172         mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
173                 new TestableConversationListener());
174         mTestableLooper.processAllMessages();
175         assertEquals(1,
176                 mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
177 
178         mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
179                 new TestableConversationListener());
180         mTestableLooper.processAllMessages();
181         assertEquals(2,
182                 mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
183 
184         mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_2,
185                 new TestableConversationListener());
186         mTestableLooper.processAllMessages();
187         assertEquals(3,
188                 mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
189     }
190 
191     @Test
testUnregisterConversationListener()192     public void testUnregisterConversationListener() throws Exception {
193         TestableConversationListener listener1 = new TestableConversationListener();
194         mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
195                 listener1);
196         TestableConversationListener listener2 = new TestableConversationListener();
197         mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
198                 listener2);
199         TestableConversationListener listener3 = new TestableConversationListener();
200         mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_2,
201                 listener3);
202         mTestableLooper.processAllMessages();
203         assertEquals(3,
204                 mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
205 
206         mIPeopleManager.unregisterConversationListener(
207                 listener2);
208         assertEquals(2,
209                 mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
210         mIPeopleManager.unregisterConversationListener(
211                 listener1);
212         assertEquals(1,
213                 mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
214         mIPeopleManager.unregisterConversationListener(
215                 listener3);
216         assertEquals(0,
217                 mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
218     }
219 
220     @Test
testOnlyTriggersConversationListenersForRegisteredConversation()221     public void testOnlyTriggersConversationListenersForRegisteredConversation() {
222         PeopleManager.ConversationListener listenerForConversation1 = mock(
223                 PeopleManager.ConversationListener.class);
224         registerListener(CONVERSATION_ID_1, listenerForConversation1);
225         PeopleManager.ConversationListener secondListenerForConversation1 = mock(
226                 PeopleManager.ConversationListener.class);
227         registerListener(CONVERSATION_ID_1, secondListenerForConversation1);
228         PeopleManager.ConversationListener listenerForConversation2 = mock(
229                 PeopleManager.ConversationListener.class);
230         registerListener(CONVERSATION_ID_2, listenerForConversation2);
231         assertEquals(3,
232                 mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
233 
234         // Update conversation with two listeners.
235         ConversationStatus status = new ConversationStatus.Builder(CONVERSATION_ID_1,
236                 ACTIVITY_GAME).build();
237         mPeopleService.mConversationListenerHelper.onConversationsUpdate(
238                 Arrays.asList(getConversation(CONVERSATION_ID_1, status)));
239         mTestLooper.dispatchAll();
240 
241         // Never update listeners for other conversations.
242         verify(listenerForConversation2, never()).onConversationUpdate(any());
243         // Should update both listeners for the conversation.
244         ArgumentCaptor<ConversationChannel> capturedConversation = ArgumentCaptor.forClass(
245                 ConversationChannel.class);
246         verify(listenerForConversation1, times(1)).onConversationUpdate(
247                 capturedConversation.capture());
248         ConversationChannel conversationChannel = capturedConversation.getValue();
249         verify(secondListenerForConversation1, times(1)).onConversationUpdate(
250                 eq(conversationChannel));
251         assertEquals(conversationChannel.getShortcutInfo().getId(), CONVERSATION_ID_1);
252         assertThat(conversationChannel.getStatuses()).containsExactly(status);
253     }
254 
registerListener(String conversationId, PeopleManager.ConversationListener listener)255     private void registerListener(String conversationId,
256             PeopleManager.ConversationListener listener) {
257         mPeopleManager.registerConversationListener(mContext.getPackageName(), mContext.getUserId(),
258                 conversationId, listener,
259                 mTestLooper.getNewExecutor());
260     }
261 
getConversation(String shortcutId, ConversationStatus status)262     private ConversationChannel getConversation(String shortcutId, ConversationStatus status) {
263         ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext,
264                 shortcutId).setLongLabel(
265                 "name").build();
266         NotificationChannel notificationChannel = new NotificationChannel("123",
267                 "channel",
268                 NotificationManager.IMPORTANCE_DEFAULT);
269         return new ConversationChannel(shortcutInfo, 0,
270                 notificationChannel, null,
271                 123L, false, false, Arrays.asList(status));
272     }
273 
274     private class TestableConversationListener extends IConversationListener.Stub {
275         @Override
onConversationUpdate(ConversationChannel conversation)276         public void onConversationUpdate(ConversationChannel conversation) {
277         }
278     }
279 
280     // Use a Testable subclass so we can simulate calls from the system without failing.
281     private static class TestablePeopleService extends PeopleService {
TestablePeopleService(Context context)282         TestablePeopleService(Context context) {
283             super(context);
284         }
285 
286         @Override
onStart()287         public void onStart() {
288             super.onStart(true);
289         }
290 
291         @Override
enforceSystemRootOrSystemUI(Context context, String message)292         protected void enforceSystemRootOrSystemUI(Context context, String message) {
293             return;
294         }
295     }
296 }
297