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 import static com.google.common.truth.Truth.assertThat; 22 import static com.google.common.truth.Truth.assertWithMessage; 23 24 import static org.junit.Assert.fail; 25 import static org.mockito.Mockito.when; 26 27 import android.annotation.UserIdInt; 28 import android.app.ActivityManager; 29 import android.car.test.mocks.AbstractExtendedMockitoTestCase; 30 import android.car.testapi.BlockingUserLifecycleListener; 31 import android.car.user.CarUserManager; 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.content.ContextWrapper; 35 import android.content.Intent; 36 import android.content.ServiceConnection; 37 import android.content.pm.UserInfo; 38 import android.content.res.Resources; 39 import android.os.Handler; 40 import android.os.Looper; 41 import android.os.UserHandle; 42 import android.os.UserManager; 43 import android.util.Log; 44 45 import androidx.test.core.app.ApplicationProvider; 46 47 import com.android.car.CarLocalServices; 48 import com.android.car.CarUxRestrictionsManagerService; 49 import com.android.car.hal.UserHalService; 50 import com.android.car.internal.common.CommonConstants.UserLifecycleEventType; 51 import com.android.car.user.CarUserService; 52 import com.android.internal.annotations.GuardedBy; 53 import com.android.internal.util.Preconditions; 54 55 import org.junit.After; 56 import org.junit.Before; 57 import org.junit.Test; 58 import org.mockito.Mock; 59 60 import java.util.ArrayList; 61 import java.util.HashMap; 62 import java.util.List; 63 import java.util.Map; 64 import java.util.concurrent.CountDownLatch; 65 import java.util.concurrent.TimeUnit; 66 67 public final class VendorServiceControllerTest extends AbstractExtendedMockitoTestCase { 68 private static final String TAG = VendorServiceControllerTest.class.getSimpleName(); 69 70 // TODO(b/152069895): decrease value once refactored. In fact, it should not even use 71 // runWithScissors(), but only rely on CountdownLatches 72 private static final long DEFAULT_TIMEOUT_MS = 5_000; 73 74 private static final int FG_USER_ID = 13; 75 76 private static final String SERVICE_BIND_ALL_USERS_ASAP = "com.android.car/.AllUsersService"; 77 private static final String SERVICE_BIND_FG_USER_UNLOCKED = "com.android.car/.ForegroundUsers"; 78 private static final String SERVICE_START_SYSTEM_UNLOCKED = "com.android.car/.SystemUser"; 79 80 private static final String[] FAKE_SERVICES = new String[] { 81 SERVICE_BIND_ALL_USERS_ASAP + "#bind=bind,user=all,trigger=asap", 82 SERVICE_BIND_FG_USER_UNLOCKED + "#bind=bind,user=foreground,trigger=userUnlocked", 83 SERVICE_START_SYSTEM_UNLOCKED + "#bind=start,user=system,trigger=userUnlocked" 84 }; 85 86 @Mock 87 private Resources mResources; 88 89 @Mock 90 private UserManager mUserManager; 91 92 @Mock 93 private UserHalService mUserHal; 94 95 @Mock 96 private CarUxRestrictionsManagerService mUxRestrictionService; 97 98 private ServiceLauncherContext mContext; 99 private CarUserService mCarUserService; 100 private VendorServiceController mController; 101 102 103 @Override onSessionBuilder(CustomMockitoSessionBuilder session)104 protected void onSessionBuilder(CustomMockitoSessionBuilder session) { 105 session.spyStatic(ActivityManager.class); 106 } 107 108 @Before setUp()109 public void setUp() { 110 mContext = new ServiceLauncherContext(ApplicationProvider.getApplicationContext()); 111 mCarUserService = new CarUserService(mContext, mUserHal, mUserManager, 112 ActivityManager.getService(), /* maxRunningUsers= */ 2, mUxRestrictionService); 113 CarLocalServices.addService(CarUserService.class, mCarUserService); 114 115 mController = new VendorServiceController(mContext, Looper.getMainLooper()); 116 117 UserInfo persistentFgUser = new UserInfo(FG_USER_ID, "persistent user", 0); 118 when(mUserManager.getUserInfo(FG_USER_ID)).thenReturn(persistentFgUser); 119 120 when(mResources.getStringArray(com.android.car.R.array.config_earlyStartupServices)) 121 .thenReturn(FAKE_SERVICES); 122 } 123 124 @After tearDown()125 public void tearDown() { 126 CarLocalServices.removeServiceForTest(CarUserService.class); 127 } 128 129 @Test init_nothingConfigured()130 public void init_nothingConfigured() { 131 when(mResources.getStringArray(com.android.car.R.array.config_earlyStartupServices)) 132 .thenReturn(new String[0]); 133 134 mController.init(); 135 136 mContext.verifyNoMoreServiceLaunches(); 137 } 138 139 @Test init_systemUser()140 public void init_systemUser() throws Exception { 141 mContext.expectServices(SERVICE_BIND_ALL_USERS_ASAP); 142 mockGetCurrentUser(UserHandle.USER_SYSTEM); 143 mController.init(); 144 145 mContext.assertBoundService(SERVICE_BIND_ALL_USERS_ASAP); 146 mContext.verifyNoMoreServiceLaunches(); 147 } 148 149 @Test systemUserUnlocked()150 public void systemUserUnlocked() throws Exception { 151 mController.init(); 152 mContext.reset(); 153 154 // TODO(b/152069895): must refactor this test because 155 // SERVICE_BIND_ALL_USERS_ASAP is bound twice (users 0 and 10) 156 mContext.expectServices(SERVICE_START_SYSTEM_UNLOCKED); 157 158 // Unlock system user 159 mockUserUnlock(UserHandle.USER_SYSTEM); 160 sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, 161 UserHandle.USER_SYSTEM); 162 163 mContext.assertStartedService(SERVICE_START_SYSTEM_UNLOCKED); 164 mContext.verifyNoMoreServiceLaunches(); 165 } 166 167 @Test fgUserUnlocked()168 public void fgUserUnlocked() throws Exception { 169 mockGetCurrentUser(UserHandle.USER_SYSTEM); 170 mController.init(); 171 mContext.reset(); 172 173 mContext.expectServices(SERVICE_BIND_ALL_USERS_ASAP, SERVICE_BIND_FG_USER_UNLOCKED); 174 175 // Switch user to foreground 176 mockGetCurrentUser(FG_USER_ID); 177 // TODO(b/155918094): Update this test, 178 UserInfo nullUser = new UserInfo(UserHandle.USER_NULL, "null user", 0); 179 when(mUserManager.getUserInfo(UserHandle.USER_NULL)).thenReturn(nullUser); 180 sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, FG_USER_ID); 181 182 // Expect only services with ASAP trigger to be started 183 mContext.assertBoundService(SERVICE_BIND_ALL_USERS_ASAP); 184 mContext.verifyNoMoreServiceLaunches(); 185 186 // Unlock foreground user 187 mockUserUnlock(FG_USER_ID); 188 sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, FG_USER_ID); 189 190 mContext.assertBoundService(SERVICE_BIND_FG_USER_UNLOCKED); 191 mContext.verifyNoMoreServiceLaunches(); 192 } 193 runOnMainThreadAndWaitForIdle(Runnable r)194 private static void runOnMainThreadAndWaitForIdle(Runnable r) { 195 Handler.getMain().runWithScissors(r, DEFAULT_TIMEOUT_MS); 196 // Run empty runnable to make sure that all posted handlers are done. 197 Handler.getMain().runWithScissors(() -> { }, DEFAULT_TIMEOUT_MS); 198 } 199 mockUserUnlock(@serIdInt int userId)200 private void mockUserUnlock(@UserIdInt int userId) { 201 when(mUserManager.isUserUnlockingOrUnlocked(isUserHandle(userId))).thenReturn(true); 202 when(mUserManager.isUserUnlockingOrUnlocked(userId)).thenReturn(true); 203 } 204 assertHasService(List<Intent> intents, String service, String action)205 private static void assertHasService(List<Intent> intents, String service, String action) { 206 assertWithMessage("Service %s not %s yet", service, action).that(intents) 207 .hasSize(1); 208 assertWithMessage("Wrong component %s", action).that(intents.get(0).getComponent()) 209 .isEqualTo(ComponentName.unflattenFromString(service)); 210 intents.clear(); 211 } 212 sendUserLifecycleEvent(@serLifecycleEventType int eventType, @UserIdInt int userId)213 private void sendUserLifecycleEvent(@UserLifecycleEventType int eventType, 214 @UserIdInt int userId) throws InterruptedException { 215 // Adding a blocking listener to ensure CarUserService event notification is completed 216 // before proceeding with test execution. 217 BlockingUserLifecycleListener blockingListener = 218 BlockingUserLifecycleListener.forAnyEvent().build(); 219 mCarUserService.addUserLifecycleListener(blockingListener); 220 221 runOnMainThreadAndWaitForIdle(() -> mCarUserService.onUserLifecycleEvent(eventType, 222 /* fromUserId= */ UserHandle.USER_NULL, userId)); 223 blockingListener.waitForAnyEvent(); 224 } 225 226 /** Overrides framework behavior to succeed on binding/starting processes. */ 227 public final class ServiceLauncherContext extends ContextWrapper { 228 229 private final Object mLock = new Object(); 230 231 @GuardedBy("mLock") 232 private List<Intent> mBoundIntents = new ArrayList<>(); 233 @GuardedBy("mLock") 234 private List<Intent> mStartedServicesIntents = new ArrayList<>(); 235 236 private final Map<String, CountDownLatch> mBoundLatches = new HashMap<>(); 237 private final Map<String, CountDownLatch> mStartedLatches = new HashMap<>(); 238 ServiceLauncherContext(Context base)239 ServiceLauncherContext(Context base) { 240 super(base); 241 } 242 243 @Override startServiceAsUser(Intent service, UserHandle user)244 public ComponentName startServiceAsUser(Intent service, UserHandle user) { 245 synchronized (mLock) { 246 mStartedServicesIntents.add(service); 247 } 248 countdown(mStartedLatches, service, "started"); 249 return service.getComponent(); 250 } 251 252 @Override bindServiceAsUser(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user)253 public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, 254 Handler handler, UserHandle user) { 255 synchronized (mLock) { 256 mBoundIntents.add(service); 257 Log.v(TAG, "Added service (" + service + ") to bound intents"); 258 } 259 conn.onServiceConnected(service.getComponent(), null); 260 countdown(mBoundLatches, service, "bound"); 261 return true; 262 } 263 264 @Override bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user)265 public boolean bindServiceAsUser(Intent service, ServiceConnection conn, 266 int flags, UserHandle user) { 267 return bindServiceAsUser(service, conn, flags, null, user); 268 } 269 270 @Override getResources()271 public Resources getResources() { 272 return mResources; 273 } 274 expectServices(String... services)275 private void expectServices(String... services) { 276 for (String service : services) { 277 Log.v(TAG, "expecting service " + service); 278 mBoundLatches.put(service, new CountDownLatch(1)); 279 mStartedLatches.put(service, new CountDownLatch(1)); 280 } 281 } 282 await(Map<String, CountDownLatch> latches, String service, String method)283 private void await(Map<String, CountDownLatch> latches, String service, String method) 284 throws InterruptedException { 285 CountDownLatch latch = latches.get(service); 286 Preconditions.checkArgument(latch != null, 287 "no latch set for %s - did you call expectBoundServices()?", service); 288 Log.d(TAG, "waiting " + DEFAULT_TIMEOUT_MS + "ms for " + method); 289 if (!latch.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 290 String errorMessage = method + " not called in " + DEFAULT_TIMEOUT_MS + "ms"; 291 Log.e(TAG, errorMessage); 292 fail(errorMessage); 293 } 294 Log.v(TAG, "latch.await for service (" + service + ") and method (" 295 + method + ") called fine"); 296 } 297 countdown(Map<String, CountDownLatch> latches, Intent service, String action)298 private void countdown(Map<String, CountDownLatch> latches, Intent service, String action) { 299 String serviceName = service.getComponent().flattenToShortString(); 300 CountDownLatch latch = latches.get(serviceName); 301 if (latch == null) { 302 Log.e(TAG, "unexpected service (" + serviceName + ") " + action + ". Expected only " 303 + mBoundLatches.keySet()); 304 } else { 305 latch.countDown(); 306 Log.v(TAG, "latch.countDown for service (" + service + ") and action (" 307 + action + ") called fine"); 308 } 309 } 310 assertBoundService(String service)311 void assertBoundService(String service) throws InterruptedException { 312 await(mBoundLatches, service, "bind()"); 313 synchronized (mLock) { 314 assertHasService(mBoundIntents, service, "bound"); 315 } 316 } 317 assertStartedService(String service)318 void assertStartedService(String service) throws InterruptedException { 319 await(mStartedLatches, service, "start()"); 320 synchronized (mLock) { 321 assertHasService(mStartedServicesIntents, service, "started"); 322 } 323 } 324 verifyNoMoreServiceLaunches()325 void verifyNoMoreServiceLaunches() { 326 synchronized (mLock) { 327 assertThat(mStartedServicesIntents).isEmpty(); 328 assertThat(mBoundIntents).isEmpty(); 329 } 330 } 331 reset()332 void reset() { 333 synchronized (mLock) { 334 mStartedServicesIntents.clear(); 335 mBoundIntents.clear(); 336 } 337 } 338 339 @Override getSystemService(String name)340 public Object getSystemService(String name) { 341 if (Context.USER_SERVICE.equals(name)) { 342 return mUserManager; 343 } 344 return super.getSystemService(name); 345 } 346 } 347 } 348