diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/documentsui/DragAndDropManager.java | 10 | ||||
-rw-r--r-- | src/com/android/documentsui/DragShadowBuilder.java | 142 | ||||
-rw-r--r-- | src/com/android/documentsui/UserManagerState.java | 264 | ||||
-rw-r--r-- | src/com/android/documentsui/base/UserId.java | 7 |
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) { |