1 /* 2 * Copyright (C) 2023 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.documentsui; 18 19 import static androidx.core.util.Preconditions.checkNotNull; 20 21 import static com.android.documentsui.DevicePolicyResources.Drawables.Style.SOLID_COLORED; 22 import static com.android.documentsui.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; 23 import static com.android.documentsui.DevicePolicyResources.Strings.PERSONAL_TAB; 24 import static com.android.documentsui.DevicePolicyResources.Strings.WORK_TAB; 25 26 import android.Manifest; 27 import android.annotation.SuppressLint; 28 import android.app.ActivityManager; 29 import android.app.admin.DevicePolicyManager; 30 import android.content.BroadcastReceiver; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.pm.PackageManager; 35 import android.content.pm.ResolveInfo; 36 import android.content.pm.UserProperties; 37 import android.graphics.drawable.Drawable; 38 import android.os.Build; 39 import android.os.UserHandle; 40 import android.os.UserManager; 41 import android.util.Log; 42 43 import androidx.annotation.GuardedBy; 44 import androidx.annotation.RequiresApi; 45 import androidx.annotation.RequiresPermission; 46 import androidx.annotation.VisibleForTesting; 47 48 import com.android.documentsui.base.Features; 49 import com.android.documentsui.base.UserId; 50 import com.android.documentsui.util.VersionUtils; 51 import com.android.modules.utils.build.SdkLevel; 52 53 import com.google.common.base.Objects; 54 55 import java.lang.reflect.Field; 56 import java.util.ArrayList; 57 import java.util.HashMap; 58 import java.util.List; 59 import java.util.Map; 60 61 @RequiresApi(Build.VERSION_CODES.S) 62 public interface UserManagerState { 63 64 /** 65 * Returns the {@link UserId} of each profile which should be queried for documents. This will 66 * always include {@link UserId#CURRENT_USER}. 67 */ getUserIds()68 List<UserId> getUserIds(); 69 70 /** Returns mapping between the {@link UserId} and the label for the profile */ getUserIdToLabelMap()71 Map<UserId, String> getUserIdToLabelMap(); 72 73 /** 74 * Returns mapping between the {@link UserId} and the drawable badge for the profile 75 * 76 * <p>returns {@code null} for non-profile userId 77 */ getUserIdToBadgeMap()78 Map<UserId, Drawable> getUserIdToBadgeMap(); 79 80 /** 81 * Returns a map of {@link UserId} to boolean value indicating whether the {@link 82 * UserId}.CURRENT_USER can forward {@link Intent} to that {@link UserId} 83 */ getCanForwardToProfileIdMap(Intent intent)84 Map<UserId, Boolean> getCanForwardToProfileIdMap(Intent intent); 85 86 /** 87 * Updates the state of the list of userIds and all the associated maps according the intent 88 * received in broadcast 89 * 90 * @param userId {@link UserId} for the profile for which the availability status changed 91 * @param action {@link Intent}.ACTION_PROFILE_UNAVAILABLE and {@link 92 * Intent}.ACTION_PROFILE_AVAILABLE, {@link Intent}.ACTION_PROFILE_ADDED} and {@link 93 * Intent}.ACTION_PROFILE_REMOVED} 94 */ onProfileActionStatusChange(String action, UserId userId)95 void onProfileActionStatusChange(String action, UserId userId); 96 97 /** Sets the intent that triggered the launch of the DocsUI */ setCurrentStateIntent(Intent intent)98 void setCurrentStateIntent(Intent intent); 99 100 /** Returns true if there are hidden profiles */ areHiddenInQuietModeProfilesPresent()101 boolean areHiddenInQuietModeProfilesPresent(); 102 103 /** Creates an implementation of {@link UserManagerState}. */ 104 // TODO: b/314746383 Make this class a singleton create(Context context)105 static UserManagerState create(Context context) { 106 return new RuntimeUserManagerState(context); 107 } 108 109 /** Implementation of {@link UserManagerState} */ 110 final class RuntimeUserManagerState implements UserManagerState { 111 112 private static final String TAG = "UserManagerState"; 113 private final Context mContext; 114 private final UserId mCurrentUser; 115 private final boolean mIsDeviceSupported; 116 private final UserManager mUserManager; 117 private final ConfigStore mConfigStore; 118 119 /** 120 * List of all the {@link UserId} that have the {@link UserProperties.ShowInSharingSurfaces} 121 * set as `SHOW_IN_SHARING_SURFACES_SEPARATE` OR it is a system/personal user 122 */ 123 @GuardedBy("mUserIds") 124 private final List<UserId> mUserIds = new ArrayList<>(); 125 126 /** Mapping between the {@link UserId} to the corresponding profile label */ 127 @GuardedBy("mUserIdToLabelMap") 128 private final Map<UserId, String> mUserIdToLabelMap = new HashMap<>(); 129 130 /** Mapping between the {@link UserId} to the corresponding profile badge */ 131 @GuardedBy("mUserIdToBadgeMap") 132 private final Map<UserId, Drawable> mUserIdToBadgeMap = new HashMap<>(); 133 134 /** 135 * Map containing {@link UserId}, other than that of the current user, as key and boolean 136 * denoting whether it is accessible by the current user or not as value 137 */ 138 @GuardedBy("mCanForwardToProfileIdMap") 139 private final Map<UserId, Boolean> mCanForwardToProfileIdMap = new HashMap<>(); 140 141 private Intent mCurrentStateIntent; 142 143 private final BroadcastReceiver mIntentReceiver = 144 new BroadcastReceiver() { 145 @Override 146 public void onReceive(Context context, Intent intent) { 147 synchronized (mUserIds) { 148 mUserIds.clear(); 149 } 150 synchronized (mUserIdToLabelMap) { 151 mUserIdToLabelMap.clear(); 152 } 153 synchronized (mUserIdToBadgeMap) { 154 mUserIdToBadgeMap.clear(); 155 } 156 synchronized (mCanForwardToProfileIdMap) { 157 mCanForwardToProfileIdMap.clear(); 158 } 159 } 160 }; 161 RuntimeUserManagerState(Context context)162 private RuntimeUserManagerState(Context context) { 163 this( 164 context, 165 UserId.CURRENT_USER, 166 Features.CROSS_PROFILE_TABS && isDeviceSupported(context), 167 DocumentsApplication.getConfigStore()); 168 } 169 170 @VisibleForTesting RuntimeUserManagerState( Context context, UserId currentUser, boolean isDeviceSupported, ConfigStore configStore)171 RuntimeUserManagerState( 172 Context context, 173 UserId currentUser, 174 boolean isDeviceSupported, 175 ConfigStore configStore) { 176 mContext = context.getApplicationContext(); 177 mCurrentUser = checkNotNull(currentUser); 178 mIsDeviceSupported = isDeviceSupported; 179 mUserManager = mContext.getSystemService(UserManager.class); 180 mConfigStore = configStore; 181 182 IntentFilter filter = new IntentFilter(); 183 filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); 184 filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); 185 if (SdkLevel.isAtLeastV() && mConfigStore.isPrivateSpaceInDocsUIEnabled()) { 186 filter.addAction(Intent.ACTION_PROFILE_ADDED); 187 filter.addAction(Intent.ACTION_PROFILE_REMOVED); 188 } 189 mContext.registerReceiver(mIntentReceiver, filter); 190 } 191 192 @Override getUserIds()193 public List<UserId> getUserIds() { 194 synchronized (mUserIds) { 195 if (mUserIds.isEmpty()) { 196 mUserIds.addAll(getUserIdsInternal()); 197 } 198 return mUserIds; 199 } 200 } 201 202 @Override getUserIdToLabelMap()203 public Map<UserId, String> getUserIdToLabelMap() { 204 synchronized (mUserIdToLabelMap) { 205 if (mUserIdToLabelMap.isEmpty()) { 206 getUserIdToLabelMapInternal(); 207 } 208 return mUserIdToLabelMap; 209 } 210 } 211 212 @Override getUserIdToBadgeMap()213 public Map<UserId, Drawable> getUserIdToBadgeMap() { 214 synchronized (mUserIdToBadgeMap) { 215 if (mUserIdToBadgeMap.isEmpty()) { 216 getUserIdToBadgeMapInternal(); 217 } 218 return mUserIdToBadgeMap; 219 } 220 } 221 222 @Override getCanForwardToProfileIdMap(Intent intent)223 public Map<UserId, Boolean> getCanForwardToProfileIdMap(Intent intent) { 224 synchronized (mCanForwardToProfileIdMap) { 225 if (mCanForwardToProfileIdMap.isEmpty()) { 226 getCanForwardToProfileIdMapInternal(intent); 227 } 228 return mCanForwardToProfileIdMap; 229 } 230 } 231 232 @Override 233 @SuppressLint("NewApi") onProfileActionStatusChange(String action, UserId userId)234 public void onProfileActionStatusChange(String action, UserId userId) { 235 if (!SdkLevel.isAtLeastV()) return; 236 UserProperties userProperties = 237 mUserManager.getUserProperties(UserHandle.of(userId.getIdentifier())); 238 if (userProperties.getShowInQuietMode() != UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) { 239 return; 240 } 241 if (Intent.ACTION_PROFILE_UNAVAILABLE.equals(action) 242 || Intent.ACTION_PROFILE_REMOVED.equals(action)) { 243 synchronized (mUserIds) { 244 mUserIds.remove(userId); 245 } 246 } else if (Intent.ACTION_PROFILE_AVAILABLE.equals(action) 247 || Intent.ACTION_PROFILE_ADDED.equals(action)) { 248 synchronized (mUserIds) { 249 if (!mUserIds.contains(userId)) { 250 mUserIds.add(userId); 251 } 252 } 253 synchronized (mUserIdToLabelMap) { 254 if (!mUserIdToLabelMap.containsKey(userId)) { 255 mUserIdToLabelMap.put(userId, getProfileLabel(userId)); 256 } 257 } 258 synchronized (mUserIdToBadgeMap) { 259 if (!mUserIdToBadgeMap.containsKey(userId)) { 260 mUserIdToBadgeMap.put(userId, getProfileBadge(userId)); 261 } 262 } 263 synchronized (mCanForwardToProfileIdMap) { 264 if (!mCanForwardToProfileIdMap.containsKey(userId)) { 265 mCanForwardToProfileIdMap.put( 266 userId, 267 isCrossProfileAllowedToUser( 268 mContext, 269 mCurrentStateIntent, 270 UserId.CURRENT_USER, 271 userId)); 272 } 273 } 274 } else { 275 Log.e(TAG, "Unexpected action received: " + action); 276 } 277 } 278 279 @Override setCurrentStateIntent(Intent intent)280 public void setCurrentStateIntent(Intent intent) { 281 mCurrentStateIntent = intent; 282 } 283 284 @Override areHiddenInQuietModeProfilesPresent()285 public boolean areHiddenInQuietModeProfilesPresent() { 286 if (!SdkLevel.isAtLeastV()) { 287 return false; 288 } 289 290 for (UserId userId : getUserIds()) { 291 if (mUserManager 292 .getUserProperties(UserHandle.of(userId.getIdentifier())) 293 .getShowInQuietMode() 294 == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) { 295 return true; 296 } 297 } 298 return false; 299 } 300 getUserIdsInternal()301 private List<UserId> getUserIdsInternal() { 302 final List<UserId> result = new ArrayList<>(); 303 304 if (!mIsDeviceSupported) { 305 result.add(mCurrentUser); 306 return result; 307 } 308 309 if (mUserManager == null) { 310 Log.e(TAG, "cannot obtain user manager"); 311 return result; 312 } 313 314 final List<UserHandle> userProfiles = mUserManager.getUserProfiles(); 315 316 result.add(mCurrentUser); 317 boolean currentUserIsManaged = 318 mUserManager.isManagedProfile(mCurrentUser.getIdentifier()); 319 320 for (UserHandle handle : userProfiles) { 321 if (SdkLevel.isAtLeastV()) { 322 if (!isProfileAllowed(handle)) { 323 continue; 324 } 325 } else { 326 // Only allow managed profiles + the parent user on lower than V. 327 if (currentUserIsManaged 328 && mUserManager.getProfileParent(mCurrentUser.getUserHandle()) 329 == handle) { 330 // Intentionally empty so that this profile gets added. 331 } else if (!mUserManager.isManagedProfile(handle.getIdentifier())) { 332 continue; 333 } 334 } 335 336 // Ensure the system user doesn't get added twice. 337 if (result.contains(UserId.of(handle))) continue; 338 result.add(UserId.of(handle)); 339 } 340 341 return result; 342 } 343 344 /** 345 * Checks if a package is installed for a given user. 346 * 347 * @param userHandle The ID of the user. 348 * @return {@code true} if the package is installed for the user, {@code false} otherwise. 349 */ 350 @RequiresPermission( 351 anyOf = { 352 "android.permission.MANAGE_USERS", 353 "android.permission.INTERACT_ACROSS_USERS" 354 }) isPackageInstalledForUser(UserHandle userHandle)355 private boolean isPackageInstalledForUser(UserHandle userHandle) { 356 String packageName = mContext.getPackageName(); 357 try { 358 Context userPackageContext = 359 mContext.createPackageContextAsUser( 360 mContext.getPackageName(), 0 /* flags */, userHandle); 361 return userPackageContext != null; 362 } catch (PackageManager.NameNotFoundException e) { 363 Log.w(TAG, "Package " + packageName + " not found for user " + userHandle); 364 return false; 365 } 366 } 367 368 /** 369 * Checks if quiet mode is enabled for a given user. 370 * 371 * @param userHandle The UserHandle of the profile to check. 372 * @return {@code true} if quiet mode is enabled, {@code false} otherwise. 373 */ isQuietModeEnabledForUser(UserHandle userHandle)374 private boolean isQuietModeEnabledForUser(UserHandle userHandle) { 375 return UserId.of(userHandle.getIdentifier()).isQuietModeEnabled(mContext); 376 } 377 378 /** 379 * Checks if a profile should be allowed, taking into account quiet mode and package 380 * installation. 381 * 382 * @param userHandle The UserHandle of the profile to check. 383 * @return {@code true} if the profile should be allowed, {@code false} otherwise. 384 */ 385 @SuppressLint("NewApi") 386 @RequiresPermission( 387 anyOf = { 388 "android.permission.MANAGE_USERS", 389 "android.permission.INTERACT_ACROSS_USERS" 390 }) isProfileAllowed(UserHandle userHandle)391 private boolean isProfileAllowed(UserHandle userHandle) { 392 final UserProperties userProperties = mUserManager.getUserProperties(userHandle); 393 394 // 1. Check if the package is installed for the user 395 if (!isPackageInstalledForUser(userHandle)) { 396 Log.w( 397 TAG, 398 "Package " 399 + mContext.getPackageName() 400 + " is not installed for user " 401 + userHandle); 402 return false; 403 } 404 405 // 2. Check user properties and quiet mode 406 if (userProperties.getShowInSharingSurfaces() 407 == UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE) { 408 // Return true if profile is not in quiet mode or if it is in quiet mode 409 // then its user properties do not require it to be hidden 410 return !isQuietModeEnabledForUser(userHandle) 411 || userProperties.getShowInQuietMode() 412 != UserProperties.SHOW_IN_QUIET_MODE_HIDDEN; 413 } 414 415 return false; 416 } 417 getUserIdToLabelMapInternal()418 private void getUserIdToLabelMapInternal() { 419 if (SdkLevel.isAtLeastV()) { 420 getUserIdToLabelMapInternalPostV(); 421 } else { 422 getUserIdToLabelMapInternalPreV(); 423 } 424 } 425 426 @SuppressLint("NewApi") getUserIdToLabelMapInternalPostV()427 private void getUserIdToLabelMapInternalPostV() { 428 if (mUserManager == null) { 429 Log.e(TAG, "cannot obtain user manager"); 430 return; 431 } 432 List<UserId> userIds = getUserIds(); 433 for (UserId userId : userIds) { 434 synchronized (mUserIdToLabelMap) { 435 mUserIdToLabelMap.put(userId, getProfileLabel(userId)); 436 } 437 } 438 } 439 getUserIdToLabelMapInternalPreV()440 private void getUserIdToLabelMapInternalPreV() { 441 if (mUserManager == null) { 442 Log.e(TAG, "cannot obtain user manager"); 443 return; 444 } 445 List<UserId> userIds = getUserIds(); 446 for (UserId userId : userIds) { 447 if (mUserManager.isManagedProfile(userId.getIdentifier())) { 448 synchronized (mUserIdToLabelMap) { 449 mUserIdToLabelMap.put( 450 userId, getEnterpriseString(WORK_TAB, R.string.work_tab)); 451 } 452 } else { 453 synchronized (mUserIdToLabelMap) { 454 mUserIdToLabelMap.put( 455 userId, getEnterpriseString(PERSONAL_TAB, R.string.personal_tab)); 456 } 457 } 458 } 459 } 460 461 @SuppressLint("NewApi") getProfileLabel(UserId userId)462 private String getProfileLabel(UserId userId) { 463 if (userId.getIdentifier() == ActivityManager.getCurrentUser()) { 464 return getEnterpriseString(PERSONAL_TAB, R.string.personal_tab); 465 } 466 try { 467 Context userContext = 468 mContext.createContextAsUser( 469 UserHandle.of(userId.getIdentifier()), 0 /* flags */); 470 UserManager userManagerAsUser = userContext.getSystemService(UserManager.class); 471 if (userManagerAsUser == null) { 472 Log.e(TAG, "cannot obtain user manager"); 473 return null; 474 } 475 return userManagerAsUser.getProfileLabel(); 476 } catch (Exception e) { 477 Log.e(TAG, "Exception occurred while trying to get profile label:\n" + e); 478 return null; 479 } 480 } 481 getEnterpriseString(String updatableStringId, int defaultStringId)482 private String getEnterpriseString(String updatableStringId, int defaultStringId) { 483 if (SdkLevel.isAtLeastT()) { 484 return getUpdatableEnterpriseString(updatableStringId, defaultStringId); 485 } else { 486 return mContext.getString(defaultStringId); 487 } 488 } 489 490 @RequiresApi(Build.VERSION_CODES.TIRAMISU) getUpdatableEnterpriseString(String updatableStringId, int defaultStringId)491 private String getUpdatableEnterpriseString(String updatableStringId, int defaultStringId) { 492 DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); 493 if (Objects.equal(dpm, null)) { 494 Log.e(TAG, "can not get device policy manager"); 495 return mContext.getString(defaultStringId); 496 } 497 return dpm.getResources() 498 .getString(updatableStringId, () -> mContext.getString(defaultStringId)); 499 } 500 getUserIdToBadgeMapInternal()501 private void getUserIdToBadgeMapInternal() { 502 if (SdkLevel.isAtLeastV()) { 503 getUserIdToBadgeMapInternalPostV(); 504 } else { 505 getUserIdToBadgeMapInternalPreV(); 506 } 507 } 508 509 @SuppressLint("NewApi") getUserIdToBadgeMapInternalPostV()510 private void getUserIdToBadgeMapInternalPostV() { 511 if (mUserManager == null) { 512 Log.e(TAG, "cannot obtain user manager"); 513 return; 514 } 515 List<UserId> userIds = getUserIds(); 516 for (UserId userId : userIds) { 517 synchronized (mUserIdToBadgeMap) { 518 mUserIdToBadgeMap.put(userId, getProfileBadge(userId)); 519 } 520 } 521 } 522 getUserIdToBadgeMapInternalPreV()523 private void getUserIdToBadgeMapInternalPreV() { 524 if (!SdkLevel.isAtLeastR()) return; 525 if (mUserManager == null) { 526 Log.e(TAG, "cannot obtain user manager"); 527 return; 528 } 529 List<UserId> userIds = getUserIds(); 530 for (UserId userId : userIds) { 531 if (mUserManager.isManagedProfile(userId.getIdentifier())) { 532 synchronized (mUserIdToBadgeMap) { 533 mUserIdToBadgeMap.put( 534 userId, 535 SdkLevel.isAtLeastT() 536 ? getWorkProfileBadge() 537 : mContext.getDrawable(R.drawable.ic_briefcase)); 538 } 539 } 540 } 541 } 542 543 @SuppressLint("NewApi") getProfileBadge(UserId userId)544 private Drawable getProfileBadge(UserId userId) { 545 if (userId.getIdentifier() == ActivityManager.getCurrentUser()) { 546 return null; 547 } 548 try { 549 Context userContext = 550 mContext.createContextAsUser( 551 UserHandle.of(userId.getIdentifier()), 0 /* flags */); 552 UserManager userManagerAsUser = userContext.getSystemService(UserManager.class); 553 if (userManagerAsUser == null) { 554 Log.e(TAG, "cannot obtain user manager"); 555 return null; 556 } 557 return userManagerAsUser.getUserBadge(); 558 } catch (Exception e) { 559 Log.e(TAG, "Exception occurred while trying to get profile badge:\n" + e); 560 return null; 561 } 562 } 563 564 @RequiresApi(Build.VERSION_CODES.TIRAMISU) getWorkProfileBadge()565 private Drawable getWorkProfileBadge() { 566 DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); 567 Drawable drawable = 568 dpm.getResources() 569 .getDrawable( 570 WORK_PROFILE_ICON, 571 SOLID_COLORED, 572 () -> mContext.getDrawable(R.drawable.ic_briefcase)); 573 return drawable; 574 } 575 576 /** 577 * Updates Cross Profile access for all UserProfiles in {@code getUserIds()} 578 * 579 * <p>This method looks at a variety of situations for each Profile and decides if the 580 * profile's content is accessible by the current process owner user id. 581 * 582 * <ol> 583 * <li>UserProperties attributes for CrossProfileDelegation are checked first. When the 584 * profile delegates to the parent profile, the parent's access is used. 585 * <li>{@link CrossProfileIntentForwardingActivity}s are resolved via the process owner's 586 * PackageManager, and are considered when evaluating cross profile to the target 587 * profile. 588 * </ol> 589 * 590 * <p>In the event none of the above checks succeeds, the profile is considered to be 591 * inaccessible to the current process user. 592 * 593 * @param intent The intent Photopicker is currently running under, for 594 * CrossProfileForwardActivity checking. 595 */ getCanForwardToProfileIdMapInternal(Intent intent)596 private void getCanForwardToProfileIdMapInternal(Intent intent) { 597 598 synchronized (mCanForwardToProfileIdMap) { 599 mCanForwardToProfileIdMap.clear(); 600 for (UserId userId : getUserIds()) { 601 mCanForwardToProfileIdMap.put( 602 userId, 603 isCrossProfileAllowedToUser( 604 mContext, intent, mCurrentUser, userId)); 605 } 606 } 607 } 608 609 /** 610 * Determines if the provided UserIds support CrossProfile content sharing. 611 * 612 * <p>This method accepts a pair of user handles (from/to) and determines if CrossProfile 613 * access is permitted between those two profiles. 614 * 615 * <p>There are differences is on how the access is determined based on the platform SDK: 616 * 617 * <p>For Platform SDK < V: 618 * 619 * <p>A check for CrossProfileIntentForwarders in the origin (from) profile that target the 620 * destination (to) profile. If such a forwarder exists, then access is allowed, and denied 621 * otherwise. 622 * 623 * <p>For Platform SDK >= V: 624 * 625 * <p>The method now takes into account access delegation, which was first added in Android 626 * V. 627 * 628 * <p>For profiles that set the [CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT] 629 * property in its [UserProperties], its parent profile will be substituted in for its side 630 * of the check. 631 * 632 * <p>ex. For access checks between a Managed (from) and Private (to) profile, where: - 633 * Managed does not delegate to its parent - Private delegates to its parent 634 * 635 * <p>The following logic is performed: Managed -> parent(Private) 636 * 637 * <p>The same check in the other direction would yield: parent(Private) -> Managed 638 * 639 * <p>Note how the private profile is never actually used for either side of the check, 640 * since it is delegating its access check to the parent. And thus, if Managed can access 641 * the parent, it can also access the private. 642 * 643 * @param context Current context object, for switching user contexts. 644 * @param intent The current intent the Photopicker is running under. 645 * @param fromUser The Origin profile, where the user is coming from 646 * @param toUser The destination profile, where the user is attempting to go to. 647 * @return Whether CrossProfile content sharing is supported in this handle. 648 */ isCrossProfileAllowedToUser( Context context, Intent intent, UserId fromUser, UserId toUser)649 private boolean isCrossProfileAllowedToUser( 650 Context context, Intent intent, UserId fromUser, UserId toUser) { 651 652 // Early exit conditions, accessing self. 653 // NOTE: It is also possible to reach this state if this method is recursively checking 654 // from: parent(A) to:parent(B) where A and B are both children of the same parent. 655 if (fromUser.getIdentifier() == toUser.getIdentifier()) { 656 return true; 657 } 658 659 // Decide if we should use actual from or parent(from) 660 UserHandle currentFromUser = 661 getProfileToCheckCrossProfileAccess(fromUser.getUserHandle()); 662 663 // Decide if we should use actual to or parent(to) 664 UserHandle currentToUser = getProfileToCheckCrossProfileAccess(toUser.getUserHandle()); 665 666 // When the from/to has changed from the original parameters, recursively restart the 667 // checks with the new from/to handles. 668 if (fromUser.getIdentifier() != currentFromUser.getIdentifier() 669 || toUser.getIdentifier() != currentToUser.getIdentifier()) { 670 return isCrossProfileAllowedToUser( 671 context, intent, UserId.of(currentFromUser), UserId.of(currentToUser)); 672 } 673 674 PackageManager pm = context.getPackageManager(); 675 return doesCrossProfileIntentForwarderExist(intent, pm, fromUser, toUser); 676 } 677 678 /** 679 * Determines if the target UserHandle delegates its content sharing to its parent. 680 * 681 * @param userHandle The target handle to check delegation for. 682 * @return TRUE if V+ and the handle delegates to parent. False otherwise. 683 */ isCrossProfileStrategyDelegatedToParent(UserHandle userHandle)684 private boolean isCrossProfileStrategyDelegatedToParent(UserHandle userHandle) { 685 if (SdkLevel.isAtLeastV()) { 686 if (mUserManager == null) { 687 Log.e(TAG, "Cannot obtain user manager"); 688 return false; 689 } 690 UserProperties userProperties = mUserManager.getUserProperties(userHandle); 691 if (userProperties.getCrossProfileContentSharingStrategy() 692 == userProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT) { 693 return true; 694 } 695 } 696 return false; 697 } 698 699 /** 700 * Acquires the correct {@link UserHandle} which should be used for CrossProfile access 701 * checks. 702 * 703 * @param userHandle the origin handle. 704 * @return The UserHandle that should be used for cross profile access checks. In the event 705 * the origin handle delegates its access, this may not be the same handle as the origin 706 * handle. 707 */ getProfileToCheckCrossProfileAccess(UserHandle userHandle)708 private UserHandle getProfileToCheckCrossProfileAccess(UserHandle userHandle) { 709 if (mUserManager == null) { 710 Log.e(TAG, "Cannot obtain user manager"); 711 return null; 712 } 713 return isCrossProfileStrategyDelegatedToParent(userHandle) 714 ? mUserManager.getProfileParent(userHandle) 715 : userHandle; 716 } 717 718 /** 719 * Looks for a matching CrossProfileIntentForwardingActivity in the targetUserId for the 720 * given intent. 721 * 722 * @param intent The intent the forwarding activity needs to match. 723 * @param targetUserId The target user to check for. 724 * @return whether a CrossProfileIntentForwardingActivity could be found for the given 725 * intent, and user. 726 */ doesCrossProfileIntentForwarderExist( Intent intent, PackageManager pm, UserId fromUser, UserId targetUserId)727 private boolean doesCrossProfileIntentForwarderExist( 728 Intent intent, PackageManager pm, UserId fromUser, UserId targetUserId) { 729 730 final Intent intentToCheck = (Intent) intent.clone(); 731 intentToCheck.setComponent(null); 732 intentToCheck.setPackage(null); 733 734 for (ResolveInfo resolveInfo : 735 pm.queryIntentActivitiesAsUser( 736 intentToCheck, 737 PackageManager.MATCH_DEFAULT_ONLY, 738 fromUser.getUserHandle())) { 739 740 if (resolveInfo.isCrossProfileIntentForwarderActivity()) { 741 /* 742 * IMPORTANT: This is a reflection based hack to ensure the profile is 743 * actually the installer of the CrossProfileIntentForwardingActivity. 744 * 745 * ResolveInfo.targetUserId exists, but is a hidden API not available to 746 * mainline modules, and no such API exists, so it is accessed via 747 * reflection below. All exceptions are caught to protect against 748 * reflection related issues such as: 749 * NoSuchFieldException / IllegalAccessException / SecurityException. 750 * 751 * In the event of an exception, the code fails "closed" for the current 752 * profile to avoid showing content that should not be visible. 753 */ 754 try { 755 Field targetUserIdField = 756 resolveInfo.getClass().getDeclaredField("targetUserId"); 757 targetUserIdField.setAccessible(true); 758 int activityTargetUserId = (int) targetUserIdField.get(resolveInfo); 759 760 if (activityTargetUserId == targetUserId.getIdentifier()) { 761 762 // Found a match for this profile 763 return true; 764 } 765 766 } catch (NoSuchFieldException | IllegalAccessException | SecurityException ex) { 767 // Couldn't check the targetUserId via reflection, so fail without 768 // further iterations. 769 Log.e(TAG, "Could not access targetUserId via reflection.", ex); 770 return false; 771 } catch (Exception ex) { 772 Log.e(TAG, "Exception occurred during cross profile checks", ex); 773 } 774 } 775 } 776 777 // No match found, so return false. 778 return false; 779 } 780 781 @SuppressLint("NewApi") isCrossProfileContentSharingStrategyDelegatedFromParent( UserHandle userHandle)782 private boolean isCrossProfileContentSharingStrategyDelegatedFromParent( 783 UserHandle userHandle) { 784 if (mUserManager == null) { 785 Log.e(TAG, "can not obtain user manager"); 786 return false; 787 } 788 UserProperties userProperties = mUserManager.getUserProperties(userHandle); 789 if (java.util.Objects.equals(userProperties, null)) { 790 Log.e(TAG, "can not obtain user properties"); 791 return false; 792 } 793 794 return userProperties.getCrossProfileContentSharingStrategy() 795 == UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT; 796 } 797 isDeviceSupported(Context context)798 private static boolean isDeviceSupported(Context context) { 799 // The feature requires Android R DocumentsContract APIs and 800 // INTERACT_ACROSS_USERS_FULL permission. 801 return VersionUtils.isAtLeastR() 802 && context.checkSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS) 803 == PackageManager.PERMISSION_GRANTED; 804 } 805 } 806 } 807