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