Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index 6422f8d..5f7bc70 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -95,7 +95,7 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Postavke navigacije sustavom"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Podijeli"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Snimka zaslona"</string>
- <string name="action_split" msgid="2098009717623550676">"Podijeli"</string>
+ <string name="action_split" msgid="2098009717623550676">"Podjela"</string>
<string name="action_save_app_pair" msgid="5974823919237645229">"Spremi par apl."</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Dodirnite drugu aplikaciju za podijeljeni zaslon"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Odaberite drugu aplikaciju za upotrebu podijeljenog zaslona"</string>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index d35aba1..b89cd3c 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -118,7 +118,7 @@
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Ամրացրեք հավելվածների վահանակը"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Հավելվածների վահանակն էկրանի ներքևում ամրացնելու համար հպեք և պահեք բաժանիչը"</string>
<string name="taskbar_search_edu_title" msgid="5569194922234364530">"Սեղմած պահեք գործողության ստեղնը՝ էկրանին բովանդակություն որոնելու համար"</string>
- <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Այս պրոդուկտն օգտագործում է էկրանի ընտրված հատվածը որոնման համար։ Կիրառվում են Google-ի <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>գաղտնիության քաղաքականությունը<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> և <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>օգտագործման պայմանները<xliff:g id="END_TOS_LINK"></a></xliff:g>։"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Այս պրոդուկտն օգտագործում է էկրանի ընտրված հատվածը որոնման համար։ Կիրառվում են Google-ի <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Գաղտնիության քաղաքականությունը<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> և <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Օգտագործման պայմանները<xliff:g id="END_TOS_LINK"></a></xliff:g>։"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Փակել"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Պատրաստ է"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Սկիզբ"</string>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 0b40961..1737f07 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -117,7 +117,7 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Panel aplikácií vám ponúka ďalšie možnosti"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Vždy zobrazovať panel aplikácií"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Ak chcete, aby sa panel aplikácií vždy zobrazoval v dolnej časti obrazovky, pridržte rozdeľovač"</string>
- <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Ak chcete vyhľadávať, čo je na obrazovke, pridržte akčný kláves."</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Ak chcete vyhľadávať, čo je na obrazovke, pridržte akčný kláves"</string>
<string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Táto služba používa na účely vyhľadávania vybranú časť obrazovky. Uplatňujú sa <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>pravidlá ochrany súkromia<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> a <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>zmluvné podmienky<xliff:g id="END_TOS_LINK"></a></xliff:g> spoločnosti Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Zavrieť"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Hotovo"</string>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index 2a1eea6..cec75d9 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -95,7 +95,7 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Systemnavigeringsinställningar"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Dela"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Skärmbild"</string>
- <string name="action_split" msgid="2098009717623550676">"Delat"</string>
+ <string name="action_split" msgid="2098009717623550676">"Delad skärm"</string>
<string name="action_save_app_pair" msgid="5974823919237645229">"Spara app-par"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tryck på en annan app för att använda delad skärm"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Välj en annan app för att använda delad skärm"</string>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 0ee3d7f..93814b7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -17,6 +17,7 @@
import static com.android.app.animation.Interpolators.FINAL_FRAME;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
@@ -689,6 +690,12 @@
&& mIsStashed) {
// Prevent All Apps icon from appearing when going from hotseat to nav handle.
setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0f, 0f));
+ } else if (enableScalingRevealHomeAnimation()) {
+ // Tighten clamp so that these icons do not linger as the spring settles.
+ setter.setViewAlpha(child, 0,
+ isToHome
+ ? Interpolators.clampToProgress(LINEAR, 0f, 0.07f)
+ : Interpolators.clampToProgress(LINEAR, 0.93f, 1f));
} else {
setter.setViewAlpha(child, 0,
isToHome
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index d08015e..f3ac1e4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -174,6 +174,8 @@
private BubbleView mDraggedBubbleView;
private float mAlphaDuringDrag = 1f;
+ private Controller mController;
+
private int mPreviousLayoutDirection = LayoutDirection.UNDEFINED;
public BubbleBarView(Context context) {
@@ -366,22 +368,23 @@
*/
public PointF getBubbleBarDragReleaseTranslation(PointF initialTranslation,
BubbleBarLocation location) {
- // Start with the initial translation. Value on y-axis can be reused.
- final PointF dragEndTranslation = new PointF(initialTranslation);
- // Bubble bar is laid out on left or right side of the screen. And the desired new
- // location is on the other side. Calculate x translation value required to shift
- // bubble bar from one side to the other.
- final float shift = getDistanceFromOtherSide();
- if (location.isOnLeft(isLayoutRtl())) {
- // New location is on the left, shift left
- // before -> |......ooo.| after -> |.ooo......|
- dragEndTranslation.x = -shift;
- } else {
- // New location is on the right, shift right
- // before -> |.ooo......| after -> |......ooo.|
- dragEndTranslation.x = shift;
+ float dragEndTranslationX = initialTranslation.x;
+ if (getBubbleBarLocation().isOnLeft(isLayoutRtl()) != location.isOnLeft(isLayoutRtl())) {
+ // Bubble bar is laid out on left or right side of the screen. And the desired new
+ // location is on the other side. Calculate x translation value required to shift
+ // bubble bar from one side to the other.
+ final float shift = getDistanceFromOtherSide();
+ if (location.isOnLeft(isLayoutRtl())) {
+ // New location is on the left, shift left
+ // before -> |......ooo.| after -> |.ooo......|
+ dragEndTranslationX = -shift;
+ } else {
+ // New location is on the right, shift right
+ // before -> |.ooo......| after -> |......ooo.|
+ dragEndTranslationX = shift;
+ }
}
- return dragEndTranslation;
+ return new PointF(dragEndTranslationX, mController.getBubbleBarTranslationY());
}
/**
@@ -791,6 +794,10 @@
mUpdateSelectedBubbleAfterCollapse = updateSelectedBubbleAfterCollapse;
}
+ void setController(Controller controller) {
+ mController = controller;
+ }
+
/**
* Sets which bubble view should be shown as selected.
*/
@@ -963,7 +970,10 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (!mIsBarExpanded && !mIsAnimatingNewBubble) {
+ if (mIsAnimatingNewBubble) {
+ mController.onBubbleBarTouchedWhileAnimating();
+ }
+ if (!mIsBarExpanded) {
// When the bar is collapsed, all taps on it should expand it.
return true;
}
@@ -974,4 +984,14 @@
public boolean isAnimatingNewBubble() {
return mIsAnimatingNewBubble;
}
+
+ /** Interface for BubbleBarView to communicate with its controller. */
+ interface Controller {
+
+ /** Returns the translation Y that the bubble bar should have. */
+ float getBubbleBarTranslationY();
+
+ /** Notifies the controller that the bubble bar was touched while it was animating. */
+ void onBubbleBarTouchedWhileAnimating();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index cd8eaf9..fbdb2ed 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -132,6 +132,17 @@
});
mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController);
+ mBarView.setController(new BubbleBarView.Controller() {
+ @Override
+ public float getBubbleBarTranslationY() {
+ return mBubbleStashController.getBubbleBarTranslationY();
+ }
+
+ @Override
+ public void onBubbleBarTouchedWhileAnimating() {
+ BubbleBarViewController.this.onBubbleBarTouchedWhileAnimating();
+ }
+ });
}
private void onBubbleClicked(View v) {
@@ -140,14 +151,6 @@
Log.e(TAG, "bubble click listener, bubble was null");
}
- if (mBarView.isAnimatingNewBubble()) {
- mBubbleBarViewAnimator.onBubbleClickedWhileAnimating();
- mBubbleStashController.showBubbleBarImmediate();
- setExpanded(true);
- mBubbleBarController.showAndSelectBubble(bubble);
- return;
- }
-
final String currentlySelected = mBubbleBarController.getSelectedBubbleKey();
if (mBarView.isExpanded() && Objects.equals(bubble.getKey(), currentlySelected)) {
// Tapping the currently selected bubble while expanded collapses the view.
@@ -158,6 +161,11 @@
}
}
+ private void onBubbleBarTouchedWhileAnimating() {
+ mBubbleBarViewAnimator.onBubbleBarTouchedWhileAnimating();
+ mBubbleStashController.onNewBubbleAnimationInterrupted(false, mBarView.getTranslationY());
+ }
+
private void onBubbleBarClicked() {
if (mShouldShowEducation) {
mShouldShowEducation = false;
@@ -169,6 +177,10 @@
// Show user education relative to the reference point
mSystemUiProxy.showUserEducation(position);
} else {
+ // ensure that the bubble bar has the correct translation. we may have just interrupted
+ // the animation by touching the bubble bar.
+ mBubbleBarTranslationY.animateToValue(mBubbleStashController.getBubbleBarTranslationY())
+ .start();
setExpanded(true);
}
}
@@ -505,10 +517,17 @@
/**
* Notifies {@link BubbleBarView} that drag and all animations are finished.
*/
- public void onDragEnd() {
+ public void onDragBubbleEnded() {
mBarView.setDraggedBubble(null);
}
+ /** Notifies that dragging the bubble bar ended. */
+ public void onDragBubbleBarEnded() {
+ // we may have changed the bubble bar translation Y value from the value it had at the
+ // beginning of the drag, so update the translation Y animator state
+ mBubbleBarTranslationY.updateValue(mBarView.getTranslationY());
+ }
+
/**
* Get translation for bubble bar when drag is released.
*
@@ -516,9 +535,6 @@
*/
public PointF getBubbleBarDragReleaseTranslation(PointF initialTranslation,
BubbleBarLocation location) {
- if (location == mBarView.getBubbleBarLocation()) {
- return initialTranslation;
- }
return mBarView.getBubbleBarDragReleaseTranslation(initialTranslation, location);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
index 15de1b8..f4b393a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
@@ -124,7 +124,7 @@
@Override
void onDragEnd() {
mBubbleBarController.updateBubbleBarLocation(mReleasedLocation);
- mBubbleBarViewController.onDragEnd();
+ mBubbleBarViewController.onDragBubbleEnded();
mBubblePinController.setListener(null);
}
@@ -192,6 +192,7 @@
bubbleBarView.setIsDragging(false);
// Restoring the initial pivot for the bubble bar view
bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
+ mBubbleBarViewController.onDragBubbleBarEnded();
mBubbleBarPinController.setListener(null);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
index be935d8..d88e272 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -42,6 +42,8 @@
const val FLYOUT_DELAY_MS: Long = 2500
/** The initial scale Y value that the new bubble is set to before the animation starts. */
const val BUBBLE_ANIMATION_INITIAL_SCALE_Y = 0.3f
+ /** The minimum alpha value to make the bubble bar touchable. */
+ const val MIN_ALPHA_FOR_TOUCHABLE = 0.5f
}
/** Wrapper around the animating bubble with its show and hide animations. */
@@ -167,6 +169,9 @@
bubbleBarView.scaleY =
BUBBLE_ANIMATION_INITIAL_SCALE_Y +
(1 - BUBBLE_ANIMATION_INITIAL_SCALE_Y) * fraction
+ if (bubbleBarView.alpha > MIN_ALPHA_FOR_TOUCHABLE) {
+ bubbleStashController.updateTaskbarTouchRegion()
+ }
}
}
else -> {
@@ -176,6 +181,7 @@
bubbleBarView.alpha = 1f
bubbleBarView.scaleY = 1f
bubbleBarView.translationY = ty - offset
+ bubbleStashController.updateTaskbarTouchRegion()
}
}
}
@@ -233,6 +239,9 @@
(totalTranslationY - ty) / (totalTranslationY - stashedHandleTranslationY)
bubbleBarView.alpha = 1 - fraction
bubbleBarView.scaleY = 1 - (1 - BUBBLE_ANIMATION_INITIAL_SCALE_Y) * fraction
+ if (bubbleBarView.alpha > MIN_ALPHA_FOR_TOUCHABLE) {
+ bubbleStashController.updateTaskbarTouchRegion()
+ }
}
ty <= 0 -> {
// this is the second part of the animation. make the bubble bar invisible and
@@ -279,6 +288,7 @@
animatingBubble = null
bubbleStashController.showBubbleBarImmediate()
bubbleBarView.onAnimatingBubbleCompleted()
+ bubbleStashController.updateTaskbarTouchRegion()
}
}
animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation)
@@ -298,6 +308,7 @@
val animator = PhysicsAnimator.getInstance(bubbleBarView)
animator.setDefaultSpringConfig(springConfig)
animator.spring(DynamicAnimation.TRANSLATION_Y, bubbleStashController.bubbleBarTranslationY)
+ animator.addUpdateListener { _, _ -> bubbleStashController.updateTaskbarTouchRegion() }
animator.addEndListener { _, _, _, _, _, _, _ ->
// the bubble bar is now fully settled in. update taskbar touch region so it's touchable
bubbleStashController.updateTaskbarTouchRegion()
@@ -305,8 +316,10 @@
animator.start()
}
- /** Handles clicking on the animating bubble while the animation is still playing. */
- fun onBubbleClickedWhileAnimating() {
+ /** Handles touching the animating bubble bar. */
+ fun onBubbleBarTouchedWhileAnimating() {
+ PhysicsAnimator.getInstance(bubbleBarView).cancelIfRunning()
+ bubbleStashController.stashedHandlePhysicsAnimator.cancelIfRunning()
val hideAnimation = animatingBubble?.hideAnimation ?: return
scheduler.cancel(hideAnimation)
bubbleBarView.onAnimatingBubbleCompleted()
@@ -327,4 +340,8 @@
bubbleBarView.translationY
)
}
+
+ private fun <T> PhysicsAnimator<T>.cancelIfRunning() {
+ if (isRunning()) cancel()
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 317e6f2..e02ec41 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -40,6 +40,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
@@ -49,7 +50,6 @@
import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.LauncherRecentsView;
-import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
/**
@@ -167,8 +167,8 @@
propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
clearAllButtonAlpha, LINEAR);
float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS) ? 1 : 0;
- propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlphaSetter(),
- OverviewActionsView.FLOAT_SETTER, overviewButtonAlpha, config.getInterpolator(
+ propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
+ AnimatedFloat.VALUE, overviewButtonAlpha, config.getInterpolator(
ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index fc0df76..7e0a10f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -248,7 +248,7 @@
TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, fromState.showTaskThumbnailSplash() ? 1f : 0);
mRecentsView.setContentAlpha(1);
mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
- mLauncher.getActionsView().getVisibilityAlphaSetter().accept(
+ mLauncher.getActionsView().getVisibilityAlpha().updateValue(
(fromState.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0 ? 1f : 0f);
mRecentsView.setTaskIconScaledDown(true);
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index b7cbb47..f6eef62 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -148,7 +148,7 @@
* @param callback The callback to receive the task after its data has been populated.
* @return A cancelable handle to the request
*/
- public CancellableTask updateThumbnailInBackground(
+ public CancellableTask<ThumbnailData> updateThumbnailInBackground(
Task task, Consumer<ThumbnailData> callback) {
Preconditions.assertUIThread();
@@ -184,8 +184,8 @@
return newSize > oldSize;
}
- private CancellableTask updateThumbnailInBackground(TaskKey key, boolean lowResolution,
- Consumer<ThumbnailData> callback) {
+ private CancellableTask<ThumbnailData> updateThumbnailInBackground(TaskKey key,
+ boolean lowResolution, Consumer<ThumbnailData> callback) {
Preconditions.assertUIThread();
ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 1bf129c..644e4f9 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -42,13 +42,13 @@
import androidx.annotation.NonNull;
+import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.views.ClearAllButton;
-import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
/**
@@ -96,8 +96,8 @@
setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
clearAllButtonAlpha, LINEAR);
float overviewButtonAlpha = state.hasOverviewActions() ? 1 : 0;
- setter.setFloat(mActivity.getActionsView().getVisibilityAlphaSetter(),
- OverviewActionsView.FLOAT_SETTER, overviewButtonAlpha, LINEAR);
+ setter.setFloat(mActivity.getActionsView().getVisibilityAlpha(),
+ AnimatedFloat.VALUE, overviewButtonAlpha, LINEAR);
float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index 6b3e6e9..9a25c32 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -28,6 +28,8 @@
import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -52,6 +54,9 @@
*/
public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
+ private static final int HOVER_TASKBAR_UNSTASH_TIMEOUT = 500;
+ private static final Handler sUnstashHandler = new Handler(Looper.getMainLooper());
+
private final TaskbarActivityContext mTaskbarActivityContext;
private final OverviewCommandHelper mOverviewCommandHelper;
private final float mUnstashArea;
@@ -308,16 +313,25 @@
dp.heightPx);
if (mBottomEdgeBounds.contains(x, y)) {
- // If hovering stashed taskbar and then hover screen bottom edge, unstash it.
- mTaskbarActivityContext.onSwipeToUnstashTaskbar();
- mIsStashedTaskbarHovered = false;
+ // start a single unstash timeout if hovering bottom edge under the hinted taskbar.
+ if (!sUnstashHandler.hasMessagesOrCallbacks()) {
+ sUnstashHandler.postDelayed(() -> {
+ mTaskbarActivityContext.onSwipeToUnstashTaskbar();
+ mIsStashedTaskbarHovered = false;
+ }, HOVER_TASKBAR_UNSTASH_TIMEOUT);
+ }
} else if (!isStashedTaskbarHovered(x, y)) {
- // If exit hovering stashed taskbar, remove hint.
+ // If exit hovering stashed taskbar, remove hint and clear pending unstash calls.
+ sUnstashHandler.removeCallbacksAndMessages(null);
startStashedTaskbarHover(/* isHovered = */ false);
+ } else {
+ sUnstashHandler.removeCallbacksAndMessages(null);
}
}
private void updateUnhoveredTaskbarState(int x, int y) {
+ sUnstashHandler.removeCallbacksAndMessages(null);
+
DeviceProfile dp = mTaskbarActivityContext.getDeviceProfile();
mBottomEdgeBounds.set(
0,
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index b7c0236..3565174 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -191,25 +191,20 @@
}
override fun launchTaskAnimated(): RunnableList? {
- val recentsView = recentsView
- if (recentsView == null) {
- Log.d(TAG, "launchTaskAnimated - recentsView is null")
- return null
- }
-
+ val recentsView = recentsView ?: return null
val endCallback = RunnableList()
val desktopController = recentsView.desktopRecentsController
- if (desktopController == null) {
- Log.d(
- TAG,
- "launchTaskAnimated - recentsController is null: ${taskIds.contentToString()}"
- )
- } else {
+ if (desktopController != null) {
desktopController.launchDesktopFromRecents(this) { endCallback.executeAllAndDestroy() }
Log.d(
TAG,
"launchTaskAnimated - launchDesktopFromRecents: ${taskIds.contentToString()}"
)
+ } else {
+ Log.d(
+ TAG,
+ "launchTaskAnimated - recentsController is null: ${taskIds.contentToString()}"
+ )
}
// Callbacks get run from recentsView for case when recents animation already running
@@ -222,7 +217,7 @@
callback.accept(true)
}
- public override fun refreshThumbnails(thumbnailDatas: HashMap<Int, ThumbnailData>?) {
+ public override fun refreshThumbnails(thumbnailDatas: HashMap<Int, ThumbnailData?>?) {
// Sets new thumbnails based on the incoming data and refreshes the rest.
thumbnailDatas?.let {
mTaskContainers.forEach {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
deleted file mode 100644
index 1ccb764..0000000
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ /dev/null
@@ -1,560 +0,0 @@
-package com.android.quickstep.views;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.quickstep.util.SplitScreenUtils.convertLauncherSplitBoundsToShell;
-
-import android.app.ActivityTaskManager;
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Pair;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.jank.Cuj;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.CancellableTask;
-import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.launcher3.util.TransformingTouchDelegate;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.TaskIconCache;
-import com.android.quickstep.TaskThumbnailCache;
-import com.android.quickstep.util.RecentsOrientedState;
-import com.android.quickstep.util.SplitSelectStateController;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
-import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
-
-import kotlin.Unit;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Optional;
-import java.util.function.Consumer;
-
-/**
- * TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks
- *
- * That's right. If you call within the next 5 minutes we'll go ahead and double your order and
- * send you !! TWO !! Tasks along with their TaskThumbnailViews complimentary. On. The. House.
- * And not only that, we'll even clean up your thumbnail request if you don't like it.
- * All the benefits of one TaskView, except DOUBLED!
- *
- * (Icon loading sold separately, fees may apply. Shipping & Handling for Overlays not included).
- */
-public class GroupedTaskView extends TaskView {
-
- private static final String TAG = "GroupedTaskView";
- // TODO(b/336612373): Support new TTV for GroupedTaskView
- private TaskThumbnailViewDeprecated mSnapshotView2;
- private TaskViewIcon mIconView2;
- @Nullable
- private CancellableTask<ThumbnailData> mThumbnailLoadRequest2;
- @Nullable
- private CancellableTask mIconLoadRequest2;
- private final float[] mIcon2CenterCoords = new float[2];
- private TransformingTouchDelegate mIcon2TouchDelegate;
- @Nullable
- private SplitBounds mSplitBoundsConfig;
- private final DigitalWellBeingToast mDigitalWellBeingToast2;
-
- public GroupedTaskView(Context context) {
- this(context, null);
- }
-
- public GroupedTaskView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public GroupedTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mDigitalWellBeingToast2 = new DigitalWellBeingToast(mContainer, this);
- }
-
- /**
- * Returns the second task bound to this TaskView.
- *
- * @deprecated Use {@link #mTaskContainers} instead.
- */
- @Deprecated
- @Nullable
- private Task getSecondTask() {
- return mTaskContainers.size() > 1 ? mTaskContainers.get(1).getTask() : null;
- }
-
- @Override
- public Unit getThumbnailBounds(@NonNull Rect bounds, boolean relativeToDragLayer) {
- if (mSplitBoundsConfig == null) {
- super.getThumbnailBounds(bounds, relativeToDragLayer);
- return Unit.INSTANCE;
- }
- if (relativeToDragLayer) {
- Rect firstThumbnailBounds = new Rect();
- Rect secondThumbnailBounds = new Rect();
- BaseDragLayer dragLayer = mContainer.getDragLayer();
- dragLayer.getDescendantRectRelativeToSelf(
- mTaskThumbnailViewDeprecated, firstThumbnailBounds);
- dragLayer.getDescendantRectRelativeToSelf(mSnapshotView2, secondThumbnailBounds);
-
- bounds.set(firstThumbnailBounds);
- bounds.union(secondThumbnailBounds);
- } else {
- bounds.set(getSnapshotViewBounds(mTaskThumbnailViewDeprecated));
- bounds.union(getSnapshotViewBounds(mSnapshotView2));
- }
- return Unit.INSTANCE;
- }
-
- private Rect getSnapshotViewBounds(@NonNull View snapshotView) {
- int snapshotViewX = Math.round(snapshotView.getX());
- int snapshotViewY = Math.round(snapshotView.getY());
- return new Rect(snapshotViewX,
- snapshotViewY,
- snapshotViewX + snapshotView.getWidth(),
- snapshotViewY + snapshotView.getHeight());
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mSnapshotView2 = findViewById(R.id.bottomright_snapshot);
- ViewStub iconViewStub2 = findViewById(R.id.bottomRight_icon);
- if (enableOverviewIconMenu()) {
- iconViewStub2.setLayoutResource(R.layout.icon_app_chip_view);
- } else {
- iconViewStub2.setLayoutResource(R.layout.icon_view);
- }
- mIconView2 = (TaskViewIcon) iconViewStub2.inflate();
- mIcon2TouchDelegate = new TransformingTouchDelegate(mIconView2.asView());
- }
-
- public void bind(Task primary, Task secondary, RecentsOrientedState orientedState,
- @Nullable SplitBounds splitBoundsConfig) {
- super.bind(primary, orientedState);
- mTaskContainers = Arrays.asList(
- mTaskContainers.get(0),
- new TaskContainer(secondary, findViewById(R.id.bottomright_snapshot),
- mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT, mDigitalWellBeingToast2));
- mTaskContainers.get(0).setStagePosition(
- SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT);
- mSnapshotView2.bind(secondary);
- mSplitBoundsConfig = splitBoundsConfig;
- if (mSplitBoundsConfig == null) {
- return;
- }
- mTaskThumbnailViewDeprecated.getPreviewPositionHelper().setSplitBounds(
- convertLauncherSplitBoundsToShell(splitBoundsConfig),
- PreviewPositionHelper.STAGE_POSITION_TOP_OR_LEFT);
- mSnapshotView2.getPreviewPositionHelper().setSplitBounds(
- convertLauncherSplitBoundsToShell(splitBoundsConfig),
- PreviewPositionHelper.STAGE_POSITION_BOTTOM_OR_RIGHT);
- }
-
- /**
- * Sets up an on-click listener and the visibility for show_windows icon on top of each task.
- */
- @Override
- public void setUpShowAllInstancesListener() {
- // sets up the listener for the left/top task
- super.setUpShowAllInstancesListener();
- if (mTaskContainers.size() < 2) {
- return;
- }
-
- // right/bottom task's base package name
- String taskPackageName = mTaskContainers.get(1).getTask().key.getPackageName();
-
- // icon of the right/bottom task
- View showWindowsView = findViewById(R.id.show_windows_right);
- updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName));
- }
-
- @Override
- public void onTaskListVisibilityChanged(boolean visible, int changes) {
- super.onTaskListVisibilityChanged(visible, changes);
- if (visible) {
- RecentsModel model = RecentsModel.INSTANCE.get(getContext());
- TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
- TaskIconCache iconCache = model.getIconCache();
-
- if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- mThumbnailLoadRequest2 = thumbnailCache.updateThumbnailInBackground(
- getSecondTask(),
- thumbnailData -> mSnapshotView2.setThumbnail(getSecondTask(),
- thumbnailData
- ));
- }
-
- if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
- mIconLoadRequest2 = iconCache.updateIconInBackground(getSecondTask(),
- (task) -> {
- setIcon(mIconView2, task.icon);
- if (enableOverviewIconMenu()) {
- setText(mIconView2, task.title);
- }
- mDigitalWellBeingToast2.initialize(getSecondTask());
- mDigitalWellBeingToast2.setSplitConfiguration(mSplitBoundsConfig);
- mDigitalWellBeingToast.setSplitConfiguration(mSplitBoundsConfig);
- });
- }
- } else {
- if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- mSnapshotView2.setThumbnail(null, null);
- // Reset the task thumbnail reference as well (it will be fetched from the cache or
- // reloaded next time we need it)
- getSecondTask().thumbnail = null;
- }
- if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
- setIcon(mIconView2, null);
- if (enableOverviewIconMenu()) {
- setText(mIconView2, null);
- }
- }
- }
- }
-
- public void updateSplitBoundsConfig(SplitBounds splitBounds) {
- mSplitBoundsConfig = splitBounds;
- invalidate();
- }
-
- @Nullable
- public SplitBounds getSplitBoundsConfig() {
- return mSplitBoundsConfig;
- }
-
- /**
- * Returns the {@link PersistentSnapPosition} of this pair of tasks.
- */
- public @PersistentSnapPosition int getSnapPosition() {
- if (mSplitBoundsConfig == null) {
- throw new IllegalStateException("mSplitBoundsConfig is null");
- }
-
- return mSplitBoundsConfig.snapPosition;
- }
-
- @Override
- public boolean offerTouchToChildren(MotionEvent event) {
- computeAndSetIconTouchDelegate(mIconView2, mIcon2CenterCoords, mIcon2TouchDelegate);
- if (mIcon2TouchDelegate.onTouchEvent(event)) {
- return true;
- }
-
- return super.offerTouchToChildren(event);
- }
-
- @Override
- protected void cancelPendingLoadTasks() {
- super.cancelPendingLoadTasks();
- if (mThumbnailLoadRequest2 != null) {
- mThumbnailLoadRequest2.cancel();
- mThumbnailLoadRequest2 = null;
- }
- if (mIconLoadRequest2 != null) {
- mIconLoadRequest2.cancel();
- mIconLoadRequest2 = null;
- }
- }
-
- @Nullable
- @Override
- public RunnableList launchTaskAnimated() {
- if (mTaskContainers.isEmpty()) {
- return null;
- }
-
- RunnableList endCallback = new RunnableList();
- RecentsView recentsView = getRecentsView();
- // Callbacks run from remote animation when recents animation not currently running
- InteractionJankMonitorWrapper.begin(this, Cuj.CUJ_SPLIT_SCREEN_ENTER,
- "Enter form GroupedTaskView");
- launchTaskInternal(success -> {
- endCallback.executeAllAndDestroy();
- InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
- }, false /* freezeTaskList */, true /*launchingExistingTaskview*/);
-
- // Callbacks get run from recentsView for case when recents animation already running
- recentsView.addSideTaskLaunchCallback(endCallback);
- return endCallback;
- }
-
- @Override
- public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
- launchTaskInternal(callback, isQuickswitch, false /*launchingExistingTaskview*/);
- }
-
- /**
- * @param launchingExistingTaskView {@link SplitSelectStateController#launchExistingSplitPair}
- * uses existence of GroupedTaskView as control flow of how to animate in the incoming task. If
- * we're launching from overview (from overview thumbnails) then pass in {@code true},
- * otherwise pass in {@code false} for case like quickswitching from home to task
- */
- private void launchTaskInternal(@NonNull Consumer<Boolean> callback, boolean isQuickswitch,
- boolean launchingExistingTaskView) {
- getRecentsView().getSplitSelectController().launchExistingSplitPair(
- launchingExistingTaskView ? this : null, getFirstTask().key.id,
- getSecondTask().key.id, SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
- callback, isQuickswitch, getSnapPosition());
- Log.d(TAG, "launchTaskInternal - launchExistingSplitPair: " + Arrays.toString(
- getTaskIds()));
- }
-
- @Override
- void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
- super.refreshThumbnails(thumbnailDatas);
- if (getSecondTask() != null && thumbnailDatas != null) {
- final ThumbnailData thumbnailData = thumbnailDatas.get(getSecondTask().key.id);
- if (thumbnailData != null) {
- mSnapshotView2.setThumbnail(getSecondTask(), thumbnailData);
- return;
- }
- }
-
- mSnapshotView2.refresh();
- }
-
- /**
- * Returns taskId that split selection was initiated with,
- * {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of
- * split selection
- */
- protected int getThisTaskCurrentlyInSplitSelection() {
- int initialTaskId = getRecentsView().getSplitSelectController().getInitialTaskId();
- return containsTaskId(initialTaskId) ? initialTaskId : INVALID_TASK_ID;
- }
-
- @Override
- protected int getLastSelectedChildTaskIndex() {
- SplitSelectStateController splitSelectController =
- getRecentsView().getSplitSelectController();
- if (splitSelectController.isDismissingFromSplitPair()) {
- // return the container index of the task that wasn't initially selected to split with
- // because that is the only remaining app that can be selected. The coordinate checks
- // below aren't reliable since both of those views may be gone/transformed
- int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
- if (initSplitTaskId != INVALID_TASK_ID) {
- return initSplitTaskId == getFirstTask().key.id ? 1 : 0;
- }
- }
-
- // Check which of the two apps was selected
- if (isCoordInView(mIconView2.asView(), mLastTouchDownPosition)
- || isCoordInView(mSnapshotView2, mLastTouchDownPosition)) {
- return 1;
- }
- return super.getLastSelectedChildTaskIndex();
- }
-
- private boolean isCoordInView(View v, PointF position) {
- float[] localPos = new float[]{position.x, position.y};
- Utilities.mapCoordInSelfToDescendant(v, this, localPos);
- return Utilities.pointInView(v, localPos[0], localPos[1], 0f /* slop */);
- }
-
- @Override
- public void onRecycle() {
- super.onRecycle();
- mSnapshotView2.setThumbnail(getSecondTask(), null);
- mSplitBoundsConfig = null;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- setMeasuredDimension(widthSize, heightSize);
- if (mSplitBoundsConfig == null || mTaskThumbnailViewDeprecated == null
- || mSnapshotView2 == null) {
- return;
- }
- int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
- if (initSplitTaskId == INVALID_TASK_ID) {
- getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(
- mTaskThumbnailViewDeprecated,
- mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
- mContainer.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
- // Should we be having a separate translation step apart from the measuring above?
- // The following only applies to large screen for now, but for future reference
- // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
- // translation directions
- mTaskThumbnailViewDeprecated.applySplitSelectTranslateX(
- mTaskThumbnailViewDeprecated.getTranslationX());
- mTaskThumbnailViewDeprecated.applySplitSelectTranslateY(
- mTaskThumbnailViewDeprecated.getTranslationY());
- mSnapshotView2.applySplitSelectTranslateX(mSnapshotView2.getTranslationX());
- mSnapshotView2.applySplitSelectTranslateY(mSnapshotView2.getTranslationY());
- } else {
- // Currently being split with this taskView, let the non-split selected thumbnail
- // take up full thumbnail area
- Optional<TaskContainer> nonSplitContainer = mTaskContainers.stream().filter(
- container -> container.getTask().key.id != initSplitTaskId).findAny();
- nonSplitContainer.ifPresent(
- taskIdAttributeContainer -> taskIdAttributeContainer.getThumbnailView().measure(
- widthMeasureSpec, MeasureSpec.makeMeasureSpec(
- heightSize - mContainer.getDeviceProfile()
- .overviewTaskThumbnailTopMarginPx,
- MeasureSpec.EXACTLY)));
- }
- if (!enableOverviewIconMenu()) {
- updateIconPlacement();
- }
- }
-
- @Override
- public void setOverlayEnabled(boolean overlayEnabled) {
- if (FeatureFlags.enableAppPairs()) {
- super.setOverlayEnabled(overlayEnabled);
- } else {
- // Intentional no-op to prevent setting smart actions overlay on thumbnails
- }
- }
-
- @Override
- public void setOrientationState(RecentsOrientedState orientationState) {
- DeviceProfile deviceProfile = mContainer.getDeviceProfile();
- if (enableOverviewIconMenu() && mSplitBoundsConfig != null) {
- ViewGroup.LayoutParams layoutParams = getLayoutParams();
- Pair<Point, Point> groupedTaskViewSizes =
- orientationState.getOrientationHandler().getGroupedTaskViewSizes(
- deviceProfile,
- mSplitBoundsConfig,
- layoutParams.width,
- layoutParams.height
- );
- int iconViewMarginStart = getResources().getDimensionPixelSize(
- R.dimen.task_thumbnail_icon_menu_expanded_top_start_margin);
- int iconViewBackgroundMarginStart = getResources().getDimensionPixelSize(
- R.dimen.task_thumbnail_icon_menu_background_margin_top_start);
- int iconMargins = (iconViewMarginStart + iconViewBackgroundMarginStart) * 2;
- ((IconAppChipView) mIconView).setMaxWidth(groupedTaskViewSizes.first.x - iconMargins);
- ((IconAppChipView) mIconView2).setMaxWidth(groupedTaskViewSizes.second.x - iconMargins);
- }
- // setMaxWidth() needs to be called before mIconView.setIconOrientation which is called in
- // the super below.
- super.setOrientationState(orientationState);
-
- boolean isGridTask = deviceProfile.isTablet && !isFocusedTask();
- mIconView2.setIconOrientation(orientationState, isGridTask);
- updateIconPlacement();
- updateSecondaryDwbPlacement();
- }
-
- private void updateIconPlacement() {
- if (mSplitBoundsConfig == null) {
- return;
- }
-
- DeviceProfile deviceProfile = mContainer.getDeviceProfile();
- int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
- boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-
- if (enableOverviewIconMenu()) {
- ViewGroup.LayoutParams layoutParams = getLayoutParams();
- Pair<Point, Point> groupedTaskViewSizes =
- getPagedOrientationHandler()
- .getGroupedTaskViewSizes(
- deviceProfile,
- mSplitBoundsConfig,
- layoutParams.width,
- layoutParams.height
- );
-
- getPagedOrientationHandler().setSplitIconParams(mIconView.asView(), mIconView2.asView(),
- taskIconHeight, groupedTaskViewSizes.first.x, groupedTaskViewSizes.first.y,
- getLayoutParams().height, getLayoutParams().width, isRtl, deviceProfile,
- mSplitBoundsConfig);
- } else {
- getPagedOrientationHandler().setSplitIconParams(mIconView.asView(), mIconView2.asView(),
- taskIconHeight, mTaskThumbnailViewDeprecated.getMeasuredWidth(),
- mTaskThumbnailViewDeprecated.getMeasuredHeight(), getMeasuredHeight(),
- getMeasuredWidth(),
- isRtl, deviceProfile, mSplitBoundsConfig);
- }
- }
-
- private void updateSecondaryDwbPlacement() {
- if (getSecondTask() == null) {
- return;
- }
- mDigitalWellBeingToast2.initialize(getSecondTask());
- }
-
- @Override
- protected void updateSnapshotRadius() {
- super.updateSnapshotRadius();
- mSnapshotView2.setFullscreenParams(mCurrentFullscreenParams);
- }
-
- @Override
- protected void setIconsAndBannersTransitionProgress(float progress, boolean invert) {
- super.setIconsAndBannersTransitionProgress(progress, invert);
- // Value set by super call
- float scale = mIconView.getAlpha();
- mIconView2.setContentAlpha(scale);
- mDigitalWellBeingToast2.updateBannerOffset(1f - scale);
- }
-
- @Override
- public void setColorTint(float amount, int tintColor) {
- super.setColorTint(amount, tintColor);
- mIconView2.setIconColorTint(tintColor, amount);
- mSnapshotView2.setDimAlpha(amount);
- mDigitalWellBeingToast2.setBannerColorTint(tintColor, amount);
- }
-
- @Override
- protected void applyThumbnailSplashAlpha() {
- super.applyThumbnailSplashAlpha();
- mSnapshotView2.setSplashAlpha(mTaskThumbnailSplashAlpha);
- }
-
- @Override
- protected void refreshTaskThumbnailSplash() {
- super.refreshTaskThumbnailSplash();
- mSnapshotView2.refreshSplashView();
- }
-
- @Override
- protected void resetViewTransforms() {
- super.resetViewTransforms();
- mSnapshotView2.resetViewTransforms();
- }
-
- /**
- * Sets visibility for thumbnails and associated elements (DWB banners).
- * IconView is unaffected.
- *
- * When setting INVISIBLE, sets the visibility for the last selected child task.
- * When setting VISIBLE (as a reset), sets the visibility for both tasks.
- */
- @Override
- void setThumbnailVisibility(int visibility, int taskId) {
- for (TaskContainer container : mTaskContainers) {
- if (visibility == VISIBLE || container.getTask().key.id == taskId) {
- container.getThumbnailView().setVisibility(visibility);
- container.getDigitalWellBeingToast().setBannerVisibility(visibility);
- }
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
new file mode 100644
index 0000000..93a7200
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -0,0 +1,529 @@
+/*
+ * 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.views
+
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.content.Context
+import android.graphics.PointF
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.util.Log
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewStub
+import com.android.internal.jank.Cuj
+import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.util.CancellableTask
+import com.android.launcher3.util.RunnableList
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.TransformingTouchDelegate
+import com.android.quickstep.RecentsModel
+import com.android.quickstep.util.RecentsOrientedState
+import com.android.quickstep.util.SplitScreenUtils.Companion.convertLauncherSplitBoundsToShell
+import com.android.quickstep.util.SplitSelectStateController
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition
+import java.util.function.Consumer
+
+/**
+ * TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks
+ *
+ * That's right. If you call within the next 5 minutes we'll go ahead and double your order and send
+ * you !! TWO !! Tasks along with their TaskThumbnailViews complimentary. On. The. House. And not
+ * only that, we'll even clean up your thumbnail request if you don't like it. All the benefits of
+ * one TaskView, except DOUBLED!
+ *
+ * (Icon loading sold separately, fees may apply. Shipping & Handling for Overlays not included).
+ */
+class GroupedTaskView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
+ TaskView(context, attrs) {
+ // TODO(b/336612373): Support new TTV for GroupedTaskView
+ private val icon2CenterCoords = FloatArray(2)
+ private val digitalWellBeingToast2: DigitalWellBeingToast =
+ DigitalWellBeingToast(mContainer, this)
+ private lateinit var snapshotView2: TaskThumbnailViewDeprecated
+ private lateinit var iconView2: TaskViewIcon
+ private lateinit var icon2TouchDelegate: TransformingTouchDelegate
+ private var thumbnailLoadRequest2: CancellableTask<ThumbnailData>? = null
+ private var iconLoadRequest2: CancellableTask<*>? = null
+ var splitBoundsConfig: SplitConfigurationOptions.SplitBounds? = null
+ private set
+
+ @get:Deprecated("Use {@link #mTaskContainers} instead.")
+ private val secondTask: Task
+ /** Returns the second task bound to this TaskView. */
+ get() {
+ assert(mTaskContainers.size > 1) { "GroupedTaskView is not bound" }
+ return mTaskContainers[1].task
+ }
+
+ @get:PersistentSnapPosition
+ val snapPosition: Int
+ /** Returns the [PersistentSnapPosition] of this pair of tasks. */
+ get() {
+ checkNotNull(splitBoundsConfig) { "mSplitBoundsConfig is null" }
+ return splitBoundsConfig!!.snapPosition
+ }
+
+ override fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean) {
+ splitBoundsConfig ?: return super.getThumbnailBounds(bounds, relativeToDragLayer)
+ if (relativeToDragLayer) {
+ val firstThumbnailBounds = Rect()
+ val secondThumbnailBounds = Rect()
+ with(mContainer.dragLayer) {
+ getDescendantRectRelativeToSelf(mTaskThumbnailViewDeprecated, firstThumbnailBounds)
+ getDescendantRectRelativeToSelf(snapshotView2, secondThumbnailBounds)
+ }
+ bounds.set(firstThumbnailBounds)
+ bounds.union(secondThumbnailBounds)
+ } else {
+ bounds.set(getSnapshotViewBounds(mTaskThumbnailViewDeprecated))
+ bounds.union(getSnapshotViewBounds(snapshotView2))
+ }
+ }
+
+ private fun getSnapshotViewBounds(snapshotView: View): Rect {
+ val snapshotViewX = Math.round(snapshotView.x)
+ val snapshotViewY = Math.round(snapshotView.y)
+ return Rect(
+ snapshotViewX,
+ snapshotViewY,
+ snapshotViewX + snapshotView.width,
+ snapshotViewY + snapshotView.height
+ )
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ snapshotView2 = findViewById(R.id.bottomright_snapshot)!!
+ val iconViewStub2 =
+ findViewById<ViewStub>(R.id.bottomRight_icon)!!.apply {
+ layoutResource =
+ if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+ else R.layout.icon_view
+ }
+ iconView2 = iconViewStub2.inflate() as TaskViewIcon
+ icon2TouchDelegate = TransformingTouchDelegate(iconView2.asView())
+ }
+
+ fun bind(
+ primaryTask: Task,
+ secondaryTask: Task,
+ orientedState: RecentsOrientedState,
+ splitBoundsConfig: SplitConfigurationOptions.SplitBounds?,
+ ) {
+ cancelPendingLoadTasks()
+ setupTaskContainers(primaryTask)
+ mTaskContainers =
+ listOf(
+ mTaskContainers[0].apply {
+ stagePosition = SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+ },
+ TaskContainer(
+ secondaryTask,
+ findViewById(R.id.bottomright_snapshot)!!,
+ iconView2,
+ SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT,
+ digitalWellBeingToast2
+ )
+ )
+ snapshotView2.bind(secondaryTask)
+ this.splitBoundsConfig = splitBoundsConfig
+ this.splitBoundsConfig?.let {
+ mTaskThumbnailViewDeprecated.previewPositionHelper.setSplitBounds(
+ convertLauncherSplitBoundsToShell(it),
+ PreviewPositionHelper.STAGE_POSITION_TOP_OR_LEFT
+ )
+ snapshotView2.previewPositionHelper.setSplitBounds(
+ convertLauncherSplitBoundsToShell(it),
+ PreviewPositionHelper.STAGE_POSITION_BOTTOM_OR_RIGHT
+ )
+ }
+ setOrientationState(orientedState)
+ }
+
+ /**
+ * Sets up an on-click listener and the visibility for show_windows icon on top of each task.
+ */
+ override fun setUpShowAllInstancesListener() {
+ // sets up the listener for the left/top task
+ super.setUpShowAllInstancesListener()
+ if (mTaskContainers.size < 2) {
+ return
+ }
+
+ // right/bottom task's base package name
+ val taskPackageName = mTaskContainers[1].task.key.packageName
+
+ // icon of the right/bottom task
+ val showWindowsView = findViewById<View>(R.id.show_windows_right)!!
+ updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName))
+ }
+
+ override fun onTaskListVisibilityChanged(visible: Boolean, changes: Int) {
+ super.onTaskListVisibilityChanged(visible, changes)
+ val model = RecentsModel.INSTANCE[context]
+ if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+ if (visible) {
+ thumbnailLoadRequest2 =
+ model.thumbnailCache.updateThumbnailInBackground(secondTask) {
+ snapshotView2.setThumbnail(secondTask, it)
+ }
+ } else {
+ snapshotView2.setThumbnail(null, null)
+ // Reset the task thumbnail reference as well (it will be fetched from the cache or
+ // reloaded next time we need it)
+ secondTask.thumbnail = null
+ }
+ }
+ if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+ if (visible) {
+ iconLoadRequest2 =
+ model.iconCache.updateIconInBackground(secondTask) {
+ setIcon(iconView2, it.icon)
+ if (enableOverviewIconMenu()) {
+ setText(iconView2, it.title)
+ }
+ digitalWellBeingToast2.initialize(secondTask)
+ digitalWellBeingToast2.setSplitConfiguration(splitBoundsConfig)
+ mDigitalWellBeingToast.setSplitConfiguration(splitBoundsConfig)
+ }
+ } else {
+ setIcon(iconView2, null)
+ if (enableOverviewIconMenu()) {
+ setText(iconView2, null)
+ }
+ }
+ }
+ }
+
+ fun updateSplitBoundsConfig(splitBounds: SplitConfigurationOptions.SplitBounds?) {
+ splitBoundsConfig = splitBounds
+ invalidate()
+ }
+
+ override fun offerTouchToChildren(event: MotionEvent): Boolean {
+ computeAndSetIconTouchDelegate(iconView2, icon2CenterCoords, icon2TouchDelegate)
+ return if (icon2TouchDelegate.onTouchEvent(event)) {
+ true
+ } else super.offerTouchToChildren(event)
+ }
+
+ override fun cancelPendingLoadTasks() {
+ super.cancelPendingLoadTasks()
+ thumbnailLoadRequest2?.cancel()
+ thumbnailLoadRequest2 = null
+ iconLoadRequest2?.cancel()
+ iconLoadRequest2 = null
+ }
+
+ override fun launchTaskAnimated(): RunnableList? {
+ if (mTaskContainers.isEmpty()) {
+ Log.d(TAG, "launchTaskAnimated - task is not bound")
+ return null
+ }
+ val recentsView = recentsView ?: return null
+ val endCallback = RunnableList()
+ // Callbacks run from remote animation when recents animation not currently running
+ InteractionJankMonitorWrapper.begin(
+ this,
+ Cuj.CUJ_SPLIT_SCREEN_ENTER,
+ "Enter form GroupedTaskView"
+ )
+ launchTaskInternal(isQuickSwitch = false, launchingExistingTaskView = true) {
+ endCallback.executeAllAndDestroy()
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER)
+ }
+
+ // Callbacks get run from recentsView for case when recents animation already running
+ recentsView.addSideTaskLaunchCallback(endCallback)
+ return endCallback
+ }
+
+ override fun launchTask(callback: Consumer<Boolean>, isQuickswitch: Boolean) {
+ launchTaskInternal(isQuickswitch, false, callback /*launchingExistingTaskview*/)
+ }
+
+ /**
+ * @param launchingExistingTaskView [SplitSelectStateController.launchExistingSplitPair] uses
+ * existence of GroupedTaskView as control flow of how to animate in the incoming task. If
+ * we're launching from overview (from overview thumbnails) then pass in `true`, otherwise
+ * pass in `false` for case like quickswitching from home to task
+ */
+ private fun launchTaskInternal(
+ isQuickSwitch: Boolean,
+ launchingExistingTaskView: Boolean,
+ callback: Consumer<Boolean>
+ ) {
+ check(mTaskContainers.size >= 2) { "task not bound" }
+ recentsView?.let {
+ it.splitSelectController.launchExistingSplitPair(
+ if (launchingExistingTaskView) this else null,
+ mTaskContainers[0].task.key.id,
+ mTaskContainers[1].task.key.id,
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+ callback,
+ isQuickSwitch,
+ snapPosition
+ )
+ Log.d(TAG, "launchTaskInternal - launchExistingSplitPair: ${taskIds.contentToString()}")
+ }
+ }
+
+ public override fun refreshThumbnails(thumbnailDatas: HashMap<Int, ThumbnailData?>?) {
+ super.refreshThumbnails(thumbnailDatas)
+ thumbnailDatas?.get(secondTask.key.id)?.let { snapshotView2.setThumbnail(secondTask, it) }
+ ?: { snapshotView2.refresh() }
+ }
+
+ /**
+ * Returns taskId that split selection was initiated with, [INVALID_TASK_ID] if no tasks in this
+ * TaskView are part of split selection
+ */
+ private fun getThisTaskCurrentlyInSplitSelection(): Int {
+ val initialTaskId = recentsView?.splitSelectController?.initialTaskId
+ return if (initialTaskId != null && containsTaskId(initialTaskId)) initialTaskId
+ else INVALID_TASK_ID
+ }
+
+ override fun getLastSelectedChildTaskIndex(): Int {
+ if (recentsView?.splitSelectController?.isDismissingFromSplitPair == true) {
+ // return the container index of the task that wasn't initially selected to split
+ // with because that is the only remaining app that can be selected. The coordinate
+ // checks below aren't reliable since both of those views may be gone/transformed
+ val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
+ if (initSplitTaskId != INVALID_TASK_ID) {
+ return if (initSplitTaskId == firstTask!!.key.id) 1 else 0
+ }
+ }
+
+ // Check which of the two apps was selected
+ if (
+ iconView2.asView().containsPoint(mLastTouchDownPosition) ||
+ snapshotView2.containsPoint(mLastTouchDownPosition)
+ ) {
+ return 1
+ }
+ return super.getLastSelectedChildTaskIndex()
+ }
+
+ private fun View.containsPoint(position: PointF): Boolean {
+ val localPos = floatArrayOf(position.x, position.y)
+ Utilities.mapCoordInSelfToDescendant(this, this@GroupedTaskView, localPos)
+ return Utilities.pointInView(this, localPos[0], localPos[1], 0f /* slop */)
+ }
+
+ override fun onRecycle() {
+ super.onRecycle()
+ snapshotView2.setThumbnail(secondTask, null)
+ splitBoundsConfig = null
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ val widthSize = MeasureSpec.getSize(widthMeasureSpec)
+ val heightSize = MeasureSpec.getSize(heightMeasureSpec)
+ setMeasuredDimension(widthSize, heightSize)
+ val splitBoundsConfig = splitBoundsConfig ?: return
+ val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
+ if (initSplitTaskId == INVALID_TASK_ID) {
+ pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
+ mTaskThumbnailViewDeprecated,
+ snapshotView2,
+ widthSize,
+ heightSize,
+ splitBoundsConfig,
+ mContainer.deviceProfile,
+ layoutDirection == LAYOUT_DIRECTION_RTL
+ )
+ // Should we be having a separate translation step apart from the measuring above?
+ // The following only applies to large screen for now, but for future reference
+ // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
+ // translation directions
+ mTaskThumbnailViewDeprecated.applySplitSelectTranslateX(
+ mTaskThumbnailViewDeprecated.translationX
+ )
+ mTaskThumbnailViewDeprecated.applySplitSelectTranslateY(
+ mTaskThumbnailViewDeprecated.translationY
+ )
+ snapshotView2.applySplitSelectTranslateX(snapshotView2.translationX)
+ snapshotView2.applySplitSelectTranslateY(snapshotView2.translationY)
+ } else {
+ // Currently being split with this taskView, let the non-split selected thumbnail
+ // take up full thumbnail area
+ mTaskContainers
+ .firstOrNull { it.task.key.id != initSplitTaskId }
+ ?.thumbnailView
+ ?.measure(
+ widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(
+ heightSize - mContainer.deviceProfile.overviewTaskThumbnailTopMarginPx,
+ MeasureSpec.EXACTLY
+ )
+ )
+ }
+ if (!enableOverviewIconMenu()) {
+ updateIconPlacement()
+ }
+ }
+
+ override fun setOverlayEnabled(overlayEnabled: Boolean) {
+ if (FeatureFlags.enableAppPairs()) {
+ super.setOverlayEnabled(overlayEnabled)
+ } else {
+ // Intentional no-op to prevent setting smart actions overlay on thumbnails
+ }
+ }
+
+ override fun setOrientationState(orientationState: RecentsOrientedState) {
+ if (enableOverviewIconMenu()) {
+ splitBoundsConfig?.let {
+ val groupedTaskViewSizes =
+ orientationState.orientationHandler.getGroupedTaskViewSizes(
+ mContainer.deviceProfile,
+ it,
+ layoutParams.width,
+ layoutParams.height
+ )
+ val iconViewMarginStart =
+ resources.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_expanded_top_start_margin
+ )
+ val iconViewBackgroundMarginStart =
+ resources.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_background_margin_top_start
+ )
+ val iconMargins = (iconViewMarginStart + iconViewBackgroundMarginStart) * 2
+ // setMaxWidth() needs to be called before mIconView.setIconOrientation which is
+ // called in the super below.
+ (mIconView as IconAppChipView).setMaxWidth(
+ groupedTaskViewSizes.first.x - iconMargins
+ )
+ (iconView2 as IconAppChipView).setMaxWidth(
+ groupedTaskViewSizes.second.x - iconMargins
+ )
+ }
+ }
+ super.setOrientationState(orientationState)
+ iconView2.setIconOrientation(orientationState, isGridTask())
+ updateIconPlacement()
+ }
+
+ override fun setThumbnailOrientation(orientationState: RecentsOrientedState?) {
+ super.setThumbnailOrientation(orientationState)
+ digitalWellBeingToast2.initialize(secondTask)
+ }
+
+ private fun updateIconPlacement() {
+ val splitBoundsConfig = splitBoundsConfig ?: return
+ val taskIconHeight = mContainer.deviceProfile.overviewTaskIconSizePx
+ val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
+ if (enableOverviewIconMenu()) {
+ val groupedTaskViewSizes =
+ pagedOrientationHandler.getGroupedTaskViewSizes(
+ mContainer.deviceProfile,
+ splitBoundsConfig,
+ layoutParams.width,
+ layoutParams.height
+ )
+ pagedOrientationHandler.setSplitIconParams(
+ mIconView.asView(),
+ iconView2.asView(),
+ taskIconHeight,
+ groupedTaskViewSizes.first.x,
+ groupedTaskViewSizes.first.y,
+ layoutParams.height,
+ layoutParams.width,
+ isRtl,
+ mContainer.deviceProfile,
+ splitBoundsConfig
+ )
+ } else {
+ pagedOrientationHandler.setSplitIconParams(
+ mIconView.asView(),
+ iconView2.asView(),
+ taskIconHeight,
+ mTaskThumbnailViewDeprecated.measuredWidth,
+ mTaskThumbnailViewDeprecated.measuredHeight,
+ measuredHeight,
+ measuredWidth,
+ isRtl,
+ mContainer.deviceProfile,
+ splitBoundsConfig
+ )
+ }
+ }
+
+ override fun updateSnapshotRadius() {
+ super.updateSnapshotRadius()
+ snapshotView2.setFullscreenParams(mCurrentFullscreenParams)
+ }
+
+ override fun setIconsAndBannersTransitionProgress(progress: Float, invert: Boolean) {
+ super.setIconsAndBannersTransitionProgress(progress, invert)
+ // Value set by super call
+ val scale = mIconView.alpha
+ iconView2.setContentAlpha(scale)
+ digitalWellBeingToast2.updateBannerOffset(1f - scale)
+ }
+
+ override fun setColorTint(amount: Float, tintColor: Int) {
+ super.setColorTint(amount, tintColor)
+ iconView2.setIconColorTint(tintColor, amount)
+ snapshotView2.dimAlpha = amount
+ digitalWellBeingToast2.setBannerColorTint(tintColor, amount)
+ }
+
+ override fun applyThumbnailSplashAlpha() {
+ super.applyThumbnailSplashAlpha()
+ snapshotView2.setSplashAlpha(mTaskThumbnailSplashAlpha)
+ }
+
+ override fun refreshTaskThumbnailSplash() {
+ super.refreshTaskThumbnailSplash()
+ snapshotView2.refreshSplashView()
+ }
+
+ override fun resetViewTransforms() {
+ super.resetViewTransforms()
+ snapshotView2.resetViewTransforms()
+ }
+
+ /**
+ * Sets visibility for thumbnails and associated elements (DWB banners). IconView is unaffected.
+ *
+ * When setting INVISIBLE, sets the visibility for the last selected child task. When setting
+ * VISIBLE (as a reset), sets the visibility for both tasks.
+ */
+ public override fun setThumbnailVisibility(visibility: Int, taskId: Int) {
+ mTaskContainers.forEach {
+ if (visibility == VISIBLE || it.task.key.id == taskId) {
+ it.thumbnailView.visibility = visibility
+ it.digitalWellBeingToast.setBannerVisibility(visibility)
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "GroupedTaskView"
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index ba60141..83a2ceb 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -20,7 +20,6 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.FloatProperty;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
@@ -34,36 +33,22 @@
import com.android.launcher3.Flags;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.NavigationMode;
import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
-import com.android.quickstep.util.AppPairsController;
import com.android.quickstep.util.LayoutUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
-import java.util.function.Consumer;
/**
* View for showing action buttons in Overview
*/
public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout
implements OnClickListener, Insettable {
- public static final FloatProperty<Consumer<Float>> FLOAT_SETTER =
- new FloatProperty<>("floatSetter") {
- @Override
- public void setValue(Consumer<Float> consumer, float v) {
- consumer.accept(v);
- }
-
- @Override
- public Float get(Consumer<Float> consumer) {
- return -1f;
- }
- };
-
private final Rect mInsets = new Rect();
@IntDef(flag = true, value = {
@@ -110,7 +95,13 @@
public @interface SplitButtonHiddenFlags { }
public static final int FLAG_SMALL_SCREEN_HIDE_SPLIT = 1 << 0;
- /** Holds MultiValueAlpha values for all actions buttons */
+ /**
+ * Holds an AnimatedFloat for each alpha property, used to set or animate alpha values in
+ * {@link #mMultiValueAlphas}.
+ */
+ private final AnimatedFloat[] mAlphaProperties = new AnimatedFloat[NUM_ALPHAS];
+
+ /** Holds MultiValueAlpha values for all actions bars */
private final MultiValueAlpha[] mMultiValueAlphas = new MultiValueAlpha[2];
/** Index used for single-task actions in the mMultiValueAlphas array */
private static final int ACTIONS_ALPHAS = 0;
@@ -161,11 +152,21 @@
// These will take up the same space on the screen and alternate visibility as needed.
mActionButtons = findViewById(R.id.action_buttons);
mGroupActionButtons = findViewById(R.id.group_action_buttons);
- // Initialize a list to set alpha on mActionButtons and mGroupActionButtons simultaneously.
+ // Initialize a list to hold alphas for mActionButtons and mGroupActionButtons.
mMultiValueAlphas[ACTIONS_ALPHAS] = new MultiValueAlpha(mActionButtons, NUM_ALPHAS);
mMultiValueAlphas[GROUP_ACTIONS_ALPHAS] =
new MultiValueAlpha(mGroupActionButtons, NUM_ALPHAS);
Arrays.stream(mMultiValueAlphas).forEach(a -> a.setUpdateVisibility(true));
+ // To control alpha simultaneously on mActionButtons and mGroupActionButtons, we set up an
+ // AnimatedFloat for each alpha property.
+ for (int i = 0; i < NUM_ALPHAS; i++) {
+ final int index = i;
+ mAlphaProperties[index] = new AnimatedFloat(() -> {
+ for (MultiValueAlpha multiValueAlpha : mMultiValueAlphas) {
+ multiValueAlpha.get(index).setValue(mAlphaProperties[index].value);
+ }
+ }, 1f /* initialValue */);
+ }
// The screenshot button is implemented as a Button in launcher3 and NexusLauncher, but is
// an ImageButton in go launcher (does not share a common class with Button). Take care when
@@ -222,7 +223,7 @@
mHiddenFlags &= ~visibilityFlags;
}
boolean isHidden = mHiddenFlags != 0;
- setActionsAlpha(INDEX_HIDDEN_FLAGS_ALPHA, isHidden ? 0 : 1);
+ mAlphaProperties[INDEX_HIDDEN_FLAGS_ALPHA].updateValue(isHidden ? 0 : 1);
}
/**
@@ -310,28 +311,24 @@
}
}
- private void setActionsAlpha(int index, float value) {
- Arrays.stream(mMultiValueAlphas).forEach(a -> a.get(index).setValue(value));
+ public AnimatedFloat getContentAlpha() {
+ return mAlphaProperties[INDEX_CONTENT_ALPHA];
}
- public Consumer<Float> getContentAlphaSetter() {
- return v -> setActionsAlpha(INDEX_CONTENT_ALPHA, v);
+ public AnimatedFloat getVisibilityAlpha() {
+ return mAlphaProperties[INDEX_VISIBILITY_ALPHA];
}
- public Consumer<Float> getVisibilityAlphaSetter() {
- return v -> setActionsAlpha(INDEX_VISIBILITY_ALPHA, v);
+ public AnimatedFloat getFullscreenAlpha() {
+ return mAlphaProperties[INDEX_FULLSCREEN_ALPHA];
}
- public Consumer<Float> getFullscreenAlphaSetter() {
- return v -> setActionsAlpha(INDEX_FULLSCREEN_ALPHA, v);
+ public AnimatedFloat getShareTargetAlpha() {
+ return mAlphaProperties[INDEX_SHARE_TARGET_ALPHA];
}
- public Consumer<Float> getShareTargetAlphaSetter() {
- return v -> setActionsAlpha(INDEX_SHARE_TARGET_ALPHA, v);
- }
-
- public Consumer<Float> getIndexScrollAlphaSetter() {
- return v -> setActionsAlpha(INDEX_SCROLL_ALPHA, v);
+ public AnimatedFloat getIndexScrollAlpha() {
+ return mAlphaProperties[INDEX_SCROLL_ALPHA];
}
/**
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index a242e1d..731b839 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -2029,7 +2029,7 @@
mClearAllButton.setFullscreenProgress(fullscreenProgress);
// Fade out the actions view quickly (0.1 range)
- mActionsView.getFullscreenAlphaSetter().accept(
+ mActionsView.getFullscreenAlpha().updateValue(
mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR));
}
@@ -2280,8 +2280,8 @@
}
private void animateActionsViewAlpha(float alphaValue, long duration) {
- mActionsViewAlphaAnimator = ObjectAnimator.ofFloat(mActionsView.getVisibilityAlphaSetter(),
- OverviewActionsView.FLOAT_SETTER, alphaValue);
+ mActionsViewAlphaAnimator = ObjectAnimator.ofFloat(mActionsView.getVisibilityAlpha(),
+ AnimatedFloat.VALUE, alphaValue);
mActionsViewAlphaAnimatorFinalValue = alphaValue;
mActionsViewAlphaAnimator.setDuration(duration);
// Set autocancel to prevent race-conditiony setting of alpha from other animations
@@ -2300,7 +2300,7 @@
mClearAllButton.onRecentsViewScroll(scroll, mOverviewGridEnabled);
// Clear all button alpha was set by the previous line.
- mActionsView.getIndexScrollAlphaSetter().accept(1 - mClearAllButton.getScrollAlpha());
+ mActionsView.getIndexScrollAlpha().updateValue(1 - mClearAllButton.getScrollAlpha());
}
@Override
@@ -4309,7 +4309,7 @@
int alphaInt = Math.round(alpha * 255);
mEmptyMessagePaint.setAlpha(alphaInt);
mEmptyIcon.setAlpha(alphaInt);
- mActionsView.getContentAlphaSetter().accept(mContentAlpha);
+ mActionsView.getContentAlpha().updateValue(mContentAlpha);
if (alpha > 0) {
setVisibility(VISIBLE);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 1187222..cc24bc3 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -687,6 +687,11 @@
*/
public void bind(Task task, RecentsOrientedState orientedState) {
cancelPendingLoadTasks();
+ setupTaskContainers(task);
+ setOrientationState(orientedState);
+ }
+
+ protected void setupTaskContainers(Task task) {
mTaskContainers = Collections.singletonList(
new TaskContainer(task, mTaskThumbnailViewDeprecated, mIconView,
STAGE_POSITION_UNDEFINED, mDigitalWellBeingToast));
@@ -695,7 +700,6 @@
} else {
mTaskThumbnailViewDeprecated.bind(task);
}
- setOrientationState(orientedState);
}
private void bindTaskThumbnailView() {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index 2bcfa3f..7ad432b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -44,6 +44,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
+import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -139,12 +140,12 @@
assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
- verify(bubbleStashController).updateTaskbarTouchRegion()
+ verify(bubbleStashController, atLeastOnce()).updateTaskbarTouchRegion()
// verify the hide bubble animation is pending
assertThat(animatorScheduler.delayedBlock).isNotNull()
- animator.onBubbleClickedWhileAnimating()
+ animator.onBubbleBarTouchedWhileAnimating()
assertThat(animatorScheduler.delayedBlock).isNull()
assertThat(bubbleBarView.alpha).isEqualTo(1)
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 81a2d54..7877e8a 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -181,12 +181,6 @@
public void testOverviewActions() throws Exception {
assumeFalse("Skipping Overview Actions tests for grid only overview",
mLauncher.isTablet() && mLauncher.isGridOnlyOverviewEnabled());
- // Experimenting for b/165029151:
- final Overview overview = mLauncher.goHome().switchToOverview();
- if (overview.hasTasks()) overview.dismissAllTasks();
- mLauncher.goHome();
- //
-
startTestAppsWithCheck();
OverviewActions actionsView =
mLauncher.goHome().switchToOverview().getOverviewActions();
@@ -434,9 +428,7 @@
@Test
@PortraitLandscape
@TaskbarModeSwitch()
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/309820115
@Ignore("b/315376057")
- @ScreenRecord // b/309820115
public void testOverviewForTablet() throws Exception {
assumeTrue(mLauncher.isTablet());
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 4a26a18..19a3002 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -645,7 +645,7 @@
} else {
// Wrap the main icon in AID
try (LauncherIcons li = LauncherIcons.obtain(context)) {
- result = li.wrapToAdaptiveIcon(mainIcon);
+ result = li.wrapToAdaptiveIcon(mainIcon, null);
}
}
if (result == null) {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 60c413e..e1a7d66 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -203,9 +203,6 @@
"ENABLE_EXPANDING_PAUSE_WORK_BUTTON", DISABLED,
"Expand and collapse pause work button while scrolling");
- public static final BooleanFlag COLLECT_SEARCH_HISTORY = getReleaseFlag(270391455,
- "COLLECT_SEARCH_HISTORY", DISABLED, "Allow launcher to collect search history for log");
-
// Aconfig migration complete for ENABLE_TWOLINE_ALLAPPS.
public static final BooleanFlag ENABLE_TWOLINE_ALLAPPS = getDebugFlag(270390937,
"ENABLE_TWOLINE_ALLAPPS", DISABLED, "Enables two line label inside all apps.");
diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
index 70a3dd0..69b42cb 100644
--- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
@@ -76,7 +76,6 @@
String output =
mDevice.executeShellCommand(
"pm create-user --profileOf 0 --managed TestProfile");
- // b/203817455
updateWorkProfileSetupSuccessful("pm create-user", output);
String[] tokens = output.split("\\s+");