1 /*
2  * Copyright (C) 2020 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 package android.car.test.mocks;
17 
18 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
19 
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import static org.mockito.Matchers.anyString;
23 import static org.mockito.Mockito.when;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.annotation.UserIdInt;
28 import android.app.Activity;
29 import android.app.ActivityManager;
30 import android.car.test.util.UserTestingHelper;
31 import android.car.test.util.UserTestingHelper.UserInfoBuilder;
32 import android.car.test.util.Visitor;
33 import android.content.Context;
34 import android.content.pm.PackageManager;
35 import android.content.pm.UserInfo;
36 import android.content.pm.UserInfo.UserInfoFlag;
37 import android.os.Binder;
38 import android.os.Handler;
39 import android.os.IBinder;
40 import android.os.IInterface;
41 import android.os.Looper;
42 import android.os.ServiceManager;
43 import android.os.UserHandle;
44 import android.os.UserManager;
45 import android.os.UserManager.RemoveResult;
46 import android.util.Log;
47 
48 import java.util.List;
49 import java.util.Objects;
50 import java.util.concurrent.Callable;
51 import java.util.concurrent.CountDownLatch;
52 import java.util.concurrent.atomic.AtomicReference;
53 import java.util.stream.Collectors;
54 
55 /**
56  * Provides common Mockito calls for core Android classes.
57  */
58 public final class AndroidMockitoHelper {
59 
60     private static final String TAG = AndroidMockitoHelper.class.getSimpleName();
61 
62     /**
63      * Mocks a call to {@link ActivityManager#getCurrentUser()}.
64      *
65      * <p><b>Note: </b>it must be made inside a
66      * {@link com.android.dx.mockito.inline.extended.StaticMockitoSession} built with
67      * {@code spyStatic(ActivityManager.class)}.
68      *
69      * @param userId result of such call
70      */
mockAmGetCurrentUser(@serIdInt int userId)71     public static void mockAmGetCurrentUser(@UserIdInt int userId) {
72         doReturn(userId).when(() -> ActivityManager.getCurrentUser());
73     }
74 
75     /**
76      * Mocks a call to {@link UserManager#isHeadlessSystemUserMode()}.
77      *
78      * <p><b>Note: </b>it must be made inside a
79      * {@linkcom.android.dx.mockito.inline.extended.StaticMockitoSession} built with
80      * {@code spyStatic(UserManager.class)}.
81      *
82      * @param mode result of such call
83      */
mockUmIsHeadlessSystemUserMode(boolean mode)84     public static void mockUmIsHeadlessSystemUserMode(boolean mode) {
85         doReturn(mode).when(() -> UserManager.isHeadlessSystemUserMode());
86     }
87 
88     /**
89      * Mocks {@code UserManager#getUserInfo(userId)} to return a {@link UserInfo} with the given
90      * {@code flags}.
91      */
92     @NonNull
mockUmGetUserInfo(@onNull UserManager um, @UserIdInt int userId, @UserInfoFlag int flags)93     public static UserInfo mockUmGetUserInfo(@NonNull UserManager um, @UserIdInt int userId,
94             @UserInfoFlag int flags) {
95         Objects.requireNonNull(um);
96         UserInfo user = new UserTestingHelper.UserInfoBuilder(userId).setFlags(flags).build();
97         mockUmGetUserInfo(um, user);
98         return user;
99     }
100 
101     /**
102      * Mocks {@code UserManager.getUserInfo(userId)} to return the given {@link UserInfo}.
103      */
104     @NonNull
mockUmGetUserInfo(@onNull UserManager um, @NonNull UserInfo user)105     public static void mockUmGetUserInfo(@NonNull UserManager um, @NonNull UserInfo user) {
106         when(um.getUserInfo(user.id)).thenReturn(user);
107     }
108 
109     /**
110      * Mocks {@code UserManager#getUserInfo(userId)} when the {@code userId} is the system user's.
111      */
112     @NonNull
mockUmGetSystemUser(@onNull UserManager um)113     public static void mockUmGetSystemUser(@NonNull UserManager um) {
114         UserInfo user = new UserTestingHelper.UserInfoBuilder(UserHandle.USER_SYSTEM)
115                 .setFlags(UserInfo.FLAG_SYSTEM).build();
116         when(um.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(user);
117     }
118 
119     /**
120      * Mocks {@code UserManager#getAliveUsers()} to return the given users.
121      */
mockUmGetAliveUsers(@onNull UserManager um, @NonNull UserInfo... users)122     public static void mockUmGetAliveUsers(@NonNull UserManager um, @NonNull UserInfo... users) {
123         Objects.requireNonNull(um);
124         when(um.getAliveUsers()).thenReturn(UserTestingHelper.toList(users));
125     }
126 
127     /**
128      * Mocks {@code UserManager#getAliveUsers()} to return the simple users with the given ids.
129      */
mockUmGetAliveUsers(@onNull UserManager um, @NonNull @UserIdInt int... userIds)130     public static void mockUmGetAliveUsers(@NonNull UserManager um,
131             @NonNull @UserIdInt int... userIds) {
132         List<UserInfo> users = UserTestingHelper.newUsers(userIds);
133         when(um.getAliveUsers()).thenReturn(users);
134     }
135 
136     /**
137      * Mocks {@code UserManager#getUserHandles()} to return the simple users with the given ids.
138      */
mockUmGetUserHandles(@onNull UserManager um, boolean excludeDying, @NonNull @UserIdInt int... userIds)139     public static void mockUmGetUserHandles(@NonNull UserManager um, boolean excludeDying,
140             @NonNull @UserIdInt int... userIds) {
141         List<UserHandle> result = UserTestingHelper.newUsers(userIds).stream().map(
142                 UserInfo::getUserHandle).collect(Collectors.toList());
143         when(um.getUserHandles(excludeDying)).thenReturn(result);
144     }
145 
146     /**
147      * Mocks {@code UserManager#getUsers(excludePartial, excludeDying, excludeDying)} to return the
148      * given users.
149      */
mockUmGetUsers(@onNull UserManager um, boolean excludePartial, boolean excludeDying, boolean excludePreCreated, @NonNull List<UserInfo> users)150     public static void mockUmGetUsers(@NonNull UserManager um, boolean excludePartial,
151             boolean excludeDying, boolean excludePreCreated, @NonNull List<UserInfo> users) {
152         Objects.requireNonNull(um);
153         when(um.getUsers(excludePartial, excludeDying, excludePreCreated)).thenReturn(users);
154     }
155 
156     /**
157      * Mocks a call to {@code UserManager#getUsers()}, which includes dying users.
158      */
mockUmGetAllUsers(@onNull UserManager um, @NonNull UserInfo... userInfos)159     public static void mockUmGetAllUsers(@NonNull UserManager um, @NonNull UserInfo... userInfos) {
160         when(um.getUsers()).thenReturn(UserTestingHelper.toList(userInfos));
161     }
162 
163     /**
164      * Mocks a call to {@code UserManager#isUserRunning(userId)}.
165      */
mockUmIsUserRunning(@onNull UserManager um, @UserIdInt int userId, boolean isRunning)166     public static void mockUmIsUserRunning(@NonNull UserManager um, @UserIdInt int userId,
167             boolean isRunning) {
168         when(um.isUserRunning(userId)).thenReturn(isRunning);
169     }
170 
171     /**
172      * Mocks a successful call to {@code UserManager#createUser(String, String, int)}, returning
173      * a user with the passed arguments.
174      */
175     @NonNull
mockUmCreateUser(@onNull UserManager um, @Nullable String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId)176     public static UserInfo mockUmCreateUser(@NonNull UserManager um, @Nullable String name,
177             @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId) {
178         UserInfo userInfo = new UserInfoBuilder(userId)
179                         .setName(name)
180                         .setType(userType)
181                         .setFlags(flags)
182                         .build();
183         when(um.createUser(name, userType, flags)).thenReturn(userInfo);
184         return userInfo;
185     }
186 
187     /**
188      * Mocks a call to {@code UserManager#createUser(String, String, int)} that throws the given
189      * runtime exception.
190      */
191     @NonNull
mockUmCreateUser(@onNull UserManager um, @Nullable String name, @NonNull String userType, @UserInfoFlag int flags, @NonNull RuntimeException e)192     public static void mockUmCreateUser(@NonNull UserManager um, @Nullable String name,
193             @NonNull String userType, @UserInfoFlag int flags, @NonNull RuntimeException e) {
194         when(um.createUser(name, userType, flags)).thenThrow(e);
195     }
196 
197     /**
198      * Mocks a successful call to {@code UserManager#removeUserOrSetEphemeral(int)}, and notifies
199      * {@code listener} when it's called.
200      */
mockUmRemoveUserOrSetEphemeral(@onNull UserManager um, @NonNull UserInfo user, boolean evenWhenDisallowed, @RemoveResult int result, @Nullable Visitor<UserInfo> listener)201     public static void mockUmRemoveUserOrSetEphemeral(@NonNull UserManager um,
202             @NonNull UserInfo user, boolean evenWhenDisallowed, @RemoveResult int result,
203             @Nullable Visitor<UserInfo> listener) {
204         int userId = user.id;
205         when(um.removeUserOrSetEphemeral(userId, evenWhenDisallowed)).thenAnswer((inv) -> {
206             if (listener != null) {
207                 Log.v(TAG, "mockUmRemoveUserOrSetEphemeral(" + user + "): notifying " + listener);
208                 listener.visit(user);
209             }
210             return result;
211         });
212     }
213 
214     /**
215      * Mocks a call to {@link ServiceManager#getService(name)}.
216      *
217      * <p><b>Note: </b>it must be made inside a
218      * {@link com.android.dx.mockito.inline.extended.StaticMockitoSession} built with
219      * {@code spyStatic(ServiceManager.class)}.
220      *
221      * @param name interface name of the service
222      * @param binder result of such call
223      */
mockSmGetService(@onNull String name, @NonNull IBinder binder)224     public static void mockSmGetService(@NonNull String name, @NonNull IBinder binder) {
225         doReturn(binder).when(() -> ServiceManager.getService(name));
226     }
227 
228     /**
229      * Returns mocked binder implementation from the given interface name.
230      *
231      * <p><b>Note: </b>it must be made inside a
232      * {@link com.android.dx.mockito.inline.extended.StaticMockitoSession} built with
233      * {@code spyStatic(ServiceManager.class)}.
234      *
235      * @param name interface name of the service
236      * @param binder mocked return of ServiceManager.getService
237      * @param service binder implementation
238      */
mockQueryService(@onNull String name, @NonNull IBinder binder, @NonNull T service)239     public static <T extends IInterface> void mockQueryService(@NonNull String name,
240             @NonNull IBinder binder, @NonNull T service) {
241         doReturn(binder).when(() -> ServiceManager.getService(name));
242         when(binder.queryLocalInterface(anyString())).thenReturn(service);
243     }
244 
245     /**
246      * Mocks a call to {@link Binder.getCallingUserHandle()}.
247      *
248      * <p><b>Note: </b>it must be made inside a
249      * {@link com.android.dx.mockito.inline.extended.StaticMockitoSession} built with
250      * {@code spyStatic(Binder.class)}.
251      *
252      * @param userId identifier of the {@link UserHandle} that will be returned.
253      */
mockBinderGetCallingUserHandle(@serIdInt int userId)254     public static void mockBinderGetCallingUserHandle(@UserIdInt int userId) {
255         doReturn(UserHandle.of(userId)).when(() -> Binder.getCallingUserHandle());
256     }
257 
258     /**
259      * Mocks a call to {@link Context#getSystemService(Class)}.
260      */
mockContextGetService(@onNull Context context, @NonNull Class<T> serviceClass, @NonNull T service)261     public static <T> void mockContextGetService(@NonNull Context context,
262             @NonNull Class<T> serviceClass, @NonNull T service) {
263         when(context.getSystemService(serviceClass)).thenReturn(service);
264         if (serviceClass.equals(PackageManager.class)) {
265             when(context.getPackageManager()).thenReturn(PackageManager.class.cast(service));
266         }
267     }
268 
269     // TODO(b/192307581): add unit tests
270     /**
271      * Returns the result of the giving {@code callable} in the main thread, preparing the
272      * {@link Looper} if needed and using a default timeout.
273      */
syncCallOnMainThread(Callable<T> c)274     public static <T> T syncCallOnMainThread(Callable<T> c) throws Exception {
275         return syncCallOnMainThread(JavaMockitoHelper.ASYNC_TIMEOUT_MS, c);
276     }
277 
278     // TODO(b/192307581): add unit tests
279     /**
280      * Returns the result of the giving {@code callable} in the main thread, preparing the
281      * {@link Looper} if needed.
282      */
syncCallOnMainThread(long timeoutMs, Callable<T> callable)283     public static <T> T syncCallOnMainThread(long timeoutMs, Callable<T> callable)
284             throws Exception {
285         boolean quitLooper = false;
286         Looper looper = Looper.getMainLooper();
287         if (looper == null) {
288             Log.i(TAG, "preparing main looper");
289             Looper.prepareMainLooper();
290             looper = Looper.getMainLooper();
291             assertWithMessage("Looper.getMainLooper()").that(looper).isNotNull();
292             quitLooper = true;
293         }
294         Log.i(TAG, "looper: " + looper);
295         AtomicReference<Exception> exception = new AtomicReference<>();
296         AtomicReference<T> ref = new AtomicReference<>();
297         try {
298             Handler handler = new Handler(looper);
299             CountDownLatch latch = new CountDownLatch(1);
300             handler.post(() -> {
301                 T result = null;
302                 try {
303                     result = callable.call();
304                 } catch (Exception e) {
305                     exception.set(e);
306                 }
307                 ref.set(result);
308                 latch.countDown();
309             });
310             JavaMockitoHelper.await(latch, timeoutMs);
311             Exception e = exception.get();
312             if (e != null) throw e;
313             return ref.get();
314         } finally {
315             if (quitLooper) {
316                 Log.i(TAG, "quitting looper: " + looper);
317                 looper.quitSafely();
318             }
319         }
320     }
321 
322     // TODO(b/192307581): add unit tests
323     /**
324      * Runs the giving {@code runnable} in the activity's UI thread, using a default timeout.
325      */
syncRunOnUiThread(Activity activity, Runnable runnable)326     public static void syncRunOnUiThread(Activity activity, Runnable runnable) throws Exception {
327         syncRunOnUiThread(JavaMockitoHelper.ASYNC_TIMEOUT_MS, activity, runnable);
328     }
329 
330     // TODO(b/192307581): add unit tests
331     /**
332      * Runs the giving {@code runnable} in the activity's UI thread.
333      */
syncRunOnUiThread(long timeoutMs, Activity activity, Runnable runnable)334     public static void syncRunOnUiThread(long timeoutMs, Activity activity, Runnable runnable)
335             throws Exception {
336         CountDownLatch latch = new CountDownLatch(1);
337         activity.runOnUiThread(() -> {
338             runnable.run();
339             latch.countDown();
340         });
341         JavaMockitoHelper.await(latch, timeoutMs);
342     }
343 
AndroidMockitoHelper()344     private AndroidMockitoHelper() {
345         throw new UnsupportedOperationException("contains only static methods");
346     }
347 }
348