• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.providers.media.photopicker.data;
18 
19 import static androidx.core.util.Preconditions.checkNotNull;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.PackageManager;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.util.Log;
31 
32 import androidx.lifecycle.MutableLiveData;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.providers.media.photopicker.data.model.UserId;
36 import com.android.providers.media.photopicker.util.CrossProfileUtils;
37 
38 import java.util.List;
39 
40 /**
41  * Interface to query user ids {@link UserId}
42  */
43 public interface UserIdManager {
44 
45     /**
46      * Whether there are more than 1 user profiles associated with the current user.
47      * @return
48      */
isMultiUserProfiles()49     boolean isMultiUserProfiles();
50 
51     /**
52      * Returns the personal user profile id iff there are at least 2 user profiles for current
53      * user. Otherwise, returns null.
54      */
55     @Nullable
getPersonalUserId()56     UserId getPersonalUserId();
57 
58     /**
59      * Returns the managed user profile id iff there are at least 2 user profiles for current user.
60      * Otherwise, returns null.
61      */
62     @Nullable
getManagedUserId()63     UserId getManagedUserId();
64 
65     /**
66      * Returns the current user profile id. This can be managed user profile id, personal user
67      * profile id. If the user does not have a corresponding managed profile, then this always
68      * returns the current user.
69      */
70     @Nullable
getCurrentUserProfileId()71     UserId getCurrentUserProfileId();
72 
73     /**
74      * Set Managed User as current user profile
75      */
setManagedAsCurrentUserProfile()76     void setManagedAsCurrentUserProfile();
77 
78     /**
79      * Set Personal User as current user profile
80      */
setPersonalAsCurrentUserProfile()81     void setPersonalAsCurrentUserProfile();
82 
83     /**
84      * @return true iff current user is the current user profile selected
85      */
isCurrentUserSelected()86     boolean isCurrentUserSelected();
87 
88     /**
89      * @return true iff managed user is the current user profile selected
90      */
isManagedUserSelected()91     boolean isManagedUserSelected();
92 
93     /**
94      * Whether the current user is the personal user profile iff there are at least 2 user
95      * profiles for current user. Otherwise, returns false.
96      */
isPersonalUserId()97     boolean isPersonalUserId();
98 
99     /**
100      * Whether the current user is the managed user profile iff there are at least 2 user
101      * profiles for current user. Otherwise, returns false.
102      */
isManagedUserId()103     boolean isManagedUserId();
104 
105     /**
106      * Whether the current user is allowed to access other profile data.
107      */
isCrossProfileAllowed()108     boolean isCrossProfileAllowed();
109 
110     /**
111      * Whether cross profile access is blocked by admin for the current user.
112      */
isBlockedByAdmin()113     boolean isBlockedByAdmin();
114 
115     /**
116      * Whether the work profile corresponding to the current user is turned off.
117      */
isWorkProfileOff()118     boolean isWorkProfileOff();
119 
120     /**
121      * Set intent to check for device admin policy.
122      */
setIntentAndCheckRestrictions(Intent intent)123     void setIntentAndCheckRestrictions(Intent intent);
124 
125     /**
126      * Waits for Media Provider of the work profile to be available.
127      */
waitForMediaProviderToBeAvailable()128     void waitForMediaProviderToBeAvailable();
129 
130     /**
131      * Checks if work profile is switched off and updates the data.
132      */
updateWorkProfileOffValue()133     void updateWorkProfileOffValue();
134 
135     /**
136      * Resets the user ids. This is usually called as a result of receiving broadcast that
137      * managed profile has been added or removed.
138      */
resetUserIds()139     void resetUserIds();
140 
141     /**
142      * @return {@link MutableLiveData} to check if cross profile interaction allowed or not
143      */
144     @NonNull
getCrossProfileAllowed()145     MutableLiveData<Boolean> getCrossProfileAllowed();
146 
147     /**
148      * @return {@link MutableLiveData} to check if there are multiple user profiles or not
149      */
150     @NonNull
getIsMultiUserProfiles()151     MutableLiveData<Boolean> getIsMultiUserProfiles();
152 
153     /**
154      * Creates an implementation of {@link UserIdManager}.
155      */
create(Context context)156     static UserIdManager create(Context context) {
157         return new RuntimeUserIdManager(context);
158     }
159 
160     /**
161      * Implementation of {@link UserIdManager}. The class assumes that all its public methods are
162      * called from main thread only.
163      */
164     final class RuntimeUserIdManager implements UserIdManager {
165 
166         private static final String TAG = "UserIdManager";
167 
168         // These values are copied from DocumentsUI
169         private static final int PROVIDER_AVAILABILITY_MAX_RETRIES = 10;
170         private static final long PROVIDER_AVAILABILITY_CHECK_DELAY = 4000;
171 
172         private final Context mContext;
173         private final UserId mCurrentUser;
174         private final Handler mHandler;
175 
176         private Runnable mIsProviderAvailableRunnable;
177 
178         private UserId mPersonalUser = null;
179         private UserId mManagedUser = null;
180 
181         private UserId mCurrentUserProfile = null;
182 
183         // Set default values to negative case, only set as false if checks pass.
184         private boolean mIsBlockedByAdmin = true;
185         private boolean mIsWorkProfileOff = true;
186 
187         private final MutableLiveData<Boolean> mIsMultiUserProfiles = new MutableLiveData<>();
188         private final MutableLiveData<Boolean> mIsCrossProfileAllowed = new MutableLiveData<>();
189 
RuntimeUserIdManager(Context context)190         private RuntimeUserIdManager(Context context) {
191             this(context, UserId.CURRENT_USER);
192         }
193 
194         @VisibleForTesting
RuntimeUserIdManager(Context context, UserId currentUser)195         RuntimeUserIdManager(Context context, UserId currentUser) {
196             mContext = context.getApplicationContext();
197             mCurrentUser = checkNotNull(currentUser);
198             mCurrentUserProfile = mCurrentUser;
199             mHandler = new Handler(Looper.getMainLooper());
200             setUserIds();
201         }
202 
203         @Override
getCrossProfileAllowed()204         public MutableLiveData<Boolean> getCrossProfileAllowed() {
205             return mIsCrossProfileAllowed;
206         }
207 
208         @Override
getIsMultiUserProfiles()209         public MutableLiveData<Boolean> getIsMultiUserProfiles() {
210             return mIsMultiUserProfiles;
211         }
212 
213         @Override
resetUserIds()214         public void resetUserIds() {
215             assertMainThread();
216             setUserIds();
217         }
218 
219         @Override
isMultiUserProfiles()220         public boolean isMultiUserProfiles() {
221             assertMainThread();
222             return mPersonalUser != null;
223         }
224 
225         @Override
getPersonalUserId()226         public UserId getPersonalUserId() {
227             assertMainThread();
228             return mPersonalUser;
229         }
230 
231         @Override
getManagedUserId()232         public UserId getManagedUserId() {
233             assertMainThread();
234             return mManagedUser;
235         }
236 
237         @Override
getCurrentUserProfileId()238         public UserId getCurrentUserProfileId() {
239             assertMainThread();
240             return mCurrentUserProfile;
241         }
242 
243         @Override
setManagedAsCurrentUserProfile()244         public void setManagedAsCurrentUserProfile() {
245             assertMainThread();
246             setCurrentUserProfileId(getManagedUserId());
247         }
248 
249         @Override
setPersonalAsCurrentUserProfile()250         public void setPersonalAsCurrentUserProfile() {
251             assertMainThread();
252             setCurrentUserProfileId(getPersonalUserId());
253         }
254 
255         @Override
setIntentAndCheckRestrictions(Intent intent)256         public void setIntentAndCheckRestrictions(Intent intent) {
257             assertMainThread();
258             if (isMultiUserProfiles()) {
259                 updateCrossProfileValues(intent);
260             }
261         }
262 
isCurrentUserSelected()263         public boolean isCurrentUserSelected() {
264             assertMainThread();
265             return mCurrentUserProfile.equals(UserId.CURRENT_USER);
266         }
267 
isManagedUserSelected()268         public boolean isManagedUserSelected() {
269             assertMainThread();
270             return mCurrentUserProfile.equals(getManagedUserId());
271         }
272 
273         @Override
isPersonalUserId()274         public boolean isPersonalUserId() {
275             assertMainThread();
276             return mCurrentUser.equals(getPersonalUserId());
277         }
278 
279         @Override
isManagedUserId()280         public boolean isManagedUserId() {
281             assertMainThread();
282             return mCurrentUser.equals(getManagedUserId());
283         }
284 
setCurrentUserProfileId(UserId userId)285         private void setCurrentUserProfileId(UserId userId) {
286             mCurrentUserProfile = userId;
287         }
288 
setUserIds()289         private void setUserIds() {
290             setUserIdsInternal();
291             mIsMultiUserProfiles.postValue(isMultiUserProfiles());
292         }
293 
setUserIdsInternal()294         private void setUserIdsInternal() {
295             mPersonalUser = null;
296             mManagedUser = null;
297             UserManager userManager =  mContext.getSystemService(UserManager.class);
298             if (userManager == null) {
299                 Log.e(TAG, "Cannot obtain user manager");
300                 return;
301             }
302 
303             final List<UserHandle> userProfiles = userManager.getUserProfiles();
304             if (userProfiles.size() < 2) {
305                 Log.d(TAG, "Only 1 user profile found");
306                 return;
307             }
308 
309             if (mCurrentUser.isManagedProfile(userManager)) {
310                 final UserId managedUser = mCurrentUser;
311                 final UserHandle parentUser =
312                         userManager.getProfileParent(managedUser.getUserHandle());
313                 if (parentUser != null) {
314                     mPersonalUser = UserId.of(parentUser);
315                     mManagedUser = managedUser;
316                 }
317 
318             } else {
319                 final UserId personalUser = mCurrentUser;
320                 // Check if this personal profile is a parent of any other managed profile.
321                 for (UserHandle userHandle : userProfiles) {
322                     if (userManager.isManagedProfile(userHandle.getIdentifier())) {
323                         final UserHandle parentUser =
324                                 userManager.getProfileParent(userHandle);
325                         if (parentUser != null &&
326                                 parentUser.equals(personalUser.getUserHandle())) {
327                             mPersonalUser = personalUser;
328                             mManagedUser = UserId.of(userHandle);
329                         }
330                     }
331                 }
332             }
333         }
334 
335         @Override
isCrossProfileAllowed()336         public boolean isCrossProfileAllowed() {
337             assertMainThread();
338             return (!isWorkProfileOff() && !isBlockedByAdmin());
339         }
340 
341         @Override
isWorkProfileOff()342         public boolean isWorkProfileOff() {
343             assertMainThread();
344             return mIsWorkProfileOff;
345         }
346 
347         @Override
isBlockedByAdmin()348         public boolean isBlockedByAdmin() {
349             assertMainThread();
350             return mIsBlockedByAdmin;
351         }
352 
353         @Override
updateWorkProfileOffValue()354         public void updateWorkProfileOffValue() {
355             assertMainThread();
356             mIsWorkProfileOff = isWorkProfileOffInternal(getManagedUserId());
357             mIsCrossProfileAllowed.postValue(isCrossProfileAllowed());
358         }
359 
360         @Override
waitForMediaProviderToBeAvailable()361         public void waitForMediaProviderToBeAvailable() {
362             assertMainThread();
363             final UserId managedUserProfileId = getManagedUserId();
364             if (CrossProfileUtils.isMediaProviderAvailable(managedUserProfileId, mContext)) {
365                 mIsWorkProfileOff = false;
366                 mIsCrossProfileAllowed.postValue(isCrossProfileAllowed());
367                 stopWaitingForProviderToBeAvailable();
368                 return;
369             }
370             waitForProviderToBeAvailable(managedUserProfileId, /* numOfTries */ 1);
371         }
372 
updateCrossProfileValues(Intent intent)373         private void updateCrossProfileValues(Intent intent) {
374             setCrossProfileValues(intent);
375             mIsCrossProfileAllowed.postValue(isCrossProfileAllowed());
376         }
377 
setCrossProfileValues(Intent intent)378         private void setCrossProfileValues(Intent intent) {
379             // 1. Check if PICK_IMAGES intent is allowed by admin to show cross user content
380             setBlockedByAdminValue(intent);
381 
382             // 2. Check if work profile is off
383             updateWorkProfileOffValue();
384 
385             // 3. For first initial setup, wait for MediaProvider to be on.
386             // (This is not blocking)
387             if (mIsWorkProfileOff) {
388                 waitForMediaProviderToBeAvailable();
389             }
390         }
391 
setBlockedByAdminValue(Intent intent)392         private void setBlockedByAdminValue(Intent intent) {
393             if (intent == null) {
394                 Log.e(TAG, "No intent specified to check if cross profile forwarding is"
395                         + " allowed.");
396                 return;
397             }
398             final PackageManager packageManager = mContext.getPackageManager();
399             if (!CrossProfileUtils.isIntentAllowedCrossProfileAccess(intent, packageManager)) {
400                 mIsBlockedByAdmin = true;
401                 return;
402             }
403             mIsBlockedByAdmin = false;
404         }
405 
isWorkProfileOffInternal(UserId managedUserProfileId)406         private boolean isWorkProfileOffInternal(UserId managedUserProfileId) {
407             return CrossProfileUtils.isQuietModeEnabled(managedUserProfileId, mContext) ||
408                     !CrossProfileUtils.isMediaProviderAvailable(managedUserProfileId, mContext);
409         }
410 
waitForProviderToBeAvailable(UserId userId, int numOfTries)411         private void waitForProviderToBeAvailable(UserId userId, int numOfTries) {
412             // The runnable should make sure to post update on the live data if it is changed.
413             mIsProviderAvailableRunnable = () -> {
414                 // We stop the recursive check when
415                 // 1. the provider is available
416                 // 2. the profile is in quiet mode, i.e. provider will not be available
417                 // 3. after maximum retries
418                 if (CrossProfileUtils.isMediaProviderAvailable(userId, mContext)) {
419                     mIsWorkProfileOff = false;
420                     mIsCrossProfileAllowed.postValue(isCrossProfileAllowed());
421                     return;
422                 }
423 
424                 if (CrossProfileUtils.isQuietModeEnabled(userId, mContext)) {
425                     return;
426                 }
427 
428                 if (numOfTries <= PROVIDER_AVAILABILITY_MAX_RETRIES) {
429                     Log.d(TAG, "MediaProvider is not available. Retry after " +
430                             PROVIDER_AVAILABILITY_CHECK_DELAY);
431                     waitForProviderToBeAvailable(userId, numOfTries + 1);
432                     return;
433                 }
434 
435                 Log.w(TAG, "Failed waiting for MediaProvider for user:" + userId +
436                         " to be available");
437             };
438 
439             mHandler.postDelayed(mIsProviderAvailableRunnable, PROVIDER_AVAILABILITY_CHECK_DELAY);
440         }
441 
stopWaitingForProviderToBeAvailable()442         private void stopWaitingForProviderToBeAvailable() {
443             if (mIsProviderAvailableRunnable == null) {
444                 return;
445             }
446             mHandler.removeCallbacks(mIsProviderAvailableRunnable);
447             mIsProviderAvailableRunnable = null;
448         }
449 
assertMainThread()450         private void assertMainThread() {
451             if (Looper.getMainLooper().isCurrentThread()) return;
452 
453             throw new IllegalStateException("UserIdManager methods are expected to be called from "
454                     + "main thread. " + (Looper.myLooper() == null ? "" : "Current thread "
455                     + Looper.myLooper().getThread() + ", Main thread "
456                     + Looper.getMainLooper().getThread()));
457         }
458     }
459 }
460