Snap for 11654651 from 1bc33421de to 24Q3-release
Change-Id: I182405b7193aa209728cc0e581e8275da7c0b84a
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
|
||||
<item android:alpha="0.35" android:color="?androidprv:attr/materialColorPrimaryContainer" />
|
||||
</selector>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/bubblebar_drop_target_corner_radius" />
|
||||
<solid android:color="@color/bubblebar_drop_target_bg_color" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="?androidprv:attr/materialColorPrimaryContainer" />
|
||||
</shape>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="@dimen/bubblebar_size"
|
||||
android:layout_height="@dimen/bubblebar_size"
|
||||
android:background="@drawable/bg_bubble_bar_drop_target"
|
||||
android:elevation="@dimen/bubblebar_elevation" />
|
||||
@@ -417,6 +417,7 @@
|
||||
<!-- Container size with pointer included: bubblebar_size + bubblebar_pointer_size -->
|
||||
<dimen name="bubblebar_size_with_pointer">80dp</dimen>
|
||||
<dimen name="bubblebar_elevation">1dp</dimen>
|
||||
<dimen name="bubblebar_drag_elevation">2dp</dimen>
|
||||
<dimen name="bubblebar_hotseat_adjustment_threshold">90dp</dimen>
|
||||
|
||||
<dimen name="bubblebar_icon_size">50dp</dimen>
|
||||
@@ -432,6 +433,11 @@
|
||||
<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>
|
||||
<dimen name="bubblebar_dismiss_zone_width">192dp</dimen>
|
||||
<dimen name="bubblebar_dismiss_zone_height">242dp</dimen>
|
||||
|
||||
<!-- Bubble bar drop target -->
|
||||
<dimen name="bubblebar_drop_target_corner_radius">36dp</dimen>
|
||||
|
||||
<!-- Launcher splash screen -->
|
||||
<!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
|
||||
|
||||
@@ -99,6 +99,7 @@ import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.AutohideSu
|
||||
import com.android.launcher3.taskbar.TaskbarTranslationController.TransitionCallback;
|
||||
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarPinController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarView;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
|
||||
@@ -255,7 +256,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
new BubbleStashController(this),
|
||||
new BubbleStashedHandleViewController(this, bubbleHandleView),
|
||||
new BubbleDragController(this),
|
||||
new BubbleDismissController(this, mDragLayer)));
|
||||
new BubbleDismissController(this, mDragLayer),
|
||||
new BubbleBarPinController(this, mDragLayer,
|
||||
() -> getDeviceProfile().getDisplayInfo().currentSize)
|
||||
));
|
||||
}
|
||||
|
||||
// Construct controllers.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar.bubbles
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.ColorFilter
|
||||
@@ -27,12 +28,10 @@ import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.Utilities.mapToRange
|
||||
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext
|
||||
import com.android.wm.shell.common.TriangleShape
|
||||
|
||||
/** Drawable for the background of the bubble bar. */
|
||||
class BubbleBarBackground(context: TaskbarActivityContext, private val backgroundHeight: Float) :
|
||||
Drawable() {
|
||||
class BubbleBarBackground(context: Context, private val backgroundHeight: Float) : Drawable() {
|
||||
|
||||
private val DARK_THEME_SHADOW_ALPHA = 51f
|
||||
private val LIGHT_THEME_SHADOW_ALPHA = 25f
|
||||
@@ -46,6 +45,7 @@ class BubbleBarBackground(context: TaskbarActivityContext, private val backgroun
|
||||
|
||||
var arrowPositionX: Float = 0f
|
||||
private set
|
||||
|
||||
private var showingArrow: Boolean = false
|
||||
private var arrowDrawable: ShapeDrawable
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.taskbar.bubbles
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Point
|
||||
import android.graphics.RectF
|
||||
import android.view.Gravity.BOTTOM
|
||||
import android.view.Gravity.LEFT
|
||||
import android.view.Gravity.RIGHT
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.android.launcher3.R
|
||||
import com.android.wm.shell.common.bubbles.BaseBubblePinController
|
||||
import com.android.wm.shell.common.bubbles.BubbleBarLocation
|
||||
|
||||
/**
|
||||
* Controller to manage pinning bubble bar to left or right when dragging starts from the bubble bar
|
||||
*/
|
||||
class BubbleBarPinController(
|
||||
private val context: Context,
|
||||
private val container: FrameLayout,
|
||||
private val screenSizeProvider: () -> Point
|
||||
) : BaseBubblePinController() {
|
||||
|
||||
private lateinit var bubbleBarViewController: BubbleBarViewController
|
||||
private lateinit var bubbleStashController: BubbleStashController
|
||||
private var dropTargetView: View? = null
|
||||
|
||||
fun init(bubbleControllers: BubbleControllers) {
|
||||
bubbleBarViewController = bubbleControllers.bubbleBarViewController
|
||||
bubbleStashController = bubbleControllers.bubbleStashController
|
||||
}
|
||||
|
||||
override fun getScreenCenterX(): Int {
|
||||
return screenSizeProvider.invoke().x / 2
|
||||
}
|
||||
|
||||
override fun getExclusionRect(): RectF {
|
||||
val rect =
|
||||
RectF(
|
||||
0f,
|
||||
0f,
|
||||
context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_width),
|
||||
context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_height)
|
||||
)
|
||||
val screenSize = screenSizeProvider.invoke()
|
||||
val middleX = screenSize.x / 2
|
||||
// Center it around the bottom center of the screen
|
||||
rect.offsetTo(middleX - rect.width() / 2, screenSize.y - rect.height())
|
||||
return rect
|
||||
}
|
||||
|
||||
override fun createDropTargetView(): View {
|
||||
return LayoutInflater.from(context)
|
||||
.inflate(R.layout.bubble_bar_drop_target, container, false)
|
||||
.also { view ->
|
||||
dropTargetView = view
|
||||
container.addView(view)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDropTargetView(): View? {
|
||||
return dropTargetView
|
||||
}
|
||||
|
||||
override fun removeDropTargetView(view: View) {
|
||||
container.removeView(view)
|
||||
dropTargetView = null
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
override fun updateLocation(location: BubbleBarLocation) {
|
||||
val onLeft = location.isOnLeft(container.isLayoutRtl)
|
||||
|
||||
val bounds = bubbleBarViewController.bubbleBarBounds
|
||||
val horizontalMargin = bubbleBarViewController.horizontalMargin
|
||||
dropTargetView?.updateLayoutParams<FrameLayout.LayoutParams> {
|
||||
width = bounds.width()
|
||||
height = bounds.height()
|
||||
gravity = BOTTOM or (if (onLeft) LEFT else RIGHT)
|
||||
leftMargin = horizontalMargin
|
||||
rightMargin = horizontalMargin
|
||||
bottomMargin = -bubbleStashController.bubbleBarTranslationY.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,8 +39,6 @@ import androidx.dynamicanimation.animation.SpringForce;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.SpringAnimationBuilder;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
|
||||
|
||||
import java.util.List;
|
||||
@@ -159,8 +157,6 @@ public class BubbleBarView extends FrameLayout {
|
||||
|
||||
public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
TaskbarActivityContext activityContext = ActivityContext.lookupContext(context);
|
||||
|
||||
setAlpha(0);
|
||||
setVisibility(INVISIBLE);
|
||||
mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
|
||||
@@ -171,7 +167,7 @@ public class BubbleBarView extends FrameLayout {
|
||||
|
||||
setClipToPadding(false);
|
||||
|
||||
mBubbleBarBackground = new BubbleBarBackground(activityContext,
|
||||
mBubbleBarBackground = new BubbleBarBackground(context,
|
||||
getResources().getDimensionPixelSize(R.dimen.bubblebar_size));
|
||||
setBackgroundDrawable(mBubbleBarBackground);
|
||||
|
||||
@@ -379,6 +375,36 @@ public class BubbleBarView extends FrameLayout {
|
||||
return mRelativePivotY;
|
||||
}
|
||||
|
||||
/** Prepares for animating a bubble while being stashed. */
|
||||
public void prepareForAnimatingBubbleWhileStashed(String bubbleKey) {
|
||||
// we're about to animate the new bubble in. the new bubble has already been added to this
|
||||
// view, but we're currently stashed, so before we can start the animation we need make
|
||||
// everything else in the bubble bar invisible, except for the bubble that's being animated.
|
||||
setBackground(null);
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
final BubbleView view = (BubbleView) getChildAt(i);
|
||||
final String key = view.getBubble().getKey();
|
||||
if (!bubbleKey.equals(key)) {
|
||||
view.setVisibility(INVISIBLE);
|
||||
}
|
||||
}
|
||||
setVisibility(VISIBLE);
|
||||
setAlpha(1);
|
||||
setTranslationY(0);
|
||||
setScaleX(1);
|
||||
setScaleY(1);
|
||||
}
|
||||
|
||||
/** Resets the state after the bubble animation completed. */
|
||||
public void onAnimatingBubbleCompleted() {
|
||||
setBackground(mBubbleBarBackground);
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
final BubbleView view = (BubbleView) getChildAt(i);
|
||||
view.setVisibility(VISIBLE);
|
||||
view.setAlpha(1f);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: (b/280605790) animate it
|
||||
@Override
|
||||
public void addView(View child, int index, ViewGroup.LayoutParams params) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.launcher3.taskbar.TaskbarControllers;
|
||||
import com.android.launcher3.taskbar.TaskbarInsetsController;
|
||||
import com.android.launcher3.taskbar.TaskbarStashController;
|
||||
import com.android.launcher3.taskbar.bubbles.animation.BubbleBarViewAnimator;
|
||||
import com.android.launcher3.util.MultiPropertyFactory;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
@@ -81,6 +82,8 @@ public class BubbleBarViewController {
|
||||
private boolean mHiddenForNoBubbles = true;
|
||||
private boolean mShouldShowEducation;
|
||||
|
||||
private BubbleBarViewAnimator mBubbleBarViewAnimator;
|
||||
|
||||
public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) {
|
||||
mActivity = activity;
|
||||
mBarView = barView;
|
||||
@@ -113,6 +116,8 @@ public class BubbleBarViewController {
|
||||
mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
|
||||
mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
|
||||
);
|
||||
|
||||
mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController);
|
||||
}
|
||||
|
||||
private void onBubbleClicked(View v) {
|
||||
@@ -316,6 +321,12 @@ public class BubbleBarViewController {
|
||||
new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
|
||||
b.getView().setOnClickListener(mBubbleClickListener);
|
||||
mBubbleDragController.setupBubbleView(b.getView());
|
||||
|
||||
boolean isStashedOrGone =
|
||||
mBubbleStashController.isStashed() || mBarView.getVisibility() != VISIBLE;
|
||||
if (b instanceof BubbleBarBubble && isStashedOrGone) {
|
||||
mBubbleBarViewAnimator.animateBubbleInForStashed((BubbleBarBubble) b);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "addBubble, bubble was null!");
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ public class BubbleControllers {
|
||||
public final BubbleStashedHandleViewController bubbleStashedHandleViewController;
|
||||
public final BubbleDragController bubbleDragController;
|
||||
public final BubbleDismissController bubbleDismissController;
|
||||
public final BubbleBarPinController bubbleBarPinController;
|
||||
|
||||
private final RunnableList mPostInitRunnables = new RunnableList();
|
||||
|
||||
@@ -43,13 +44,15 @@ public class BubbleControllers {
|
||||
BubbleStashController bubbleStashController,
|
||||
BubbleStashedHandleViewController bubbleStashedHandleViewController,
|
||||
BubbleDragController bubbleDragController,
|
||||
BubbleDismissController bubbleDismissController) {
|
||||
BubbleDismissController bubbleDismissController,
|
||||
BubbleBarPinController bubbleBarPinController) {
|
||||
this.bubbleBarController = bubbleBarController;
|
||||
this.bubbleBarViewController = bubbleBarViewController;
|
||||
this.bubbleStashController = bubbleStashController;
|
||||
this.bubbleStashedHandleViewController = bubbleStashedHandleViewController;
|
||||
this.bubbleDragController = bubbleDragController;
|
||||
this.bubbleDismissController = bubbleDismissController;
|
||||
this.bubbleBarPinController = bubbleBarPinController;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,6 +67,7 @@ public class BubbleControllers {
|
||||
bubbleStashController.init(taskbarControllers, this);
|
||||
bubbleDragController.init(/* bubbleControllers = */ this);
|
||||
bubbleDismissController.init(/* bubbleControllers = */ this);
|
||||
bubbleBarPinController.init(this);
|
||||
|
||||
mPostInitRunnables.executeAllAndDestroy();
|
||||
}
|
||||
|
||||
@@ -67,6 +67,9 @@ public class BubbleDismissController {
|
||||
@Nullable
|
||||
private BubbleDragAnimator mAnimator;
|
||||
|
||||
@Nullable
|
||||
private Listener mListener;
|
||||
|
||||
public BubbleDismissController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
|
||||
mActivity = activity;
|
||||
mDragLayer = dragLayer;
|
||||
@@ -81,6 +84,13 @@ public class BubbleDismissController {
|
||||
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set listener to be notified of dismiss events
|
||||
*/
|
||||
public void setListener(@Nullable Listener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -189,6 +199,9 @@ public class BubbleDismissController {
|
||||
@NonNull MagnetizedObject<?> draggedObject) {
|
||||
if (mAnimator == null) return;
|
||||
mAnimator.animateDismissCaptured();
|
||||
if (mListener != null) {
|
||||
mListener.onStuckToDismissChanged(true /* stuck */);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -197,6 +210,9 @@ public class BubbleDismissController {
|
||||
float velX, float velY, boolean wasFlungOut) {
|
||||
if (mAnimator == null) return;
|
||||
mAnimator.animateDismissReleased();
|
||||
if (mListener != null) {
|
||||
mListener.onStuckToDismissChanged(false /* stuck */);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -206,4 +222,10 @@ public class BubbleDismissController {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Interface to receive updates about the dismiss state */
|
||||
public interface Listener {
|
||||
/** Called when view is stuck or unstuck from dismiss target */
|
||||
void onStuckToDismissChanged(boolean stuck);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,11 @@ import android.view.ViewConfiguration;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
|
||||
/**
|
||||
* Controls bubble bar drag to dismiss interaction.
|
||||
* Controls bubble bar drag interactions.
|
||||
* Interacts with {@link BubbleDismissController}, used by {@link BubbleBarViewController}.
|
||||
* Supported interactions:
|
||||
* - Drag a single bubble view into dismiss target to remove it.
|
||||
@@ -39,6 +40,7 @@ public class BubbleDragController {
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private BubbleBarViewController mBubbleBarViewController;
|
||||
private BubbleDismissController mBubbleDismissController;
|
||||
private BubbleBarPinController mBubbleBarPinController;
|
||||
|
||||
public BubbleDragController(TaskbarActivityContext activity) {
|
||||
mActivity = activity;
|
||||
@@ -52,6 +54,12 @@ public class BubbleDragController {
|
||||
public void init(@NonNull BubbleControllers bubbleControllers) {
|
||||
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
|
||||
mBubbleDismissController = bubbleControllers.bubbleDismissController;
|
||||
mBubbleBarPinController = bubbleControllers.bubbleBarPinController;
|
||||
mBubbleBarPinController.setListener(location -> {
|
||||
// TODO(b/330585397): update bubble bar location in shell
|
||||
});
|
||||
mBubbleDismissController.setListener(
|
||||
stuck -> mBubbleBarPinController.setDropTargetHidden(stuck));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,6 +96,10 @@ public class BubbleDragController {
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public void setupBubbleBarView(@NonNull BubbleBarView bubbleBarView) {
|
||||
PointF initialRelativePivot = new PointF();
|
||||
final int restingElevation = bubbleBarView.getResources().getDimensionPixelSize(
|
||||
R.dimen.bubblebar_elevation);
|
||||
final int dragElevation = bubbleBarView.getResources().getDimensionPixelSize(
|
||||
R.dimen.bubblebar_drag_elevation);
|
||||
bubbleBarView.setOnTouchListener(new BubbleTouchListener() {
|
||||
@Override
|
||||
protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
|
||||
@@ -102,12 +114,31 @@ public class BubbleDragController {
|
||||
// By default the bubble bar view pivot is in bottom right corner, while dragging
|
||||
// it should be centered in order to align it with the dismiss target view
|
||||
bubbleBarView.setRelativePivot(/* x = */ 0.5f, /* y = */ 0.5f);
|
||||
bubbleBarView.setElevation(dragElevation);
|
||||
mBubbleBarPinController.onDragStart(
|
||||
bubbleBarView.getBubbleBarLocation().isOnLeft(bubbleBarView.isLayoutRtl()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragUpdate(float x, float y) {
|
||||
mBubbleBarPinController.onDragUpdate(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragRelease() {
|
||||
mBubbleBarPinController.onDragEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragDismiss() {
|
||||
mBubbleBarPinController.onDragEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDragEnd() {
|
||||
// Restoring the initial pivot for the bubble bar view
|
||||
bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
|
||||
bubbleBarView.setElevation(restingElevation);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -169,6 +200,13 @@ public class BubbleDragController {
|
||||
*/
|
||||
abstract void onDragStart();
|
||||
|
||||
/**
|
||||
* Called when bubble is dragged to new coordinates.
|
||||
* Not called while bubble is stuck to the dismiss target.
|
||||
*/
|
||||
protected void onDragUpdate(float x, float y) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dragging interaction has ended and all the animations have completed
|
||||
*/
|
||||
@@ -232,8 +270,10 @@ public class BubbleDragController {
|
||||
* @param event the motion event
|
||||
*/
|
||||
protected void onTouchMove(@NonNull View view, @NonNull MotionEvent event) {
|
||||
final float dx = event.getRawX() - mTouchDownLocation.x;
|
||||
final float dy = event.getRawY() - mTouchDownLocation.y;
|
||||
float rawX = event.getRawX();
|
||||
float rawY = event.getRawY();
|
||||
final float dx = rawX - mTouchDownLocation.x;
|
||||
final float dy = rawY - mTouchDownLocation.y;
|
||||
switch (mState) {
|
||||
case TOUCHED:
|
||||
final boolean movedOut = Math.hypot(dx, dy) > mTouchSlop;
|
||||
@@ -244,7 +284,7 @@ public class BubbleDragController {
|
||||
}
|
||||
break;
|
||||
case DRAGGING:
|
||||
drag(view, event, dx, dy);
|
||||
drag(view, event, dx, dy, rawX, rawY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -293,10 +333,12 @@ public class BubbleDragController {
|
||||
mBubbleDismissController.showDismissView();
|
||||
}
|
||||
|
||||
private void drag(@NonNull View view, @NonNull MotionEvent event, float dx, float dy) {
|
||||
private void drag(@NonNull View view, @NonNull MotionEvent event, float dx, float dy,
|
||||
float x, float y) {
|
||||
if (mBubbleDismissController.handleTouchEvent(event)) return;
|
||||
view.setTranslationX(mViewInitialPosition.x + dx);
|
||||
view.setTranslationY(mViewInitialPosition.y + dy);
|
||||
onDragUpdate(x, y);
|
||||
}
|
||||
|
||||
private void stopDragging(@NonNull View view, @NonNull MotionEvent event) {
|
||||
|
||||
@@ -145,7 +145,7 @@ public class BubbleView extends ConstraintLayout {
|
||||
}
|
||||
|
||||
/** Sets the bubble being rendered in this view. */
|
||||
void setBubble(BubbleBarBubble bubble) {
|
||||
public void setBubble(BubbleBarBubble bubble) {
|
||||
mBubble = bubble;
|
||||
mBubbleIcon.setImageBitmap(bubble.getIcon());
|
||||
mAppIcon.setImageBitmap(bubble.getBadge());
|
||||
@@ -159,7 +159,7 @@ public class BubbleView extends ConstraintLayout {
|
||||
* 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) {
|
||||
public void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
|
||||
mBubble = overflow;
|
||||
mBubbleIcon.setImageBitmap(bitmap);
|
||||
mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
|
||||
@@ -168,7 +168,7 @@ public class BubbleView extends ConstraintLayout {
|
||||
|
||||
/** Returns the bubble being rendered in this view. */
|
||||
@Nullable
|
||||
BubbleBarItem getBubble() {
|
||||
public BubbleBarItem getBubble() {
|
||||
return mBubble;
|
||||
}
|
||||
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.taskbar.bubbles.animation
|
||||
|
||||
import android.view.View
|
||||
import android.view.View.VISIBLE
|
||||
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.wm.shell.shared.animation.PhysicsAnimator
|
||||
|
||||
/** Handles animations for bubble bar bubbles. */
|
||||
class BubbleBarViewAnimator
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
private val bubbleBarView: BubbleBarView,
|
||||
private val bubbleStashController: BubbleStashController,
|
||||
private val scheduler: Scheduler = HandlerScheduler(bubbleBarView)
|
||||
) {
|
||||
|
||||
private companion object {
|
||||
/** 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_TRANSLATION_Y = -50f
|
||||
}
|
||||
|
||||
/** An interface for scheduling jobs. */
|
||||
interface Scheduler {
|
||||
|
||||
/** Schedule the given [block] to run. */
|
||||
fun post(block: () -> Unit)
|
||||
|
||||
/** Schedule the given [block] to start with a delay of [delayMillis]. */
|
||||
fun postDelayed(delayMillis: Long, block: () -> Unit)
|
||||
}
|
||||
|
||||
/** A [Scheduler] that uses a Handler to run jobs. */
|
||||
private class HandlerScheduler(private val view: View) : Scheduler {
|
||||
|
||||
override fun post(block: () -> Unit) {
|
||||
view.post(block)
|
||||
}
|
||||
|
||||
override fun postDelayed(delayMillis: Long, block: () -> Unit) {
|
||||
view.postDelayed(block, delayMillis)
|
||||
}
|
||||
}
|
||||
|
||||
private val springConfig =
|
||||
PhysicsAnimator.SpringConfig(
|
||||
stiffness = SpringForce.STIFFNESS_LOW,
|
||||
dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
|
||||
)
|
||||
|
||||
/** Animates a bubble for the state where the bubble bar is stashed. */
|
||||
fun animateBubbleInForStashed(b: BubbleBarBubble) {
|
||||
val bubbleView = b.view
|
||||
val animator = PhysicsAnimator.getInstance(bubbleView)
|
||||
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 hideAnimation = buildHideAnimation(animator)
|
||||
scheduler.post(showAnimation)
|
||||
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
|
||||
}
|
||||
|
||||
/** Returns a lambda that starts the animation that shows the new bubble. */
|
||||
private fun buildShowAnimation(
|
||||
bubbleView: BubbleView,
|
||||
key: String,
|
||||
animator: PhysicsAnimator<BubbleView>
|
||||
): () -> Unit = {
|
||||
bubbleBarView.prepareForAnimatingBubbleWhileStashed(key)
|
||||
animator.setDefaultSpringConfig(springConfig)
|
||||
animator
|
||||
.spring(DynamicAnimation.ALPHA, 1f)
|
||||
.spring(DynamicAnimation.TRANSLATION_Y, BUBBLE_ANIMATION_TRANSLATION_Y)
|
||||
bubbleView.alpha = 0f
|
||||
bubbleView.visibility = VISIBLE
|
||||
animator.start()
|
||||
}
|
||||
|
||||
/** Returns a lambda that starts the animation that hides the new bubble. */
|
||||
private fun buildHideAnimation(animator: PhysicsAnimator<BubbleView>): () -> Unit = {
|
||||
animator.setDefaultSpringConfig(springConfig)
|
||||
animator
|
||||
.spring(DynamicAnimation.ALPHA, 0f)
|
||||
.spring(DynamicAnimation.TRANSLATION_Y, 0f)
|
||||
.addEndListener { _, _, _, canceled, _, _, allRelevantPropertyAnimsEnded ->
|
||||
if (!canceled && allRelevantPropertyAnimsEnded) {
|
||||
if (bubbleStashController.isStashed) {
|
||||
bubbleBarView.alpha = 0f
|
||||
}
|
||||
bubbleBarView.onAnimatingBubbleCompleted()
|
||||
}
|
||||
}
|
||||
animator.start()
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.taskbar.bubbles.animation
|
||||
|
||||
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.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarBubble
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarOverflow
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarView
|
||||
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.PhysicsAnimatorTestUtils
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.whenever
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class BubbleBarViewAnimatorTest {
|
||||
|
||||
private val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
private val animatorScheduler = TestBubbleBarViewAnimatorScheduler()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
PhysicsAnimatorTestUtils.prepareForTest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateBubbleInForStashed() {
|
||||
lateinit var overflowView: BubbleView
|
||||
lateinit var bubbleView: BubbleView
|
||||
lateinit var bubble: BubbleBarBubble
|
||||
val bubbleBarView = BubbleBarView(context)
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
|
||||
val inflater = LayoutInflater.from(context)
|
||||
|
||||
val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
|
||||
overflowView =
|
||||
inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
|
||||
overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
|
||||
bubbleBarView.addView(overflowView)
|
||||
|
||||
val bubbleInfo = BubbleInfo("key", 0, null, null, 0, context.packageName, null, false)
|
||||
bubbleView =
|
||||
inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
|
||||
bubble =
|
||||
BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
|
||||
bubbleView.setBubble(bubble)
|
||||
bubbleBarView.addView(bubbleView)
|
||||
}
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
|
||||
|
||||
val bubbleStashController = mock<BubbleStashController>()
|
||||
whenever(bubbleStashController.isStashed).thenReturn(true)
|
||||
|
||||
val animator =
|
||||
BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
animator.animateBubbleInForStashed(bubble)
|
||||
}
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
|
||||
|
||||
assertThat(overflowView.visibility).isEqualTo(INVISIBLE)
|
||||
assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
|
||||
assertThat(bubbleView.visibility).isEqualTo(VISIBLE)
|
||||
|
||||
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
|
||||
DynamicAnimation.ALPHA,
|
||||
DynamicAnimation.TRANSLATION_Y
|
||||
)
|
||||
|
||||
assertThat(bubbleView.alpha).isEqualTo(1)
|
||||
assertThat(bubbleView.translationY).isEqualTo(-50)
|
||||
|
||||
assertThat(animatorScheduler.delayedBlock).isNotNull()
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
|
||||
|
||||
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
|
||||
DynamicAnimation.ALPHA,
|
||||
DynamicAnimation.TRANSLATION_Y
|
||||
)
|
||||
|
||||
assertThat(bubbleView.alpha).isEqualTo(1)
|
||||
assertThat(bubbleView.visibility).isEqualTo(VISIBLE)
|
||||
assertThat(bubbleView.translationY).isEqualTo(0)
|
||||
assertThat(bubbleBarView.alpha).isEqualTo(0)
|
||||
assertThat(overflowView.alpha).isEqualTo(1)
|
||||
assertThat(overflowView.visibility).isEqualTo(VISIBLE)
|
||||
}
|
||||
|
||||
private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler {
|
||||
|
||||
var delayedBlock: (() -> Unit)? = null
|
||||
private set
|
||||
|
||||
override fun post(block: () -> Unit) {
|
||||
block.invoke()
|
||||
}
|
||||
|
||||
override fun postDelayed(delayMillis: Long, block: () -> Unit) {
|
||||
check(delayedBlock == null) { "there is already a pending block waiting to run" }
|
||||
delayedBlock = block
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user