1 /*
2  * Copyright (C) 2016 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.settings.testutils.shadow;
18 
19 import static android.os.Build.VERSION_CODES.LOLLIPOP;
20 import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
21 import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
22 
23 import android.annotation.UserIdInt;
24 import android.content.pm.UserInfo;
25 import android.content.pm.UserProperties;
26 import android.os.Bundle;
27 import android.os.UserHandle;
28 import android.os.UserManager;
29 import android.os.UserManager.EnforcingUser;
30 
31 import com.google.android.collect.Maps;
32 import com.google.common.collect.BiMap;
33 import com.google.common.collect.HashBiMap;
34 import com.google.common.collect.ImmutableList;
35 
36 import org.robolectric.RuntimeEnvironment;
37 import org.robolectric.annotation.Implementation;
38 import org.robolectric.annotation.Implements;
39 import org.robolectric.annotation.Resetter;
40 import org.robolectric.shadow.api.Shadow;
41 
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Collections;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 
51 @Implements(value = UserManager.class)
52 public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager {
53 
54     private static boolean sIsSupportsMultipleUsers;
55     private static boolean sIsMultipleAdminEnabled = false;
56 
57     private static final int PRIMARY_USER_ID = 0;
58 
59     private final List<String> mBaseRestrictions = new ArrayList<>();
60     private final Map<String, List<EnforcingUser>> mRestrictionSources = new HashMap<>();
61     private final List<UserInfo> mUserProfileInfos = new ArrayList<>();
62     private final Set<Integer> mManagedProfiles = new HashSet<>();
63     private final Map<Integer, Integer> mProfileToParent = new HashMap<>();
64     private final Map<Integer, UserInfo> mUserInfoMap = new HashMap<>();
65     private final Set<String> mEnabledTypes = new HashSet<>();
66     private BiMap<UserHandle, Long> mUserProfiles = HashBiMap.create();
67     private boolean mIsQuietModeEnabled = false;
68     private int[] mProfileIdsForUser = new int[0];
69     private boolean mUserSwitchEnabled;
70     private Bundle mDefaultGuestUserRestriction = new Bundle();
71     private boolean mIsGuestUser = false;
72     private long mNextUserSerial = 0;
73 
74     private @UserManager.UserSwitchabilityResult int mSwitchabilityStatus =
75             UserManager.SWITCHABILITY_STATUS_OK;
76     private final Map<Integer, Integer> mSameProfileGroupIds = Maps.newHashMap();
77 
addProfile(UserInfo userInfo)78     public void addProfile(UserInfo userInfo) {
79         mUserProfileInfos.add(userInfo);
80         mUserInfoMap.put(userInfo.id, userInfo);
81     }
82 
83     @Resetter
reset()84     public static void reset() {
85         sIsSupportsMultipleUsers = false;
86     }
87 
88     /**
89      * Creates a user with the specified name, userId and flags.
90      *
91      * @param id the unique id of user
92      * @param name name of the user
93      * @param flags 16 bits for user type. See {@link UserInfo#flags}
94      */
addUser(int id, String name, int flags)95     @Override public UserHandle addUser(int id, String name, int flags) {
96         UserHandle userHandle = super.addUser(id, name, flags);
97         mUserInfoMap.put(id, new UserInfo(id, name, flags));
98         return userHandle;
99     }
100 
101     /** Add a profile to be returned by {@link #getProfiles(int)}. */
addProfile( int userHandle, int profileUserHandle, String profileName, int profileFlags)102     public void addProfile(
103             int userHandle, int profileUserHandle, String profileName, int profileFlags) {
104         UserInfo profileUserInfo = new UserInfo(profileUserHandle, profileName, profileFlags);
105         addProfile(profileUserInfo);
106         mProfileToParent.put(profileUserHandle, userHandle);
107         if (profileFlags == UserInfo.FLAG_MANAGED_PROFILE) {
108             setManagedProfiles(new HashSet<>(Arrays.asList(profileUserHandle)));
109         }
110     }
111 
112     @Implementation
getProfiles(@serIdInt int userHandle)113     protected List<UserInfo> getProfiles(@UserIdInt int userHandle) {
114         return mUserProfileInfos;
115     }
116 
117     /**
118      * If this profile has been added using {@link #addProfile}, return its parent.
119      */
120     @Implementation(minSdk = LOLLIPOP)
getProfileParent(int userHandle)121     protected UserInfo getProfileParent(int userHandle) {
122         if (!mProfileToParent.containsKey(userHandle)) {
123             return null;
124         }
125         return mUserInfoMap.get(mProfileToParent.get(userHandle));
126     }
127 
128     @Implementation
getProfileIds(@serIdInt int userHandle, boolean enabledOnly)129     protected int[] getProfileIds(@UserIdInt int userHandle, boolean enabledOnly) {
130         int[] ids = new int[mUserProfileInfos.size()];
131         for (int i = 0; i < mUserProfileInfos.size(); i++) {
132             ids[i] = mUserProfileInfos.get(i).id;
133         }
134         return ids;
135     }
136 
137     @Implementation
getCredentialOwnerProfile(@serIdInt int userHandle)138     protected int getCredentialOwnerProfile(@UserIdInt int userHandle) {
139         return userHandle;
140     }
141 
142     @Implementation
hasBaseUserRestriction(String restrictionKey, UserHandle userHandle)143     protected boolean hasBaseUserRestriction(String restrictionKey, UserHandle userHandle) {
144         return mBaseRestrictions.contains(restrictionKey);
145     }
146 
addBaseUserRestriction(String restriction)147     public void addBaseUserRestriction(String restriction) {
148         mBaseRestrictions.add(restriction);
149     }
150 
151     @Implementation
getDefaultGuestRestrictions()152     protected Bundle getDefaultGuestRestrictions() {
153         return mDefaultGuestUserRestriction;
154     }
155 
156     @Implementation
setDefaultGuestRestrictions(Bundle restrictions)157     protected void setDefaultGuestRestrictions(Bundle restrictions) {
158         mDefaultGuestUserRestriction = restrictions;
159     }
160 
161     @Implementation
getUserProperties(UserHandle userHandle)162     protected UserProperties getUserProperties(UserHandle userHandle) {
163         return new UserProperties.Builder().build();
164     }
165 
166 
addGuestUserRestriction(String restriction)167     public void addGuestUserRestriction(String restriction) {
168         mDefaultGuestUserRestriction.putBoolean(restriction, true);
169     }
170 
hasGuestUserRestriction(String restriction, boolean expectedValue)171     public boolean hasGuestUserRestriction(String restriction, boolean expectedValue) {
172         return mDefaultGuestUserRestriction.containsKey(restriction)
173                 && mDefaultGuestUserRestriction.getBoolean(restriction) == expectedValue;
174     }
175 
176 
177     @Implementation
hasUserRestriction(String restrictionKey)178     protected boolean hasUserRestriction(String restrictionKey) {
179         return hasUserRestriction(restrictionKey, UserHandle.of(UserHandle.myUserId()));
180     }
181 
getShadow()182     public static ShadowUserManager getShadow() {
183         return Shadow.extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
184     }
185 
186     @Implementation
getUserRestrictionSources( String restrictionKey, UserHandle userHandle)187     protected List<EnforcingUser> getUserRestrictionSources(
188             String restrictionKey, UserHandle userHandle) {
189         // Return empty list when there is no enforcing user, otherwise might trigger
190         // NullPointer Exception in RestrictedLockUtils.checkIfRestrictionEnforced.
191         List<EnforcingUser> enforcingUsers =
192                 mRestrictionSources.get(restrictionKey + userHandle.getIdentifier());
193         return enforcingUsers == null ? Collections.emptyList() : enforcingUsers;
194     }
195 
setUserRestrictionSources( String restrictionKey, UserHandle userHandle, List<EnforcingUser> enforcers)196     public void setUserRestrictionSources(
197             String restrictionKey, UserHandle userHandle, List<EnforcingUser> enforcers) {
198         mRestrictionSources.put(restrictionKey + userHandle.getIdentifier(), enforcers);
199     }
200 
201     @Implementation
isQuietModeEnabled(UserHandle userHandle)202     protected boolean isQuietModeEnabled(UserHandle userHandle) {
203         return mIsQuietModeEnabled;
204     }
205 
setQuietModeEnabled(boolean enabled)206     public void setQuietModeEnabled(boolean enabled) {
207         mIsQuietModeEnabled = enabled;
208     }
209 
210     @Implementation
getProfileIdsWithDisabled(@serIdInt int userId)211     protected int[] getProfileIdsWithDisabled(@UserIdInt int userId) {
212         return mProfileIdsForUser;
213     }
214 
setProfileIdsWithDisabled(int[] profileIds)215     public void setProfileIdsWithDisabled(int[] profileIds) {
216         mProfileIdsForUser = profileIds;
217     }
218 
219     @Implementation
isUserSwitcherEnabled()220     protected boolean isUserSwitcherEnabled() {
221         return mUserSwitchEnabled;
222     }
223 
224     @Implementation
isManagedProfile(int userId)225     protected boolean isManagedProfile(int userId) {
226         return mManagedProfiles.contains(userId);
227     }
228 
setManagedProfiles(Set<Integer> profileIds)229     public void setManagedProfiles(Set<Integer> profileIds) {
230         mManagedProfiles.clear();
231         mManagedProfiles.addAll(profileIds);
232         profileIds.forEach(id -> {
233             addProfile(new UserInfo(id, "managed" + id, null, 0, USER_TYPE_PROFILE_MANAGED));
234             mProfileToParent.put(id, PRIMARY_USER_ID);
235         });
236         addPrimaryUser();
237     }
238 
setPrivateProfile(int id, String name, int flags)239     public void setPrivateProfile(int id, String name, int flags) {
240         addProfile(new UserInfo(id, name, null, flags, USER_TYPE_PROFILE_PRIVATE));
241         mProfileToParent.put(id, PRIMARY_USER_ID);
242         addPrimaryUser();
243     }
244 
addPrimaryUser()245     private void addPrimaryUser() {
246         if (mUserInfoMap.get(PRIMARY_USER_ID) == null) {
247             addProfile(getPrimaryUser());
248         }
249     }
250 
setUserSwitcherEnabled(boolean userSwitchEnabled)251     public void setUserSwitcherEnabled(boolean userSwitchEnabled) {
252         mUserSwitchEnabled = userSwitchEnabled;
253     }
254 
255     @Implementation
supportsMultipleUsers()256     protected static boolean supportsMultipleUsers() {
257         return sIsSupportsMultipleUsers;
258     }
259 
260     @Implementation
isSameProfileGroup(@serIdInt int userId, int otherUserId)261     protected boolean isSameProfileGroup(@UserIdInt int userId, int otherUserId) {
262         return mSameProfileGroupIds.containsKey(userId)
263                 && mSameProfileGroupIds.get(userId) == otherUserId
264                 || mSameProfileGroupIds.containsKey(otherUserId)
265                 && mSameProfileGroupIds.get(otherUserId) == userId;
266     }
267 
getSameProfileGroupIds()268     public Map<Integer, Integer> getSameProfileGroupIds() {
269         return mSameProfileGroupIds;
270     }
271 
setSupportsMultipleUsers(boolean supports)272     public void setSupportsMultipleUsers(boolean supports) {
273         sIsSupportsMultipleUsers = supports;
274     }
275 
276     @Implementation
getUserInfo(@serIdInt int userId)277     protected UserInfo getUserInfo(@UserIdInt int userId) {
278         return mUserProfileInfos.stream()
279                 .filter(userInfo -> userInfo.id == userId)
280                 .findFirst()
281                 .orElse(super.getUserInfo(userId));
282     }
283 
284     @Implementation
getUserSwitchability()285     protected @UserManager.UserSwitchabilityResult int getUserSwitchability() {
286         return mSwitchabilityStatus;
287     }
288 
setSwitchabilityStatus(@serManager.UserSwitchabilityResult int newStatus)289     public void setSwitchabilityStatus(@UserManager.UserSwitchabilityResult int newStatus) {
290         mSwitchabilityStatus = newStatus;
291     }
292 
293     @Implementation
isUserTypeEnabled(String userType)294     protected boolean isUserTypeEnabled(String userType) {
295         return mEnabledTypes.contains(userType);
296     }
297 
setUserTypeEnabled(String type, boolean enabled)298     public void setUserTypeEnabled(String type, boolean enabled) {
299         if (enabled) {
300             mEnabledTypes.add(type);
301         } else {
302             mEnabledTypes.remove(type);
303         }
304     }
305 
306     @Implementation
getPrimaryUser()307     protected UserInfo getPrimaryUser() {
308         return new UserInfo(PRIMARY_USER_ID, null, null,
309                 UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
310     }
311 
312     @Implementation
getMainUser()313     protected UserHandle getMainUser() {
314         return UserHandle.of(PRIMARY_USER_ID);
315     }
316 
setUserEphemeral(@serIdInt int userId, boolean enableEphemeral)317     protected boolean setUserEphemeral(@UserIdInt int userId, boolean enableEphemeral) {
318         UserInfo userInfo = mUserProfileInfos.stream()
319                 .filter(user -> user.id == userId)
320                 .findFirst()
321                 .orElse(super.getUserInfo(userId));
322 
323         boolean isSuccess = false;
324         boolean isEphemeralUser =
325                         (userInfo.flags & UserInfo.FLAG_EPHEMERAL) != 0;
326         boolean isEphemeralOnCreateUser =
327                 (userInfo.flags & UserInfo.FLAG_EPHEMERAL_ON_CREATE)
328                     != 0;
329         // when user is created in ephemeral mode via FLAG_EPHEMERAL
330         // its state cannot be changed.
331         // FLAG_EPHEMERAL_ON_CREATE is used to keep track of this state
332         if (!isEphemeralOnCreateUser) {
333             isSuccess = true;
334             if (isEphemeralUser != enableEphemeral) {
335                 if (enableEphemeral) {
336                     userInfo.flags |= UserInfo.FLAG_EPHEMERAL;
337                 } else {
338                     userInfo.flags &= ~UserInfo.FLAG_EPHEMERAL;
339                 }
340             }
341         }
342         return isSuccess;
343     }
344 
345     @Implementation
setUserAdmin(@serIdInt int userId)346     protected void setUserAdmin(@UserIdInt int userId) {
347         for (int i = 0; i < mUserProfileInfos.size(); i++) {
348             mUserProfileInfos.get(i).flags |= UserInfo.FLAG_ADMIN;
349         }
350     }
351 
352     /**
353      * Sets that the current user is an admin user; controls the return value of
354      * {@link UserManager#isAdminUser}.
355      */
setIsAdminUser(boolean isAdminUser)356     public void setIsAdminUser(boolean isAdminUser) {
357         UserInfo userInfo = getUserInfo(UserHandle.myUserId());
358         if (isAdminUser) {
359             userInfo.flags |= UserInfo.FLAG_ADMIN;
360         } else {
361             userInfo.flags &= ~UserInfo.FLAG_ADMIN;
362         }
363     }
364 
365     @Implementation
isGuestUser()366     protected boolean isGuestUser() {
367         return mIsGuestUser;
368     }
369 
setGuestUser(boolean isGuestUser)370     public void setGuestUser(boolean isGuestUser) {
371         mIsGuestUser = isGuestUser;
372     }
373 
setIsMultipleAdminEnabled(boolean enableMultipleAdmin)374     public static void setIsMultipleAdminEnabled(boolean enableMultipleAdmin) {
375         sIsMultipleAdminEnabled = enableMultipleAdmin;
376     }
377 
378     /**
379      * Adds a profile associated for the user that the calling process is running on.
380      *
381      * <p>The user is assigned an arbitrary unique serial number.
382      *
383      * @return the user's serial number
384      * @deprecated use either addUser() or addProfile()
385      */
386     @Deprecated
addUserProfile(UserHandle userHandle)387     public long addUserProfile(UserHandle userHandle) {
388         long serialNumber = mNextUserSerial++;
389         mUserProfiles.put(userHandle, serialNumber);
390         return serialNumber;
391     }
392 
getUserProfiles()393     protected List<UserHandle> getUserProfiles() {
394         return ImmutableList.copyOf(mUserProfiles.keySet());
395     }
396 }
397