summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/documentsui/DragAndDropManager.java10
-rw-r--r--src/com/android/documentsui/DragShadowBuilder.java142
-rw-r--r--src/com/android/documentsui/UserManagerState.java264
-rw-r--r--src/com/android/documentsui/base/UserId.java7
4 files changed, 287 insertions, 136 deletions
diff --git a/src/com/android/documentsui/DragAndDropManager.java b/src/com/android/documentsui/DragAndDropManager.java
index bed8764ae..158d43c95 100644
--- a/src/com/android/documentsui/DragAndDropManager.java
+++ b/src/com/android/documentsui/DragAndDropManager.java
@@ -16,6 +16,8 @@
package com.android.documentsui;
+import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled;
+
import android.content.ClipData;
import android.content.Context;
import android.graphics.drawable.Drawable;
@@ -277,7 +279,9 @@ public interface DragAndDropManager {
final Drawable icon;
final int size = srcs.size();
- if (size == 1) {
+ // If use_material3 flag is ON, we always show the icon/title for the first file even
+ // when we have multiple files.
+ if (size == 1 || isUseMaterial3FlagEnabled()) {
DocumentInfo doc = srcs.get(0);
title = doc.displayName;
icon = iconHelper.getDocumentIcon(mContext, doc);
@@ -287,6 +291,10 @@ public interface DragAndDropManager {
icon = mDefaultShadowIcon;
}
+ if (isUseMaterial3FlagEnabled()) {
+ mShadowBuilder.updateDragFileCount(size);
+ }
+
mShadowBuilder.updateTitle(title);
mShadowBuilder.updateIcon(icon);
diff --git a/src/com/android/documentsui/DragShadowBuilder.java b/src/com/android/documentsui/DragShadowBuilder.java
index 10a0106d5..be76302e8 100644
--- a/src/com/android/documentsui/DragShadowBuilder.java
+++ b/src/com/android/documentsui/DragShadowBuilder.java
@@ -16,7 +16,7 @@
package com.android.documentsui;
-import com.android.documentsui.DragAndDropManager.State;
+import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled;
import android.content.Context;
import android.graphics.Canvas;
@@ -29,6 +29,12 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
+import com.android.documentsui.DragAndDropManager.State;
+
+import java.text.NumberFormat;
+
class DragShadowBuilder extends View.DragShadowBuilder {
private final View mShadowView;
@@ -37,8 +43,18 @@ class DragShadowBuilder extends View.DragShadowBuilder {
private final int mWidth;
private final int mHeight;
private final int mShadowRadius;
- private int mPadding;
+ private final int mPadding;
private Paint paint;
+ // This will be null if use_material3 flag is OFF.
+ private final @Nullable View mAdditionalShadowView;
+ // This will always be 0 if the use_material3 flag is OFF.
+ private int mDragFileCount = 0;
+ // The following 5 dimensions will be 0 if the use_material3 flag is OFF.
+ private final int mDragContentRadius;
+ private final int mAdditionalLayerOffset;
+ private final int mDragFileCounterOffset;
+ private final int mShadow2Radius;
+ private final int mShadowYOffset;
DragShadowBuilder(Context context) {
mWidth = context.getResources().getDimensionPixelSize(R.dimen.drag_shadow_width);
@@ -49,6 +65,28 @@ class DragShadowBuilder extends View.DragShadowBuilder {
mShadowView = LayoutInflater.from(context).inflate(R.layout.drag_shadow_layout, null);
mTitle = (TextView) mShadowView.findViewById(android.R.id.title);
mIcon = (DropBadgeView) mShadowView.findViewById(android.R.id.icon);
+ if (isUseMaterial3FlagEnabled()) {
+ mAdditionalShadowView =
+ LayoutInflater.from(context).inflate(R.layout.additional_drag_shadow, null);
+ mDragContentRadius =
+ context.getResources().getDimensionPixelSize(R.dimen.drag_content_radius);
+ mAdditionalLayerOffset =
+ context.getResources()
+ .getDimensionPixelSize(R.dimen.drag_additional_layer_offset);
+ mDragFileCounterOffset =
+ context.getResources().getDimensionPixelSize(R.dimen.drag_file_counter_offset);
+ mShadow2Radius =
+ context.getResources().getDimensionPixelSize(R.dimen.drag_shadow_2_radius);
+ mShadowYOffset =
+ context.getResources().getDimensionPixelSize(R.dimen.drag_shadow_y_offset);
+ } else {
+ mAdditionalShadowView = null;
+ mDragContentRadius = 0;
+ mAdditionalLayerOffset = 0;
+ mDragFileCounterOffset = 0;
+ mShadow2Radius = 0;
+ mShadowYOffset = 0;
+ }
// Important for certain APIs
mShadowView.setLayerType(View.LAYER_TYPE_SOFTWARE, paint);
@@ -67,23 +105,86 @@ class DragShadowBuilder extends View.DragShadowBuilder {
Rect r = canvas.getClipBounds();
// Calling measure is necessary in order for all child views to get correctly laid out.
mShadowView.measure(
- View.MeasureSpec.makeMeasureSpec(r.right- r.left, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(r.right - r.left, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(r.bottom - r.top , View.MeasureSpec.EXACTLY));
mShadowView.layout(r.left, r.top, r.right, r.bottom);
// Since DragShadow is not an actual view drawn in hardware-accelerated window,
// android:elevation does not work; we need to draw the shadow ourselves manually.
paint.setColor(Color.TRANSPARENT);
- // Shadow 1
- int opacity = (int) (255 * 0.1);
- paint.setShadowLayer(mShadowRadius, 0, 0, Color.argb(opacity, 0, 0, 0));
- canvas.drawRect(r.left + mPadding, r.top + mPadding, r.right - mPadding,
- r.bottom - mPadding, paint);
- // Shadow 2
- opacity = (int) (255 * 0.24);
- paint.setShadowLayer(mShadowRadius, 0, mShadowRadius, Color.argb(opacity, 0, 0, 0));
- canvas.drawRect(r.left + mPadding, r.top + mPadding, r.right - mPadding,
- r.bottom - mPadding, paint);
+
+ // Layers on the canvas (from bottom to top):
+ // 1. Two shadows for the additional drag layer (if drag file count > 1)
+ // 2. The additional layer view itself (if drag file count > 1)
+ // 3. Two shadows for the drag content layer (icon, title)
+ // 4. The drag content layer itself
+ final int shadowOneOpacity = (int) (255 * 0.15);
+ final int shadowTwoOpacity = (int) (255 * 0.30);
+ if (mAdditionalShadowView != null && mDragFileCount > 1) {
+ mAdditionalShadowView.measure(
+ View.MeasureSpec.makeMeasureSpec(r.right - r.left, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(r.bottom - r.top , View.MeasureSpec.EXACTLY));
+ mAdditionalShadowView.layout(r.left, r.top, r.right, r.bottom);
+ // Shadow 1
+ paint.setShadowLayer(
+ mShadowRadius, 0, mShadowYOffset, Color.argb(shadowOneOpacity, 0, 0, 0));
+ canvas.drawRoundRect(
+ r.left + mShadowRadius,
+ r.top + mDragFileCounterOffset + mAdditionalLayerOffset,
+ r.right - mDragFileCounterOffset - mAdditionalLayerOffset,
+ r.bottom - mShadowRadius,
+ mDragContentRadius,
+ mDragContentRadius,
+ paint);
+ // Shadow 2
+ paint.setShadowLayer(
+ mShadow2Radius, 0, mShadowYOffset, Color.argb(shadowTwoOpacity, 0, 0, 0));
+ canvas.drawRoundRect(
+ r.left + mShadowRadius,
+ r.top + mDragFileCounterOffset + mAdditionalLayerOffset,
+ r.right - mDragFileCounterOffset - mAdditionalLayerOffset,
+ r.bottom - mShadowRadius,
+ mDragContentRadius,
+ mDragContentRadius,
+ paint);
+ mAdditionalShadowView.draw(canvas);
+ }
+
+ if (isUseMaterial3FlagEnabled()) {
+ // Shadow 1
+ paint.setShadowLayer(
+ mShadowRadius, 0, mShadowYOffset, Color.argb(shadowOneOpacity, 0, 0, 0));
+ canvas.drawRoundRect(
+ r.left + mShadowRadius + mAdditionalLayerOffset,
+ r.top + mDragFileCounterOffset,
+ r.right - mDragFileCounterOffset,
+ r.bottom - mShadowRadius - mAdditionalLayerOffset,
+ mDragContentRadius,
+ mDragContentRadius,
+ paint);
+ // Shadow 2
+ paint.setShadowLayer(
+ mShadow2Radius, 0, mShadowYOffset, Color.argb(shadowTwoOpacity, 0, 0, 0));
+ canvas.drawRoundRect(
+ r.left + mShadowRadius + mAdditionalLayerOffset,
+ r.top + mDragFileCounterOffset,
+ r.right - mDragFileCounterOffset,
+ r.bottom - mShadowRadius - mAdditionalLayerOffset,
+ mDragContentRadius,
+ mDragContentRadius,
+ paint);
+ } else {
+ // Shadow 1
+ int opacity = (int) (255 * 0.1);
+ paint.setShadowLayer(mShadowRadius, 0, 0, Color.argb(opacity, 0, 0, 0));
+ canvas.drawRect(r.left + mPadding, r.top + mPadding, r.right - mPadding,
+ r.bottom - mPadding, paint);
+ // Shadow 2
+ opacity = (int) (255 * 0.24);
+ paint.setShadowLayer(mShadowRadius, 0, mShadowRadius, Color.argb(opacity, 0, 0, 0));
+ canvas.drawRect(r.left + mPadding, r.top + mPadding, r.right - mPadding,
+ r.bottom - mPadding, paint);
+ }
mShadowView.draw(canvas);
}
@@ -98,4 +199,19 @@ class DragShadowBuilder extends View.DragShadowBuilder {
void onStateUpdated(@State int state) {
mIcon.updateState(state);
}
+
+ void updateDragFileCount(int count) {
+ if (!isUseMaterial3FlagEnabled()) {
+ return;
+ }
+ mDragFileCount = count;
+ TextView dragFileCountView = mShadowView.findViewById(R.id.drag_file_counter);
+ if (dragFileCountView != null) {
+ dragFileCountView.setVisibility(count > 1 ? View.VISIBLE : View.GONE);
+ if (count > 1) {
+ NumberFormat numberFormat = NumberFormat.getInstance();
+ dragFileCountView.setText(numberFormat.format(count));
+ }
+ }
+ }
}
diff --git a/src/com/android/documentsui/UserManagerState.java b/src/com/android/documentsui/UserManagerState.java
index d2ddae615..1023c8c1d 100644
--- a/src/com/android/documentsui/UserManagerState.java
+++ b/src/com/android/documentsui/UserManagerState.java
@@ -22,7 +22,6 @@ import static com.android.documentsui.DevicePolicyResources.Drawables.Style.SOLI
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 static com.android.documentsui.base.SharedMinimal.DEBUG;
import android.Manifest;
import android.annotation.SuppressLint;
@@ -263,35 +262,13 @@ public interface UserManagerState {
}
synchronized (mCanForwardToProfileIdMap) {
if (!mCanForwardToProfileIdMap.containsKey(userId)) {
-
- UserHandle handle = UserHandle.of(userId.getIdentifier());
-
- // Decide if to use the parent's access, or this handle's access.
- if (isCrossProfileContentSharingStrategyDelegatedFromParent(handle)) {
- UserHandle parentHandle = mUserManager.getProfileParent(handle);
- // Couldn't resolve parent to check access, so fail closed.
- if (parentHandle == null) {
- mCanForwardToProfileIdMap.put(userId, false);
- } else if (mCurrentUser.getIdentifier()
- == parentHandle.getIdentifier()) {
- // Check if the parent is the current user, if so this profile
- // is also accessible.
- mCanForwardToProfileIdMap.put(userId, true);
-
- } else {
- UserId parent = UserId.of(parentHandle);
- mCanForwardToProfileIdMap.put(
- userId,
- doesCrossProfileForwardingActivityExistForUser(
- mCurrentStateIntent, parent));
- }
- } else {
- // Update the profile map for this profile.
- mCanForwardToProfileIdMap.put(
- userId,
- doesCrossProfileForwardingActivityExistForUser(
- mCurrentStateIntent, userId));
- }
+ mCanForwardToProfileIdMap.put(
+ userId,
+ isCrossProfileAllowedToUser(
+ mContext,
+ mCurrentStateIntent,
+ UserId.CURRENT_USER,
+ userId));
}
}
} else {
@@ -331,43 +308,37 @@ public interface UserManagerState {
if (mUserManager == null) {
Log.e(TAG, "cannot obtain user manager");
- result.add(mCurrentUser);
return result;
}
final List<UserHandle> userProfiles = mUserManager.getUserProfiles();
- if (userProfiles.size() < 2) {
- result.add(mCurrentUser);
- return result;
- }
- if (SdkLevel.isAtLeastV()) {
- getUserIdsInternalPostV(userProfiles, result);
- } else {
- getUserIdsInternalPreV(userProfiles, result);
- }
- return result;
- }
+ result.add(mCurrentUser);
+ boolean currentUserIsManaged =
+ mUserManager.isManagedProfile(mCurrentUser.getIdentifier());
- @SuppressLint("NewApi")
- private void getUserIdsInternalPostV(List<UserHandle> userProfiles, List<UserId> result) {
- for (UserHandle userHandle : userProfiles) {
- if (userHandle.getIdentifier() == ActivityManager.getCurrentUser()) {
- result.add(UserId.of(userHandle));
+ for (UserHandle handle : userProfiles) {
+ if (SdkLevel.isAtLeastV()) {
+ if (!isProfileAllowed(handle)) {
+ continue;
+ }
} else {
- // Out of all the profiles returned by user manager the profiles that are
- // returned should satisfy both the following conditions:
- // 1. It has user property SHOW_IN_SHARING_SURFACES_SEPARATE
- // 2. Quite mode is not enabled, if it is enabled then the profile's user
- // property is not SHOW_IN_QUIET_MODE_HIDDEN
- if (isProfileAllowed(userHandle)) {
- result.add(UserId.of(userHandle));
+ // 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));
}
- if (result.isEmpty()) {
- result.add(mCurrentUser);
- }
+
+ return result;
}
/**
@@ -444,33 +415,6 @@ public interface UserManagerState {
return false;
}
- private void getUserIdsInternalPreV(List<UserHandle> userProfiles, List<UserId> result) {
- result.add(mCurrentUser);
- UserId systemUser = null;
- UserId managedUser = null;
- for (UserHandle userHandle : userProfiles) {
- if (userHandle.isSystem()) {
- systemUser = UserId.of(userHandle);
- } else if (mUserManager.isManagedProfile(userHandle.getIdentifier())) {
- managedUser = UserId.of(userHandle);
- }
- }
- if (mCurrentUser.isSystem() && managedUser != null) {
- result.add(managedUser);
- } else if (mCurrentUser.isManagedProfile(mUserManager) && systemUser != null) {
- result.add(0, systemUser);
- } else {
- if (DEBUG) {
- Log.w(
- TAG,
- "The current user "
- + UserId.CURRENT_USER
- + " is neither system nor managed user. has system user: "
- + (systemUser != null));
- }
- }
- }
-
private void getUserIdToLabelMapInternal() {
if (SdkLevel.isAtLeastV()) {
getUserIdToLabelMapInternalPostV();
@@ -651,50 +595,124 @@ public interface UserManagerState {
*/
private void getCanForwardToProfileIdMapInternal(Intent intent) {
- Map<UserId, Boolean> profileIsAccessibleToProcessOwner = new HashMap<>();
+ synchronized (mCanForwardToProfileIdMap) {
+ mCanForwardToProfileIdMap.clear();
+ for (UserId userId : getUserIds()) {
+ mCanForwardToProfileIdMap.put(
+ userId,
+ isCrossProfileAllowedToUser(
+ mContext, intent, mCurrentUser, userId));
+ }
+ }
+ }
- List<UserId> delegatedFromParent = new ArrayList<>();
+ /**
+ * 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) {
- for (UserId userId : getUserIds()) {
+ // 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;
+ }
- // Early exit, self is always accessible.
- if (userId.getIdentifier() == mCurrentUser.getIdentifier()) {
- profileIsAccessibleToProcessOwner.put(userId, true);
- continue;
- }
+ // Decide if we should use actual from or parent(from)
+ UserHandle currentFromUser =
+ getProfileToCheckCrossProfileAccess(fromUser.getUserHandle());
- // CrossProfileContentSharingStrategyDelegatedFromParent is only V+ sdks.
- if (SdkLevel.isAtLeastV()
- && isCrossProfileContentSharingStrategyDelegatedFromParent(
- UserHandle.of(userId.getIdentifier()))) {
- delegatedFromParent.add(userId);
- continue;
- }
+ // Decide if we should use actual to or parent(to)
+ UserHandle currentToUser = getProfileToCheckCrossProfileAccess(toUser.getUserHandle());
- // Check for cross profile & add to the map.
- profileIsAccessibleToProcessOwner.put(
- userId, doesCrossProfileForwardingActivityExistForUser(intent, userId));
+ // 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));
}
- // For profiles that delegate their access to the parent, set the access for
- // those profiles
- // equal to the same as their parent.
- for (UserId userId : delegatedFromParent) {
- UserHandle parent =
- mUserManager.getProfileParent(UserHandle.of(userId.getIdentifier()));
- profileIsAccessibleToProcessOwner.put(
- userId,
- profileIsAccessibleToProcessOwner.getOrDefault(
- UserId.of(parent), /* default= */ false));
- }
+ PackageManager pm = context.getPackageManager();
+ return doesCrossProfileIntentForwarderExist(intent, pm, fromUser, toUser);
+ }
- synchronized (mCanForwardToProfileIdMap) {
- mCanForwardToProfileIdMap.clear();
- for (Map.Entry<UserId, Boolean> entry :
- profileIsAccessibleToProcessOwner.entrySet()) {
- mCanForwardToProfileIdMap.put(entry.getKey(), entry.getValue());
+ /**
+ * 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;
}
/**
@@ -706,16 +724,18 @@ public interface UserManagerState {
* @return whether a CrossProfileIntentForwardingActivity could be found for the given
* intent, and user.
*/
- private boolean doesCrossProfileForwardingActivityExistForUser(
- Intent intent, UserId targetUserId) {
+ private boolean doesCrossProfileIntentForwarderExist(
+ Intent intent, PackageManager pm, UserId fromUser, UserId targetUserId) {
- final PackageManager pm = mContext.getPackageManager();
final Intent intentToCheck = (Intent) intent.clone();
intentToCheck.setComponent(null);
intentToCheck.setPackage(null);
for (ResolveInfo resolveInfo :
- pm.queryIntentActivities(intentToCheck, PackageManager.MATCH_DEFAULT_ONLY)) {
+ pm.queryIntentActivitiesAsUser(
+ intentToCheck,
+ PackageManager.MATCH_DEFAULT_ONLY,
+ fromUser.getUserHandle())) {
if (resolveInfo.isCrossProfileIntentForwarderActivity()) {
/*
diff --git a/src/com/android/documentsui/base/UserId.java b/src/com/android/documentsui/base/UserId.java
index 21842917a..7aff61e9b 100644
--- a/src/com/android/documentsui/base/UserId.java
+++ b/src/com/android/documentsui/base/UserId.java
@@ -95,6 +95,13 @@ public final class UserId {
}
/**
+ * Return this User's {@link UserHandle}.
+ */
+ public UserHandle getUserHandle() {
+ return mUserHandle;
+ }
+
+ /**
* Return a package manager instance of this user.
*/
public PackageManager getPackageManager(Context context) {