Merge "Fix responsive folders" into udc-qpr-dev
diff --git a/quickstep/res/drawable/bg_bubble_dismiss_circle.xml b/quickstep/res/drawable/bg_bubble_dismiss_circle.xml
new file mode 100644
index 0000000..b793eec
--- /dev/null
+++ b/quickstep/res/drawable/bg_bubble_dismiss_circle.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+
+ <stroke
+ android:width="2dp"
+ android:color="@android:color/system_accent1_600" />
+
+ <solid android:color="@android:color/system_accent1_600" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_bubble_dismiss_white.xml b/quickstep/res/drawable/ic_bubble_dismiss_white.xml
new file mode 100644
index 0000000..b15111b
--- /dev/null
+++ b/quickstep/res/drawable/ic_bubble_dismiss_white.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
+ android:fillColor="@android:color/system_neutral1_50"/>
+</vector>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index e4f6555..6140d14 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -367,6 +367,13 @@
<dimen name="bubblebar_icon_spacing">3dp</dimen>
<dimen name="bubblebar_icon_elevation">1dp</dimen>
+ <!-- Bubble bar dismiss view -->
+ <dimen name="bubblebar_dismiss_target_size">96dp</dimen>
+ <dimen name="bubblebar_dismiss_target_small_size">60dp</dimen>
+ <dimen name="bubblebar_dismiss_target_icon_size">24dp</dimen>
+ <dimen name="bubblebar_dismiss_target_bottom_margin">50dp</dimen>
+ <dimen name="bubblebar_dismiss_floating_gradient_height">548dp</dimen>
+
<!-- Launcher splash screen -->
<!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
<!-- starting_surface_exit_animation_window_shift_length -->
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 42cb290..cb9c329 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -90,6 +90,8 @@
import com.android.launcher3.taskbar.bubbles.BubbleBarView;
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
+import com.android.launcher3.taskbar.bubbles.BubbleDismissController;
+import com.android.launcher3.taskbar.bubbles.BubbleDragController;
import com.android.launcher3.taskbar.bubbles.BubbleStashController;
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
@@ -216,7 +218,9 @@
new BubbleBarController(this, bubbleBarView),
new BubbleBarViewController(this, bubbleBarView),
new BubbleStashController(this),
- new BubbleStashedHandleViewController(this, bubbleHandleView)));
+ new BubbleStashedHandleViewController(this, bubbleHandleView),
+ new BubbleDragController(this),
+ new BubbleDismissController(this, mDragLayer)));
}
// Construct controllers.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index b2d5940..6818db6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -31,8 +31,11 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+import static java.lang.Math.abs;
+
import android.annotation.BinderThread;
import android.annotation.Nullable;
+import android.app.Notification;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
@@ -118,6 +121,7 @@
private final Executor mMainExecutor;
private final LauncherApps mLauncherApps;
private final BubbleIconFactory mIconFactory;
+ private final SystemUiProxy mSystemUiProxy;
private BubbleBarItem mSelectedBubble;
private BubbleBarOverflow mOverflowBubble;
@@ -159,8 +163,10 @@
mContext = context;
mBarView = bubbleView; // Need the view for inflating bubble views.
+ mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
+
if (BUBBLE_BAR_ENABLED) {
- SystemUiProxy.INSTANCE.get(context).setBubblesListener(this);
+ mSystemUiProxy.setBubblesListener(this);
}
mMainExecutor = MAIN_EXECUTOR;
mLauncherApps = context.getSystemService(LauncherApps.class);
@@ -173,7 +179,7 @@
}
public void onDestroy() {
- SystemUiProxy.INSTANCE.get(mContext).setBubblesListener(null);
+ mSystemUiProxy.setBubblesListener(null);
}
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
@@ -240,17 +246,21 @@
BUBBLE_STATE_EXECUTOR.execute(() -> {
createAndAddOverflowIfNeeded();
if (update.addedBubble != null) {
- viewUpdate.addedBubble = populateBubble(update.addedBubble, mContext, mBarView);
+ viewUpdate.addedBubble = populateBubble(mContext, update.addedBubble, mBarView,
+ null /* existingBubble */);
}
if (update.updatedBubble != null) {
+ BubbleBarBubble existingBubble = mBubbles.get(update.updatedBubble.getKey());
viewUpdate.updatedBubble =
- populateBubble(update.updatedBubble, mContext, mBarView);
+ populateBubble(mContext, update.updatedBubble, mBarView,
+ existingBubble);
}
if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) {
List<BubbleBarBubble> currentBubbles = new ArrayList<>();
for (int i = 0; i < update.currentBubbleList.size(); i++) {
BubbleBarBubble b =
- populateBubble(update.currentBubbleList.get(i), mContext, mBarView);
+ populateBubble(mContext, update.currentBubbleList.get(i), mBarView,
+ null /* existingBubble */);
currentBubbles.add(b);
}
viewUpdate.currentBubbles = currentBubbles;
@@ -312,9 +322,11 @@
mBubbleStashedHandleViewController.setHiddenForBubbles(mBubbles.isEmpty());
if (update.updatedBubble != null) {
- // TODO: (b/269670235) handle updates:
- // (1) if content / icons change -- requires reload & add back in place
- // (2) if showing update dot changes -- tell the view to hide / show the dot
+ // Updates mean the dot state may have changed; any other changes were updated in
+ // the populateBubble step.
+ BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
+ // If we're not stashed, we're visible so animate
+ bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */);
}
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
// Create the new list
@@ -331,8 +343,8 @@
// TODO: (b/273316505) handle suppression
}
if (update.selectedBubbleKey != null) {
- if (mSelectedBubble != null
- && !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
+ if (mSelectedBubble == null
+ || !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
BubbleBarBubble newlySelected = mBubbles.get(update.selectedBubbleKey);
if (newlySelected != null) {
bubbleToSelect = newlySelected;
@@ -354,12 +366,38 @@
}
}
+ /** Tells WMShell to show the currently selected bubble. */
+ public void showSelectedBubble() {
+ if (getSelectedBubbleKey() != null) {
+ if (mSelectedBubble instanceof BubbleBarBubble) {
+ // Because we've visited this bubble, we should suppress the notification.
+ // This is updated on WMShell side when we show the bubble, but that update isn't
+ // passed to launcher, instead we apply it directly here.
+ BubbleInfo info = ((BubbleBarBubble) mSelectedBubble).getInfo();
+ info.setFlags(
+ info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+ mSelectedBubble.getView().updateDotVisibility(true /* animate */);
+ }
+ mSystemUiProxy.showBubble(getSelectedBubbleKey(),
+ getBubbleBarOffsetX(), getBubbleBarOffsetY());
+ } else {
+ Log.w(TAG, "Trying to show the selected bubble but it's null");
+ }
+ }
+
+ /** Updates the currently selected bubble for launcher views and tells WMShell to show it. */
+ public void showAndSelectBubble(BubbleBarItem b) {
+ if (DEBUG) Log.w(TAG, "showingSelectedBubble: " + b.getKey());
+ setSelectedBubble(b);
+ showSelectedBubble();
+ }
+
/**
* Sets the bubble that should be selected. This notifies the views, it does not notify
- * WMShell that the selection has changed, that should go through
- * {@link SystemUiProxy#showBubble}.
+ * WMShell that the selection has changed, that should go through either
+ * {@link #showSelectedBubble()} or {@link #showAndSelectBubble(BubbleBarItem)}.
*/
- public void setSelectedBubble(BubbleBarItem b) {
+ private void setSelectedBubble(BubbleBarItem b) {
if (!Objects.equals(b, mSelectedBubble)) {
if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey());
mSelectedBubble = b;
@@ -383,7 +421,8 @@
//
@Nullable
- private BubbleBarBubble populateBubble(BubbleInfo b, Context context, BubbleBarView bbv) {
+ private BubbleBarBubble populateBubble(Context context, BubbleInfo b, BubbleBarView bbv,
+ @Nullable BubbleBarBubble existingBubble) {
String appName;
Bitmap badgeBitmap;
Bitmap bubbleBitmap;
@@ -452,16 +491,27 @@
iconPath.transform(matrix);
dotPath = iconPath;
dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
- Color.WHITE, WHITE_SCRIM_ALPHA);
+ Color.WHITE, WHITE_SCRIM_ALPHA / 255f);
- LayoutInflater inflater = LayoutInflater.from(context);
- BubbleView bubbleView = (BubbleView) inflater.inflate(
- R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
+ if (existingBubble == null) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ BubbleView bubbleView = (BubbleView) inflater.inflate(
+ R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
- BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
- badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
- bubbleView.setBubble(bubble);
- return bubble;
+ BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
+ badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
+ bubbleView.setBubble(bubble);
+ return bubble;
+ } else {
+ // If we already have a bubble (so it already has an inflated view), update it.
+ existingBubble.setInfo(b);
+ existingBubble.setBadge(badgeBitmap);
+ existingBubble.setIcon(bubbleBitmap);
+ existingBubble.setDotColor(dotColor);
+ existingBubble.setDotPath(dotPath);
+ existingBubble.setAppName(appName);
+ return existingBubble;
+ }
}
private BubbleBarOverflow createOverflow(Context context) {
@@ -496,4 +546,13 @@
return mIconFactory.createBadgedIconBitmap(drawable).icon;
}
+
+ private int getBubbleBarOffsetY() {
+ final int translation = (int) abs(mBubbleStashController.getBubbleBarTranslationY());
+ return translation + mBarView.getHeight();
+ }
+
+ private int getBubbleBarOffsetX() {
+ return mBarView.getWidth() + mBarView.getHorizontalMargin();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
index 582dcc7..43e21f4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
@@ -20,18 +20,18 @@
import com.android.wm.shell.common.bubbles.BubbleInfo
/** An entity in the bubble bar. */
-sealed class BubbleBarItem(open val key: String, open val view: BubbleView)
+sealed class BubbleBarItem(open var key: String, open var view: BubbleView)
/** Contains state info about a bubble in the bubble bar as well as presentation information. */
data class BubbleBarBubble(
- val info: BubbleInfo,
- override val view: BubbleView,
- val badge: Bitmap,
- val icon: Bitmap,
- val dotColor: Int,
- val dotPath: Path,
- val appName: String
+ var info: BubbleInfo,
+ override var view: BubbleView,
+ var badge: Bitmap,
+ var icon: Bitmap,
+ var dotColor: Int,
+ var dotPath: Path,
+ var appName: String
) : BubbleBarItem(info.key, view)
/** Represents the overflow bubble in the bubble bar. */
-data class BubbleBarOverflow(override val view: BubbleView) : BubbleBarItem("Overflow", view)
+data class BubbleBarOverflow(override var view: BubbleView) : BubbleBarItem("Overflow", view)
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 563ba02..e93d410 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -95,6 +95,8 @@
private View.OnClickListener mOnClickListener;
private final Rect mTempRect = new Rect();
+ private float mRelativePivotX = 1f;
+ private float mRelativePivotY = 1f;
// An animator that represents the expansion state of the bubble bar, where 0 corresponds to the
// collapsed state and 1 to the fully expanded state.
@@ -109,6 +111,9 @@
@Nullable
private Consumer<String> mUpdateSelectedBubbleAfterCollapse;
+ @Nullable
+ private BubbleView mDraggedBubbleView;
+
public BubbleBarView(Context context) {
this(context, null);
}
@@ -181,9 +186,10 @@
mBubbleBarBounds.right = right;
mBubbleBarBounds.bottom = bottom;
- // The bubble bar handle is aligned to the bottom edge of the screen so scale towards that.
- setPivotX(getWidth());
- setPivotY(getHeight());
+ // The bubble bar handle is aligned according to the relative pivot,
+ // by default it's aligned to the bottom edge of the screen so scale towards that
+ setPivotX(mRelativePivotX * getWidth());
+ setPivotY(mRelativePivotY * getHeight());
// Position the views
updateChildrenRenderNodeProperties();
@@ -198,6 +204,32 @@
return mBubbleBarBounds;
}
+ /**
+ * Set bubble bar relative pivot value for X and Y, applied as a fraction of view width/height
+ * respectively. If the value is not in range of 0 to 1 it will be normalized.
+ * @param x relative X pivot value in range 0..1
+ * @param y relative Y pivot value in range 0..1
+ */
+ public void setRelativePivot(float x, float y) {
+ mRelativePivotX = Float.max(Float.min(x, 1), 0);
+ mRelativePivotY = Float.max(Float.min(y, 1), 0);
+ requestLayout();
+ }
+
+ /**
+ * Get current relative pivot for X axis
+ */
+ public float getRelativePivotX() {
+ return mRelativePivotX;
+ }
+
+ /**
+ * Get current relative pivot for Y axis
+ */
+ public float getRelativePivotY() {
+ return mRelativePivotY;
+ }
+
// TODO: (b/280605790) animate it
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
@@ -223,6 +255,12 @@
setLayoutParams(lp);
}
+ /** @return the horizontal margin between the bubble bar and the edge of the screen. */
+ int getHorizontalMargin() {
+ LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ return lp.getMarginEnd();
+ }
+
/**
* Updates the z order, positions, and badge visibility of the bubble views in the bar based
* on the expanded state.
@@ -234,6 +272,7 @@
final float collapsedWidth = collapsedWidth();
int bubbleCount = getChildCount();
final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
+ final boolean animate = getVisibility() == VISIBLE;
for (int i = 0; i < bubbleCount; i++) {
BubbleView bv = (BubbleView) getChildAt(i);
bv.setTranslationY(ty);
@@ -247,20 +286,18 @@
// where the bubble will end up when the animation ends
final float targetX = currentWidth - expandedWidth + expandedX;
bv.setTranslationX(widthState * (targetX - collapsedX) + collapsedX);
- // if we're fully expanded, set the z level to 0
+ // if we're fully expanded, set the z level to 0 or to bubble elevation if dragged
if (widthState == 1f) {
- bv.setZ(0);
+ bv.setZ(bv == mDraggedBubbleView ? mBubbleElevation : 0);
}
- bv.showBadge();
+ // When we're expanded, we're not stacked so we're not behind the stack
+ bv.setBehindStack(false, animate);
} else {
final float targetX = currentWidth - collapsedWidth + collapsedX;
bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
- if (i > 0) {
- bv.hideBadge();
- } else {
- bv.showBadge();
- }
+ // If we're not the first bubble we're behind the stack
+ bv.setBehindStack(i > 0, animate);
}
}
@@ -324,6 +361,14 @@
}
/**
+ * Sets the dragged bubble view to correctly apply Z order. Dragged view should appear on top
+ */
+ public void setDraggedBubble(@Nullable BubbleView view) {
+ mDraggedBubbleView = view;
+ requestLayout();
+ }
+
+ /**
* Update the arrow position to match the selected bubble.
*
* @param shouldAnimate whether or not to animate the arrow. If the bar was just expanded, this
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 4e9f88a..2e3c701 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -24,6 +24,8 @@
import android.view.View;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.taskbar.TaskbarActivityContext;
@@ -54,6 +56,7 @@
// Initialized in init.
private BubbleStashController mBubbleStashController;
private BubbleBarController mBubbleBarController;
+ private BubbleDragController mBubbleDragController;
private TaskbarStashController mTaskbarStashController;
private TaskbarInsetsController mTaskbarInsetsController;
private View.OnClickListener mBubbleClickListener;
@@ -85,6 +88,7 @@
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleBarController = bubbleControllers.bubbleBarController;
+ mBubbleDragController = bubbleControllers.bubbleDragController;
mTaskbarStashController = controllers.taskbarStashController;
mTaskbarInsetsController = controllers.taskbarInsetsController;
@@ -95,6 +99,7 @@
mBubbleBarScale.updateValue(1f);
mBubbleClickListener = v -> onBubbleClicked(v);
mBubbleBarClickListener = v -> setExpanded(true);
+ mBubbleDragController.setupBubbleBarView(mBarView);
mBarView.setOnClickListener(mBubbleBarClickListener);
mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
@@ -112,9 +117,7 @@
setExpanded(false);
mBubbleStashController.stashBubbleBar();
} else {
- mBubbleBarController.setSelectedBubble(bubble);
- mSystemUiProxy.showBubble(bubble.getKey(),
- mBubbleStashController.isBubblesShowingOnHome());
+ mBubbleBarController.showAndSelectBubble(bubble);
}
}
@@ -258,6 +261,7 @@
if (b != null) {
mBarView.addView(b.getView(), 0, new FrameLayout.LayoutParams(mIconSize, mIconSize));
b.getView().setOnClickListener(mBubbleClickListener);
+ mBubbleDragController.setupBubbleView(b.getView());
} else {
Log.w(TAG, "addBubble, bubble was null!");
}
@@ -291,13 +295,7 @@
if (!isExpanded) {
mSystemUiProxy.collapseBubbles();
} else {
- final String selectedKey = mBubbleBarController.getSelectedBubbleKey();
- if (selectedKey != null) {
- mSystemUiProxy.showBubble(selectedKey,
- mBubbleStashController.isBubblesShowingOnHome());
- } else {
- Log.w(TAG, "trying to expand bubbles when there isn't one selected");
- }
+ mBubbleBarController.showSelectedBubble();
mTaskbarStashController.updateAndAnimateTransientTaskbar(true /* stash */,
false /* shouldBubblesFollow */);
}
@@ -315,4 +313,41 @@
mBubbleStashController.showBubbleBar(true /* expand the bubbles */);
}
}
+
+ /**
+ * Updates the dragged bubble view in the bubble bar view, and notifies SystemUI
+ * to collapse the selected bubble while it's dragged.
+ * @param bubbleView dragged bubble view
+ */
+ public void onDragStart(@NonNull BubbleView bubbleView) {
+ mBarView.setDraggedBubble(bubbleView);
+ if (bubbleView.getBubble() == null) return;
+ mSystemUiProxy.collapseWhileDragging(bubbleView.getBubble().getKey(), true /* collapse */);
+ }
+
+ /**
+ * Removes the dragged bubble view in the bubble bar view, and notifies SystemUI
+ * to expand the selected bubble when dragging finished.
+ * @param bubbleView dragged bubble view
+ */
+ public void onDragEnd(@NonNull BubbleView bubbleView) {
+ mBarView.setDraggedBubble(null);
+ if (bubbleView.getBubble() == null) return;
+ mSystemUiProxy.collapseWhileDragging(bubbleView.getBubble().getKey(), false /* collapse */);
+ }
+
+ /**
+ * Called when bubble was dragged into the dismiss target. Notifies System
+ * @param bubble dismissed bubble item
+ */
+ public void onDismissBubbleWhileDragging(@NonNull BubbleBarItem bubble) {
+ mSystemUiProxy.removeBubble(bubble.getKey());
+ }
+
+ /**
+ * Called when bubble stack was dragged into the dismiss target
+ */
+ public void onDismissAllBubblesWhileDragging() {
+ mSystemUiProxy.removeAllBubbles();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index 6417f3c..c47427d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -27,6 +27,8 @@
public final BubbleBarViewController bubbleBarViewController;
public final BubbleStashController bubbleStashController;
public final BubbleStashedHandleViewController bubbleStashedHandleViewController;
+ public final BubbleDragController bubbleDragController;
+ public final BubbleDismissController bubbleDismissController;
private final RunnableList mPostInitRunnables = new RunnableList();
@@ -39,11 +41,15 @@
BubbleBarController bubbleBarController,
BubbleBarViewController bubbleBarViewController,
BubbleStashController bubbleStashController,
- BubbleStashedHandleViewController bubbleStashedHandleViewController) {
+ BubbleStashedHandleViewController bubbleStashedHandleViewController,
+ BubbleDragController bubbleDragController,
+ BubbleDismissController bubbleDismissController) {
this.bubbleBarController = bubbleBarController;
this.bubbleBarViewController = bubbleBarViewController;
this.bubbleStashController = bubbleStashController;
this.bubbleStashedHandleViewController = bubbleStashedHandleViewController;
+ this.bubbleDragController = bubbleDragController;
+ this.bubbleDismissController = bubbleDismissController;
}
/**
@@ -56,6 +62,8 @@
bubbleBarViewController.init(taskbarControllers, this);
bubbleStashedHandleViewController.init(taskbarControllers, this);
bubbleStashController.init(taskbarControllers, this);
+ bubbleDragController.init(/* bubbleControllers = */ this);
+ bubbleDismissController.init(/* bubbleControllers = */ this);
mPostInitRunnables.executeAllAndDestroy();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
new file mode 100644
index 0000000..6063376
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar.bubbles;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.res.Resources;
+import android.os.SystemProperties;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+import com.android.launcher3.R;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarDragLayer;
+import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+
+/**
+ * Controls dismiss view presentation for the bubble bar dismiss functionality.
+ * Provides the dragged view snapping to the target dismiss area and animates it.
+ * When the dragged bubble/bubble stack is realised inside of the target area, it gets dismissed.
+ *
+ * @see BubbleDragController
+ */
+public class BubbleDismissController {
+ private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
+ public static final boolean ENABLE_FLING_TO_DISMISS_BUBBLE =
+ SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_bubble", true);
+ private final TaskbarActivityContext mActivity;
+ private final TaskbarDragLayer mDragLayer;
+ @Nullable
+ private BubbleBarViewController mBubbleBarViewController;
+
+ // Dismiss view that's attached to drag layer. It consists of the scrim view and the circular
+ // dismiss view used as a dismiss target.
+ @Nullable
+ private DismissView mDismissView;
+
+ // The currently magnetized object, which is being dragged and will be attracted to the magnetic
+ // dismiss target. This is either the stack itself, or an individual bubble.
+ @Nullable
+ private MagnetizedObject<View> mMagnetizedObject;
+
+ // The MagneticTarget instance for our circular dismiss view. This is added to the
+ // MagnetizedObject instances for the stack and any dragged-out bubbles.
+ @Nullable
+ private MagnetizedObject.MagneticTarget mMagneticTarget;
+ @Nullable
+ private ValueAnimator mDismissAnimator;
+
+ public BubbleDismissController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
+ mActivity = activity;
+ mDragLayer = dragLayer;
+ }
+
+ /**
+ * Initializes dependencies when bubble controllers are created.
+ * Should be careful to only access things that were created in constructors for now, as some
+ * controllers may still be waiting for init().
+ */
+ public void init(@NonNull BubbleControllers bubbleControllers) {
+ mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
+ }
+
+ /**
+ * Setup the dismiss view and magnetized object that will be attracted to magnetic target.
+ * Should be called before handling events or showing/hiding dismiss view.
+ * @param magnetizedView the view to be pulled into target dismiss area
+ */
+ public void setupDismissView(@NonNull View magnetizedView) {
+ setupDismissView();
+ setupMagnetizedObject(magnetizedView);
+ }
+
+ /**
+ * Handle the touch event and pass it to the magnetized object.
+ * It should be called after {@code setupDismissView}
+ */
+ public boolean handleTouchEvent(@NonNull MotionEvent event) {
+ return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
+ }
+
+ /**
+ * Show dismiss view with animation
+ * It should be called after {@code setupDismissView}
+ */
+ public void showDismissView() {
+ if (mDismissView == null) return;
+ mDismissView.show();
+ }
+
+ /**
+ * Hide dismiss view with animation
+ * It should be called after {@code setupDismissView}
+ */
+ public void hideDismissView() {
+ if (mDismissView == null) return;
+ mDismissView.hide();
+ }
+
+ /**
+ * Dismiss magnetized object when it's released in the dismiss target area
+ */
+ private void dismissMagnetizedObject() {
+ if (mMagnetizedObject == null || mBubbleBarViewController == null) return;
+ if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleView) {
+ BubbleView bubbleView = (BubbleView) mMagnetizedObject.getUnderlyingObject();
+ if (bubbleView.getBubble() != null) {
+ mBubbleBarViewController.onDismissBubbleWhileDragging(bubbleView.getBubble());
+ }
+ } else if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleBarView) {
+ mBubbleBarViewController.onDismissAllBubblesWhileDragging();
+ }
+ cleanUpAnimatedViews();
+ }
+
+ /**
+ * Animate dismiss view when magnetized object gets stuck in the magnetic target
+ * @param view captured view
+ */
+ private void animateDismissCaptured(@NonNull View view) {
+ cancelAnimations();
+ mDismissAnimator = createDismissAnimator(view);
+ mDismissAnimator.start();
+ }
+
+ /**
+ * Animate dismiss view when magnetized object gets unstuck from the magnetic target
+ */
+ private void animateDismissReleased() {
+ if (mDismissAnimator == null) return;
+ mDismissAnimator.removeAllListeners();
+ mDismissAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cancelAnimations();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ cancelAnimations();
+ }
+ });
+ mDismissAnimator.reverse();
+ }
+
+ /**
+ * Cancel and clear dismiss animations and reset view states
+ */
+ private void cancelAnimations() {
+ if (mDismissAnimator == null) return;
+ ValueAnimator animator = mDismissAnimator;
+ mDismissAnimator = null;
+ animator.cancel();
+ }
+
+ /**
+ * Clean up views changed during animation
+ */
+ private void cleanUpAnimatedViews() {
+ // Cancel animations
+ cancelAnimations();
+ // Reset dismiss view
+ if (mDismissView != null) {
+ mDismissView.getCircle().setScaleX(1f);
+ mDismissView.getCircle().setScaleY(1f);
+ }
+ // Reset magnetized view
+ if (mMagnetizedObject != null) {
+ mMagnetizedObject.getUnderlyingObject().setAlpha(1f);
+ mMagnetizedObject.getUnderlyingObject().setScaleX(1f);
+ mMagnetizedObject.getUnderlyingObject().setScaleY(1f);
+ }
+ }
+
+ private void setupDismissView() {
+ if (mDismissView != null) return;
+ mDismissView = new DismissView(mActivity.getApplicationContext());
+ BubbleDismissViewUtils.setup(mDismissView);
+ mDragLayer.addView(mDismissView, /* index = */ 0,
+ new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ setupMagneticTarget(mDismissView.getCircle());
+ }
+
+ private void setupMagneticTarget(@NonNull View view) {
+ int magneticFieldRadius = mActivity.getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_dismiss_target_size);
+ mMagneticTarget = new MagnetizedObject.MagneticTarget(view, magneticFieldRadius);
+ }
+
+ private void setupMagnetizedObject(@NonNull View magnetizedView) {
+ mMagnetizedObject = new MagnetizedObject<>(mActivity.getApplicationContext(),
+ magnetizedView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) {
+ @Override
+ public float getWidth(@NonNull View underlyingObject) {
+ return underlyingObject.getWidth();
+ }
+
+ @Override
+ public float getHeight(@NonNull View underlyingObject) {
+ return underlyingObject.getHeight();
+ }
+
+ @Override
+ public void getLocationOnScreen(@NonNull View underlyingObject, @NonNull int[] loc) {
+ underlyingObject.getLocationOnScreen(loc);
+ }
+ };
+
+ mMagnetizedObject.setHapticsEnabled(true);
+ mMagnetizedObject.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_BUBBLE);
+ mMagnetizedObject.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+ if (mMagneticTarget != null) {
+ mMagnetizedObject.addTarget(mMagneticTarget);
+ }
+ mMagnetizedObject.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ animateDismissCaptured(magnetizedView);
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velX, float velY, boolean wasFlungOut) {
+ animateDismissReleased();
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ dismissMagnetizedObject();
+ }
+ });
+ }
+
+ private ValueAnimator createDismissAnimator(@NonNull View magnetizedView) {
+ Resources resources = mActivity.getResources();
+ int expandedSize = resources.getDimensionPixelSize(R.dimen.bubblebar_dismiss_target_size);
+ int collapsedSize = resources.getDimensionPixelSize(
+ R.dimen.bubblebar_dismiss_target_small_size);
+ float minScale = (float) collapsedSize / expandedSize;
+ ValueAnimator animator = ValueAnimator.ofFloat(1f, minScale);
+ animator.addUpdateListener(animation -> {
+ if (mDismissView == null) return;
+ final float animatedValue = (float) animation.getAnimatedValue();
+ mDismissView.getCircle().setScaleX(animatedValue);
+ mDismissView.getCircle().setScaleY(animatedValue);
+ magnetizedView.setAlpha(animatedValue);
+ if (magnetizedView instanceof BubbleBarView) {
+ magnetizedView.setScaleX(animatedValue);
+ magnetizedView.setScaleY(animatedValue);
+ }
+ });
+ return animator;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt
new file mode 100644
index 0000000..4b235a9
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+@file:JvmName("BubbleDismissViewUtils")
+
+package com.android.launcher3.taskbar.bubbles
+
+import com.android.launcher3.R
+import com.android.wm.shell.common.bubbles.DismissView
+
+/**
+ * Dismiss view is shared from WMShell. It requires setup with local resources.
+ *
+ * Usage:
+ * - Kotlin `dismissView.setup()`
+ * - Java `BubbleDismissViewUtils.setup(dismissView)`
+ */
+fun DismissView.setup() {
+ setup(
+ DismissView.Config(
+ targetSizeResId = R.dimen.bubblebar_dismiss_target_size,
+ iconSizeResId = R.dimen.bubblebar_dismiss_target_icon_size,
+ bottomMarginResId = R.dimen.bubblebar_dismiss_target_bottom_margin,
+ floatingGradientHeightResId = R.dimen.bubblebar_dismiss_floating_gradient_height,
+ floatingGradientColorResId = android.R.color.system_neutral1_900,
+ backgroundResId = R.drawable.bg_bubble_dismiss_circle,
+ iconResId = R.drawable.ic_bubble_dismiss_white
+ )
+ )
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
new file mode 100644
index 0000000..28dc62c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar.bubbles;
+
+import android.annotation.SuppressLint;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.wm.shell.common.bubbles.RelativeTouchListener;
+
+/**
+ * Controls bubble bar drag to dismiss interaction.
+ * Interacts with {@link BubbleDismissController}, used by {@link BubbleBarViewController}.
+ * Supported interactions:
+ * - Drag a single bubble view into dismiss target to remove it.
+ * - Drag the bubble stack into dismiss target to remove all.
+ * Restores initial position of dragged view if released outside of the dismiss target.
+ */
+public class BubbleDragController {
+ private final TaskbarActivityContext mActivity;
+ private BubbleBarViewController mBubbleBarViewController;
+ private BubbleDismissController mBubbleDismissController;
+
+ public BubbleDragController(TaskbarActivityContext activity) {
+ mActivity = activity;
+ }
+
+ /**
+ * Initializes dependencies when bubble controllers are created.
+ * Should be careful to only access things that were created in constructors for now, as some
+ * controllers may still be waiting for init().
+ */
+ public void init(@NonNull BubbleControllers bubbleControllers) {
+ mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
+ mBubbleDismissController = bubbleControllers.bubbleDismissController;
+ }
+
+ /**
+ * Setup the bubble view for dragging and attach touch listener to it
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ public void setupBubbleView(@NonNull BubbleView bubbleView) {
+ // Don't setup dragging for overflow bubble view
+ if (bubbleView.getBubble() == null
+ || !(bubbleView.getBubble() instanceof BubbleBarBubble)) return;
+ bubbleView.setOnTouchListener(new BaseDragListener() {
+ @Override
+ protected void onDragStart() {
+ super.onDragStart();
+ mBubbleBarViewController.onDragStart(bubbleView);
+ }
+
+ @Override
+ protected void onDragEnd() {
+ super.onDragEnd();
+ mBubbleBarViewController.onDragEnd(bubbleView);
+ }
+ });
+ }
+
+ /**
+ * Setup the bubble bar view for dragging and attach touch listener to it
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ public void setupBubbleBarView(@NonNull BubbleBarView bubbleBarView) {
+ PointF initialRelativePivot = new PointF();
+ bubbleBarView.setOnTouchListener(new BaseDragListener() {
+ @Override
+ public boolean onDown(@NonNull View view, @NonNull MotionEvent event) {
+ if (bubbleBarView.isExpanded()) return false;
+ // Setup dragging only when bubble bar is collapsed
+ return super.onDown(view, event);
+ }
+
+ @Override
+ protected void onDragStart() {
+ super.onDragStart();
+ initialRelativePivot.set(bubbleBarView.getRelativePivotX(),
+ bubbleBarView.getRelativePivotY());
+ bubbleBarView.setRelativePivot(/* x = */ 0.5f, /* y = */ 0.5f);
+ }
+
+ @Override
+ protected void onDragEnd() {
+ super.onDragEnd();
+ bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
+ }
+ });
+ }
+
+ /**
+ * Base drag listener for handling a single bubble view or bubble bar view dragging.
+ * Controls dragging interaction and interacts with {@link BubbleDismissController}
+ * to coordinate dismiss view presentation.
+ * Lifecycle methods can be overridden do add extra setup/clean up steps
+ */
+ private class BaseDragListener extends RelativeTouchListener {
+ private boolean mHandling;
+ private boolean mDragging;
+
+ @Override
+ public boolean onDown(@NonNull View view, @NonNull MotionEvent event) {
+ mHandling = true;
+ mActivity.setTaskbarWindowFullscreen(true);
+ mBubbleDismissController.setupDismissView(view);
+ mBubbleDismissController.handleTouchEvent(event);
+ return true;
+ }
+
+ @Override
+ public void onMove(@NonNull View view, @NonNull MotionEvent event, float viewInitialX,
+ float viewInitialY, float dx, float dy) {
+ if (!mHandling) return;
+ if (!mDragging) {
+ // Start dragging
+ mDragging = true;
+ onDragStart();
+ }
+ if (!mBubbleDismissController.handleTouchEvent(event)) {
+ // Drag the view if not processed by dismiss controller
+ view.setTranslationX(viewInitialX + dx);
+ view.setTranslationY(viewInitialY + dy);
+ }
+ }
+
+ @Override
+ public void onUp(@NonNull View view, @NonNull MotionEvent event, float viewInitialX,
+ float viewInitialY, float dx, float dy, float velX, float velY) {
+ onComplete(view, event, viewInitialX, viewInitialY);
+ }
+
+ @Override
+ public void onCancel(@NonNull View view, @NonNull MotionEvent event, float viewInitialX,
+ float viewInitialY, float dx, float dy) {
+ onComplete(view, event, viewInitialX, viewInitialY);
+ }
+
+ /**
+ * Prepares dismiss view for dragging.
+ * This method can be overridden to add extra setup on drag start
+ */
+ protected void onDragStart() {
+ mBubbleDismissController.showDismissView();
+ }
+
+ /**
+ * Cleans up dismiss view after dragging.
+ * This method can be overridden to add extra setup on drag end
+ */
+ protected void onDragEnd() {
+ mBubbleDismissController.hideDismissView();
+ }
+
+ /**
+ * Complete drag handling and clean up dependencies
+ */
+ private void onComplete(@NonNull View view, @NonNull MotionEvent event,
+ float viewInitialX, float viewInitialY) {
+ if (!mHandling) return;
+ if (mDragging) {
+ // Stop dragging
+ mDragging = false;
+ view.setTranslationX(viewInitialX);
+ view.setTranslationY(viewInitialY);
+ onDragEnd();
+ }
+ mBubbleDismissController.handleTouchEvent(event);
+ mActivity.setTaskbarWindowFullscreen(false);
+ mHandling = false;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index 5177d93..bd660be 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -125,6 +125,12 @@
public void setBubblesShowingOnHome(boolean onHome) {
if (mBubblesShowingOnHome != onHome) {
mBubblesShowingOnHome = onHome;
+
+ if (!mBarViewController.isBubbleBarVisible()) {
+ // if the bubble bar is not visible, there are no bubbles, so just return.
+ return;
+ }
+
if (mBubblesShowingOnHome) {
showBubbleBar(/* expanded= */ false);
// When transitioning from app to home the stash animator may already have been
@@ -309,4 +315,9 @@
return -hotseatBottomSpace - hotseatCellHeight + mUnstashedHeight - abs(
hotseatCellHeight - mUnstashedHeight) / 2;
}
+
+ float getBubbleBarTranslationY() {
+ return mBubblesShowingOnHome ? getBubbleBarTranslationYForHotseat()
+ : getBubbleBarTranslationYForTaskbar();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 92b76a6..12cb8c5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -18,7 +18,9 @@
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.Outline;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -28,10 +30,13 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.launcher3.R;
+import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
+import com.android.wm.shell.animation.Interpolators;
+
+import java.util.EnumSet;
// TODO: (b/276978250) This is will be similar to WMShell's BadgedImageView, it'd be nice to share.
-// TODO: (b/269670235) currently this doesn't show the 'update dot'
/**
* View that displays a bubble icon, along with an app badge on either the left or
@@ -39,14 +44,42 @@
*/
public class BubbleView extends ConstraintLayout {
- // TODO: (b/269670235) currently we don't render the 'update dot', this will be used for that.
public static final int DEFAULT_PATH_SIZE = 100;
+ /**
+ * Flags that suppress the visibility of the 'new' dot or the app badge, for one reason or
+ * another. If any of these flags are set, the dot will not be shown.
+ * If {@link SuppressionFlag#BEHIND_STACK} then the app badge will not be shown.
+ */
+ enum SuppressionFlag {
+ // TODO: (b/277815200) implement flyout
+ // Suppressed because the flyout is visible - it will morph into the dot via animation.
+ FLYOUT_VISIBLE,
+ // Suppressed because this bubble is behind others in the collapsed stack.
+ BEHIND_STACK,
+ }
+
+ private final EnumSet<SuppressionFlag> mSuppressionFlags =
+ EnumSet.noneOf(SuppressionFlag.class);
+
private final ImageView mBubbleIcon;
private final ImageView mAppIcon;
private final int mBubbleSize;
+ private DotRenderer mDotRenderer;
+ private DotRenderer.DrawParams mDrawParams;
+ private int mDotColor;
+ private Rect mTempBounds = new Rect();
+
+ // Whether the dot is animating
+ private boolean mDotIsAnimating;
+ // What scale value the dot is animating to
+ private float mAnimatingToDotScale;
+ // The current scale value of the dot
+ private float mDotScale;
+
// TODO: (b/273310265) handle RTL
+ // Whether the bubbles are positioned on the left or right side of the screen
private boolean mOnLeft = false;
private BubbleBarItem mBubble;
@@ -75,6 +108,8 @@
mBubbleIcon = findViewById(R.id.icon_view);
mAppIcon = findViewById(R.id.app_icon_view);
+ mDrawParams = new DotRenderer.DrawParams();
+
setFocusable(true);
setClickable(true);
setOutlineProvider(new ViewOutlineProvider() {
@@ -91,17 +126,43 @@
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
}
+ @Override
+ public void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ if (!shouldDrawDot()) {
+ return;
+ }
+
+ getDrawingRect(mTempBounds);
+
+ mDrawParams.dotColor = mDotColor;
+ mDrawParams.iconBounds = mTempBounds;
+ mDrawParams.leftAlign = mOnLeft;
+ mDrawParams.scale = mDotScale;
+
+ mDotRenderer.draw(canvas, mDrawParams);
+ }
+
/** Sets the bubble being rendered in this view. */
void setBubble(BubbleBarBubble bubble) {
mBubble = bubble;
mBubbleIcon.setImageBitmap(bubble.getIcon());
mAppIcon.setImageBitmap(bubble.getBadge());
+ mDotColor = bubble.getDotColor();
+ mDotRenderer = new DotRenderer(mBubbleSize, bubble.getDotPath(), DEFAULT_PATH_SIZE);
}
+ /**
+ * Sets that this bubble represents the overflow. The overflow appears in the list of bubbles
+ * but does not represent app content, instead it shows recent bubbles that couldn't fit into
+ * the list of bubbles. It doesn't show an app icon because it is part of system UI / doesn't
+ * come from an app.
+ */
void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
mBubble = overflow;
mBubbleIcon.setImageBitmap(bitmap);
- hideBadge();
+ mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
}
/** Returns the bubble being rendered in this view. */
@@ -110,38 +171,102 @@
return mBubble;
}
- /** Shows the app badge on this bubble. */
- void showBadge() {
+ void updateDotVisibility(boolean animate) {
+ final float targetScale = shouldDrawDot() ? 1f : 0f;
+ if (animate) {
+ animateDotScale();
+ } else {
+ mDotScale = targetScale;
+ mAnimatingToDotScale = targetScale;
+ invalidate();
+ }
+ }
+
+ void updateBadgeVisibility() {
if (mBubble instanceof BubbleBarOverflow) {
// The overflow bubble does not have a badge, so just bail.
return;
}
BubbleBarBubble bubble = (BubbleBarBubble) mBubble;
-
Bitmap appBadgeBitmap = bubble.getBadge();
- if (appBadgeBitmap == null) {
- mAppIcon.setVisibility(GONE);
+ int translationX = mOnLeft
+ ? -(bubble.getIcon().getWidth() - appBadgeBitmap.getWidth())
+ : 0;
+ mAppIcon.setTranslationX(translationX);
+ mAppIcon.setVisibility(isBehindStack() ? GONE : VISIBLE);
+ }
+
+ /** Sets whether this bubble is in the stack & not the first bubble. **/
+ void setBehindStack(boolean behindStack, boolean animate) {
+ if (behindStack) {
+ mSuppressionFlags.add(SuppressionFlag.BEHIND_STACK);
+ } else {
+ mSuppressionFlags.remove(SuppressionFlag.BEHIND_STACK);
+ }
+ updateDotVisibility(animate);
+ updateBadgeVisibility();
+ }
+
+ /** Whether this bubble is in the stack & not the first bubble. **/
+ boolean isBehindStack() {
+ return mSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK);
+ }
+
+ /** Whether the dot indicating unseen content in a bubble should be shown. */
+ private boolean shouldDrawDot() {
+ boolean bubbleHasUnseenContent = mBubble != null
+ && mBubble instanceof BubbleBarBubble
+ && mSuppressionFlags.isEmpty()
+ && !((BubbleBarBubble) mBubble).getInfo().isNotificationSuppressed();
+
+ // Always render the dot if it's animating, since it could be animating out. Otherwise, show
+ // it if the bubble wants to show it, and we aren't suppressing it.
+ return bubbleHasUnseenContent || mDotIsAnimating;
+ }
+
+ /** How big the dot should be, fraction from 0 to 1. */
+ private void setDotScale(float fraction) {
+ mDotScale = fraction;
+ invalidate();
+ }
+
+ /**
+ * Animates the dot to the given scale.
+ */
+ private void animateDotScale() {
+ float toScale = shouldDrawDot() ? 1f : 0f;
+ mDotIsAnimating = true;
+
+ // Don't restart the animation if we're already animating to the given value.
+ if (mAnimatingToDotScale == toScale || !shouldDrawDot()) {
+ mDotIsAnimating = false;
return;
}
- int translationX;
- if (mOnLeft) {
- translationX = -(bubble.getIcon().getWidth() - appBadgeBitmap.getWidth());
- } else {
- translationX = 0;
- }
+ mAnimatingToDotScale = toScale;
- mAppIcon.setTranslationX(translationX);
- mAppIcon.setVisibility(VISIBLE);
+ final boolean showDot = toScale > 0f;
+
+ // Do NOT wait until after animation ends to setShowDot
+ // to avoid overriding more recent showDot states.
+ clearAnimation();
+ animate()
+ .setDuration(200)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setUpdateListener((valueAnimator) -> {
+ float fraction = valueAnimator.getAnimatedFraction();
+ fraction = showDot ? fraction : 1f - fraction;
+ setDotScale(fraction);
+ }).withEndAction(() -> {
+ setDotScale(showDot ? 1f : 0f);
+ mDotIsAnimating = false;
+ }).start();
}
- /** Hides the app badge on this bubble. */
- void hideBadge() {
- mAppIcon.setVisibility(GONE);
- }
@Override
public String toString() {
- return "BubbleView{" + mBubble + "}";
+ String toString = mBubble != null ? mBubble.getKey() : "null";
+ return "BubbleView{" + toString + "}";
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 9fcadea..d78ca88 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -111,7 +111,7 @@
&& !toState.overviewUi;
if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
- .animateAwayPlaceholder(mLauncher));
+ .createPlaceholderDismissAnim(mLauncher));
setter.setViewAlpha(
mRecentsView.getSplitInstructionsView(),
0,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 3b53e8a..7ce87a3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -549,7 +549,7 @@
list.add(getDragController());
Consumer<AnimatorSet> splitAnimator = animatorSet -> {
AnimatorSet anim = mSplitSelectStateController.getSplitAnimationController()
- .animateAwayPlaceholder(QuickstepLauncher.this);
+ .createPlaceholderDismissAnim(QuickstepLauncher.this);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -1000,6 +1000,13 @@
return mSplitToWorkspaceController;
}
+ @Override
+ protected void handleSplitAnimationGoingToHome() {
+ super.handleSplitAnimationGoingToHome();
+ mSplitSelectStateController.getSplitAnimationController()
+ .playPlaceholderDismissAnim(this);
+ }
+
public <T extends OverviewActionsView> T getActionsView() {
return (T) mActionsView;
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 1de264a..3af9d5c 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -646,13 +646,15 @@
/**
* Tells SysUI to show the bubble with the provided key.
* @param key the key of the bubble to show.
- * @param onLauncherHome whether the bubble is showing on launcher home or not (modifies where
- * the expanded bubble view is placed).
+ * @param bubbleBarOffsetX the offset of the bubble bar from the edge of the screen on the X
+ * axis.
+ * @param bubbleBarOffsetY the offset of the bubble bar from the edge of the screen on the Y
+ * axis.
*/
- public void showBubble(String key, boolean onLauncherHome) {
+ public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
if (mBubbles != null) {
try {
- mBubbles.showBubble(key, onLauncherHome);
+ mBubbles.showBubble(key, bubbleBarOffsetX, bubbleBarOffsetY);
} catch (RemoteException e) {
Log.w(TAG, "Failed call showBubble");
}
@@ -660,6 +662,31 @@
}
/**
+ * Tells SysUI to remove the bubble with the provided key.
+ * @param key the key of the bubble to show.
+ */
+ public void removeBubble(String key) {
+ if (mBubbles == null) return;
+ try {
+ mBubbles.removeBubble(key);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call removeBubble");
+ }
+ }
+
+ /**
+ * Tells SysUI to remove all bubbles.
+ */
+ public void removeAllBubbles() {
+ if (mBubbles == null) return;
+ try {
+ mBubbles.removeAllBubbles();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call removeAllBubbles");
+ }
+ }
+
+ /**
* Tells SysUI to collapse the bubbles.
*/
public void collapseBubbles() {
@@ -672,6 +699,21 @@
}
}
+ /**
+ * Tells SysUI to collapse/expand selected bubble view while it's dragged.
+ * Should be called only when the bubble bar is expanded.
+ * @param bubbleKey the key of the bubble to collapse/expand
+ * @param collapse whether to collapse/expand selected bubble
+ */
+ public void collapseWhileDragging(@Nullable String bubbleKey, boolean collapse) {
+ if (mBubbles == null) return;
+ try {
+ mBubbles.collapseWhileDragging(bubbleKey, collapse);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call collapseWhileDragging");
+ }
+ }
+
//
// Splitscreen
//
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 2e8af4c..5740991 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -17,6 +17,8 @@
package com.android.quickstep.util
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.graphics.Bitmap
@@ -185,17 +187,32 @@
}
}
+ /** Does not play any animation if user is not currently in split selection state. */
+ fun playPlaceholderDismissAnim(launcher: Launcher) {
+ if (!splitSelectStateController.isSplitSelectActive) {
+ return
+ }
- fun animateAwayPlaceholder(mLauncher: Launcher) : AnimatorSet {
+ val anim = createPlaceholderDismissAnim(launcher)
+ anim.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ splitSelectStateController.resetState()
+ }
+ })
+ anim.start()
+ }
+
+ /** Returns [AnimatorSet] which slides initial split placeholder view offscreen. */
+ fun createPlaceholderDismissAnim(launcher: Launcher) : AnimatorSet {
val animatorSet = AnimatorSet()
- val recentsView : RecentsView<*, *> = mLauncher.getOverviewPanel()
+ val recentsView : RecentsView<*, *> = launcher.getOverviewPanel()
val floatingTask: FloatingTaskView = splitSelectStateController.firstFloatingTaskView
?: return animatorSet
// We are in split selection state currently, transitioning to another state
- val dragLayer: DragLayer = mLauncher.dragLayer
+ val dragLayer: DragLayer = launcher.dragLayer
val onScreenRectF = RectF()
- Utilities.getBoundsForViewInDragLayer(mLauncher.dragLayer, floatingTask,
+ Utilities.getBoundsForViewInDragLayer(launcher.dragLayer, floatingTask,
Rect(0, 0, floatingTask.width, floatingTask.height),
false, null, onScreenRectF)
// Get the part of the floatingTask that intersects with the DragLayer (i.e. the
@@ -214,7 +231,7 @@
floatingTask,
onScreenRectF,
floatingTask.stagePosition,
- mLauncher.deviceProfile
+ launcher.deviceProfile
)))
return animatorSet
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
index 24d8326..f3fa86a 100644
--- a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
@@ -27,21 +27,15 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.ActivityManager;
-import android.content.Intent;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.SystemClock;
-import android.os.UserHandle;
-import android.view.View;
import androidx.annotation.BinderThread;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationController;
@@ -143,11 +137,7 @@
.updateIconInBackground(
Task.from(new Task.TaskKey(runningTaskInfo), runningTaskInfo,
false /* isLocked */),
- (task) -> {
- if (task.thumbnail != null) {
- floatingTaskView.setIcon(task.thumbnail.thumbnail);
- }
- });
+ (task) -> floatingTaskView.setIcon(task.icon));
floatingTaskView.setAlpha(1);
floatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
false /* fadeWithThumbnail */, true /* isStagedTask */);
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index a5652dc..f250b8c 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -11,7 +11,6 @@
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.FloatProperty;
@@ -213,8 +212,8 @@
mSplitPlaceholderView.getIconView().setRotation(mOrientationHandler.getDegreesRotated());
}
- public void setIcon(Bitmap icon) {
- mSplitPlaceholderView.setIcon(new BitmapDrawable(icon), mSplitHolderSize);
+ public void setIcon(Drawable drawable) {
+ mSplitPlaceholderView.setIcon(drawable, mSplitHolderSize);
}
protected void initPosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 3ee9009..828d466 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -86,6 +86,10 @@
StateManager stateManager = mActivity.getStateManager();
animated &= stateManager.shouldAnimateStateChange();
stateManager.goToState(NORMAL, animated);
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ mSplitSelectStateController.getSplitAnimationController()
+ .playPlaceholderDismissAnim(mActivity);
+ }
AbstractFloatingView.closeAllOpenViews(mActivity, animated);
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 849db28..9efab36 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -424,6 +424,7 @@
@Override
@TargetApi(Build.VERSION_CODES.S)
protected void onCreate(Bundle savedInstanceState) {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onCreate 1");
mStartupLatencyLogger = createStartupLatencyLogger(
sIsNewProcess
? LockedUserState.get(this).isUserUnlockedAtLauncherStartup()
@@ -582,6 +583,7 @@
}
setTitle(R.string.home_screen);
mStartupLatencyLogger.logEnd(LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE);
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onCreate 2");
}
/**
@@ -1056,6 +1058,7 @@
@Override
protected void onStop() {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onStop 1");
super.onStop();
if (mDeferOverlayCallbacks) {
checkIfOverlayStillDeferred();
@@ -1067,10 +1070,12 @@
mAppWidgetHolder.setActivityStarted(false);
NotificationListener.removeNotificationsChangedListener(getPopupDataProvider());
FloatingIconView.resetIconLoadResult();
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onStop 2");
}
@Override
protected void onStart() {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onStart 1");
TraceHelper.INSTANCE.beginSection(ON_START_EVT);
super.onStart();
if (!mDeferOverlayCallbacks) {
@@ -1079,6 +1084,7 @@
mAppWidgetHolder.setActivityStarted(true);
TraceHelper.INSTANCE.endSection();
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onStart 2");
}
@Override
@@ -1249,6 +1255,7 @@
@Override
protected void onResume() {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onResume 1");
TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT);
super.onResume();
@@ -1260,10 +1267,12 @@
DragView.removeAllViews(this);
TraceHelper.INSTANCE.endSection();
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onResume 2");
}
@Override
protected void onPause() {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onPause 1");
// Ensure that items added to Launcher are queued until Launcher returns
ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_ACTIVITY_PAUSED);
@@ -1276,6 +1285,7 @@
mOverlayManager.onActivityPaused(this);
}
mAppWidgetHolder.setActivityResumed(false);
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onPause 2");
}
/**
@@ -1683,6 +1693,9 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onHomeIntent(internalStateHandled);
}
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ handleSplitAnimationGoingToHome();
+ }
mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
handleGestureContract(intent);
} else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
@@ -1696,6 +1709,11 @@
TraceHelper.INSTANCE.endSection();
}
+ /** Handle animating away split placeholder view when user taps on home button */
+ protected void handleSplitAnimationGoingToHome() {
+ // Overridden
+ }
+
protected void toggleAllAppsFromIntent(boolean alreadyOnHome) {
if (getStateManager().isInStableState(ALL_APPS)) {
getStateManager().goToState(NORMAL, alreadyOnHome);
@@ -1740,6 +1758,8 @@
@Override
protected void onSaveInstanceState(Bundle outState) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onSaveInstanceState 1");
outState.putIntArray(RUNTIME_STATE_CURRENT_SCREEN_IDS,
mWorkspace.getCurrentPageScreenIds().getArray().toArray());
outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal);
@@ -1771,10 +1791,13 @@
super.onSaveInstanceState(outState);
mOverlayManager.onActivitySaveInstanceState(this, outState);
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onSaveInstanceState 2");
}
@Override
public void onDestroy() {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onDestroy 1");
super.onDestroy();
ACTIVITY_TRACKER.onActivityDestroyed(this);
@@ -1797,6 +1820,7 @@
LauncherAppState.getIDP(this).removeOnChangeListener(this);
mOverlayManager.onActivityDestroyed(this);
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onDestroy 2");
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -2961,7 +2985,7 @@
Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
Preconditions.assertUIThread();
boolean hadWorkApps = mAppsView.shouldShowTabs();
- AllAppsStore appsStore = mAppsView.getAppsStore();
+ AllAppsStore<Launcher> appsStore = mAppsView.getAppsStore();
appsStore.setApps(apps, flags, packageUserKeytoUidMap);
PopupContainerWithArrow.dismissInvalidPopup(this);
if (hadWorkApps != mAppsView.shouldShowTabs()) {
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index da1bcd7..9b7a05f 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -139,7 +139,7 @@
private final SearchTransitionController mSearchTransitionController;
private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Rect mInsets = new Rect();
- private final AllAppsStore mAllAppsStore;
+ private final AllAppsStore<T> mAllAppsStore;
private final RecyclerView.OnScrollListener mScrollListener =
new RecyclerView.OnScrollListener() {
@Override
@@ -192,7 +192,7 @@
public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mActivityContext = ActivityContext.lookupContext(context);
- mAllAppsStore = new AllAppsStore(mActivityContext);
+ mAllAppsStore = new AllAppsStore<>(mActivityContext);
mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
mHeaderThreshold = getResources().getDimensionPixelSize(
@@ -892,7 +892,7 @@
container.put(R.id.work_tab_state_id, state);
}
- public AllAppsStore getAppsStore() {
+ public AllAppsStore<T> getAppsStore() {
return mAllAppsStore;
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 29767bf..0657178 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -69,7 +69,7 @@
// The set of apps from the system
private final List<AppInfo> mApps = new ArrayList<>();
@Nullable
- private final AllAppsStore mAllAppsStore;
+ private final AllAppsStore<T> mAllAppsStore;
// The number of results in current adapter
private int mAccessibilityResultsCount = 0;
@@ -86,7 +86,7 @@
private int mNumAppRowsInAdapter;
private Predicate<ItemInfo> mItemFilter;
- public AlphabeticalAppsList(Context context, @Nullable AllAppsStore appsStore,
+ public AlphabeticalAppsList(Context context, @Nullable AllAppsStore<T> appsStore,
WorkProfileManager workProfileManager) {
mAllAppsStore = appsStore;
mActivityContext = ActivityContext.lookupContext(context);
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index a070284..74b7fd3 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -182,6 +182,11 @@
"ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION", DISABLED,
"Enables predictive back animation from all apps and widgets to home");
+ // TODO(Block 11): Clean up flags
+ public static final BooleanFlag ENABLE_PARAMETRIZE_REORDER = getDebugFlag(289420844,
+ "ENABLE_PARAMETRIZE_REORDER", DISABLED,
+ "Enables generating the reorder using a set of parameters");
+
// TODO(Block 12): Clean up flags
public static final BooleanFlag ENABLE_MULTI_INSTANCE = getDebugFlag(270396680,
"ENABLE_MULTI_INSTANCE", DISABLED,
@@ -385,23 +390,23 @@
"USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES", DISABLED,
"Use local overrides for search request timeout");
- // TODO(Block 31)
+ // TODO(Block 31): Clean up flags
public static final BooleanFlag ENABLE_SPLIT_LAUNCH_DATA_REFACTOR = getDebugFlag(279494325,
"ENABLE_SPLIT_LAUNCH_DATA_REFACTOR", ENABLED,
"Use refactored split launching code path");
- // TODO(Block 32): Empty block
-
+ // TODO(Block 32): Clean up flags
public static final BooleanFlag ENABLE_RESPONSIVE_WORKSPACE = getDebugFlag(241386436,
"ENABLE_RESPONSIVE_WORKSPACE", DISABLED,
"Enables new workspace grid calculations method.");
// TODO(Block 33): Clean up flags
-
public static final BooleanFlag ENABLE_ALL_APPS_RV_PREINFLATION = getDebugFlag(288161355,
"ENABLE_ALL_APPS_RV_PREINFLATION", DISABLED,
"Enables preinflating all apps icons to avoid scrolling jank.");
+ // TODO(Block 34): Empty block
+
public static class BooleanFlag {
private final boolean mCurrentValue;
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index a572a60..66001d8 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -32,6 +32,7 @@
import android.view.View;
import androidx.annotation.ColorInt;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
@@ -87,6 +88,7 @@
private final View mRoot;
private final BaseDraggingActivity mActivity;
private final boolean mHideSysUiScrim;
+ private boolean mSkipScrimAnimationForTest = false;
private boolean mAnimateScrimOnNextDraw = false;
private final AnimatedFloat mSysUiAnimMultiplier = new AnimatedFloat(this::reapplySysUiAlpha);
@@ -189,6 +191,15 @@
mBottomMaskRect.set(0, h - mBottomMaskHeight, w, h);
}
+ /**
+ * Sets whether the SysUiScrim should hide for testing.
+ */
+ @VisibleForTesting
+ public void skipScrimAnimation() {
+ mSkipScrimAnimationForTest = true;
+ reapplySysUiAlpha();
+ }
+
private void reapplySysUiAlpha() {
reapplySysUiAlphaNoInvalidate();
if (!mHideSysUiScrim) {
@@ -198,6 +209,7 @@
private void reapplySysUiAlphaNoInvalidate() {
float factor = mSysUiProgress.value * mSysUiAnimMultiplier.value;
+ if (mSkipScrimAnimationForTest) factor = 1f;
mBottomMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor));
mTopMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor));
}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index b2c64b3..073e523 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -381,7 +381,9 @@
// Draw all page indicators;
float circleGap = mCircleGap;
- float startX = (getWidth() - (mNumPages * circleGap) + mDotRadius) / 2;
+ float startX = ((float) getWidth() / 2)
+ - (mCircleGap * (((float) mNumPages - 1) / 2))
+ - mDotRadius;
float x = startX + mDotRadius;
float y = getHeight() / 2;
@@ -420,9 +422,9 @@
float startCircle = (int) mCurrentPosition;
float delta = mCurrentPosition - startCircle;
float diameter = 2 * mDotRadius;
- float startX;
-
- startX = ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2);
+ float startX = ((float) getWidth() / 2)
+ - (mCircleGap * (((float) mNumPages - 1) / 2))
+ - mDotRadius;
sTempRect.top = (getHeight() * 0.5f) - mDotRadius;
sTempRect.bottom = (getHeight() * 0.5f) + mDotRadius;
sTempRect.left = startX + (startCircle * mCircleGap);
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 08be026..e0f245f 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -673,10 +673,6 @@
}
mIsOpen = false;
- mOpenCloseAnimator = getOpenCloseAnimator(false, mCloseDuration, mCloseFadeStartDelay,
- mCloseFadeDuration, mCloseChildFadeStartDelay, mCloseChildFadeDuration,
- ACCELERATED_EASE);
-
mOpenCloseAnimator = ENABLE_MATERIAL_U_POPUP.get()
? getMaterialUOpenCloseAnimator(
false,
diff --git a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
index f03c62a..2d69bfa 100644
--- a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
+++ b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
@@ -60,13 +60,15 @@
private final OnClickListener mOnClickListener;
private final OnLongClickListener mOnLongClickListener;
private final SharedPreferences mPrefs;
- private final AllAppsStore mAllAppsList;
+ private final AllAppsStore<SecondaryDisplayLauncher> mAllAppsList;
private final AppInfoComparator mAppNameComparator;
private final Set<ComponentKey> mPinnedApps = new HashSet<>();
private final ArrayList<AppInfo> mItems = new ArrayList<>();
- public PinnedAppsAdapter(SecondaryDisplayLauncher launcher, AllAppsStore allAppsStore,
+ public PinnedAppsAdapter(
+ SecondaryDisplayLauncher launcher,
+ AllAppsStore<SecondaryDisplayLauncher> allAppsStore,
OnLongClickListener onLongClickListener) {
mLauncher = launcher;
mOnClickListener = launcher.getItemOnClickListener();
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index e751730..a10c0ad 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -302,7 +302,7 @@
public void bindAllApplications(AppInfo[] apps, int flags,
Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
Preconditions.assertUIThread();
- AllAppsStore appsStore = mAppsView.getAppsStore();
+ AllAppsStore<SecondaryDisplayLauncher> appsStore = mAppsView.getAppsStore();
appsStore.setApps(apps, flags, packageUserKeytoUidMap);
PopupContainerWithArrow.dismissInvalidPopup(this);
}
@@ -335,7 +335,7 @@
@Override
public View.OnLongClickListener getAllAppsItemLongClickListener() {
- return mDragLayer::onIconLongClicked;
+ return v -> mDragLayer.onIconLongClicked(v);
}
private void onIconClicked(View v) {
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index ad812f0..447d22b 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.touch;
+import static com.android.app.animation.Interpolators.DECELERATED_EASE;
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
@@ -211,8 +212,8 @@
if (!config.userControlled) {
config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED);
}
- config.setInterpolator(ANIM_WORKSPACE_SCALE, EMPHASIZED);
- config.setInterpolator(ANIM_DEPTH, EMPHASIZED);
+ config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
+ config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
} else {
if (config.userControlled) {
config.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR_MANUAL));
@@ -252,8 +253,8 @@
if (!config.userControlled) {
config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED);
}
- config.setInterpolator(ANIM_WORKSPACE_SCALE, EMPHASIZED);
- config.setInterpolator(ANIM_DEPTH, EMPHASIZED);
+ config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
+ config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
} else {
config.setInterpolator(ANIM_DEPTH, config.userControlled ? BLUR_MANUAL : BLUR_ATOMIC);
config.setInterpolator(ANIM_WORKSPACE_FADE,
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 4073517..75cee2f 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -157,6 +157,7 @@
public static final String TWO_TASKBAR_LONG_CLICKS = "b/262282528";
public static final String FLAKY_ACTIVITY_COUNT = "b/260260325";
public static final String ICON_MISSING = "b/282963545";
+ public static final String ACTIVITY_LIFECYCLE_RULE = "b/289161193";
public static final String REQUEST_EMULATE_DISPLAY = "emulate-display";
public static final String REQUEST_STOP_EMULATE_DISPLAY = "stop-emulate-display";
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index c1f2bb6..0d63a68 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -530,7 +530,6 @@
}
@Test
- @ScreenRecord // b/258071914
@PortraitLandscape
@PlatinumTest(focusArea = "launcher")
public void testUninstallFromAllApps() throws Exception {
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 435649b..b05ebf8 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.PlatinumTest;
+import android.platform.test.rule.ScreenRecordRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
@@ -53,7 +54,9 @@
@PlatinumTest(focusArea = "launcher")
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/289161193
public void testDragIcon() throws Throwable {
+ mLauncher.enableDebugTracing(); // b/289161193
new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
@@ -79,6 +82,7 @@
DEFAULT_UI_TIMEOUT);
assertNotNull("Widget not found on the workspace", widget);
widget.launch(getAppPackageName());
+ mLauncher.disableDebugTracing(); // b/289161193
}
/**
diff --git a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
index 2eedec3..b5d8193 100644
--- a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
@@ -22,6 +22,8 @@
import androidx.test.InstrumentationRegistry;
+import com.android.launcher3.testing.shared.TestProtocol;
+
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -71,33 +73,57 @@
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
if (activity != null && mClass.isInstance(activity)) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityCreated");
mActivity = (T) activity;
}
}
@Override
public void onActivityStarted(Activity activity) {
+ if (activity == mActivity) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityStarted");
+ }
}
@Override
public void onActivityResumed(Activity activity) {
+ if (activity == mActivity) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityResumed");
+ }
}
@Override
public void onActivityPaused(Activity activity) {
+ if (activity == mActivity) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityPaused");
+ }
}
@Override
public void onActivityStopped(Activity activity) {
+ if (activity == mActivity) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onAcgtivityStopped");
+ }
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
+ if (activity == mActivity) {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE,
+ "MyStatement.onActivitySaveInstanceState");
+ }
}
@Override
public void onActivityDestroyed(Activity activity) {
if (activity == mActivity) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityDestroyed");
mActivity = null;
}
}