Merge "Fix install apps button" into main
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index a31ee80..edbea88 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -52,18 +52,18 @@
name in the permissions. eq com.mypackage.permission.READ_SETTINGS
-->
<permission
- android:name="${packageName}.permission.READ_SETTINGS"
+ android:name="${applicationId}.permission.READ_SETTINGS"
android:protectionLevel="signatureOrSystem"
android:label="@string/permlab_read_settings"
android:description="@string/permdesc_read_settings"/>
<permission
- android:name="${packageName}.permission.WRITE_SETTINGS"
+ android:name="${applicationId}.permission.WRITE_SETTINGS"
android:protectionLevel="signatureOrSystem"
android:label="@string/permlab_write_settings"
android:description="@string/permdesc_write_settings"/>
- <uses-permission android:name="${packageName}.permission.READ_SETTINGS" />
- <uses-permission android:name="${packageName}.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="${applicationId}.permission.READ_SETTINGS" />
+ <uses-permission android:name="${applicationId}.permission.WRITE_SETTINGS" />
<application
android:backupAgent="com.android.launcher3.LauncherBackupAgent"
@@ -126,10 +126,10 @@
-->
<provider
android:name="com.android.launcher3.LauncherProvider"
- android:authorities="${packageName}.settings"
+ android:authorities="${applicationId}.settings"
android:exported="true"
- android:writePermission="${packageName}.permission.WRITE_SETTINGS"
- android:readPermission="${packageName}.permission.READ_SETTINGS" />
+ android:writePermission="${applicationId}.permission.WRITE_SETTINGS"
+ android:readPermission="${applicationId}.permission.READ_SETTINGS" />
<!--
The content provider for exposing various launcher grid options.
@@ -137,7 +137,7 @@
-->
<provider
android:name="com.android.launcher3.graphics.GridCustomizationsProvider"
- android:authorities="${packageName}.grid_control"
+ android:authorities="${applicationId}.grid_control"
android:exported="true" />
<!--
@@ -157,7 +157,7 @@
<provider
android:name="com.android.launcher3.testing.TestInformationProvider"
- android:authorities="${packageName}.TestInfo"
+ android:authorities="${applicationId}.TestInfo"
android:readPermission="android.permission.WRITE_SECURE_SETTINGS"
android:writePermission="android.permission.WRITE_SECURE_SETTINGS"
android:exported="true"
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 8274bd6..6b07bb6 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -229,3 +229,13 @@
description: "Enables an add button in the widget picker"
bug: "323886237"
}
+
+flag {
+ name: "enable_handle_delayed_gesture_callbacks"
+ namespace: "launcher"
+ description: "Enables additional handling for delayed mid-gesture callbacks"
+ bug: "285636175"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 2d2fb97..bf198b6 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -50,7 +50,7 @@
<uses-permission android:name="android.permission.ACCESS_HIDDEN_PROFILES_FULL" />
<!-- Permission required to start a WidgetPickerActivity. -->
- <permission android:name="${packageName}.permission.START_WIDGET_PICKER_ACTIVITY"
+ <permission android:name="${applicationId}.permission.START_WIDGET_PICKER_ACTIVITY"
android:protectionLevel="signature|privileged" />
<application android:backupAgent="com.android.launcher3.LauncherBackupAgent"
@@ -88,7 +88,7 @@
<!-- Content provider to settings search. The autority should be same as the packageName -->
<provider android:name="com.android.quickstep.LauncherSearchIndexablesProvider"
- android:authorities="${packageName}"
+ android:authorities="${applicationId}"
android:grantUriPermissions="true"
android:multiprocess="true"
android:permission="android.permission.READ_SEARCH_INDEXABLES"
@@ -100,7 +100,7 @@
<!-- FileProvider used for sharing images. -->
<provider android:name="androidx.core.content.FileProvider"
- android:authorities="${packageName}.overview.fileprovider"
+ android:authorities="${applicationId}.overview.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 69e07f2..dc28614 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -427,11 +427,14 @@
<dimen name="bubblebar_drag_elevation">2dp</dimen>
<dimen name="bubblebar_hotseat_adjustment_threshold">90dp</dimen>
- <dimen name="bubblebar_icon_size">50dp</dimen>
+ <dimen name="bubblebar_icon_size_small">32dp</dimen>
+ <dimen name="bubblebar_icon_size">36dp</dimen>
<dimen name="bubblebar_badge_size">24dp</dimen>
<dimen name="bubblebar_icon_overlap">12dp</dimen>
- <dimen name="bubblebar_overflow_inset">24dp</dimen>
- <dimen name="bubblebar_icon_spacing">3dp</dimen>
+ <dimen name="bubblebar_overflow_inset">16dp</dimen>
+ <dimen name="bubblebar_icon_spacing">6dp</dimen>
+ <dimen name="bubblebar_icon_spacing_large">8dp</dimen>
+ <dimen name="bubblebar_expanded_icon_spacing">12dp</dimen>
<dimen name="bubblebar_icon_elevation">1dp</dimen>
<!-- Bubble bar dismiss view -->
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 6ceec3e..8e05686 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -207,13 +207,6 @@
}
@Override
- protected void onUserSwipeToDismissProgressChanged() {
- super.onUserSwipeToDismissProgressChanged();
- mAppsView.setClipChildren(!mIsDismissInProgress);
- mAppsView.getAppsRecyclerViewContainer().setClipChildren(!mIsDismissInProgress);
- }
-
- @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
setTranslationShift(mTranslationShift);
@@ -259,12 +252,28 @@
return getPopupContainer().isEventOverView(mAppsView.getVisibleContainerView(), ev);
}
+ /**
+ * In taskbar all apps search mode, we should scale down content inside all apps, rather
+ * than the whole all apps bottom sheet, to indicate we will navigate back within the all apps.
+ */
+ @Override
+ public boolean shouldAnimateContentViewInBackSwipe() {
+ return mAllAppsCallbacks.canHandleSearchBackInvoked();
+ }
+
+ @Override
+ protected void onUserSwipeToDismissProgressChanged() {
+ super.onUserSwipeToDismissProgressChanged();
+ mAppsView.setClipChildren(!mIsDismissInProgress);
+ mAppsView.getAppsRecyclerViewContainer().setClipChildren(!mIsDismissInProgress);
+ }
+
@Override
public void onBackInvoked() {
if (mAllAppsCallbacks.handleSearchBackInvoked()) {
// We need to scale back taskbar all apps if we navigate back within search inside all
// apps
- animateSwipeToDismissProgressToStart();
+ post(this::animateSwipeToDismissProgressToStart);
} else {
super.onBackInvoked();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index ba4fa45..52f7176 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -143,6 +143,11 @@
}
}
+ /** Check if search session can handle back. This check doesn't perform any action. */
+ boolean canHandleSearchBackInvoked() {
+ return mSearchSessionController.canHandleBackInvoked();
+ }
+
/** Invoked on back press, returning {@code true} if the search session handled it. */
boolean handleSearchBackInvoked() {
return mSearchSessionController.handleBackInvoked();
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
index 3d15fbd..4d0b376 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
@@ -49,6 +49,8 @@
/** Creates a [PreDragCondition] for [view], if it is a search result that requires one. */
open fun createPreDragConditionForSearch(view: View): PreDragCondition? = null
+ open fun canHandleBackInvoked(): Boolean = false
+
open fun handleBackInvoked(): Boolean = false
open fun onAllAppsAnimationPending(
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index 8eeb055..9799349 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -31,7 +31,7 @@
import com.android.wm.shell.common.TriangleShape
/** Drawable for the background of the bubble bar. */
-class BubbleBarBackground(context: Context, private val backgroundHeight: Float) : Drawable() {
+class BubbleBarBackground(context: Context, private var backgroundHeight: Float) : Drawable() {
private val DARK_THEME_SHADOW_ALPHA = 51f
private val LIGHT_THEME_SHADOW_ALPHA = 25f
@@ -171,4 +171,8 @@
fun setArrowAlpha(alpha: Int) {
arrowDrawable.paint.alpha = alpha
}
+
+ fun setHeight(newHeight: Float) {
+ backgroundHeight = newHeight
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index a2c1b07..981c9f9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -313,8 +313,11 @@
|| (!update.expandedChanged && !mBubbleBarViewController.isExpanded());
final boolean isExpanding = update.expandedChanged && update.expanded;
// don't animate bubbles if this is the initial state because we may be unfolding or
- // enabling gesture nav
- final boolean suppressAnimation = update.initialState;
+ // enabling gesture nav. also suppress animation if the bubble bar is hidden for sysui e.g.
+ // the shade is open, or we're locked.
+ final boolean suppressAnimation =
+ update.initialState || mBubbleBarViewController.isHiddenForSysui();
+
BubbleBarItem previouslySelectedBubble = mSelectedBubble;
BubbleBarBubble bubbleToSelect = null;
if (!update.removedBubbles.isEmpty()) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 711ba62..1003c3f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -106,10 +106,12 @@
private final Rect mBubbleBarBounds = new Rect();
// The amount the bubbles overlap when they are stacked in the bubble bar
private final float mIconOverlapAmount;
- // The spacing between the bubbles when they are expanded in the bubble bar
- private final float mIconSpacing;
+ // The spacing between the bubbles when bubble bar is expanded
+ private final float mExpandedBarIconsSpacing;
+ // The spacing between the bubbles and the borders of the bubble bar
+ private float mBubbleBarPadding;
// The size of a bubble in the bar
- private final float mIconSize;
+ private float mIconSize;
// The elevation of the bubbles within the bar
private final float mBubbleElevation;
private final float mDragElevation;
@@ -169,16 +171,17 @@
setAlpha(0);
setVisibility(INVISIBLE);
mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
- mIconSpacing = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
+ mBubbleBarPadding = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
mIconSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
+ mExpandedBarIconsSpacing = getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_expanded_icon_spacing);
mBubbleElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_elevation);
mDragElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_drag_elevation);
mPointerSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_pointer_size);
setClipToPadding(false);
- mBubbleBarBackground = new BubbleBarBackground(context,
- getResources().getDimensionPixelSize(R.dimen.bubblebar_size));
+ mBubbleBarBackground = new BubbleBarBackground(context, getBubbleBarHeight());
setBackgroundDrawable(mBubbleBarBackground);
mWidthAnimator.setDuration(WIDTH_ANIMATION_DURATION_MS);
@@ -219,6 +222,29 @@
});
}
+ /**
+ * Sets new icon size and spacing between icons and bubble bar borders.
+ *
+ * @param newIconSize new icon size
+ * @param spacing spacing between icons and bubble bar borders.
+ */
+ // TODO(b/335575529): animate bubble bar icons size change
+ public void setIconSizeAndPadding(float newIconSize, float spacing) {
+ // TODO(b/335457839): handle new bubble animation during the size change
+ mBubbleBarPadding = spacing;
+ mIconSize = newIconSize;
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View childView = getChildAt(i);
+ FrameLayout.LayoutParams params = (LayoutParams) childView.getLayoutParams();
+ params.height = (int) mIconSize;
+ params.width = (int) mIconSize;
+ childView.setLayoutParams(params);
+ }
+ mBubbleBarBackground.setHeight(getBubbleBarHeight());
+ updateLayoutParams();
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
@@ -516,6 +542,13 @@
setLayoutParams(lp);
}
+ private void updateLayoutParams() {
+ LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ lp.height = getBubbleBarHeight();
+ lp.width = (int) (mIsBarExpanded ? expandedWidth() : collapsedWidth());
+ setLayoutParams(lp);
+ }
+
/** @return the horizontal margin between the bubble bar and the edge of the screen. */
int getHorizontalMargin() {
LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
@@ -551,12 +584,12 @@
final float collapsedX;
if (onLeft) {
// If bar is on the left, bubbles are ordered right to left
- expandedX = (bubbleCount - i - 1) * (mIconSize + mIconSpacing);
+ expandedX = (bubbleCount - i - 1) * (mIconSize + mExpandedBarIconsSpacing);
// Shift the first bubble only if there are more bubbles in addition to overflow
collapsedX = i == 0 && bubbleCount > 2 ? mIconOverlapAmount : 0;
} else {
// Bubbles ordered left to right, don't move the first bubble
- expandedX = i * (mIconSize + mIconSpacing);
+ expandedX = i * (mIconSize + mExpandedBarIconsSpacing);
collapsedX = i == 0 ? 0 : mIconOverlapAmount;
}
@@ -599,14 +632,14 @@
final float interpolatedWidth =
widthState * (expandedWidth - collapsedWidth) + collapsedWidth;
final float arrowPosition;
+
+ float interpolatedShift = (expandedArrowPosition - collapsedArrowPosition) * widthState;
if (onLeft) {
- float interpolatedShift = (expandedArrowPosition - collapsedArrowPosition) * widthState;
arrowPosition = collapsedArrowPosition + interpolatedShift;
} else {
if (mIsBarExpanded) {
- // when the bar is expanding, the selected bubble is always the first, so the arrow
- // always shifts with the interpolated width.
- arrowPosition = currentWidth - interpolatedWidth + collapsedArrowPosition;
+ arrowPosition = currentWidth - interpolatedWidth + collapsedArrowPosition
+ + interpolatedShift;
} else {
final float targetPosition = currentWidth - collapsedWidth + collapsedArrowPosition;
arrowPosition =
@@ -709,7 +742,8 @@
} else {
bubblePosition = index;
}
- return getPaddingStart() + bubblePosition * (mIconSize + mIconSpacing) + mIconSize / 2f;
+ return getPaddingStart() + bubblePosition * (mIconSize + mExpandedBarIconsSpacing)
+ + mIconSize / 2f;
}
private float arrowPositionForSelectedWhenCollapsed() {
@@ -770,7 +804,9 @@
public float expandedWidth() {
final int childCount = getChildCount();
final int horizontalPadding = getPaddingStart() + getPaddingEnd();
- return childCount * (mIconSize + mIconSpacing) + horizontalPadding;
+ // spaces amount is less than child count by 1, or 0 if no child views
+ int spacesCount = Math.max(childCount - 1, 0);
+ return childCount * mIconSize + spacesCount * mExpandedBarIconsSpacing + horizontalPadding;
}
private float collapsedWidth() {
@@ -783,6 +819,10 @@
: mIconSize + horizontalPadding;
}
+ private int getBubbleBarHeight() {
+ return (int) (mIconSize + mBubbleBarPadding * 2 + mPointerSize);
+ }
+
/**
* Returns whether the given MotionEvent, *in screen coordinates*, is within bubble bar
* touch bounds.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index aa1b4df..a1a2898 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -18,9 +18,12 @@
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
+import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
@@ -52,12 +55,13 @@
public class BubbleBarViewController {
private static final String TAG = BubbleBarViewController.class.getSimpleName();
-
+ private static final float APP_ICON_SMALL_DP = 44f;
+ private static final float APP_ICON_MEDIUM_DP = 48f;
+ private static final float APP_ICON_LARGE_DP = 52f;
private final SystemUiProxy mSystemUiProxy;
private final TaskbarActivityContext mActivity;
private final BubbleBarView mBarView;
- private final int mIconSize;
- private final int mPointerSize;
+ private int mIconSize;
// Initialized in init.
private BubbleStashController mBubbleStashController;
@@ -96,9 +100,8 @@
mSystemUiProxy = SystemUiProxy.INSTANCE.get(mActivity);
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);
+ mIconSize = activity.getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_icon_size);
}
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
@@ -108,12 +111,8 @@
mTaskbarStashController = controllers.taskbarStashController;
mTaskbarInsetsController = controllers.taskbarInsetsController;
- mActivity.addOnDeviceProfileChangeListener(dp ->
- mBarView.getLayoutParams().height =
- mActivity.getDeviceProfile().taskbarHeight + mPointerSize
- );
- mBarView.getLayoutParams().height =
- mActivity.getDeviceProfile().taskbarHeight + mPointerSize;
+ mActivity.addOnDeviceProfileChangeListener(dp -> setBubbleBarIconSize(dp.taskbarIconSize));
+ setBubbleBarIconSize(mActivity.getDeviceProfile().taskbarIconSize);
mBubbleBarScale.updateValue(1f);
mBubbleClickListener = v -> onBubbleClicked(v);
mBubbleBarClickListener = v -> onBubbleBarClicked();
@@ -260,12 +259,44 @@
}
}
+ private void setBubbleBarIconSize(int newIconSize) {
+ if (newIconSize == mIconSize) {
+ return;
+ }
+ Resources res = mActivity.getResources();
+ DisplayMetrics dm = res.getDisplayMetrics();
+ float smallIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ APP_ICON_SMALL_DP, dm);
+ float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ APP_ICON_MEDIUM_DP, dm);
+ float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ APP_ICON_LARGE_DP, dm);
+ float smallMediumThreshold = (smallIconSize + mediumIconSize) / 2f;
+ float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f;
+ mIconSize = newIconSize <= smallMediumThreshold
+ ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_small) :
+ res.getDimensionPixelSize(R.dimen.bubblebar_icon_size);
+ float bubbleBarPadding = newIconSize >= mediumLargeThreshold
+ ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing_large) :
+ res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
+
+ mBarView.setIconSizeAndPadding(mIconSize, bubbleBarPadding);
+ mBarView.setPadding((int) bubbleBarPadding, mBarView.getPaddingTop(),
+ (int) bubbleBarPadding,
+ mBarView.getPaddingBottom());
+ }
+
/** Sets a callback that updates the selected bubble after the bubble bar collapses. */
public void setUpdateSelectedBubbleAfterCollapse(
Consumer<String> updateSelectedBubbleAfterCollapse) {
mBarView.setUpdateSelectedBubbleAfterCollapse(updateSelectedBubbleAfterCollapse);
}
+ /** Returns whether the bubble bar should be hidden because of the current sysui state. */
+ boolean isHiddenForSysui() {
+ return mHiddenForSysui;
+ }
+
/**
* Sets whether the bubble bar should be hidden due to SysUI state (e.g. on lockscreen).
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index 61a2e22..76d86de 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -23,6 +23,7 @@
import android.annotation.Nullable;
import android.view.InsetsController;
import android.view.MotionEvent;
+import android.view.View;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.taskbar.StashedHandleViewController;
@@ -32,6 +33,7 @@
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
/**
* Coordinates between controllers such as BubbleBarView and BubbleHandleViewController to
@@ -51,15 +53,6 @@
*/
private static final float STASHED_BAR_SCALE = 0.5f;
- /** The duration of hiding and showing the stashed handle as part of a new bubble animation. */
- private static final long NEW_BUBBLE_HANDLE_ANIMATION_DURATION_MS = 200;
-
- /** The translation Y value the handle animates to when hiding it for a new bubble. */
- private static final int NEW_BUBBLE_HIDE_HANDLE_ANIMATION_TRANSLATION_Y = -20;
-
- /** The alpha value the handle animates to when hiding it for a new bubble. */
- public static final float NEW_BUBBLE_HIDE_HANDLE_ANIMATION_ALPHA = 0.5f;
-
protected final TaskbarActivityContext mActivity;
// Initialized in init.
@@ -73,7 +66,6 @@
private AnimatedFloat mIconScaleForStash;
private AnimatedFloat mIconTranslationYForStash;
private MultiPropertyFactory.MultiProperty mBubbleStashedHandleAlpha;
- private AnimatedFloat mBubbleStashedHandleTranslationY;
private boolean mRequestedStashState;
private boolean mRequestedExpandedState;
@@ -105,7 +97,6 @@
mBubbleStashedHandleAlpha = mHandleViewController.getStashedHandleAlpha().get(
StashedHandleViewController.ALPHA_INDEX_STASHED);
- mBubbleStashedHandleTranslationY = mHandleViewController.getStashedHandleTranslationY();
mStashedHeight = mHandleViewController.getStashedHeight();
mUnstashedHeight = mHandleViewController.getUnstashedHeight();
@@ -379,29 +370,8 @@
return mHandleViewController.getStashedHandleCenterX();
}
- /** Returns the animation for hiding the handle before a new bubble animates in. */
- public AnimatorSet buildHideHandleAnimationForNewBubble() {
- AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(
- mBubbleStashedHandleTranslationY.animateToValue(
- NEW_BUBBLE_HIDE_HANDLE_ANIMATION_TRANSLATION_Y),
- mBubbleStashedHandleAlpha.animateToValue(NEW_BUBBLE_HIDE_HANDLE_ANIMATION_ALPHA));
- animatorSet.setDuration(NEW_BUBBLE_HANDLE_ANIMATION_DURATION_MS);
- return animatorSet;
- }
-
- /** Sets the alpha value of the stashed handle. */
- public void setStashAlpha(float alpha) {
- mBubbleStashedHandleAlpha.setValue(alpha);
- }
-
- /** Returns the animation for showing the handle after a new bubble animated in. */
- public AnimatorSet buildShowHandleAnimationForNewBubble() {
- AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(
- mBubbleStashedHandleTranslationY.animateToValue(0),
- mBubbleStashedHandleAlpha.animateToValue(1));
- animatorSet.setDuration(NEW_BUBBLE_HANDLE_ANIMATION_DURATION_MS);
- return animatorSet;
+ /** Returns the [PhysicsAnimator] for the stashed handle view. */
+ public PhysicsAnimator<View> getStashedHandlePhysicsAnimator() {
+ return mHandleViewController.getPhysicsAnimator();
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
index 2a5912a..6f1a093 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -29,7 +29,6 @@
import android.view.ViewOutlineProvider;
import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.RevealOutlineAnimation;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.taskbar.StashedHandleView;
@@ -40,6 +39,7 @@
import com.android.launcher3.util.MultiValueAlpha;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
/**
* Handles properties/data collection, then passes the results to our stashed handle View to render.
@@ -59,12 +59,6 @@
private int mStashedHandleWidth;
private int mStashedHandleHeight;
- private final AnimatedFloat mStashedHandleTranslationY =
- new AnimatedFloat(this::updateTranslationY);
-
- // Modified when swipe up is happening on the stashed handle or task bar.
- private float mSwipeUpTranslationY;
-
// The bounds we want to clip to in the settled state when showing the stashed handle.
private final Rect mStashedHandleBounds = new Rect();
@@ -129,6 +123,11 @@
updateBounds(mBarViewController.getBubbleBarLocation()));
}
+ /** Returns the [PhysicsAnimator] for the stashed handle view. */
+ public PhysicsAnimator<View> getPhysicsAnimator() {
+ return PhysicsAnimator.getInstance(mStashedHandleView);
+ }
+
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.
@@ -238,21 +237,11 @@
}
}
- /** Returns an animator for translation Y. */
- public AnimatedFloat getStashedHandleTranslationY() {
- return mStashedHandleTranslationY;
- }
-
/**
* Sets the translation of the stashed handle during the swipe up gesture.
*/
public void setTranslationYForSwipe(float transY) {
- mSwipeUpTranslationY = transY;
- updateTranslationY();
- }
-
- private void updateTranslationY() {
- mStashedHandleView.setTranslationY(mStashedHandleTranslationY.value + mSwipeUpTranslationY);
+ mStashedHandleView.setTranslationY(transY);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS b/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS
index edabae2..3f947a0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS
@@ -1,3 +1,5 @@
atsjenk@google.com
liranb@google.com
madym@google.com
+mpodolian@google.com
+
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 1db5103..2d8983f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -18,16 +18,12 @@
import android.view.View
import android.view.View.VISIBLE
-import androidx.core.animation.AnimatorSet
-import androidx.core.animation.ObjectAnimator
-import androidx.core.animation.doOnEnd
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.launcher3.taskbar.bubbles.BubbleBarBubble
import com.android.launcher3.taskbar.bubbles.BubbleBarView
import com.android.launcher3.taskbar.bubbles.BubbleStashController
import com.android.launcher3.taskbar.bubbles.BubbleView
-import com.android.systemui.util.doOnEnd
import com.android.wm.shell.shared.animation.PhysicsAnimator
/** Handles animations for bubble bar bubbles. */
@@ -43,17 +39,19 @@
/** The time to show the flyout. */
const val FLYOUT_DELAY_MS: Long = 2500
/** The translation Y the new bubble will animate to. */
- const val BUBBLE_ANIMATION_FINAL_TRANSLATION_Y = -50f
+ const val BUBBLE_ANIMATION_BUBBLE_TRANSLATION_Y = -50f
/** The initial translation Y value the new bubble is set to before the animation starts. */
// TODO(liranb): get rid of this and calculate this based on the y-distance between the
// bubble and the stash handle.
- const val BUBBLE_ANIMATION_INITIAL_TRANSLATION_Y = 50f
+ const val BUBBLE_ANIMATION_TRANSLATION_Y_OFFSET = 50f
/** 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 initial alpha value that the new bubble is set to before the animation starts. */
- const val BUBBLE_ANIMATION_INITIAL_ALPHA = 0.5f
- /** The duration of the hide bubble animation. */
- const val HIDE_BUBBLE_ANIMATION_DURATION_MS = 250L
+ /**
+ * The distance the stashed handle will travel as it gets hidden as part of the new bubble
+ * animation.
+ */
+ // TODO(liranb): calculate this based on the position of the views
+ const val BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y = -20f
}
/** An interface for scheduling jobs. */
@@ -91,7 +89,7 @@
if (animator.isRunning()) animator.cancel()
// the animation of a new bubble is divided into 2 parts. The first part shows the bubble
// and the second part hides it after a delay.
- val showAnimation = buildShowAnimation(bubbleView, b.key, animator)
+ val showAnimation = buildShowAnimation(bubbleView, b.key)
val hideAnimation = buildHideAnimation(bubbleView)
scheduler.post(showAnimation)
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
@@ -100,71 +98,139 @@
/**
* Returns a lambda that starts the animation that shows the new bubble.
*
- * The animation is divided into 2 parts. First the stash handle starts animating up and fades
- * out. When it ends the bubble starts fading in. The bubble and stashed handle are aligned to
- * give the impression of the stash handle morphing into the bubble.
+ * Visually, the animation is divided into 2 parts. The stash handle starts animating up and
+ * fading out and then the bubble starts animating up and fading in.
+ *
+ * To make the transition from the handle to the bubble smooth, the positions and movement of
+ * the 2 views must be synchronized. To do that we use a single spring path along the Y axis,
+ * starting from the handle's position to the eventual bubble's position. The path is split into
+ * 3 parts.
+ * 1. In the first part, we only animate the handle.
+ * 1. In the second part the handle is fully hidden, and the bubble is animating in.
+ * 1. The third part is the overshoot of the spring animation, where we make the bubble fully
+ * visible which helps avoiding further updates when we re-enter the second part.
*/
private fun buildShowAnimation(
bubbleView: BubbleView,
key: String,
- bubbleAnimator: PhysicsAnimator<BubbleView>
): () -> Unit = {
+ bubbleBarView.prepareForAnimatingBubbleWhileStashed(key)
// calculate the initial translation x the bubble should have in order to align it with the
// stash handle.
val initialTranslationX =
bubbleStashController.stashedHandleCenterX - bubbleView.centerXOnScreen
- bubbleBarView.prepareForAnimatingBubbleWhileStashed(key)
- bubbleAnimator.setDefaultSpringConfig(springConfig)
- bubbleAnimator
- .spring(DynamicAnimation.ALPHA, 1f)
- .spring(DynamicAnimation.TRANSLATION_Y, BUBBLE_ANIMATION_FINAL_TRANSLATION_Y)
- .spring(DynamicAnimation.SCALE_Y, 1f)
// prepare the bubble for the animation
bubbleView.alpha = 0f
bubbleView.translationX = initialTranslationX
- bubbleView.translationY = BUBBLE_ANIMATION_INITIAL_TRANSLATION_Y
bubbleView.scaleY = BUBBLE_ANIMATION_INITIAL_SCALE_Y
bubbleView.visibility = VISIBLE
- // start the stashed handle animation. when it ends, start the bubble animation.
- val stashedHandleAnimation = bubbleStashController.buildHideHandleAnimationForNewBubble()
- stashedHandleAnimation.doOnEnd {
- bubbleView.alpha = BUBBLE_ANIMATION_INITIAL_ALPHA
- bubbleAnimator.start()
- bubbleStashController.setStashAlpha(0f)
+
+ // this is the total distance that both the stashed handle and the bubble will be traveling
+ val totalTranslationY =
+ BUBBLE_ANIMATION_BUBBLE_TRANSLATION_Y + BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y
+ val animator = bubbleStashController.stashedHandlePhysicsAnimator
+ animator.setDefaultSpringConfig(springConfig)
+ animator.spring(DynamicAnimation.TRANSLATION_Y, totalTranslationY)
+ animator.addUpdateListener { target, values ->
+ val ty = values[DynamicAnimation.TRANSLATION_Y]?.value ?: return@addUpdateListener
+ when {
+ ty >= BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y -> {
+ // we're in the first leg of the animation. only animate the handle. the bubble
+ // remains hidden during this part of the animation
+
+ // map the path [0, BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y] to [0,1]
+ val fraction = ty / BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y
+ target.alpha = 1 - fraction / 2
+ }
+ ty >= totalTranslationY -> {
+ // this is the second leg of the animation. the handle should be completely
+ // hidden and the bubble should start animating in.
+ // it's possible that we're re-entering this leg because this is a spring
+ // animation, so only set the alpha and scale for the bubble if we didn't
+ // already fully animate in.
+ target.alpha = 0f
+ bubbleView.translationY = ty + BUBBLE_ANIMATION_TRANSLATION_Y_OFFSET
+ if (bubbleView.alpha != 1f) {
+ // map the path
+ // [BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y, totalTranslationY]
+ // to [0, 1]
+ val fraction =
+ (ty - BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y) /
+ BUBBLE_ANIMATION_BUBBLE_TRANSLATION_Y
+ bubbleView.alpha = fraction
+ bubbleView.scaleY =
+ BUBBLE_ANIMATION_INITIAL_SCALE_Y +
+ (1 - BUBBLE_ANIMATION_INITIAL_SCALE_Y) * fraction
+ }
+ }
+ else -> {
+ // we're past the target animated value, set the alpha and scale for the bubble
+ // so that it's fully visible and no longer changing, but keep moving it along
+ // the animation path
+ bubbleView.alpha = 1f
+ bubbleView.scaleY = 1f
+ bubbleView.translationY = ty + BUBBLE_ANIMATION_TRANSLATION_Y_OFFSET
+ }
+ }
}
- stashedHandleAnimation.start()
+ animator.start()
}
/**
* Returns a lambda that starts the animation that hides the new bubble.
*
- * Similarly to the show animation, this is divided into 2 parts. We first animate the bubble
- * out, and then animate the stash handle in. At the end of the animation we reset the values of
- * the bubble.
+ * Similarly to the show animation, this is visually divided into 2 parts. We first animate the
+ * bubble out, and then animate the stash handle in. At the end of the animation we reset the
+ * values of the bubble.
+ *
+ * This is a spring animation that goes along the same path of the show animation in the
+ * opposite order, and is split into 3 parts:
+ * 1. In the first part the bubble animates out.
+ * 1. In the second part the bubble is fully hidden and the handle animates in.
+ * 1. The third part is the overshoot. The handle is made fully visible.
*/
private fun buildHideAnimation(bubbleView: BubbleView): () -> Unit = {
- val stashAnimation = bubbleStashController.buildShowHandleAnimationForNewBubble()
- val alphaAnimator =
- ObjectAnimator.ofFloat(bubbleView, View.ALPHA, BUBBLE_ANIMATION_INITIAL_ALPHA)
- val translationYAnimator =
- ObjectAnimator.ofFloat(
- bubbleView,
- View.TRANSLATION_Y,
- BUBBLE_ANIMATION_INITIAL_TRANSLATION_Y
- )
- val scaleYAnimator =
- ObjectAnimator.ofFloat(bubbleView, View.SCALE_Y, BUBBLE_ANIMATION_INITIAL_SCALE_Y)
- val hideBubbleAnimation = AnimatorSet()
- hideBubbleAnimation.playTogether(alphaAnimator, translationYAnimator, scaleYAnimator)
- hideBubbleAnimation.duration = HIDE_BUBBLE_ANIMATION_DURATION_MS
- hideBubbleAnimation.doOnEnd {
- // the bubble is now hidden, start the stash handle animation and reset bubble
- // properties
- bubbleStashController.setStashAlpha(
- BubbleStashController.NEW_BUBBLE_HIDE_HANDLE_ANIMATION_ALPHA
- )
+ // this is the total distance that both the stashed handle and the bubble will be traveling
+ val totalTranslationY =
+ BUBBLE_ANIMATION_BUBBLE_TRANSLATION_Y + BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y
+ val animator = bubbleStashController.stashedHandlePhysicsAnimator
+ animator.setDefaultSpringConfig(springConfig)
+ animator.spring(DynamicAnimation.TRANSLATION_Y, 0f)
+ animator.addUpdateListener { target, values ->
+ val ty = values[DynamicAnimation.TRANSLATION_Y]?.value ?: return@addUpdateListener
+ when {
+ ty <= BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y -> {
+ // this is the first leg of the animation. only animate the bubble. the handle
+ // is hidden during this part
+ bubbleView.translationY = ty + BUBBLE_ANIMATION_TRANSLATION_Y_OFFSET
+ // map the path
+ // [totalTranslationY, BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y]
+ // to [0, 1]
+ val fraction = (totalTranslationY - ty) / BUBBLE_ANIMATION_BUBBLE_TRANSLATION_Y
+ bubbleView.alpha = 1 - fraction / 2
+ bubbleView.scaleY = 1 - (1 - BUBBLE_ANIMATION_INITIAL_SCALE_Y) * fraction
+ }
+ ty <= 0 -> {
+ // this is the second part of the animation. make the bubble invisible and
+ // start fading in the handle, but don't update the alpha if it's already fully
+ // visible
+ bubbleView.alpha = 0f
+ if (target.alpha != 1f) {
+ // map the path [BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y, 0] to [0, 1]
+ val fraction =
+ (BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y - ty) /
+ BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y
+ target.alpha = fraction
+ }
+ }
+ else -> {
+ // we reached the target value. set the alpha of the handle to 1
+ target.alpha = 1f
+ }
+ }
+ }
+ animator.addEndListener { _, _, _, _, _, _, _ ->
bubbleView.alpha = 0f
- stashAnimation.start()
bubbleView.translationY = 0f
bubbleView.scaleY = 1f
if (bubbleStashController.isStashed) {
@@ -172,7 +238,7 @@
}
bubbleBarView.onAnimatingBubbleCompleted()
}
- hideBubbleAnimation.start()
+ animator.start()
}
}
diff --git a/quickstep/src/com/android/quickstep/DesktopModeStatus.java b/quickstep/src/com/android/quickstep/DesktopModeStatus.java
new file mode 100644
index 0000000..b1aae16
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/DesktopModeStatus.java
@@ -0,0 +1,58 @@
+/*
+ * 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 android.content.Context;
+import android.os.SystemProperties;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
+
+// TODO(b/335401172): Explore unifying logic across core and shell
+public class DesktopModeStatus {
+
+ /**
+ * Flag to indicate whether to restrict desktop mode to supported devices.
+ */
+ private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
+
+ /**
+ * Return {@code true} if desktop mode should be restricted to supported devices.
+ */
+ @VisibleForTesting
+ public static boolean enforceDeviceRestrictions() {
+ return ENFORCE_DEVICE_RESTRICTIONS;
+ }
+
+ /**
+ * Return {@code true} if the current device supports desktop mode.
+ */
+ @VisibleForTesting
+ public static boolean isDesktopModeSupported(Context context) {
+ return context.getResources().getBoolean(
+ com.android.internal.R.bool.config_isDesktopModeSupported);
+ }
+
+ /**
+ * Return {@code true} if desktop mode can be entered on the current device.
+ */
+ public static boolean canEnterDesktopMode(Context context) {
+ return Flags.enableDesktopWindowingMode()
+ && (!enforceDeviceRestrictions() || isDesktopModeSupported(context));
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
index aa0f728..f26d594 100644
--- a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
+++ b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
@@ -24,7 +24,6 @@
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
-import com.android.window.flags.Flags
/** A menu item, "Desktop", that allows the user to bring the current app into Desktop Windowing. */
class DesktopSystemShortcut(
@@ -52,7 +51,7 @@
}
companion object {
- /** Creates a factory for creating Desktop system shorcuts. */
+ /** Creates a factory for creating Desktop system shortcuts. */
@JvmOverloads
fun createFactory(
abstractFloatingViewHelper: AbstractFloatingViewHelper = AbstractFloatingViewHelper()
@@ -62,7 +61,7 @@
container: RecentsViewContainer,
taskContainer: TaskIdAttributeContainer
): List<DesktopSystemShortcut>? {
- return if (!Flags.enableDesktopWindowingMode()) null
+ return if (!DesktopModeStatus.canEnterDesktopMode(container.asContext())) null
else if (!taskContainer.task.isDockable) null
else
listOf(
diff --git a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
index 1cc54d8..f68f793 100644
--- a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
+++ b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
@@ -33,7 +33,7 @@
val customLphThresholds =
propReader.get(
"CUSTOM_LPH_THRESHOLDS",
- false,
+ true,
"Server side control to customize LPH timeout and touch slop"
)
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 9dab7a3..c8a91df 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -513,13 +513,14 @@
return mSwipeUpStartTimeMs;
}
- public void dump(PrintWriter pw) {
- pw.println("GestureState:");
- pw.println(" gestureID=" + mGestureId);
- pw.println(" runningTask=" + mRunningTask);
- pw.println(" endTarget=" + mEndTarget);
- pw.println(" lastAppearedTaskTargetId=" + Arrays.toString(mLastAppearedTaskTargets));
- pw.println(" lastStartedTaskId=" + Arrays.toString(mLastStartedTaskId));
- pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning());
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "GestureState:");
+ pw.println(prefix + "\tgestureID=" + mGestureId);
+ pw.println(prefix + "\trunningTask=" + mRunningTask);
+ pw.println(prefix + "\tendTarget=" + mEndTarget);
+ pw.println(prefix + "\tlastAppearedTaskTargetId="
+ + Arrays.toString(mLastAppearedTaskTargets));
+ pw.println(prefix + "\tlastStartedTaskId=" + Arrays.toString(mLastStartedTaskId));
+ pw.println(prefix + "\tisRecentsAnimationRunning=" + isRecentsAnimationRunning());
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 5d26ec0..da7a98f 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -39,6 +39,7 @@
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -219,6 +220,13 @@
}
}
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "RecentsAnimationCallbacks:");
+
+ pw.println(prefix + "\tmAllowMinimizeSplitScreen=" + mAllowMinimizeSplitScreen);
+ pw.println(prefix + "\tmCancelled=" + mCancelled);
+ }
+
/**
* Listener for the recents animation callbacks.
*/
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 06a442b..1b05e28 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -41,6 +41,7 @@
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import java.io.PrintWriter;
import java.util.function.Consumer;
/**
@@ -267,4 +268,14 @@
public boolean getFinishTargetIsLauncher() {
return mFinishTargetIsLauncher;
}
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "RecentsAnimationController:");
+
+ pw.println(prefix + "\tmAllowMinimizeSplitScreen=" + mAllowMinimizeSplitScreen);
+ pw.println(prefix + "\tmUseLauncherSysBarFlags=" + mUseLauncherSysBarFlags);
+ pw.println(prefix + "\tmSplitScreenMinimized=" + mSplitScreenMinimized);
+ pw.println(prefix + "\tmFinishRequested=" + mFinishRequested);
+ pw.println(prefix + "\tmFinishTargetIsLauncher=" + mFinishTargetIsLauncher);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index f936882..82bb453 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -25,6 +25,8 @@
import android.os.Bundle;
import android.view.RemoteAnimationTarget;
+import java.io.PrintWriter;
+
/**
* Extension of {@link RemoteAnimationTargets} with additional information about swipe
* up animation
@@ -63,4 +65,14 @@
}
return false;
}
+
+ @Override
+ public void dump(String prefix, PrintWriter pw) {
+ super.dump(prefix, pw);
+ prefix += '\t';
+ pw.println(prefix + "RecentsAnimationTargets:");
+
+ pw.println(prefix + "\thomeContentInsets=" + homeContentInsets);
+ pw.println(prefix + "\tminimizedHomeBounds=" + minimizedHomeBounds);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index e0c7403..57edd82 100644
--- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -22,6 +22,7 @@
import android.os.Bundle;
import android.view.RemoteAnimationTarget;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -149,6 +150,14 @@
}
}
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "RemoteAnimationTargets:");
+
+ pw.println(prefix + "\ttargetMode=" + targetMode);
+ pw.println(prefix + "\thasRecents=" + hasRecents);
+ pw.println(prefix + "\tmReleased=" + mReleased);
+ }
+
/**
* Interface for intercepting surface release method
*/
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 3b1ed46..dec8a12 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -17,6 +17,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static com.android.launcher3.Flags.enableHandleDelayedGestureCallbacks;
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.NavigationMode.NO_BUTTON;
@@ -49,6 +50,7 @@
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import java.io.PrintWriter;
import java.util.HashMap;
public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
@@ -67,6 +69,7 @@
private Context mCtx;
private boolean mRecentsAnimationStartPending = false;
+ private boolean mShouldIgnoreMotionEvents = false;
private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
@Override
@@ -103,8 +106,16 @@
.startRecentsActivity(intent, 0, null, null, null));
}
- public boolean isRecentsAnimationStartPending() {
- return mRecentsAnimationStartPending;
+ boolean shouldIgnoreMotionEvents() {
+ return mShouldIgnoreMotionEvents;
+ }
+
+ void notifyNewGestureStart() {
+ // If mRecentsAnimationStartPending is true at the beginning of a gesture, block all motion
+ // events for this new gesture so that this new gesture does not interfere with the
+ // previously-requested recents animation. Otherwise, clean up mShouldIgnoreMotionEvents.
+ // NOTE: this can lead to misleading logs
+ mShouldIgnoreMotionEvents = mRecentsAnimationStartPending;
}
/**
@@ -145,7 +156,12 @@
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
- mRecentsAnimationStartPending = false;
+ if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TaskAnimationManager.startRecentsAnimation(onRecentsAnimationStart): ")
+ .append("Setting mRecentsAnimationStartPending = false"));
+ mRecentsAnimationStartPending = false;
+ }
if (mCallbacks == null) {
// It's possible for the recents animation to have finished and be cleaned up
// by the time we process the start callback, and in that case, just we can skip
@@ -186,13 +202,25 @@
@Override
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
- mRecentsAnimationStartPending = false;
+ if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TaskAnimationManager.startRecentsAnimation")
+ .append("(onRecentsAnimationCanceled): ")
+ .append("Setting mRecentsAnimationStartPending = false"));
+ mRecentsAnimationStartPending = false;
+ }
cleanUpRecentsAnimation(newCallbacks);
}
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
- mRecentsAnimationStartPending = false;
+ if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TaskAnimationManager.startRecentsAnimation")
+ .append("(onRecentsAnimationFinished): ")
+ .append("Setting mRecentsAnimationStartPending = false"));
+ mRecentsAnimationStartPending = false;
+ }
cleanUpRecentsAnimation(newCallbacks);
}
@@ -303,13 +331,29 @@
options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
mRecentsAnimationStartPending = SystemUiProxy.INSTANCE.getNoCreate()
.startRecentsActivity(intent, options, mCallbacks);
+ if (enableHandleDelayedGestureCallbacks()) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TaskAnimationManager.startRecentsAnimation(shell transition path): ")
+ .append("Setting mRecentsAnimationStartPending = ")
+ .append(mRecentsAnimationStartPending));
+ }
} else {
UI_HELPER_EXECUTOR.execute(
() -> ActivityManagerWrapper.getInstance().startRecentsActivity(
intent,
eventTime,
mCallbacks,
- result -> mRecentsAnimationStartPending = result,
+ result -> {
+ if (enableHandleDelayedGestureCallbacks()) {
+ ActiveGestureLog.INSTANCE.addLog(
+ new ActiveGestureLog.CompoundString(
+ "TaskAnimationManager.startRecentsAnimation")
+ .append("(legacy path): Setting ")
+ .append("mRecentsAnimationStartPending = ")
+ .append(result));
+ }
+ mRecentsAnimationStartPending = result;
+ },
MAIN_EXECUTOR.getHandler()));
}
gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED);
@@ -439,7 +483,24 @@
return mCallbacks;
}
- public void dump() {
- // TODO
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "TaskAnimationManager:");
+
+ if (enableHandleDelayedGestureCallbacks()) {
+ pw.println(prefix + "\tmRecentsAnimationStartPending=" + mRecentsAnimationStartPending);
+ pw.println(prefix + "\tmShouldIgnoreUpcomingGestures=" + mShouldIgnoreMotionEvents);
+ }
+ if (mController != null) {
+ mController.dump(prefix + '\t', pw);
+ }
+ if (mCallbacks != null) {
+ mCallbacks.dump(prefix + '\t', pw);
+ }
+ if (mTargets != null) {
+ mTargets.dump(prefix + '\t', pw);
+ }
+ if (mLastGestureState != null) {
+ mLastGestureState.dump(prefix + '\t', pw);
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 552a0cd..a842b51 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,6 +24,7 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.Flags.enableCursorHoverStates;
+import static com.android.launcher3.Flags.enableHandleDelayedGestureCallbacks;
import static com.android.launcher3.Flags.useActivityOverlay;
import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE;
import static com.android.launcher3.LauncherPrefs.backedUpItem;
@@ -41,6 +42,7 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENTS_ANIMATION_START_PENDING;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
@@ -715,16 +717,22 @@
boolean isHoverActionWithoutConsumer = enableCursorHoverStates()
&& isHoverActionWithoutConsumer(event);
- // TODO(b/285636175): Uncomment this once WM can properly guarantee all animation callbacks
-// if (mTaskAnimationManager.isRecentsAnimationStartPending()
-// && (action == ACTION_DOWN || isHoverActionWithoutConsumer)) {
-// ActiveGestureLog.INSTANCE.addLog(
-// new CompoundString("TIS.onInputEvent: ")
-// .append("Cannot process input event: a recents animation has been ")
-// .append("requested, but hasn't started."),
-// RECENTS_ANIMATION_START_PENDING);
-// return;
-// }
+ if (enableHandleDelayedGestureCallbacks()) {
+ if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
+ mTaskAnimationManager.notifyNewGestureStart();
+ }
+ if (mTaskAnimationManager.shouldIgnoreMotionEvents()) {
+ if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
+ ActiveGestureLog.INSTANCE.addLog(
+ new CompoundString("TIS.onMotionEvent: A new gesture has been ")
+ .append("started, but a previously-requested recents ")
+ .append("animation hasn't started. Ignoring all following ")
+ .append("motion events."),
+ RECENTS_ANIMATION_START_PENDING);
+ }
+ return;
+ }
+ }
SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
@@ -1429,28 +1437,31 @@
mOverviewCommandHelper.dump(pw);
}
if (mGestureState != null) {
- mGestureState.dump(pw);
+ mGestureState.dump("", pw);
}
pw.println("Input state:");
- pw.println(" mInputMonitorCompat=" + mInputMonitorCompat);
- pw.println(" mInputEventReceiver=" + mInputEventReceiver);
+ pw.println("\tmInputMonitorCompat=" + mInputMonitorCompat);
+ pw.println("\tmInputEventReceiver=" + mInputEventReceiver);
DisplayController.INSTANCE.get(this).dump(pw);
pw.println("TouchState:");
BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
: mOverviewComponentObserver.getActivityInterface().getCreatedContainer();
boolean resumed = mOverviewComponentObserver != null
&& mOverviewComponentObserver.getActivityInterface().isResumed();
- pw.println(" createdOverviewActivity=" + createdOverviewActivity);
- pw.println(" resumed=" + resumed);
- pw.println(" mConsumer=" + mConsumer.getName());
+ pw.println("\tcreatedOverviewActivity=" + createdOverviewActivity);
+ pw.println("\tresumed=" + resumed);
+ pw.println("\tmConsumer=" + mConsumer.getName());
ActiveGestureLog.INSTANCE.dump("", pw);
RecentsModel.INSTANCE.get(this).dump("", pw);
+ if (mTaskAnimationManager != null) {
+ mTaskAnimationManager.dump("", pw);
+ }
if (createdOverviewActivity != null) {
createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
}
mTaskbarManager.dumpLogs("", pw);
pw.println("AssistStateManager:");
- AssistStateManager.INSTANCE.get(this).dump(" ", pw);
+ AssistStateManager.INSTANCE.get(this).dump("\t", pw);
SystemUiProxy.INSTANCE.get(this).dump(pw);
DeviceConfigWrapper.get().dump(" ", pw);
}
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index e3772bd..cfa6b98 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -277,7 +277,8 @@
errorDetected |= printErrorIfTrue(
true,
prefix,
- /* errorMessage= */ "new gesture attempted while a requested recents"
+ /* errorMessage= */ (eventEntry.getDuplicateCount() + 1)
+ + " gesture(s) attempted while a requested recents"
+ " animation is still pending.",
writer);
break;
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index 1e05a69..c54862a 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -216,6 +216,10 @@
return gestureEvent;
}
+ public int getDuplicateCount() {
+ return duplicateCount;
+ }
+
private void update(
@NonNull CompoundString compoundString,
ActiveGestureErrorDetector.GestureEvent gestureEvent) {
diff --git a/quickstep/src/com/android/quickstep/util/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
index a3904bc..4a35c3b 100644
--- a/quickstep/src/com/android/quickstep/util/AssistStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
@@ -20,12 +20,13 @@
import com.android.launcher3.R;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.util.SafeCloseable;
import java.io.PrintWriter;
import java.util.Optional;
/** Class to manage Assistant states. */
-public class AssistStateManager implements ResourceBasedOverride {
+public class AssistStateManager implements ResourceBasedOverride, SafeCloseable {
public static final MainThreadInitializedObject<AssistStateManager> INSTANCE =
forOverride(AssistStateManager.class, R.string.assist_state_manager_class);
@@ -42,6 +43,11 @@
return false;
}
+ /** Whether search supports showing on the lockscreen. */
+ public boolean supportsShowWhenLocked() {
+ return false;
+ }
+
/** Whether CsHelper CtS invocation path is available. */
public Optional<Boolean> isCsHelperAvailable() {
return Optional.empty();
@@ -91,4 +97,7 @@
/** Dump states. */
public void dump(String prefix, PrintWriter writer) {}
+
+ @Override
+ public void close() {}
}
diff --git a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
index 48c2407..0c1ac25 100644
--- a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
+++ b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
@@ -52,11 +52,11 @@
init {
// Make sure the starting state is right for the animation.
- val config = StateAnimationConfig()
- config.animFlags = SKIP_OVERVIEW.or(SKIP_DEPTH_CONTROLLER).or(SKIP_SCRIM)
- config.duration = 0
+ val setupConfig = StateAnimationConfig()
+ setupConfig.animFlags = SKIP_OVERVIEW.or(SKIP_DEPTH_CONTROLLER).or(SKIP_SCRIM)
+ setupConfig.duration = 0
launcher.stateManager
- .createAtomicAnimation(LauncherState.BACKGROUND_APP, LauncherState.NORMAL, config)
+ .createAtomicAnimation(LauncherState.BACKGROUND_APP, LauncherState.NORMAL, setupConfig)
.start()
launcher
.getOverviewPanel<RecentsView<QuickstepLauncher, LauncherState>>()
@@ -64,7 +64,7 @@
launcher.workspace.stateTransitionAnimation.setScrim(
PropertySetter.NO_ANIM_PROPERTY_SETTER,
LauncherState.BACKGROUND_APP,
- config
+ setupConfig
)
val workspace = launcher.workspace
@@ -103,11 +103,20 @@
Interpolators.clampToProgress(LINEAR, 0f, fadeClamp)
)
+ val transitionConfig = StateAnimationConfig()
+
// Match the Wallpaper animation to the rest of the content.
val depthController = (launcher as? QuickstepLauncher)?.depthController
- val depthConfig = StateAnimationConfig()
- depthConfig.setInterpolator(StateAnimationConfig.ANIM_DEPTH, EMPHASIZED)
- depthController?.setStateWithAnimation(LauncherState.NORMAL, depthConfig, animation)
+ transitionConfig.setInterpolator(StateAnimationConfig.ANIM_DEPTH, EMPHASIZED)
+ depthController?.setStateWithAnimation(LauncherState.NORMAL, transitionConfig, animation)
+
+ // Make sure that the contrast scrim animates correctly if needed.
+ transitionConfig.setInterpolator(StateAnimationConfig.ANIM_SCRIM_FADE, EMPHASIZED)
+ launcher.workspace.stateTransitionAnimation.setScrim(
+ animation,
+ LauncherState.NORMAL,
+ transitionConfig
+ )
// Needed to avoid text artefacts during the scale animation.
workspace.setLayerType(View.LAYER_TYPE_HARDWARE, null)
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 791ef04..ae6f703 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -68,7 +68,6 @@
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SCREEN;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -167,6 +166,7 @@
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.ViewPool;
import com.android.quickstep.BaseContainerInterface;
+import com.android.quickstep.DesktopModeStatus;
import com.android.quickstep.GestureState;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.RecentsAnimationController;
@@ -2817,7 +2817,7 @@
}
private boolean hasDesktopTask(Task[] runningTasks) {
- if (!enableDesktopWindowingMode()) {
+ if (!DesktopModeStatus.canEnterDesktopMode(mContext)) {
return false;
}
for (Task task : runningTasks) {
@@ -6229,7 +6229,7 @@
*/
public void moveTaskToDesktop(TaskIdAttributeContainer taskContainer,
Runnable successCallback) {
- if (!enableDesktopWindowingMode()) {
+ if (!DesktopModeStatus.canEnterDesktopMode(mContext)) {
return;
}
switchToScreenshot(() -> finishRecentsAnimation(/* toRecents= */true, /* shouldPip= */false,
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 b478efa..d90e048 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
@@ -16,19 +16,15 @@
package com.android.launcher3.taskbar.bubbles.animation
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.AnimatorSet
import android.content.Context
import android.graphics.Color
import android.graphics.Path
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
+import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule
-import androidx.core.animation.doOnEnd
import androidx.core.graphics.drawable.toBitmap
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.test.core.app.ApplicationProvider
@@ -42,16 +38,13 @@
import com.android.launcher3.taskbar.bubbles.BubbleStashController
import com.android.launcher3.taskbar.bubbles.BubbleView
import com.android.wm.shell.common.bubbles.BubbleInfo
+import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.Semaphore
-import java.util.concurrent.TimeUnit
import org.junit.Before
-import org.junit.ClassRule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
-import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@SmallTest
@@ -61,10 +54,6 @@
private val context = ApplicationProvider.getApplicationContext<Context>()
private val animatorScheduler = TestBubbleBarViewAnimatorScheduler()
- companion object {
- @JvmField @ClassRule val animatorTestRule = AnimatorTestRule()
- }
-
@Before
fun setUp() {
PhysicsAnimatorTestUtils.prepareForTest()
@@ -99,14 +88,9 @@
val bubbleStashController = mock<BubbleStashController>()
whenever(bubbleStashController.isStashed).thenReturn(true)
- val semaphore = Semaphore(0)
- val hideHandleAnimator = AnimatorSet()
- hideHandleAnimator.duration = 0
- whenever(bubbleStashController.buildHideHandleAnimationForNewBubble())
- .thenReturn(hideHandleAnimator)
- // add an end listener to the hide handle animation. we add it when the animation starts
- // to ensure that it gets called after all other end listeners.
- hideHandleAnimator.doOnStart { hideHandleAnimator.doOnEnd { semaphore.release() } }
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
val animator =
BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
@@ -115,44 +99,26 @@
animator.animateBubbleInForStashed(bubble)
}
- // wait for the stash handle animation to complete
- assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
- // stash handle animation finished. verify that the stash handle is now hidden
- verify(bubbleStashController).setStashAlpha(0f)
-
+ // let the animation start and wait for it to complete
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+ assertThat(handle.alpha).isEqualTo(0)
+ assertThat(handle.translationY).isEqualTo(-70)
assertThat(overflowView.visibility).isEqualTo(INVISIBLE)
assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
assertThat(bubbleView.visibility).isEqualTo(VISIBLE)
-
- // wait for the show bubble animation to complete
- PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
- DynamicAnimation.ALPHA,
- DynamicAnimation.TRANSLATION_Y,
- DynamicAnimation.SCALE_Y,
- )
-
assertThat(bubbleView.alpha).isEqualTo(1)
- assertThat(bubbleView.translationY).isEqualTo(-50)
+ assertThat(bubbleView.translationY).isEqualTo(-20)
assertThat(bubbleView.scaleY).isEqualTo(1)
- val showHandleAnimator = AnimatorSet()
- showHandleAnimator.duration = 0
- whenever(bubbleStashController.buildShowHandleAnimationForNewBubble())
- .thenReturn(showHandleAnimator)
- var showHandleAnimationStarted = false
- showHandleAnimator.doOnStart { showHandleAnimationStarted = true }
-
// execute the hide bubble animation
assertThat(animatorScheduler.delayedBlock).isNotNull()
InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
- // finish the hide bubble animation
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- animatorTestRule.advanceTimeBy(250)
- }
- assertThat(showHandleAnimationStarted).isTrue()
+ // let the animation start and wait for it to complete
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
assertThat(bubbleView.alpha).isEqualTo(1)
assertThat(bubbleView.visibility).isEqualTo(VISIBLE)
@@ -160,16 +126,8 @@
assertThat(bubbleBarView.alpha).isEqualTo(0)
assertThat(overflowView.alpha).isEqualTo(1)
assertThat(overflowView.visibility).isEqualTo(VISIBLE)
- }
-
- private fun AnimatorSet.doOnStart(onStart: () -> Unit) {
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animator: Animator) {
- onStart()
- }
- }
- )
+ assertThat(handle.alpha).isEqualTo(1)
+ assertThat(handle.translationY).isEqualTo(0)
}
private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler {
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/OWNERS b/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/OWNERS
new file mode 100644
index 0000000..3f947a0
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/OWNERS
@@ -0,0 +1,5 @@
+atsjenk@google.com
+liranb@google.com
+madym@google.com
+mpodolian@google.com
+
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index 7dabbca..0f9d96c 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -16,12 +16,14 @@
package com.android.quickstep
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
import android.content.ComponentName
import android.content.Intent
import android.platform.test.flag.junit.SetFlagsRule
import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.AbstractFloatingViewHelper
-import com.android.launcher3.Launcher
import com.android.launcher3.logging.StatsLogManager
import com.android.launcher3.logging.StatsLogManager.LauncherEvent
import com.android.launcher3.model.data.WorkspaceItemInfo
@@ -33,8 +35,11 @@
import com.android.systemui.shared.recents.model.Task.TaskKey
import com.android.window.flags.Flags
import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import org.mockito.quality.Strictness
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
@@ -56,8 +61,23 @@
private val factory: TaskShortcutFactory =
DesktopSystemShortcut.createFactory(abstractFloatingViewHelper)
+ private lateinit var mockitoSession: StaticMockitoSession
+
+ @Before
+ fun setUp(){
+ mockitoSession = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java).startMocking()
+ doReturn(true).`when` { DesktopModeStatus.enforceDeviceRestrictions() }
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ }
+
+ @After
+ fun tearDown(){
+ mockitoSession.finishMocking()
+ }
+
@Test
- fun createDesktopTaskShortcutFactory_featureOff() {
+ fun createDesktopTaskShortcutFactory_desktopModeDisabled() {
setFlagsRule.disableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
val task =
@@ -77,6 +97,49 @@
}
@Test
+ fun createDesktopTaskShortcutFactory_desktopModeEnabled_DeviceNotSupported() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+ val task =
+ Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+ isDockable = true
+ }
+ val taskContainer =
+ taskView.TaskIdAttributeContainer(
+ task,
+ null,
+ null,
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+ )
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
+ fun createDesktopTaskShortcutFactory_desktopModeEnabled_DeviceNotSupported_OverrideEnabled() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ doReturn(false).`when` { DesktopModeStatus.enforceDeviceRestrictions() }
+
+ val task =
+ Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+ isDockable = true
+ }
+ val taskContainer =
+ taskView.TaskIdAttributeContainer(
+ task,
+ null,
+ null,
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+ )
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNotNull()
+ }
+
+ @Test
fun createDesktopTaskShortcutFactory_undockable() {
setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
diff --git a/res/drawable/ic_private_profile_app_scroller_badge.xml b/res/drawable/ic_private_profile_app_scroller_badge.xml
new file mode 100644
index 0000000..b52a277
--- /dev/null
+++ b/res/drawable/ic_private_profile_app_scroller_badge.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+ android:viewportWidth="32"
+ android:viewportHeight="32"
+ android:width="32dp"
+ android:height="32dp">
+ <path
+ android:pathData="M16.0007 2.66602L5.33398 6.66602V14.786C5.33398 21.5194 9.88065 27.7993 16.0007 29.3327C22.1207 27.7993 26.6673 21.5194 26.6673 14.786V6.66602L16.0007 2.66602ZM20.0007 19.9993V22.666H17.334V23.9993H14.6673V17.1193C12.7473 16.546 11.334 14.786 11.334 12.666C11.334 10.0927 13.4273 7.99935 16.0007 7.99935C18.574 7.99935 20.6673 10.0927 20.6673 12.666C20.6673 14.7727 19.254 16.546 17.334 17.1193V19.9993H20.0007Z"
+ android:fillType="evenOdd"
+ android:fillColor="@android:color/white" />
+ <path
+ android:pathData="M16 14.666C17.1046 14.666 18 13.7706 18 12.666C18 11.5614 17.1046 10.666 16 10.666C14.8954 10.666 14 11.5614 14 12.666C14 13.7706 14.8954 14.666 16 14.666Z"
+ android:fillColor="@android:color/white" />
+</vector>
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index 51c7a05..eff748a 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -155,7 +155,7 @@
* Maps the touch (from 0..1) to the adapter position that should be visible.
* <p>Override in each subclass of this base class.
*/
- public abstract String scrollToPositionAtProgress(float touchFraction);
+ public abstract CharSequence scrollToPositionAtProgress(float touchFraction);
/**
* Updates the bounds for the scrollbar.
@@ -193,14 +193,4 @@
}
scrollToPosition(0);
}
-
- /**
- * Scrolls this recycler view to the bottom with easing and duration.
- */
- public void scrollToBottomWithMotion(int duration) {
- if (mScrollbar != null) {
- mScrollbar.reattachThumbToScroll();
- }
- smoothScrollBy(0, getAvailableScrollHeight(), Interpolators.EMPHASIZED, duration);
- }
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 2fd5ebd..98cb84e 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -262,7 +262,7 @@
// Get the display info based on default display and interpolate it to existing display
Info defaultInfo = DisplayController.INSTANCE.get(context).getInfo();
- @DeviceType int defaultDeviceType = getDeviceType(defaultInfo);
+ @DeviceType int defaultDeviceType = defaultInfo.getDeviceType();
DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
defaultInfo,
getPredefinedDeviceProfiles(context, gridName, defaultDeviceType,
@@ -271,7 +271,7 @@
Context displayContext = context.createDisplayContext(display);
Info myInfo = new Info(displayContext);
- @DeviceType int deviceType = getDeviceType(myInfo);
+ @DeviceType int deviceType = myInfo.getDeviceType();
DisplayOption myDisplayOption = invDistWeightedInterpolate(
myInfo,
getPredefinedDeviceProfiles(context, gridName, deviceType,
@@ -324,30 +324,13 @@
}
}
- private static @DeviceType int getDeviceType(Info displayInfo) {
- int flagPhone = 1 << 0;
- int flagTablet = 1 << 1;
-
- int type = displayInfo.supportedBounds.stream()
- .mapToInt(bounds -> displayInfo.isTablet(bounds) ? flagTablet : flagPhone)
- .reduce(0, (a, b) -> a | b);
- if (type == (flagPhone | flagTablet)) {
- // device has profiles supporting both phone and table modes
- return TYPE_MULTI_DISPLAY;
- } else if (type == flagTablet) {
- return TYPE_TABLET;
- } else {
- return TYPE_PHONE;
- }
- }
-
public static String getCurrentGridName(Context context) {
return LauncherPrefs.get(context).get(GRID_NAME);
}
private String initGrid(Context context, String gridName) {
Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
- @DeviceType int deviceType = getDeviceType(displayInfo);
+ @DeviceType int deviceType = displayInfo.getDeviceType();
ArrayList<DisplayOption> allOptions =
getPredefinedDeviceProfiles(context, gridName, deviceType,
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 36a44cc..ba34f59 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -180,7 +180,7 @@
* Maps the touch (from 0..1) to the adapter position that should be visible.
*/
@Override
- public String scrollToPositionAtProgress(float touchFraction) {
+ public CharSequence scrollToPositionAtProgress(float touchFraction) {
int rowCount = mApps.getNumAppRows();
if (rowCount == 0) {
return "";
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 4d4b8d2..60df7c5 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -22,6 +22,10 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_USER_INSTALLED_APPS_COUNT;
import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ImageSpan;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -65,11 +69,11 @@
*/
public static class FastScrollSectionInfo {
// The section name
- public final String sectionName;
+ public final CharSequence sectionName;
// The item position
public final int position;
- public FastScrollSectionInfo(String sectionName, int position) {
+ public FastScrollSectionInfo(CharSequence sectionName, int position) {
this.sectionName = sectionName;
this.position = position;
}
@@ -93,6 +97,7 @@
// The of ordered component names as a result of a search query
private final ArrayList<AdapterItem> mSearchResults = new ArrayList<>();
+ private final SpannableString mPrivateProfileAppScrollerBadge;
private BaseAllAppsAdapter<T> mAdapter;
private AppInfoComparator mAppNameComparator;
private int mNumAppsPerRowAllApps;
@@ -110,6 +115,10 @@
if (mAllAppsStore != null) {
mAllAppsStore.addUpdateListener(this);
}
+ mPrivateProfileAppScrollerBadge = new SpannableString(" ");
+ mPrivateProfileAppScrollerBadge.setSpan(new ImageSpan(context,
+ R.drawable.ic_private_profile_app_scroller_badge, ImageSpan.ALIGN_CENTER),
+ 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
/** Set the number of apps per row when device profile changes. */
@@ -383,6 +392,7 @@
private int addAppsWithSections(List<AppInfo> appList, int startPosition) {
String lastSectionName = null;
boolean hasPrivateApps = false;
+ int position = startPosition;
if (mPrivateProviderManager != null) {
hasPrivateApps = appList.stream().
allMatch(mPrivateProviderManager.getItemInfoMatcher());
@@ -403,11 +413,12 @@
// Create a new section if the section names do not match
if (!sectionName.equals(lastSectionName)) {
lastSectionName = sectionName;
- mFastScrollerSections.add(new FastScrollSectionInfo(sectionName, startPosition));
+ mFastScrollerSections.add(new FastScrollSectionInfo(hasPrivateApps ?
+ mPrivateProfileAppScrollerBadge : sectionName, position));
}
- startPosition++;
+ position++;
}
- return startPosition;
+ return position;
}
/**
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 05fdcef..2873e73 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -184,7 +184,7 @@
// user sees
TextView widgetAppName = findViewById(R.id.widget_appName);
WidgetSections.WidgetSection section = targetApp.widgetCategory == NO_CATEGORY ? null
- : WidgetSections.getWidgetSections(this).get(targetApp.widgetCategory);
+ : WidgetSections.get(this).get(targetApp.widgetCategory);
widgetAppName.setText(section == null ? info.loadLabel(getPackageManager())
: getString(section.mSectionTitle));
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 0e4b48e..6bed9dc 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -419,7 +419,7 @@
private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
- info.providerName, info.user);
+ info.providerName, info.user, mContext);
if (widgetItem == null) {
return;
}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 329f717..5b9e61c 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -559,7 +559,7 @@
return;
}
- WidgetSection widgetSection = WidgetSections.getWidgetSections(mContext)
+ WidgetSection widgetSection = WidgetSections.get(mContext)
.get(infoInOut.widgetCategory);
infoInOut.title = mContext.getString(widgetSection.mSectionTitle);
infoInOut.contentDescription = getUserBadgedLabel(infoInOut.title, infoInOut.user);
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 91ce5ea..519eeaa 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -18,6 +18,7 @@
import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
+import android.util.SparseArray;
import androidx.annotation.Nullable;
import androidx.collection.ArrayMap;
@@ -201,8 +202,16 @@
// add and update.
mWidgetsList.putAll(rawWidgetsShortcuts.stream()
.filter(new WidgetValidityCheck(app))
- .flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
- .map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem)))
+ .flatMap(
+ widgetItem -> getPackageUserKeys(app.getContext(), widgetItem)
+ .stream()
+ .map(
+ key -> new Pair<>(
+ packageItemInfoCache.getOrCreate(key),
+ widgetItem
+ )
+ )
+ )
.collect(groupingBy(pair -> pair.first, mapping(pair -> pair.second, toList()))));
// Update each package entry
@@ -240,19 +249,26 @@
}
public WidgetItem getWidgetProviderInfoByProviderName(
- ComponentName providerName, UserHandle user) {
+ ComponentName providerName, UserHandle user, Context context) {
+ SparseArray<WidgetSections.WidgetSection> sections = WidgetSections.get(
+ context);
if (!WIDGETS_ENABLED) {
return null;
}
- List<WidgetItem> widgetsList = mWidgetsList.get(
- new PackageItemInfo(providerName.getPackageName(), user));
- if (widgetsList == null) {
- return null;
- }
- for (WidgetItem item : widgetsList) {
- if (item.componentName.equals(providerName)) {
- return item;
+ // Checking if we hav ea provider in any of the categories.
+ for (int i = 0; i < sections.size(); i++) {
+ PackageItemInfo key = new PackageItemInfo(
+ providerName.getPackageName(),
+ sections.get(i).mCategory,
+ user
+ );
+ if (mWidgetsList.containsKey(key)) {
+ return mWidgetsList.get(key).stream().filter(
+ item -> item.componentName.equals(providerName)
+ )
+ .findFirst()
+ .orElse(null);
}
}
return null;
@@ -286,10 +302,12 @@
categories.forEach(category -> {
if (category == NO_CATEGORY) {
packageUserKeys.add(
- new PackageUserKey(item.componentName.getPackageName(),
- item.user));
+ new PackageUserKey(item.componentName.getPackageName(), item.user)
+ );
} else {
- packageUserKeys.add(new PackageUserKey(category, item.user));
+ packageUserKeys.add(
+ new PackageUserKey(item.componentName.getPackageName(), category, item.user)
+ );
}
});
return packageUserKeys;
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index ff95212..8806e27 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -19,6 +19,9 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE;
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_TABLET;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
import static com.android.launcher3.Utilities.dpiFromPx;
@@ -47,6 +50,7 @@
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.InvariantDeviceProfile.DeviceType;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.FileLog;
@@ -466,6 +470,23 @@
public int getDensityDpi() {
return densityDpi;
}
+
+ public @DeviceType int getDeviceType() {
+ int flagPhone = 1 << 0;
+ int flagTablet = 1 << 1;
+
+ int type = supportedBounds.stream()
+ .mapToInt(bounds -> isTablet(bounds) ? flagTablet : flagPhone)
+ .reduce(0, (a, b) -> a | b);
+ if (type == (flagPhone | flagTablet)) {
+ // device has profiles supporting both phone and tablet modes
+ return TYPE_MULTI_DISPLAY;
+ } else if (type == flagTablet) {
+ return TYPE_TABLET;
+ } else {
+ return TYPE_PHONE;
+ }
+ }
}
/**
diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java
index 92d9737..c1fb379 100644
--- a/src/com/android/launcher3/util/PackageUserKey.java
+++ b/src/com/android/launcher3/util/PackageUserKey.java
@@ -48,6 +48,10 @@
update(/* packageName= */ "", widgetCategory, user);
}
+ public PackageUserKey(String packageName, int widgetCategory, UserHandle user) {
+ update(packageName, widgetCategory, user);
+ }
+
public void update(String packageName, UserHandle user) {
update(packageName, NO_CATEGORY, user);
}
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 9f6e8f8..6a77d08 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -25,6 +25,8 @@
import static com.android.launcher3.allapps.AllAppsTransitionController.REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS;
import static com.android.launcher3.util.ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE;
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
@@ -135,6 +137,7 @@
protected final AnimatedFloat mSwipeToDismissProgress =
new AnimatedFloat(this::onUserSwipeToDismissProgressChanged, 0f);
protected boolean mIsDismissInProgress;
+ protected View mViewToAnimateInSwipeToDismiss = this;
private @Nullable Drawable mContentBackground;
private @Nullable View mContentBackgroundParentView;
@@ -286,18 +289,37 @@
@Override
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void onBackStarted(BackEvent backEvent) {
+ super.onBackStarted(backEvent);
+ mViewToAnimateInSwipeToDismiss = shouldAnimateContentViewInBackSwipe() ? mContent : this;
+ }
+
+ @Override
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void onBackProgressed(BackEvent backEvent) {
final float progress = backEvent.getProgress();
float deceleratedProgress = Interpolators.BACK_GESTURE.getInterpolation(progress);
mSwipeToDismissProgress.updateValue(deceleratedProgress);
}
+ /**
+ * During predictive back swipe, the default behavior is to scale {@link AbstractSlideInView}
+ * during back swipe. This method allow subclass to scale {@link #mContent}, typically to exit
+ * search mode.
+ *
+ * <p>Note that this method can be expensive, and should only be called from
+ * {@link #onBackStarted(BackEvent)}, not from {@link #onBackProgressed(BackEvent)}.
+ */
+ protected boolean shouldAnimateContentViewInBackSwipe() {
+ return false;
+ }
+
protected void onUserSwipeToDismissProgressChanged() {
float progress = mSwipeToDismissProgress.value;
mIsDismissInProgress = progress > 0f;
float scale = PREDICTIVE_BACK_MIN_SCALE + (1 - PREDICTIVE_BACK_MIN_SCALE) * (1f - progress);
- SCALE_PROPERTY.set(this, scale);
+ SCALE_PROPERTY.set(mViewToAnimateInSwipeToDismiss, scale);
setClipChildren(!mIsDismissInProgress);
setClipToPadding(!mIsDismissInProgress);
mContent.setClipChildren(!mIsDismissInProgress);
@@ -312,9 +334,32 @@
}
protected void animateSwipeToDismissProgressToStart() {
- mSwipeToDismissProgress.animateToValue(0f)
- .setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS)
- .start();
+ ObjectAnimator objectAnimator = mSwipeToDismissProgress.animateToValue(0f)
+ .setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS);
+
+ // If we are animating a different view, we should reset the animating view back to
+ // AbstractSlideInView as it is the default view to animate.
+ if (this != mViewToAnimateInSwipeToDismiss) {
+ objectAnimator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ mViewToAnimateInSwipeToDismiss = AbstractSlideInView.this;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mViewToAnimateInSwipeToDismiss = AbstractSlideInView.this;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {}
+
+ @Override
+ public void onAnimationStart(Animator animator) {}
+ });
+ }
+
+ objectAnimator.start();
}
@Override
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 8408cc7..df8f635 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -30,6 +30,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Property;
@@ -121,7 +122,7 @@
// Fast scroller popup
private TextView mPopupView;
private boolean mPopupVisible;
- private String mPopupSectionName;
+ private CharSequence mPopupSectionName;
private Insets mSystemGestureInsets;
protected FastScrollRecyclerView mRv;
@@ -307,13 +308,13 @@
// Update the fastscroller section name at this touch position
int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight;
float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));
- String sectionName = mRv.scrollToPositionAtProgress(boundedY / bottom);
+ CharSequence sectionName = mRv.scrollToPositionAtProgress(boundedY / bottom);
if (!sectionName.equals(mPopupSectionName)) {
mPopupSectionName = sectionName;
mPopupView.setText(sectionName);
performHapticFeedback(CLOCK_TICK);
}
- animatePopupVisibility(!sectionName.isEmpty());
+ animatePopupVisibility(!TextUtils.isEmpty(sectionName));
mLastTouchY = boundedY;
setThumbOffsetY((int) mLastTouchY);
}
diff --git a/src/com/android/launcher3/widget/WidgetSections.java b/src/com/android/launcher3/widget/WidgetSections.java
index c45b095..f5fd2a9 100644
--- a/src/com/android/launcher3/widget/WidgetSections.java
+++ b/src/com/android/launcher3/widget/WidgetSections.java
@@ -50,7 +50,7 @@
private static Map<ComponentName, IntSet> sWidgetsToCategories;
/** Returns a list of widget sections that are shown in the widget picker. */
- public static synchronized SparseArray<WidgetSection> getWidgetSections(Context context) {
+ public static synchronized SparseArray<WidgetSection> get(Context context) {
if (sWidgetSections != null) {
return sWidgetSections;
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index c6cbb60..28eeb10 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -902,11 +902,23 @@
return isFoldUnFold || useDifferentLayoutOnOrientationChange;
}
+ /**
+ * In widget search mode, we should scale down content inside widget bottom sheet, rather
+ * than the whole bottom sheet, to indicate we will navigate back within the widget
+ * bottom sheet.
+ */
+ @Override
+ public boolean shouldAnimateContentViewInBackSwipe() {
+ return mIsInSearchMode;
+ }
+
@Override
public void onBackInvoked() {
if (mIsInSearchMode) {
mSearchBar.reset();
- animateSwipeToDismissProgressToStart();
+ // Posting animation to next frame will let widget sheet finish updating UI first, and
+ // make animation smoother.
+ post(this::animateSwipeToDismissProgressToStart);
} else {
super.onBackInvoked();
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 698e764..a47818f 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -73,7 +73,7 @@
* Maps the touch (from 0..1) to the adapter position that should be visible.
*/
@Override
- public String scrollToPositionAtProgress(float touchFraction) {
+ public CharSequence scrollToPositionAtProgress(float touchFraction) {
// Skip early if widgets are not bound.
if (isModelNotReady()) {
return "";