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.car.user.CarUserManager.UserLifecycleEventType;
33 import android.car.userlib.CarUserManagerHelper;
34 import android.content.ComponentName;
35 import android.content.Context;
36 import android.content.ContextWrapper;
37 import android.content.Intent;
38 import android.content.ServiceConnection;
39 import android.content.pm.UserInfo;
40 import android.content.res.Resources;
41 import android.os.Handler;
42 import android.os.Looper;
43 import android.os.UserHandle;
44 import android.os.UserManager;
45 import android.util.Log;
46 
47 import androidx.test.core.app.ApplicationProvider;
48 
49 import com.android.car.CarLocalServices;
50 import com.android.car.hal.UserHalService;
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 import org.mockito.Mockito;
60 
61 import java.util.ArrayList;
62 import java.util.HashMap;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.concurrent.CountDownLatch;
66 import java.util.concurrent.TimeUnit;
67 
68 public final class VendorServiceControllerTest extends AbstractExtendedMockitoTestCase {
69     private static final String TAG = VendorServiceControllerTest.class.getSimpleName();
70 
71     // TODO(b/152069895): decrease value once refactored. In fact, it should not even use
72     // runWithScissors(), but only rely on CountdownLatches
73     private static final long DEFAULT_TIMEOUT_MS = 5_000;
74 
75     private static final int FG_USER_ID = 13;
76 
77     private static final String SERVICE_BIND_ALL_USERS_ASAP = "com.android.car/.AllUsersService";
78     private static final String SERVICE_BIND_FG_USER_UNLOCKED = "com.android.car/.ForegroundUsers";
79     private static final String SERVICE_START_SYSTEM_UNLOCKED = "com.android.car/.SystemUser";
80 
81     private static final String[] FAKE_SERVICES = new String[] {
82             SERVICE_BIND_ALL_USERS_ASAP + "#bind=bind,user=all,trigger=asap",
83             SERVICE_BIND_FG_USER_UNLOCKED + "#bind=bind,user=foreground,trigger=userUnlocked",
84             SERVICE_START_SYSTEM_UNLOCKED + "#bind=start,user=system,trigger=userUnlocked"
85     };
86 
87     @Mock
88     private Resources mResources;
89 
90     @Mock
91     private UserManager mUserManager;
92 
93     @Mock
94     private UserHalService mUserHal;
95 
96     private ServiceLauncherContext mContext;
97     private CarUserManagerHelper mUserManagerHelper;
98     private CarUserService mCarUserService;
99     private VendorServiceController mController;
100 
101 
102     @Override
onSessionBuilder(CustomMockitoSessionBuilder session)103     protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
104         session.spyStatic(ActivityManager.class);
105     }
106 
107     @Before
setUp()108     public void setUp() {
109         mContext = new ServiceLauncherContext(ApplicationProvider.getApplicationContext());
110         mUserManagerHelper = Mockito.spy(new CarUserManagerHelper(mContext));
111         mCarUserService = new CarUserService(mContext, mUserHal, mUserManagerHelper, mUserManager,
112                 ActivityManager.getService(), 2 /* max running users */);
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                 /* timestampMs= */ 0, /* 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