* Added dismiss view to the taskbar * Bubble drag to dismiss of: * A single bubble when the bubble bar stack is expanded * All bubbles when the bubble bar stack is colapsed * Added dismiss related calls to WMShell in SystemUI through SystemUIProxy Bug: 271466616 Test: manual, TBD Flag: WM_BUBBLE_BAR Change-Id: Ibb62a0b1ab9fafa8a3acf07450ce2132ed605adc
277 lines
11 KiB
Java
277 lines
11 KiB
Java
/*
|
|
* 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;
|
|
}
|
|
}
|