• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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