blob: 1023c8c1d7c21ebeace346f655458f5c9562d386 [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.documentsui;
import static androidx.core.util.Preconditions.checkNotNull;
import static com.android.documentsui.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
import static com.android.documentsui.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
import static com.android.documentsui.DevicePolicyResources.Strings.PERSONAL_TAB;
import static com.android.documentsui.DevicePolicyResources.Strings.WORK_TAB;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserProperties;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import androidx.annotation.GuardedBy;
import androidx.annotation.RequiresApi;
import androidx.annotation.RequiresPermission;
import androidx.annotation.VisibleForTesting;
import com.android.documentsui.base.Features;
import com.android.documentsui.base.UserId;
import com.android.documentsui.util.VersionUtils;
import com.android.modules.utils.build.SdkLevel;
import com.google.common.base.Objects;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RequiresApi(Build.VERSION_CODES.S)
public interface UserManagerState {
/**
* Returns the {@link UserId} of each profile which should be queried for documents. This will
* always include {@link UserId#CURRENT_USER}.
*/
List<UserId> getUserIds();
/** Returns mapping between the {@link UserId} and the label for the profile */
Map<UserId, String> getUserIdToLabelMap();
/**
* Returns mapping between the {@link UserId} and the drawable badge for the profile
*
* <p>returns {@code null} for non-profile userId
*/
Map<UserId, Drawable> getUserIdToBadgeMap();
/**
* Returns a map of {@link UserId} to boolean value indicating whether the {@link
* UserId}.CURRENT_USER can forward {@link Intent} to that {@link UserId}
*/
Map<UserId, Boolean> getCanForwardToProfileIdMap(Intent intent);
/**
* Updates the state of the list of userIds and all the associated maps according the intent
* received in broadcast
*
* @param userId {@link UserId} for the profile for which the availability status changed
* @param action {@link Intent}.ACTION_PROFILE_UNAVAILABLE and {@link
* Intent}.ACTION_PROFILE_AVAILABLE, {@link Intent}.ACTION_PROFILE_ADDED} and {@link
* Intent}.ACTION_PROFILE_REMOVED}
*/
void onProfileActionStatusChange(String action, UserId userId);
/** Sets the intent that triggered the launch of the DocsUI */
void setCurrentStateIntent(Intent intent);
/** Returns true if there are hidden profiles */
boolean areHiddenInQuietModeProfilesPresent();
/** Creates an implementation of {@link UserManagerState}. */
// TODO: b/314746383 Make this class a singleton
static UserManagerState create(Context context) {
return new RuntimeUserManagerState(context);
}
/** Implementation of {@link UserManagerState} */
final class RuntimeUserManagerState implements UserManagerState {
private static final String TAG = "UserManagerState";
private final Context mContext;
private final UserId mCurrentUser;
private final boolean mIsDeviceSupported;
private final UserManager mUserManager;
private final ConfigStore mConfigStore;
/**
* List of all the {@link UserId} that have the {@link UserProperties.ShowInSharingSurfaces}
* set as `SHOW_IN_SHARING_SURFACES_SEPARATE` OR it is a system/personal user
*/
@GuardedBy("mUserIds")
private final List<UserId> mUserIds = new ArrayList<>();
/** Mapping between the {@link UserId} to the corresponding profile label */
@GuardedBy("mUserIdToLabelMap")
private final Map<UserId, String> mUserIdToLabelMap = new HashMap<>();
/** Mapping between the {@link UserId} to the corresponding profile badge */
@GuardedBy("mUserIdToBadgeMap")
private final Map<UserId, Drawable> mUserIdToBadgeMap = new HashMap<>();
/**
* Map containing {@link UserId}, other than that of the current user, as key and boolean
* denoting whether it is accessible by the current user or not as value
*/
@GuardedBy("mCanForwardToProfileIdMap")
private final Map<UserId, Boolean> mCanForwardToProfileIdMap = new HashMap<>();
private Intent mCurrentStateIntent;
private final BroadcastReceiver mIntentReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
synchronized (mUserIds) {
mUserIds.clear();
}
synchronized (mUserIdToLabelMap) {
mUserIdToLabelMap.clear();
}
synchronized (mUserIdToBadgeMap) {
mUserIdToBadgeMap.clear();
}
synchronized (mCanForwardToProfileIdMap) {
mCanForwardToProfileIdMap.clear();
}
}
};
private RuntimeUserManagerState(Context context) {
this(
context,
UserId.CURRENT_USER,
Features.CROSS_PROFILE_TABS && isDeviceSupported(context),
DocumentsApplication.getConfigStore());
}
@VisibleForTesting
RuntimeUserManagerState(
Context context,
UserId currentUser,
boolean isDeviceSupported,
ConfigStore configStore) {
mContext = context.getApplicationContext();
mCurrentUser = checkNotNull(currentUser);
mIsDeviceSupported = isDeviceSupported;
mUserManager = mContext.getSystemService(UserManager.class);
mConfigStore = configStore;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
if (SdkLevel.isAtLeastV() && mConfigStore.isPrivateSpaceInDocsUIEnabled()) {
filter.addAction(Intent.ACTION_PROFILE_ADDED);
filter.addAction(Intent.ACTION_PROFILE_REMOVED);
}
mContext.registerReceiver(mIntentReceiver, filter);
}
@Override
public List<UserId> getUserIds() {
synchronized (mUserIds) {
if (mUserIds.isEmpty()) {
mUserIds.addAll(getUserIdsInternal());
}
return mUserIds;
}
}
@Override
public Map<UserId, String> getUserIdToLabelMap() {
synchronized (mUserIdToLabelMap) {
if (mUserIdToLabelMap.isEmpty()) {
getUserIdToLabelMapInternal();
}
return mUserIdToLabelMap;
}
}
@Override
public Map<UserId, Drawable> getUserIdToBadgeMap() {
synchronized (mUserIdToBadgeMap) {
if (mUserIdToBadgeMap.isEmpty()) {
getUserIdToBadgeMapInternal();
}
return mUserIdToBadgeMap;
}
}
@Override
public Map<UserId, Boolean> getCanForwardToProfileIdMap(Intent intent) {
synchronized (mCanForwardToProfileIdMap) {
if (mCanForwardToProfileIdMap.isEmpty()) {
getCanForwardToProfileIdMapInternal(intent);
}
return mCanForwardToProfileIdMap;
}
}
@Override
@SuppressLint("NewApi")
public void onProfileActionStatusChange(String action, UserId userId) {
if (!SdkLevel.isAtLeastV()) return;
UserProperties userProperties =
mUserManager.getUserProperties(UserHandle.of(userId.getIdentifier()));
if (userProperties.getShowInQuietMode() != UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) {
return;
}
if (Intent.ACTION_PROFILE_UNAVAILABLE.equals(action)
|| Intent.ACTION_PROFILE_REMOVED.equals(action)) {
synchronized (mUserIds) {
mUserIds.remove(userId);
}
} else if (Intent.ACTION_PROFILE_AVAILABLE.equals(action)
|| Intent.ACTION_PROFILE_ADDED.equals(action)) {
synchronized (mUserIds) {
if (!mUserIds.contains(userId)) {
mUserIds.add(userId);
}
}
synchronized (mUserIdToLabelMap) {
if (!mUserIdToLabelMap.containsKey(userId)) {
mUserIdToLabelMap.put(userId, getProfileLabel(userId));
}
}
synchronized (mUserIdToBadgeMap) {
if (!mUserIdToBadgeMap.containsKey(userId)) {
mUserIdToBadgeMap.put(userId, getProfileBadge(userId));
}
}
synchronized (mCanForwardToProfileIdMap) {
if (!mCanForwardToProfileIdMap.containsKey(userId)) {
mCanForwardToProfileIdMap.put(
userId,
isCrossProfileAllowedToUser(
mContext,
mCurrentStateIntent,
UserId.CURRENT_USER,
userId));
}
}
} else {
Log.e(TAG, "Unexpected action received: " + action);
}
}
@Override
public void setCurrentStateIntent(Intent intent) {
mCurrentStateIntent = intent;
}
@Override
public boolean areHiddenInQuietModeProfilesPresent() {
if (!SdkLevel.isAtLeastV()) {
return false;
}
for (UserId userId : getUserIds()) {
if (mUserManager
.getUserProperties(UserHandle.of(userId.getIdentifier()))
.getShowInQuietMode()
== UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) {
return true;
}
}
return false;
}
private List<UserId> getUserIdsInternal() {
final List<UserId> result = new ArrayList<>();
if (!mIsDeviceSupported) {
result.add(mCurrentUser);
return result;
}
if (mUserManager == null) {
Log.e(TAG, "cannot obtain user manager");
return result;
}
final List<UserHandle> userProfiles = mUserManager.getUserProfiles();
result.add(mCurrentUser);
boolean currentUserIsManaged =
mUserManager.isManagedProfile(mCurrentUser.getIdentifier());
for (UserHandle handle : userProfiles) {
if (SdkLevel.isAtLeastV()) {
if (!isProfileAllowed(handle)) {
continue;
}
} else {
// Only allow managed profiles + the parent user on lower than V.
if (currentUserIsManaged
&& mUserManager.getProfileParent(mCurrentUser.getUserHandle())
== handle) {
// Intentionally empty so that this profile gets added.
} else if (!mUserManager.isManagedProfile(handle.getIdentifier())) {
continue;
}
}
// Ensure the system user doesn't get added twice.
if (result.contains(UserId.of(handle))) continue;
result.add(UserId.of(handle));
}
return result;
}
/**
* Checks if a package is installed for a given user.
*
* @param userHandle The ID of the user.
* @return {@code true} if the package is installed for the user, {@code false} otherwise.
*/
@RequiresPermission(
anyOf = {
"android.permission.MANAGE_USERS",
"android.permission.INTERACT_ACROSS_USERS"
})
private boolean isPackageInstalledForUser(UserHandle userHandle) {
String packageName = mContext.getPackageName();
try {
Context userPackageContext =
mContext.createPackageContextAsUser(
mContext.getPackageName(), 0 /* flags */, userHandle);
return userPackageContext != null;
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Package " + packageName + " not found for user " + userHandle);
return false;
}
}
/**
* Checks if quiet mode is enabled for a given user.
*
* @param userHandle The UserHandle of the profile to check.
* @return {@code true} if quiet mode is enabled, {@code false} otherwise.
*/
private boolean isQuietModeEnabledForUser(UserHandle userHandle) {
return UserId.of(userHandle.getIdentifier()).isQuietModeEnabled(mContext);
}
/**
* Checks if a profile should be allowed, taking into account quiet mode and package
* installation.
*
* @param userHandle The UserHandle of the profile to check.
* @return {@code true} if the profile should be allowed, {@code false} otherwise.
*/
@SuppressLint("NewApi")
@RequiresPermission(
anyOf = {
"android.permission.MANAGE_USERS",
"android.permission.INTERACT_ACROSS_USERS"
})
private boolean isProfileAllowed(UserHandle userHandle) {
final UserProperties userProperties = mUserManager.getUserProperties(userHandle);
// 1. Check if the package is installed for the user
if (!isPackageInstalledForUser(userHandle)) {
Log.w(
TAG,
"Package "
+ mContext.getPackageName()
+ " is not installed for user "
+ userHandle);
return false;
}
// 2. Check user properties and quiet mode
if (userProperties.getShowInSharingSurfaces()
== UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE) {
// Return true if profile is not in quiet mode or if it is in quiet mode
// then its user properties do not require it to be hidden
return !isQuietModeEnabledForUser(userHandle)
|| userProperties.getShowInQuietMode()
!= UserProperties.SHOW_IN_QUIET_MODE_HIDDEN;
}
return false;
}
private void getUserIdToLabelMapInternal() {
if (SdkLevel.isAtLeastV()) {
getUserIdToLabelMapInternalPostV();
} else {
getUserIdToLabelMapInternalPreV();
}
}
@SuppressLint("NewApi")
private void getUserIdToLabelMapInternalPostV() {
if (mUserManager == null) {
Log.e(TAG, "cannot obtain user manager");
return;
}
List<UserId> userIds = getUserIds();
for (UserId userId : userIds) {
synchronized (mUserIdToLabelMap) {
mUserIdToLabelMap.put(userId, getProfileLabel(userId));
}
}
}
private void getUserIdToLabelMapInternalPreV() {
if (mUserManager == null) {
Log.e(TAG, "cannot obtain user manager");
return;
}
List<UserId> userIds = getUserIds();
for (UserId userId : userIds) {
if (mUserManager.isManagedProfile(userId.getIdentifier())) {
synchronized (mUserIdToLabelMap) {
mUserIdToLabelMap.put(
userId, getEnterpriseString(WORK_TAB, R.string.work_tab));
}
} else {
synchronized (mUserIdToLabelMap) {
mUserIdToLabelMap.put(
userId, getEnterpriseString(PERSONAL_TAB, R.string.personal_tab));
}
}
}
}
@SuppressLint("NewApi")
private String getProfileLabel(UserId userId) {
if (userId.getIdentifier() == ActivityManager.getCurrentUser()) {
return getEnterpriseString(PERSONAL_TAB, R.string.personal_tab);
}
try {
Context userContext =
mContext.createContextAsUser(
UserHandle.of(userId.getIdentifier()), 0 /* flags */);
UserManager userManagerAsUser = userContext.getSystemService(UserManager.class);
if (userManagerAsUser == null) {
Log.e(TAG, "cannot obtain user manager");
return null;
}
return userManagerAsUser.getProfileLabel();
} catch (Exception e) {
Log.e(TAG, "Exception occurred while trying to get profile label:\n" + e);
return null;
}
}
private String getEnterpriseString(String updatableStringId, int defaultStringId) {
if (SdkLevel.isAtLeastT()) {
return getUpdatableEnterpriseString(updatableStringId, defaultStringId);
} else {
return mContext.getString(defaultStringId);
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private String getUpdatableEnterpriseString(String updatableStringId, int defaultStringId) {
DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
if (Objects.equal(dpm, null)) {
Log.e(TAG, "can not get device policy manager");
return mContext.getString(defaultStringId);
}
return dpm.getResources()
.getString(updatableStringId, () -> mContext.getString(defaultStringId));
}
private void getUserIdToBadgeMapInternal() {
if (SdkLevel.isAtLeastV()) {
getUserIdToBadgeMapInternalPostV();
} else {
getUserIdToBadgeMapInternalPreV();
}
}
@SuppressLint("NewApi")
private void getUserIdToBadgeMapInternalPostV() {
if (mUserManager == null) {
Log.e(TAG, "cannot obtain user manager");
return;
}
List<UserId> userIds = getUserIds();
for (UserId userId : userIds) {
synchronized (mUserIdToBadgeMap) {
mUserIdToBadgeMap.put(userId, getProfileBadge(userId));
}
}
}
private void getUserIdToBadgeMapInternalPreV() {
if (!SdkLevel.isAtLeastR()) return;
if (mUserManager == null) {
Log.e(TAG, "cannot obtain user manager");
return;
}
List<UserId> userIds = getUserIds();
for (UserId userId : userIds) {
if (mUserManager.isManagedProfile(userId.getIdentifier())) {
synchronized (mUserIdToBadgeMap) {
mUserIdToBadgeMap.put(
userId,
SdkLevel.isAtLeastT()
? getWorkProfileBadge()
: mContext.getDrawable(R.drawable.ic_briefcase));
}
}
}
}
@SuppressLint("NewApi")
private Drawable getProfileBadge(UserId userId) {
if (userId.getIdentifier() == ActivityManager.getCurrentUser()) {
return null;
}
try {
Context userContext =
mContext.createContextAsUser(
UserHandle.of(userId.getIdentifier()), 0 /* flags */);
UserManager userManagerAsUser = userContext.getSystemService(UserManager.class);
if (userManagerAsUser == null) {
Log.e(TAG, "cannot obtain user manager");
return null;
}
return userManagerAsUser.getUserBadge();
} catch (Exception e) {
Log.e(TAG, "Exception occurred while trying to get profile badge:\n" + e);
return null;
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private Drawable getWorkProfileBadge() {
DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
Drawable drawable =
dpm.getResources()
.getDrawable(
WORK_PROFILE_ICON,
SOLID_COLORED,
() -> mContext.getDrawable(R.drawable.ic_briefcase));
return drawable;
}
/**
* Updates Cross Profile access for all UserProfiles in {@code getUserIds()}
*
* <p>This method looks at a variety of situations for each Profile and decides if the
* profile's content is accessible by the current process owner user id.
*
* <ol>
* <li>UserProperties attributes for CrossProfileDelegation are checked first. When the
* profile delegates to the parent profile, the parent's access is used.
* <li>{@link CrossProfileIntentForwardingActivity}s are resolved via the process owner's
* PackageManager, and are considered when evaluating cross profile to the target
* profile.
* </ol>
*
* <p>In the event none of the above checks succeeds, the profile is considered to be
* inaccessible to the current process user.
*
* @param intent The intent Photopicker is currently running under, for
* CrossProfileForwardActivity checking.
*/
private void getCanForwardToProfileIdMapInternal(Intent intent) {
synchronized (mCanForwardToProfileIdMap) {
mCanForwardToProfileIdMap.clear();
for (UserId userId : getUserIds()) {
mCanForwardToProfileIdMap.put(
userId,
isCrossProfileAllowedToUser(
mContext, intent, mCurrentUser, userId));
}
}
}
/**
* Determines if the provided UserIds support CrossProfile content sharing.
*
* <p>This method accepts a pair of user handles (from/to) and determines if CrossProfile
* access is permitted between those two profiles.
*
* <p>There are differences is on how the access is determined based on the platform SDK:
*
* <p>For Platform SDK < V:
*
* <p>A check for CrossProfileIntentForwarders in the origin (from) profile that target the
* destination (to) profile. If such a forwarder exists, then access is allowed, and denied
* otherwise.
*
* <p>For Platform SDK >= V:
*
* <p>The method now takes into account access delegation, which was first added in Android
* V.
*
* <p>For profiles that set the [CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT]
* property in its [UserProperties], its parent profile will be substituted in for its side
* of the check.
*
* <p>ex. For access checks between a Managed (from) and Private (to) profile, where: -
* Managed does not delegate to its parent - Private delegates to its parent
*
* <p>The following logic is performed: Managed -> parent(Private)
*
* <p>The same check in the other direction would yield: parent(Private) -> Managed
*
* <p>Note how the private profile is never actually used for either side of the check,
* since it is delegating its access check to the parent. And thus, if Managed can access
* the parent, it can also access the private.
*
* @param context Current context object, for switching user contexts.
* @param intent The current intent the Photopicker is running under.
* @param fromUser The Origin profile, where the user is coming from
* @param toUser The destination profile, where the user is attempting to go to.
* @return Whether CrossProfile content sharing is supported in this handle.
*/
private boolean isCrossProfileAllowedToUser(
Context context, Intent intent, UserId fromUser, UserId toUser) {
// Early exit conditions, accessing self.
// NOTE: It is also possible to reach this state if this method is recursively checking
// from: parent(A) to:parent(B) where A and B are both children of the same parent.
if (fromUser.getIdentifier() == toUser.getIdentifier()) {
return true;
}
// Decide if we should use actual from or parent(from)
UserHandle currentFromUser =
getProfileToCheckCrossProfileAccess(fromUser.getUserHandle());
// Decide if we should use actual to or parent(to)
UserHandle currentToUser = getProfileToCheckCrossProfileAccess(toUser.getUserHandle());
// When the from/to has changed from the original parameters, recursively restart the
// checks with the new from/to handles.
if (fromUser.getIdentifier() != currentFromUser.getIdentifier()
|| toUser.getIdentifier() != currentToUser.getIdentifier()) {
return isCrossProfileAllowedToUser(
context, intent, UserId.of(currentFromUser), UserId.of(currentToUser));
}
PackageManager pm = context.getPackageManager();
return doesCrossProfileIntentForwarderExist(intent, pm, fromUser, toUser);
}
/**
* Determines if the target UserHandle delegates its content sharing to its parent.
*
* @param userHandle The target handle to check delegation for.
* @return TRUE if V+ and the handle delegates to parent. False otherwise.
*/
private boolean isCrossProfileStrategyDelegatedToParent(UserHandle userHandle) {
if (SdkLevel.isAtLeastV()) {
if (mUserManager == null) {
Log.e(TAG, "Cannot obtain user manager");
return false;
}
UserProperties userProperties = mUserManager.getUserProperties(userHandle);
if (userProperties.getCrossProfileContentSharingStrategy()
== userProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT) {
return true;
}
}
return false;
}
/**
* Acquires the correct {@link UserHandle} which should be used for CrossProfile access
* checks.
*
* @param userHandle the origin handle.
* @return The UserHandle that should be used for cross profile access checks. In the event
* the origin handle delegates its access, this may not be the same handle as the origin
* handle.
*/
private UserHandle getProfileToCheckCrossProfileAccess(UserHandle userHandle) {
if (mUserManager == null) {
Log.e(TAG, "Cannot obtain user manager");
return null;
}
return isCrossProfileStrategyDelegatedToParent(userHandle)
? mUserManager.getProfileParent(userHandle)
: userHandle;
}
/**
* Looks for a matching CrossProfileIntentForwardingActivity in the targetUserId for the
* given intent.
*
* @param intent The intent the forwarding activity needs to match.
* @param targetUserId The target user to check for.
* @return whether a CrossProfileIntentForwardingActivity could be found for the given
* intent, and user.
*/
private boolean doesCrossProfileIntentForwarderExist(
Intent intent, PackageManager pm, UserId fromUser, UserId targetUserId) {
final Intent intentToCheck = (Intent) intent.clone();
intentToCheck.setComponent(null);
intentToCheck.setPackage(null);
for (ResolveInfo resolveInfo :
pm.queryIntentActivitiesAsUser(
intentToCheck,
PackageManager.MATCH_DEFAULT_ONLY,
fromUser.getUserHandle())) {
if (resolveInfo.isCrossProfileIntentForwarderActivity()) {
/*
* IMPORTANT: This is a reflection based hack to ensure the profile is
* actually the installer of the CrossProfileIntentForwardingActivity.
*
* ResolveInfo.targetUserId exists, but is a hidden API not available to
* mainline modules, and no such API exists, so it is accessed via
* reflection below. All exceptions are caught to protect against
* reflection related issues such as:
* NoSuchFieldException / IllegalAccessException / SecurityException.
*
* In the event of an exception, the code fails "closed" for the current
* profile to avoid showing content that should not be visible.
*/
try {
Field targetUserIdField =
resolveInfo.getClass().getDeclaredField("targetUserId");
targetUserIdField.setAccessible(true);
int activityTargetUserId = (int) targetUserIdField.get(resolveInfo);
if (activityTargetUserId == targetUserId.getIdentifier()) {
// Found a match for this profile
return true;
}
} catch (NoSuchFieldException | IllegalAccessException | SecurityException ex) {
// Couldn't check the targetUserId via reflection, so fail without
// further iterations.
Log.e(TAG, "Could not access targetUserId via reflection.", ex);
return false;
} catch (Exception ex) {
Log.e(TAG, "Exception occurred during cross profile checks", ex);
}
}
}
// No match found, so return false.
return false;
}
@SuppressLint("NewApi")
private boolean isCrossProfileContentSharingStrategyDelegatedFromParent(
UserHandle userHandle) {
if (mUserManager == null) {
Log.e(TAG, "can not obtain user manager");
return false;
}
UserProperties userProperties = mUserManager.getUserProperties(userHandle);
if (java.util.Objects.equals(userProperties, null)) {
Log.e(TAG, "can not obtain user properties");
return false;
}
return userProperties.getCrossProfileContentSharingStrategy()
== UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT;
}
private static boolean isDeviceSupported(Context context) {
// The feature requires Android R DocumentsContract APIs and
// INTERACT_ACROSS_USERS_FULL permission.
return VersionUtils.isAtLeastR()
&& context.checkSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS)
== PackageManager.PERMISSION_GRANTED;
}
}
}