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.car.pm; 18 19 import static android.car.test.mocks.CarArgumentMatchers.isUserHandle; 20 21 22 import static com.google.common.truth.Truth.assertThat; 23 import static com.google.common.truth.Truth.assertWithMessage; 24 25 import static org.junit.Assert.fail; 26 import static org.mockito.Mockito.when; 27 28 import android.annotation.UserIdInt; 29 import android.app.ActivityManager; 30 import android.car.test.mocks.AbstractExtendedMockitoTestCase; 31 import android.car.testapi.BlockingUserLifecycleListener; 32 import android.car.user.CarUserManager; 33 import android.content.BroadcastReceiver; 34 import android.content.ComponentName; 35 import android.content.Context; 36 import android.content.ContextWrapper; 37 import android.content.Intent; 38 import android.content.IntentFilter; 39 import android.content.ServiceConnection; 40 import android.content.pm.UserInfo; 41 import android.content.res.Resources; 42 import android.net.Uri; 43 import android.os.Handler; 44 import android.os.Looper; 45 import android.os.UserHandle; 46 import android.os.UserManager; 47 import android.util.Log; 48 49 import androidx.annotation.Nullable; 50 import androidx.test.core.app.ApplicationProvider; 51 52 import com.android.car.CarLocalServices; 53 import com.android.car.CarUxRestrictionsManagerService; 54 import com.android.car.hal.UserHalService; 55 import com.android.car.internal.common.CommonConstants.UserLifecycleEventType; 56 import com.android.car.user.CarUserService; 57 import com.android.internal.annotations.GuardedBy; 58 import com.android.internal.util.Preconditions; 59 60 import org.junit.After; 61 import org.junit.Before; 62 import org.junit.Test; 63 import org.mockito.Mock; 64 65 import java.util.ArrayList; 66 import java.util.HashMap; 67 import java.util.List; 68 import java.util.Map; 69 import java.util.concurrent.CountDownLatch; 70 import java.util.concurrent.Executor; 71 import java.util.concurrent.TimeUnit; 72 73 public final class VendorServiceControllerTest extends AbstractExtendedMockitoTestCase { 74 private static final String TAG = VendorServiceControllerTest.class.getSimpleName(); 75 76 // TODO(b/152069895): decrease value once refactored. In fact, it should not even use 77 // runWithScissors(), but only rely on CountdownLatches 78 private static final long DEFAULT_TIMEOUT_MS = 5_000; 79 80 private static final int FG_USER_ID = 13; 81 82 private static final String SERVICE_BIND_ALL_USERS_ASAP = "com.android.car/.AllUsersService"; 83 private static final String SERVICE_BIND_FG_USER_UNLOCKED = "com.android.car/.ForegroundUsers"; 84 private static final String SERVICE_BIND_FG_USER_POST_UNLOCKED = 85 "com.android.car/.ForegroundUsersPostUnlocked"; 86 private static final String SERVICE_START_SYSTEM_UNLOCKED = "com.android.car/.SystemUser"; 87 88 private static final String[] FAKE_SERVICES = new String[] { 89 SERVICE_BIND_ALL_USERS_ASAP + "#bind=bind,user=all,trigger=asap", 90 SERVICE_BIND_FG_USER_UNLOCKED + "#bind=bind,user=foreground,trigger=userUnlocked", 91 SERVICE_BIND_FG_USER_POST_UNLOCKED 92 + "#bind=bind,user=foreground,trigger=userPostUnlocked", 93 SERVICE_START_SYSTEM_UNLOCKED + "#bind=start,user=system,trigger=userUnlocked" 94 }; 95 96 @Mock 97 private Resources mResources; 98 99 @Mock 100 private UserManager mUserManager; 101 102 @Mock 103 private UserHalService mUserHal; 104 105 @Mock 106 private CarUxRestrictionsManagerService mUxRestrictionService; 107 108 @Mock 109 private CarPackageManagerService mCarPackageManagerService; 110 111 private ServiceLauncherContext mContext; 112 private CarUserService mCarUserService; 113 private VendorServiceController mController; 114 VendorServiceControllerTest()115 public VendorServiceControllerTest() { 116 super(VendorServiceController.TAG); 117 } 118 119 @Override onSessionBuilder(CustomMockitoSessionBuilder session)120 protected void onSessionBuilder(CustomMockitoSessionBuilder session) { 121 session.spyStatic(ActivityManager.class); 122 } 123 124 @Before setUp()125 public void setUp() { 126 mContext = new ServiceLauncherContext(ApplicationProvider.getApplicationContext()); 127 128 mCarUserService = new CarUserService(mContext, mUserHal, mUserManager, 129 /* maxRunningUsers= */ 2, mUxRestrictionService, mCarPackageManagerService); 130 CarLocalServices.addService(CarUserService.class, mCarUserService); 131 132 mController = new VendorServiceController(mContext, Looper.getMainLooper()); 133 134 UserInfo persistentFgUser = new UserInfo(FG_USER_ID, "persistent user", /* flags= */ 0); 135 when(mUserManager.getUserInfo(FG_USER_ID)).thenReturn(persistentFgUser); 136 137 when(mResources.getStringArray(com.android.car.R.array.config_earlyStartupServices)) 138 .thenReturn(FAKE_SERVICES); 139 } 140 141 @After tearDown()142 public void tearDown() { 143 CarLocalServices.removeServiceForTest(CarUserService.class); 144 } 145 146 @Test init_nothingConfigured()147 public void init_nothingConfigured() { 148 when(mResources.getStringArray(com.android.car.R.array.config_earlyStartupServices)) 149 .thenReturn(new String[0]); 150 151 mController.init(); 152 153 mContext.verifyNoMoreServiceLaunches(); 154 } 155 156 @Test init_systemUser()157 public void init_systemUser() throws Exception { 158 mContext.expectServices(SERVICE_BIND_ALL_USERS_ASAP); 159 mockGetCurrentUser(UserHandle.USER_SYSTEM); 160 mController.init(); 161 162 mContext.assertRecentBoundService(SERVICE_BIND_ALL_USERS_ASAP); 163 mContext.verifyNoMoreServiceLaunches(); 164 } 165 166 @Test systemUserUnlocked()167 public void systemUserUnlocked() throws Exception { 168 mController.init(); 169 mContext.reset(); 170 171 // TODO(b/152069895): must refactor this test because 172 // SERVICE_BIND_ALL_USERS_ASAP is bound twice (users 0 and 10) 173 mContext.expectServices(SERVICE_START_SYSTEM_UNLOCKED); 174 175 // Unlock system user 176 mockUserUnlock(UserHandle.USER_SYSTEM); 177 sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, 178 UserHandle.USER_SYSTEM); 179 180 mContext.assertRecentStartedService(SERVICE_START_SYSTEM_UNLOCKED); 181 mContext.verifyNoMoreServiceLaunches(); 182 } 183 184 @Test fgUserUnlocked()185 public void fgUserUnlocked() throws Exception { 186 mockGetCurrentUser(UserHandle.USER_SYSTEM); 187 mController.init(); 188 mContext.reset(); 189 190 mContext.expectServices(SERVICE_BIND_ALL_USERS_ASAP, SERVICE_BIND_FG_USER_UNLOCKED, 191 SERVICE_BIND_FG_USER_POST_UNLOCKED); 192 193 // Switch user to foreground 194 mockGetCurrentUser(FG_USER_ID); 195 // TODO(b/155918094): Update this test, 196 UserInfo nullUser = new UserInfo(UserHandle.USER_NULL, "null user", /* flags= */ 0); 197 when(mUserManager.getUserInfo(UserHandle.USER_NULL)).thenReturn(nullUser); 198 sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, FG_USER_ID); 199 200 // Expect only services with ASAP trigger to be started 201 mContext.assertRecentBoundService(SERVICE_BIND_ALL_USERS_ASAP); 202 mContext.verifyNoMoreServiceLaunches(); 203 204 // Unlock foreground user 205 mockUserUnlock(FG_USER_ID); 206 sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, FG_USER_ID); 207 208 mContext.assertRecentBoundService(SERVICE_BIND_FG_USER_UNLOCKED); 209 mContext.verifyNoMoreServiceLaunches(); 210 211 // Send USER_POST_UNLOCKED event. 212 sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED, FG_USER_ID); 213 214 mContext.assertRecentBoundService(SERVICE_BIND_FG_USER_POST_UNLOCKED); 215 mContext.verifyNoMoreServiceLaunches(); 216 } 217 218 @Test packageChanged_attemptsRebind()219 public void packageChanged_attemptsRebind() throws Exception { 220 mockGetCurrentUser(UserHandle.USER_SYSTEM); 221 mController.init(); 222 mContext.reset(); 223 224 mContext.expectServices(SERVICE_BIND_ALL_USERS_ASAP, SERVICE_BIND_FG_USER_UNLOCKED, 225 SERVICE_BIND_FG_USER_POST_UNLOCKED); 226 227 // Switch user to foreground 228 mockGetCurrentUser(FG_USER_ID); 229 UserInfo nullUser = new UserInfo(UserHandle.USER_NULL, "null user", /* flags= */ 0); 230 when(mUserManager.getUserInfo(UserHandle.USER_NULL)).thenReturn(nullUser); 231 sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, FG_USER_ID); 232 mContext.assertRecentBoundService(SERVICE_BIND_ALL_USERS_ASAP); 233 mockUserUnlock(FG_USER_ID); 234 235 // assertRecentBoundService() is important after every sendUserLifecycleEvent to ensure 236 // that the event has been handled completely. 237 sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, FG_USER_ID); 238 mContext.assertRecentBoundService(SERVICE_BIND_FG_USER_UNLOCKED); 239 sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED, FG_USER_ID); 240 mContext.assertRecentBoundService(SERVICE_BIND_FG_USER_POST_UNLOCKED); 241 242 Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED); 243 int appId = 123; 244 packageIntent.setData(new Uri.Builder().path("Any package").build()); 245 packageIntent.putExtra(Intent.EXTRA_UID, UserHandle.getUid(FG_USER_ID, appId)); 246 mContext.mPackageChangeReceiver.onReceive(mContext, packageIntent); 247 runOnMainThreadAndWaitForIdle(() -> {}); 248 249 assertThat(((VendorServiceController.VendorServiceConnection) 250 mContext.mBoundServiceToConnectionMap.get(SERVICE_BIND_FG_USER_POST_UNLOCKED)) 251 .isPendingRebind()).isTrue(); 252 assertThat(((VendorServiceController.VendorServiceConnection) 253 mContext.mBoundServiceToConnectionMap.get(SERVICE_BIND_FG_USER_UNLOCKED)) 254 .isPendingRebind()).isTrue(); 255 } 256 257 @Test packageRemoved_unbindsTheService()258 public void packageRemoved_unbindsTheService() throws Exception { 259 mockGetCurrentUser(UserHandle.USER_SYSTEM); 260 mController.init(); 261 mContext.reset(); 262 mContext.expectServices(SERVICE_BIND_ALL_USERS_ASAP, SERVICE_BIND_FG_USER_UNLOCKED, 263 SERVICE_BIND_FG_USER_POST_UNLOCKED); 264 265 // Switch user to foreground 266 mockGetCurrentUser(FG_USER_ID); 267 UserInfo nullUser = new UserInfo(UserHandle.USER_NULL, "null user", /* flags= */ 0); 268 when(mUserManager.getUserInfo(UserHandle.USER_NULL)).thenReturn(nullUser); 269 sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, FG_USER_ID); 270 mContext.assertRecentBoundService(SERVICE_BIND_ALL_USERS_ASAP); 271 mockUserUnlock(FG_USER_ID); 272 273 // assertRecentBoundService() is important after every sendUserLifecycleEvent to ensure 274 // that the event has been handled completely. 275 sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, FG_USER_ID); 276 mContext.assertRecentBoundService(SERVICE_BIND_FG_USER_UNLOCKED); 277 sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED, FG_USER_ID); 278 mContext.assertRecentBoundService(SERVICE_BIND_FG_USER_POST_UNLOCKED); 279 280 Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED); 281 int appId = 123; 282 packageIntent.setData(new Uri.Builder().path("com.android.car").build()); 283 packageIntent.putExtra(Intent.EXTRA_UID, UserHandle.getUid(FG_USER_ID, appId)); 284 mContext.mPackageChangeReceiver.onReceive(mContext, packageIntent); 285 runOnMainThreadAndWaitForIdle(() -> {}); 286 287 mContext.assertServiceNotBound(SERVICE_BIND_FG_USER_POST_UNLOCKED); 288 mContext.assertServiceNotBound(SERVICE_BIND_FG_USER_UNLOCKED); 289 } 290 runOnMainThreadAndWaitForIdle(Runnable r)291 private static void runOnMainThreadAndWaitForIdle(Runnable r) { 292 Handler.getMain().runWithScissors(r, DEFAULT_TIMEOUT_MS); 293 // Run empty runnable to make sure that all posted handlers are done. 294 Handler.getMain().runWithScissors(() -> { }, DEFAULT_TIMEOUT_MS); 295 } 296 mockUserUnlock(@serIdInt int userId)297 private void mockUserUnlock(@UserIdInt int userId) { 298 when(mUserManager.isUserUnlockingOrUnlocked(isUserHandle(userId))).thenReturn(true); 299 when(mUserManager.isUserUnlockingOrUnlocked(userId)).thenReturn(true); 300 } 301 assertHasService(List<ComponentName> recentServices, String service, String action)302 private static void assertHasService(List<ComponentName> recentServices, String service, 303 String action) { 304 assertWithMessage("Number of recent %s services", action).that(recentServices) 305 .hasSize(1); 306 assertWithMessage("Recent service").that(recentServices.get(0)) 307 .isEqualTo(ComponentName.unflattenFromString(service)); 308 recentServices.clear(); 309 } 310 sendUserLifecycleEvent(@serLifecycleEventType int eventType, @UserIdInt int userId)311 private void sendUserLifecycleEvent(@UserLifecycleEventType int eventType, 312 @UserIdInt int userId) throws InterruptedException { 313 // Adding a blocking listener to ensure CarUserService event notification is completed 314 // before proceeding with test execution. 315 BlockingUserLifecycleListener blockingListener = 316 BlockingUserLifecycleListener.forAnyEvent().build(); 317 mCarUserService.addUserLifecycleListener(/* filter= */null, blockingListener); 318 319 runOnMainThreadAndWaitForIdle(() -> mCarUserService.onUserLifecycleEvent(eventType, 320 /* fromUserId= */ UserHandle.USER_NULL, userId)); 321 blockingListener.waitForAnyEvent(); 322 } 323 324 /** Overrides framework behavior to succeed on binding/starting processes. */ 325 public final class ServiceLauncherContext extends ContextWrapper { 326 327 private final Object mLock = new Object(); 328 329 @GuardedBy("mLock") 330 private Map<ServiceConnection, ComponentName> mBoundConnectionToServiceMap = 331 new HashMap<>(); 332 @GuardedBy("mLock") 333 private List<ComponentName> mRecentBoundServices = new ArrayList<>(); 334 @GuardedBy("mLock") 335 private List<ComponentName> mRecentStartedServices = new ArrayList<>(); 336 337 private final Map<String, CountDownLatch> mBoundLatches = new HashMap<>(); 338 private final Map<String, CountDownLatch> mStartedLatches = new HashMap<>(); 339 private final Map<String, ServiceConnection> mBoundServiceToConnectionMap = 340 new HashMap<>(); 341 private BroadcastReceiver mPackageChangeReceiver; 342 ServiceLauncherContext(Context base)343 ServiceLauncherContext(Context base) { 344 super(base); 345 } 346 347 @Override createContextAsUser(UserHandle user, int flags)348 public Context createContextAsUser(UserHandle user, int flags) { 349 Log.v(TAG, "using same context for user " + user); 350 return this; 351 } 352 353 @Override startService(Intent service)354 public ComponentName startService(Intent service) { 355 synchronized (mLock) { 356 mRecentStartedServices.add(service.getComponent()); 357 } 358 countdown(mStartedLatches, service, "started"); 359 return service.getComponent(); 360 } 361 362 @Override bindService(Intent service, int flags, Executor executor, ServiceConnection conn)363 public boolean bindService(Intent service, int flags, Executor executor, 364 ServiceConnection conn) { 365 synchronized (mLock) { 366 mRecentBoundServices.add(service.getComponent()); 367 mBoundServiceToConnectionMap.put(service.getComponent().flattenToShortString(), 368 conn); 369 mBoundConnectionToServiceMap.put(conn, service.getComponent()); 370 Log.v(TAG, "Added service (" + service + ") to bound intents"); 371 } 372 conn.onServiceConnected(service.getComponent(), null); 373 countdown(mBoundLatches, service, "bound"); 374 return true; 375 } 376 377 @Override unbindService(ServiceConnection conn)378 public void unbindService(ServiceConnection conn) { 379 synchronized (mLock) { 380 ComponentName serviceComponent = mBoundConnectionToServiceMap.get(conn); 381 Log.v(TAG, "Remove service (" + serviceComponent + ") from bound services"); 382 mRecentBoundServices.remove(serviceComponent); 383 mBoundServiceToConnectionMap.remove(serviceComponent.flattenToShortString()); 384 mBoundConnectionToServiceMap.remove(conn); 385 } 386 } 387 388 @Override getResources()389 public Resources getResources() { 390 return mResources; 391 } 392 expectServices(String... services)393 private void expectServices(String... services) { 394 for (String service : services) { 395 Log.v(TAG, "expecting service " + service); 396 mBoundLatches.put(service, new CountDownLatch(1)); 397 mStartedLatches.put(service, new CountDownLatch(1)); 398 } 399 } 400 await(Map<String, CountDownLatch> latches, String service, String method)401 private void await(Map<String, CountDownLatch> latches, String service, String method) 402 throws InterruptedException { 403 CountDownLatch latch = latches.get(service); 404 Preconditions.checkArgument(latch != null, 405 "no latch set for %s - did you call expectBoundServices()?", service); 406 Log.d(TAG, "waiting " + DEFAULT_TIMEOUT_MS + "ms for " + method); 407 if (!latch.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 408 String errorMessage = method + " not called in " + DEFAULT_TIMEOUT_MS + "ms"; 409 Log.e(TAG, errorMessage); 410 fail(errorMessage); 411 } 412 Log.v(TAG, "latch.await for service (" + service + ") and method (" 413 + method + ") called fine"); 414 } 415 countdown(Map<String, CountDownLatch> latches, Intent service, String action)416 private void countdown(Map<String, CountDownLatch> latches, Intent service, String action) { 417 String serviceName = service.getComponent().flattenToShortString(); 418 CountDownLatch latch = latches.get(serviceName); 419 if (latch == null) { 420 Log.e(TAG, "unexpected service (" + serviceName + ") " + action + ". Expected only " 421 + mBoundLatches.keySet()); 422 } else { 423 latch.countDown(); 424 Log.v(TAG, "latch.countDown for service (" + service + ") and action (" 425 + action + ") called fine"); 426 } 427 } 428 assertRecentBoundService(String service)429 void assertRecentBoundService(String service) throws InterruptedException { 430 await(mBoundLatches, service, "bind()"); 431 synchronized (mLock) { 432 assertHasService(mRecentBoundServices, service, "bound"); 433 } 434 } 435 assertServiceNotBound(String service)436 void assertServiceNotBound(String service) throws InterruptedException { 437 synchronized (mLock) { 438 assertWithMessage("Service is binded.").that(mRecentBoundServices) 439 .doesNotContain(ComponentName.unflattenFromString(service)); 440 } 441 } 442 assertRecentStartedService(String service)443 void assertRecentStartedService(String service) throws InterruptedException { 444 await(mStartedLatches, service, "start()"); 445 synchronized (mLock) { 446 assertHasService(mRecentStartedServices, service, "started"); 447 } 448 } 449 verifyNoMoreServiceLaunches()450 void verifyNoMoreServiceLaunches() { 451 synchronized (mLock) { 452 assertThat(mRecentStartedServices).isEmpty(); 453 assertThat(mRecentBoundServices).isEmpty(); 454 } 455 } 456 reset()457 void reset() { 458 synchronized (mLock) { 459 mRecentStartedServices.clear(); 460 mRecentBoundServices.clear(); 461 mBoundServiceToConnectionMap.clear(); 462 mBoundConnectionToServiceMap.clear(); 463 } 464 } 465 466 @Override getSystemService(String name)467 public Object getSystemService(String name) { 468 if (Context.USER_SERVICE.equals(name)) { 469 return mUserManager; 470 } 471 return super.getSystemService(name); 472 } 473 474 @Nullable 475 @Override registerReceiverForAllUsers(@ullable BroadcastReceiver receiver, IntentFilter filter, @Nullable String broadcastPermission, @Nullable Handler scheduler, int flags)476 public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver, 477 IntentFilter filter, @Nullable String broadcastPermission, 478 @Nullable Handler scheduler, int flags) { 479 mPackageChangeReceiver = receiver; 480 return null; 481 } 482 } 483 } 484