diff --git a/quickstep/Android.bp b/quickstep/Android.bp index ec4f6fccd3..a290e84d88 100644 --- a/quickstep/Android.bp +++ b/quickstep/Android.bp @@ -23,15 +23,23 @@ filegroup { } filegroup { - name: "launcher3-quickstep-robolectric-src", - path: "robolectric_tests", - srcs: ["robolectric_tests/src/**/*.java"], + name: "launcher3-quickstep-robo-src", + path: "tests/multivalentTests", + srcs: [ + "tests/multivalentTests/src/**/*.java", + "tests/multivalentTests/src/**/*.kt", + ], } filegroup { name: "launcher3-quickstep-tests-src", path: "tests", - srcs: ["tests/src/**/*.java", "tests/src/**/*.kt"], + srcs: [ + "tests/multivalentTests/src/**/*.java", + "tests/multivalentTests/src/**/*.kt", + "tests/src/**/*.java", + "tests/src/**/*.kt", + ], } filegroup { @@ -44,5 +52,5 @@ filegroup { "tests/src/com/android/quickstep/TaplOverviewIconTest.java", "tests/src/com/android/quickstep/TaplTestsQuickstep.java", "tests/src/com/android/quickstep/TaplTestsSplitscreen.java", - ] + ], } diff --git a/quickstep/res/layout/transient_taskbar.xml b/quickstep/res/layout/transient_taskbar.xml index 6af7cf466c..3c6878a6e5 100644 --- a/quickstep/res/layout/transient_taskbar.xml +++ b/quickstep/res/layout/transient_taskbar.xml @@ -41,9 +41,10 @@ @dimen/transient_taskbar_stashed_height @dimen/taskbar_stashed_handle_height 8dp + + 80dp 1dp 90dp diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt index aa2b29de39..9f14ebffff 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt @@ -19,6 +19,7 @@ import android.graphics.Canvas import android.graphics.Color import android.graphics.ColorFilter import android.graphics.Paint +import android.graphics.PixelFormat import android.graphics.drawable.Drawable import android.graphics.drawable.ShapeDrawable import com.android.app.animation.Interpolators @@ -122,14 +123,22 @@ class BubbleBarBackground(context: TaskbarActivityContext, private val backgroun // Draw background. val radius = backgroundHeight / 2f - val left = if (anchorLeft) 0f else canvas.width.toFloat() - width - val right = if (anchorLeft) width else canvas.width.toFloat() - canvas.drawRoundRect(left, 0f, right, canvas.height.toFloat(), radius, radius, paint) + val left = if (anchorLeft) 0f else bounds.width().toFloat() - width + val right = if (anchorLeft) width else bounds.width().toFloat() + canvas.drawRoundRect( + left, + pointerSize, + right, + bounds.height().toFloat(), + radius, + radius, + paint + ) if (showingArrow) { // Draw arrow. val transX = arrowPositionX - pointerSize / 2f - canvas.translate(transX, -pointerSize) + canvas.translate(transX, 0f) arrowDrawable.draw(canvas) } @@ -137,11 +146,20 @@ class BubbleBarBackground(context: TaskbarActivityContext, private val backgroun } override fun getOpacity(): Int { - return paint.alpha + return when (paint.alpha) { + 255 -> PixelFormat.OPAQUE + 0 -> PixelFormat.TRANSPARENT + else -> PixelFormat.TRANSLUCENT + } } override fun setAlpha(alpha: Int) { paint.alpha = alpha + arrowDrawable.paint.alpha = alpha + } + + override fun getAlpha(): Int { + return paint.alpha } override fun setColorFilter(colorFilter: ColorFilter?) { diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java index 6dc7db766e..1f3c4839ab 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java @@ -73,6 +73,7 @@ import com.android.launcher3.util.Executors.SimpleThreadFactory; import com.android.quickstep.SystemUiProxy; import com.android.wm.shell.Flags; import com.android.wm.shell.bubbles.IBubblesListener; +import com.android.wm.shell.common.bubbles.BubbleBarLocation; import com.android.wm.shell.common.bubbles.BubbleBarUpdate; import com.android.wm.shell.common.bubbles.BubbleInfo; import com.android.wm.shell.common.bubbles.RemovedBubble; @@ -155,12 +156,14 @@ public class BubbleBarController extends IBubblesListener.Stub { * {@link BubbleBarBubble}s so that it can be used to update the views. */ private static class BubbleBarViewUpdate { + final boolean initialState; boolean expandedChanged; boolean expanded; boolean shouldShowEducation; String selectedBubbleKey; String suppressedBubbleKey; String unsuppressedBubbleKey; + BubbleBarLocation bubbleBarLocation; List removedBubbles; List bubbleKeysInOrder; @@ -170,12 +173,14 @@ public class BubbleBarController extends IBubblesListener.Stub { List currentBubbles; BubbleBarViewUpdate(BubbleBarUpdate update) { + initialState = update.initialState; expandedChanged = update.expandedChanged; expanded = update.expanded; shouldShowEducation = update.shouldShowEducation; selectedBubbleKey = update.selectedBubbleKey; suppressedBubbleKey = update.suppressedBubbleKey; unsuppressedBubbleKey = update.unsupressedBubbleKey; + bubbleBarLocation = update.bubbleBarLocation; removedBubbles = update.removedBubbles; bubbleKeysInOrder = update.bubbleKeysInOrder; } @@ -400,6 +405,14 @@ public class BubbleBarController extends IBubblesListener.Stub { Log.w(TAG, "expansion was changed but is the same"); } } + if (update.bubbleBarLocation != null) { + if (update.bubbleBarLocation != mBubbleBarViewController.getBubbleBarLocation()) { + // Animate when receiving updates. Skip it if we received the initial state. + boolean animate = !update.initialState; + mBubbleBarViewController.setBubbleBarLocation(update.bubbleBarLocation, animate); + mBubbleStashController.setBubbleBarLocation(update.bubbleBarLocation); + } + } } /** Tells WMShell to show the currently selected bubble. */ @@ -593,7 +606,7 @@ public class BubbleBarController extends IBubblesListener.Stub { Rect location = new Rect(); // currentBarBounds is only useful for distance from left or right edge. // It contains the current bounds, calculate the expanded bounds. - if (mBarView.isOnLeft()) { + if (mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl())) { location.left = currentBarBounds.left; location.right = (int) (currentBarBounds.left + mBarView.expandedWidth()); } else { @@ -601,7 +614,7 @@ public class BubbleBarController extends IBubblesListener.Stub { location.right = currentBarBounds.right; } final int translation = (int) abs(mBubbleStashController.getBubbleBarTranslationY()); - location.top = displaySize.y - mBarView.getHeight() - translation; + location.top = displaySize.y - currentBarBounds.height() - translation; location.bottom = displaySize.y - translation; return location; } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java index 8f693a6d53..a5da65f660 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java @@ -15,21 +15,33 @@ */ package com.android.launcher3.taskbar.bubbles; +import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE; +import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; + import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; +import android.util.LayoutDirection; import android.util.Log; +import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.dynamicanimation.animation.SpringForce; + import com.android.launcher3.R; +import com.android.launcher3.anim.SpringAnimationBuilder; import com.android.launcher3.taskbar.TaskbarActivityContext; import com.android.launcher3.views.ActivityContext; +import com.android.wm.shell.common.bubbles.BubbleBarLocation; import java.util.List; import java.util.function.Consumer; @@ -70,6 +82,18 @@ public class BubbleBarView extends FrameLayout { private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200; private static final int WIDTH_ANIMATION_DURATION_MS = 200; + private static final long FADE_OUT_ANIM_ALPHA_DURATION_MS = 50L; + private static final long FADE_OUT_ANIM_ALPHA_DELAY_MS = 50L; + private static final long FADE_OUT_ANIM_POSITION_DURATION_MS = 100L; + // During fade out animation we shift the bubble bar 1/80th of the screen width + private static final float FADE_OUT_ANIM_POSITION_SHIFT = 1 / 80f; + + private static final long FADE_IN_ANIM_ALPHA_DURATION_MS = 100L; + // Use STIFFNESS_MEDIUMLOW which is not defined in the API constants + private static final float FADE_IN_ANIM_POSITION_SPRING_STIFFNESS = 400f; + // During fade in animation we shift the bubble bar 1/60th of the screen width + private static final float FADE_IN_ANIM_POSITION_SHIFT = 1 / 60f; + private final BubbleBarBackground mBubbleBarBackground; /** @@ -86,11 +110,13 @@ public class BubbleBarView extends FrameLayout { private final float mIconSize; // The elevation of the bubbles within the bar private final float mBubbleElevation; + private final int mPointerSize; // Whether the bar is expanded (i.e. the bubble activity is being displayed). private boolean mIsBarExpanded = false; // The currently selected bubble view. private BubbleView mSelectedBubbleView; + private BubbleBarLocation mBubbleBarLocation = BubbleBarLocation.DEFAULT; // The click listener when the bubble bar is collapsed. private View.OnClickListener mOnClickListener; @@ -102,6 +128,9 @@ public class BubbleBarView extends FrameLayout { // collapsed state and 1 to the fully expanded state. private final ValueAnimator mWidthAnimator = ValueAnimator.ofFloat(0, 1); + @Nullable + private Animator mBubbleBarLocationAnimator = null; + // We don't reorder the bubbles when they are expanded as it could be jarring for the user // this runnable will be populated with any reordering of the bubbles that should be applied // once they are collapsed. @@ -114,6 +143,8 @@ public class BubbleBarView extends FrameLayout { @Nullable private BubbleView mDraggedBubbleView; + private int mPreviousLayoutDirection = LayoutDirection.UNDEFINED; + public BubbleBarView(Context context) { this(context, null); } @@ -136,6 +167,8 @@ public class BubbleBarView extends FrameLayout { mIconSpacing = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing); mIconSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size); mBubbleElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_elevation); + mPointerSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_pointer_size); + setClipToPadding(false); mBubbleBarBackground = new BubbleBarBackground(activityContext, @@ -184,7 +217,7 @@ public class BubbleBarView extends FrameLayout { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mBubbleBarBounds.left = left; - mBubbleBarBounds.top = top; + mBubbleBarBounds.top = top + mPointerSize; mBubbleBarBounds.right = right; mBubbleBarBounds.bottom = bottom; @@ -199,24 +232,123 @@ public class BubbleBarView extends FrameLayout { @Override public void onRtlPropertiesChanged(int layoutDirection) { - // TODO(b/313661121): set this based on bubble bar position and not LTR or RTL - boolean onLeft = layoutDirection == LAYOUT_DIRECTION_RTL; + if (mBubbleBarLocation == BubbleBarLocation.DEFAULT + && mPreviousLayoutDirection != layoutDirection) { + Log.d(TAG, "BubbleBar RTL properties changed, new layoutDirection=" + layoutDirection + + " previous layoutDirection=" + mPreviousLayoutDirection); + mPreviousLayoutDirection = layoutDirection; + onBubbleBarLocationChanged(); + } + } + + private void onBubbleBarLocationChanged() { + final boolean onLeft = mBubbleBarLocation.isOnLeft(isLayoutRtl()); mBubbleBarBackground.setAnchorLeft(onLeft); mRelativePivotX = onLeft ? 0f : 1f; + ViewGroup.LayoutParams layoutParams = getLayoutParams(); + if (layoutParams instanceof LayoutParams lp) { + lp.gravity = Gravity.BOTTOM | (onLeft ? Gravity.LEFT : Gravity.RIGHT); + setLayoutParams(lp); + } + invalidate(); } /** - * @return true when bar is pinned to the left edge of the screen + * @return current {@link BubbleBarLocation} */ - public boolean isOnLeft() { - return getLayoutDirection() == LAYOUT_DIRECTION_RTL; + public BubbleBarLocation getBubbleBarLocation() { + return mBubbleBarLocation; + } + + /** + * Update {@link BubbleBarLocation} + */ + public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation, boolean animate) { + if (animate) { + animateToBubbleBarLocation(bubbleBarLocation); + } else { + setBubbleBarLocationInternal(bubbleBarLocation); + } + } + + private void setBubbleBarLocationInternal(BubbleBarLocation bubbleBarLocation) { + if (bubbleBarLocation != mBubbleBarLocation) { + mBubbleBarLocation = bubbleBarLocation; + onBubbleBarLocationChanged(); + invalidate(); + } + } + + private void animateToBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { + if (bubbleBarLocation == mBubbleBarLocation) { + // nothing to do, already at expected location + return; + } + if (mBubbleBarLocationAnimator != null && mBubbleBarLocationAnimator.isRunning()) { + mBubbleBarLocationAnimator.cancel(); + } + + // Location animation uses two separate animators. + // First animator hides the bar. + // After it completes, location update is sent to layout the bar in the new location. + // Second animator is started to show the bar. + mBubbleBarLocationAnimator = getLocationUpdateFadeOutAnimator(); + mBubbleBarLocationAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Bubble bar is not visible, update the location + setBubbleBarLocationInternal(bubbleBarLocation); + // Animate it in + mBubbleBarLocationAnimator = getLocationUpdateFadeInAnimator(); + mBubbleBarLocationAnimator.start(); + } + }); + mBubbleBarLocationAnimator.start(); + } + + private AnimatorSet getLocationUpdateFadeOutAnimator() { + final float shift = + getResources().getDisplayMetrics().widthPixels * FADE_OUT_ANIM_POSITION_SHIFT; + final float tx = mBubbleBarLocation.isOnLeft(isLayoutRtl()) ? shift : -shift; + + ObjectAnimator positionAnim = ObjectAnimator.ofFloat(this, TRANSLATION_X, tx) + .setDuration(FADE_OUT_ANIM_POSITION_DURATION_MS); + positionAnim.setInterpolator(EMPHASIZED_ACCELERATE); + + ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, ALPHA, 0f) + .setDuration(FADE_OUT_ANIM_ALPHA_DURATION_MS); + alphaAnim.setStartDelay(FADE_OUT_ANIM_ALPHA_DELAY_MS); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(positionAnim, alphaAnim); + return animatorSet; + } + + private Animator getLocationUpdateFadeInAnimator() { + final float shift = + getResources().getDisplayMetrics().widthPixels * FADE_IN_ANIM_POSITION_SHIFT; + final float startTx = mBubbleBarLocation.isOnLeft(isLayoutRtl()) ? shift : -shift; + + ValueAnimator positionAnim = new SpringAnimationBuilder(getContext()) + .setStartValue(startTx) + .setEndValue(0) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) + .setStiffness(FADE_IN_ANIM_POSITION_SPRING_STIFFNESS) + .build(this, VIEW_TRANSLATE_X); + + ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, ALPHA, 1f) + .setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(positionAnim, alphaAnim); + return animatorSet; } /** * Updates the bounds with translation that may have been applied and returns the result. */ public Rect getBubbleBarBounds() { - mBubbleBarBounds.top = getTop() + (int) getTranslationY(); + mBubbleBarBounds.top = getTop() + (int) getTranslationY() + mPointerSize; mBubbleBarBounds.bottom = getBottom() + (int) getTranslationY(); return mBubbleBarBounds; } @@ -290,7 +422,7 @@ public class BubbleBarView extends FrameLayout { int bubbleCount = getChildCount(); final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f; final boolean animate = getVisibility() == VISIBLE; - final boolean onLeft = isOnLeft(); + final boolean onLeft = mBubbleBarLocation.isOnLeft(isLayoutRtl()); for (int i = 0; i < bubbleCount; i++) { BubbleView bv = (BubbleView) getChildAt(i); bv.setTranslationY(ty); @@ -453,7 +585,7 @@ public class BubbleBarView extends FrameLayout { private float arrowPositionForSelectedWhenExpanded() { final int index = indexOfChild(mSelectedBubbleView); final int bubblePosition; - if (isOnLeft()) { + if (mBubbleBarLocation.isOnLeft(isLayoutRtl())) { // Bubble positions are reversed. First bubble is on the right. bubblePosition = getChildCount() - index - 1; } else { @@ -465,7 +597,7 @@ public class BubbleBarView extends FrameLayout { private float arrowPositionForSelectedWhenCollapsed() { final int index = indexOfChild(mSelectedBubbleView); final int bubblePosition; - if (isOnLeft()) { + if (mBubbleBarLocation.isOnLeft(isLayoutRtl())) { // Bubble positions are reversed. First bubble may be shifted, if there are more // bubbles than the current bubble and overflow. bubblePosition = index == 0 && getChildCount() > 2 ? 1 : 0; diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index 6bb7b04c98..d46ee4020f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -37,6 +37,7 @@ import com.android.launcher3.taskbar.TaskbarStashController; import com.android.launcher3.util.MultiPropertyFactory; import com.android.launcher3.util.MultiValueAlpha; import com.android.quickstep.SystemUiProxy; +import com.android.wm.shell.common.bubbles.BubbleBarLocation; import java.util.List; import java.util.Objects; @@ -54,6 +55,7 @@ public class BubbleBarViewController { private final TaskbarActivityContext mActivity; private final BubbleBarView mBarView; private final int mIconSize; + private final int mPointerSize; // Initialized in init. private BubbleStashController mBubbleStashController; @@ -86,6 +88,8 @@ public class BubbleBarViewController { mBubbleBarAlpha = new MultiValueAlpha(mBarView, 1 /* num alpha channels */); mBubbleBarAlpha.setUpdateVisibility(true); mIconSize = activity.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size); + mPointerSize = activity.getResources().getDimensionPixelSize( + R.dimen.bubblebar_pointer_size); } public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) { @@ -96,9 +100,11 @@ public class BubbleBarViewController { mTaskbarInsetsController = controllers.taskbarInsetsController; mActivity.addOnDeviceProfileChangeListener(dp -> - mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight + mBarView.getLayoutParams().height = + mActivity.getDeviceProfile().taskbarHeight + mPointerSize ); - mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight; + mBarView.getLayoutParams().height = + mActivity.getDeviceProfile().taskbarHeight + mPointerSize; mBubbleBarScale.updateValue(1f); mBubbleClickListener = v -> onBubbleClicked(v); mBubbleBarClickListener = v -> onBubbleBarClicked(); @@ -168,6 +174,20 @@ public class BubbleBarViewController { return mBubbleBarController.getSelectedBubbleKey() != null; } + /** + * @return current {@link BubbleBarLocation} + */ + public BubbleBarLocation getBubbleBarLocation() { + return mBarView.getBubbleBarLocation(); + } + + /** + * Update bar {@link BubbleBarLocation} + */ + public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation, boolean animate) { + mBarView.setBubbleBarLocation(bubbleBarLocation, animate); + } + /** * The bounds of the bubble bar. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java index 09021edb91..e25e58653a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java @@ -31,6 +31,7 @@ import com.android.launcher3.taskbar.TaskbarControllers; import com.android.launcher3.taskbar.TaskbarInsetsController; import com.android.launcher3.taskbar.TaskbarStashController; import com.android.launcher3.util.MultiPropertyFactory; +import com.android.wm.shell.common.bubbles.BubbleBarLocation; /** * Coordinates between controllers such as BubbleBarView and BubbleHandleViewController to @@ -356,4 +357,9 @@ public class BubbleStashController { public boolean isEventOverStashHandle(MotionEvent ev) { return mHandleViewController.isEventOverHandle(ev); } + + /** Set a bubble bar location */ + public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { + mHandleViewController.setBubbleBarLocation(bubbleBarLocation); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java index f88460ff13..f64517af21 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java @@ -16,7 +16,6 @@ package com.android.launcher3.taskbar.bubbles; import static android.view.View.INVISIBLE; -import static android.view.View.LAYOUT_DIRECTION_RTL; import static android.view.View.VISIBLE; import android.animation.Animator; @@ -39,6 +38,7 @@ import com.android.launcher3.util.Executors; import com.android.launcher3.util.MultiPropertyFactory; import com.android.launcher3.util.MultiValueAlpha; import com.android.systemui.shared.navigationbar.RegionSamplingHelper; +import com.android.wm.shell.common.bubbles.BubbleBarLocation; /** * Handles properties/data collection, then passes the results to our stashed handle View to render. @@ -119,14 +119,14 @@ public class BubbleStashedHandleViewController { }, Executors.UI_HELPER_EXECUTOR); mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> - updateBounds()); + updateBounds(mBarViewController.getBubbleBarLocation())); } - private void updateBounds() { + private void updateBounds(BubbleBarLocation bubbleBarLocation) { // As more bubbles get added, the icon bounds become larger. To ensure a consistent // handle bar position, we pin it to the edge of the screen. final int stashedCenterY = mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2; - if (isOnLeft()) { + if (bubbleBarLocation.isOnLeft(mStashedHandleView.isLayoutRtl())) { final int left = mBarViewController.getHorizontalMargin(); mStashedHandleBounds.set( left, @@ -149,11 +149,6 @@ public class BubbleStashedHandleViewController { mStashedHandleView.setPivotY(mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2f); } - private boolean isOnLeft() { - // TODO(b/313661121): set this based on bubble bar position and not LTR or RTL - return mStashedHandleView.getLayoutDirection() == LAYOUT_DIRECTION_RTL; - } - public void onDestroy() { mRegionSamplingHelper.stopAndDestroy(); mRegionSamplingHelper = null; @@ -301,4 +296,9 @@ public class BubbleStashedHandleViewController { public boolean containsX(int x) { return x >= mStashedHandleBounds.left && x <= mStashedHandleBounds.right; } + + /** Set a bubble bar location */ + public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { + updateBounds(bubbleBarLocation); + } } diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java index c1b3a16c57..36bdad4da4 100644 --- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java @@ -45,6 +45,7 @@ import com.android.launcher3.R; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.StatsLogManager.LauncherEvent; import com.android.launcher3.model.WellbeingModel; +import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.popup.SystemShortcut.AppInfo; import com.android.launcher3.util.InstantAppResolver; @@ -61,6 +62,7 @@ import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFutur import com.android.systemui.shared.recents.view.RecentsTransition; import com.android.systemui.shared.system.ActivityManagerWrapper; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Function; @@ -319,13 +321,18 @@ public interface TaskShortcutFactory { recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView)); boolean shouldShowActionsButtonInstead = isLargeTileFocusedTask && isInExpectedScrollPosition; + boolean hasUnpinnableApp = Arrays.stream(taskView.getTaskIdAttributeContainers()) + .anyMatch(att -> att != null && att.getItemInfo() != null + && ((att.getItemInfo().runtimeStatusFlags + & ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0)); // No "save app pair" menu item if: // - app pairs feature is not enabled // - the task in question is a single task + // - at least one app in app pair is unpinnable // - the Overview Actions Button should be visible if (!FeatureFlags.enableAppPairs() || !taskView.containsMultipleTasks() - || shouldShowActionsButtonInstead) { + || hasUnpinnableApp || shouldShowActionsButtonInstead) { return null; } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index 085c5024a8..9b48082015 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -29,6 +29,7 @@ import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP; +import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; @@ -83,6 +84,7 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.pm.UserCache; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.testing.TestLogging; @@ -501,6 +503,11 @@ public class TaskView extends FrameLayout implements Reusable { if (getRecentsView() != null) { stubInfo.screenId = getRecentsView().indexOfChild(this); } + if (Flags.privateSpaceRestrictAccessibilityDrag()) { + if (UserCache.getInstance(getContext()).getUserInfo(componentKey.user).isPrivate()) { + stubInfo.runtimeStatusFlags |= FLAG_NOT_PINNABLE; + } + } return stubInfo; } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RobolectricTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/RobolectricTest.kt new file mode 100644 index 0000000000..0694aec459 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RobolectricTest.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 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.quickstep + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RobolectricTest { + @Test + fun test1() { + val actual = 1 + 1 + assertThat(actual).isEqualTo(2) + } +} diff --git a/quickstep/tests/multivalentTestsForDevice b/quickstep/tests/multivalentTestsForDevice new file mode 120000 index 0000000000..fa0fabf27b --- /dev/null +++ b/quickstep/tests/multivalentTestsForDevice @@ -0,0 +1 @@ +./multivalentTests \ No newline at end of file diff --git a/quickstep/tests/multivalentTestsForDeviceless b/quickstep/tests/multivalentTestsForDeviceless new file mode 120000 index 0000000000..fa0fabf27b --- /dev/null +++ b/quickstep/tests/multivalentTestsForDeviceless @@ -0,0 +1 @@ +./multivalentTests \ No newline at end of file diff --git a/src/com/android/launcher3/RectUtils.kt b/src/com/android/launcher3/RectUtils.kt deleted file mode 100644 index 68d2eaf9ab..0000000000 --- a/src/com/android/launcher3/RectUtils.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2024 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.launcher3 - -import android.graphics.Rect - -/** - * Fit [this] into [targetRect] with letter boxing. After calling this method, [this] will be - * modified to be letter boxed. - * - * @param targetRect target [Rect] that [this] should be fitted into - */ -fun Rect.letterBox(targetRect: Rect) { - letterBox(targetRect, this) -} - -/** - * Fit [this] into [targetRect] with letter boxing. After calling this method, [resultRect] will be - * modified to be letter boxed. - * - * @param targetRect target [Rect] that [this] should be fitted into - * @param resultRect the letter boxed [Rect] - */ -fun Rect.letterBox(targetRect: Rect, resultRect: Rect) { - val widthRatio: Float = 1f * targetRect.width() / width() - val heightRatio: Float = 1f * targetRect.height() / height() - if (widthRatio < heightRatio) { - val scaledHeight: Int = (widthRatio * height()).toInt() - val verticalPadding: Int = (targetRect.height() - scaledHeight) / 2 - resultRect.set( - targetRect.left, - targetRect.top + verticalPadding, - targetRect.right, - targetRect.bottom - verticalPadding - ) - } else { - val scaledWidth: Int = (heightRatio * width()).toInt() - val horizontalPadding: Int = (targetRect.width() - scaledWidth) / 2 - resultRect.set( - targetRect.left + horizontalPadding, - targetRect.top, - targetRect.right - horizontalPadding, - targetRect.bottom - ) - } -} diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java index a54e52c905..96998a3e38 100644 --- a/src/com/android/launcher3/allapps/WorkProfileManager.java +++ b/src/com/android/launcher3/allapps/WorkProfileManager.java @@ -73,7 +73,7 @@ public class WorkProfileManager extends UserProfileManager * Posts quite mode enable/disable call for work profile user */ public void setWorkProfileEnabled(boolean enabled) { - setCurrentState(STATE_TRANSITION); + updateCurrentState(STATE_TRANSITION); setQuietMode(!enabled); } diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java index 7f9a1fc4c2..9c9b80d80f 100644 --- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java @@ -29,9 +29,11 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -53,7 +55,6 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; -import com.android.launcher3.RectUtilsKt; import com.android.launcher3.icons.FastBitmapDrawable; import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; import com.android.launcher3.model.data.ItemInfoWithIcon; @@ -77,9 +78,9 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView private final Rect mRect = new Rect(); - private final Rect mPreviewBitmapRect = new Rect(); - private final Rect mCanvasRect = new Rect(); - private final Rect mLetterBoxedPreviewBitmapRect = new Rect(); + private final Matrix mMatrix = new Matrix(); + private final RectF mPreviewBitmapRect = new RectF(); + private final RectF mCanvasRect = new RectF(); private final LauncherWidgetHolder mWidgetHolder; private final LauncherAppWidgetProviderInfo mAppwidget; @@ -458,9 +459,8 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView mPreviewBitmapRect.set(0, 0, mPreviewBitmap.getWidth(), mPreviewBitmap.getHeight()); mCanvasRect.set(0, 0, getWidth(), getHeight()); - RectUtilsKt.letterBox(mPreviewBitmapRect, mCanvasRect, mLetterBoxedPreviewBitmapRect); - canvas.drawBitmap(mPreviewBitmap, mPreviewBitmapRect, mLetterBoxedPreviewBitmapRect, - mPreviewPaint); + mMatrix.setRectToRect(mPreviewBitmapRect, mCanvasRect, Matrix.ScaleToFit.CENTER); + canvas.drawBitmap(mPreviewBitmap, mMatrix, mPreviewPaint); return; } if (mCenterDrawable == null) { diff --git a/tests/Launcher3Tests.xml b/tests/Launcher3Tests.xml index 0b746630fb..29c34be546 100644 --- a/tests/Launcher3Tests.xml +++ b/tests/Launcher3Tests.xml @@ -18,6 +18,15 @@