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