• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.taskbar.bubbles;
17 
18 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
19 
20 import android.util.Log;
21 import android.view.MotionEvent;
22 import android.view.View;
23 import android.widget.FrameLayout;
24 
25 import androidx.annotation.NonNull;
26 import androidx.annotation.Nullable;
27 import androidx.dynamicanimation.animation.DynamicAnimation;
28 
29 import com.android.launcher3.R;
30 import com.android.launcher3.taskbar.TaskbarActivityContext;
31 import com.android.launcher3.taskbar.TaskbarDragLayer;
32 import com.android.wm.shell.common.bubbles.DismissView;
33 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
34 
35 /**
36  * Controls dismiss view presentation for the bubble bar dismiss functionality.
37  * Provides the dragged view snapping to the target dismiss area and animates it.
38  * When the dragged bubble/bubble stack is released inside of the target area, it gets dismissed.
39  *
40  * @see BubbleDragController
41  */
42 public class BubbleDismissController {
43     private static final String TAG = BubbleDismissController.class.getSimpleName();
44     private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
45     private final TaskbarActivityContext mActivity;
46     private final TaskbarDragLayer mDragLayer;
47     @Nullable
48     private BubbleBarViewController mBubbleBarViewController;
49 
50     // Dismiss view that's attached to drag layer. It consists of the scrim view and the circular
51     // dismiss view used as a dismiss target.
52     @Nullable
53     private DismissView mDismissView;
54 
55     // The currently magnetized object, which is being dragged and will be attracted to the magnetic
56     // dismiss target. This is either the stack itself, or an individual bubble.
57     @Nullable
58     private MagnetizedObject<View> mMagnetizedObject;
59 
60     // The MagneticTarget instance for our circular dismiss view. This is added to the
61     // MagnetizedObject instances for the stack and any dragged-out bubbles.
62     @Nullable
63     private MagnetizedObject.MagneticTarget mMagneticTarget;
64 
65     // The bubble drag animator that synchronizes bubble drag and dismiss view animations
66     // A new instance is provided when the dismiss view is setup
67     @Nullable
68     private BubbleDragAnimator mAnimator;
69 
BubbleDismissController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer)70     public BubbleDismissController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
71         mActivity = activity;
72         mDragLayer = dragLayer;
73     }
74 
75     /**
76      * Initializes dependencies when bubble controllers are created.
77      * Should be careful to only access things that were created in constructors for now, as some
78      * controllers may still be waiting for init().
79      */
init(@onNull BubbleControllers bubbleControllers)80     public void init(@NonNull BubbleControllers bubbleControllers) {
81         mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
82     }
83 
84     /**
85      * Setup the dismiss view and magnetized object that will be attracted to magnetic target.
86      * Should be called before handling events or showing/hiding dismiss view.
87      *
88      * @param magnetizedView the view to be pulled into target dismiss area
89      * @param animator       the bubble animator to be used for the magnetized view, it syncs bubble
90      *                       dragging and dismiss animations with the dismiss view provided.
91      */
setupDismissView(@onNull View magnetizedView, @NonNull BubbleDragAnimator animator)92     public void setupDismissView(@NonNull View magnetizedView,
93             @NonNull BubbleDragAnimator animator) {
94         setupDismissView();
95         setupMagnetizedObject(magnetizedView);
96         if (mDismissView != null) {
97             animator.setDismissView(mDismissView);
98             mAnimator = animator;
99         }
100     }
101 
102     /**
103      * Handle the touch event and pass it to the magnetized object.
104      * It should be called after {@code setupDismissView}
105      */
handleTouchEvent(@onNull MotionEvent event)106     public boolean handleTouchEvent(@NonNull MotionEvent event) {
107         return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
108     }
109 
110     /**
111      * Show dismiss view with animation
112      * It should be called after {@code setupDismissView}
113      */
showDismissView()114     public void showDismissView() {
115         if (mDismissView == null) return;
116         mDismissView.show();
117     }
118 
119     /**
120      * Hide dismiss view with animation
121      * It should be called after {@code setupDismissView}
122      */
hideDismissView()123     public void hideDismissView() {
124         if (mDismissView == null) return;
125         mDismissView.hide();
126     }
127 
128     /**
129      * Dismiss magnetized object when it's released in the dismiss target area
130      */
dismissMagnetizedObject()131     private void dismissMagnetizedObject() {
132         if (mMagnetizedObject == null || mBubbleBarViewController == null) return;
133         if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleView) {
134             BubbleView bubbleView = (BubbleView) mMagnetizedObject.getUnderlyingObject();
135             if (bubbleView.getBubble() != null) {
136                 mBubbleBarViewController.onDismissBubbleWhileDragging(bubbleView.getBubble());
137             }
138         } else if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleBarView) {
139             mBubbleBarViewController.onDismissAllBubblesWhileDragging();
140         }
141     }
142 
setupDismissView()143     private void setupDismissView() {
144         if (mDismissView != null) return;
145         mDismissView = new DismissView(mActivity.getApplicationContext());
146         BubbleDismissViewUtils.setup(mDismissView);
147         mDragLayer.addView(mDismissView, /* index = */ 0,
148                 new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
149         mDismissView.setElevation(mDismissView.getResources().getDimensionPixelSize(
150                 R.dimen.bubblebar_elevation));
151         setupMagneticTarget(mDismissView.getCircle());
152     }
153 
setupMagneticTarget(@onNull View view)154     private void setupMagneticTarget(@NonNull View view) {
155         int magneticFieldRadius = mActivity.getResources().getDimensionPixelSize(
156                 R.dimen.bubblebar_dismiss_target_size);
157         mMagneticTarget = new MagnetizedObject.MagneticTarget(view, magneticFieldRadius);
158     }
159 
setupMagnetizedObject(@onNull View magnetizedView)160     private void setupMagnetizedObject(@NonNull View magnetizedView) {
161         mMagnetizedObject = new MagnetizedObject<>(mActivity.getApplicationContext(),
162                 magnetizedView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) {
163             @Override
164             public float getWidth(@NonNull View underlyingObject) {
165                 return underlyingObject.getWidth() * underlyingObject.getScaleX();
166             }
167 
168             @Override
169             public float getHeight(@NonNull View underlyingObject) {
170                 return underlyingObject.getHeight() * underlyingObject.getScaleY();
171             }
172 
173             @Override
174             public void getLocationOnScreen(@NonNull View underlyingObject, @NonNull int[] loc) {
175                 underlyingObject.getLocationOnScreen(loc);
176             }
177         };
178 
179         mMagnetizedObject.setHapticsEnabled(true);
180         mMagnetizedObject.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
181         if (mMagneticTarget != null) {
182             mMagnetizedObject.addTarget(mMagneticTarget);
183         } else {
184             Log.e(TAG,"Requires MagneticTarget to add target to MagnetizedObject!");
185         }
186         mMagnetizedObject.setMagnetListener(new MagnetizedObject.MagnetListener() {
187             @Override
188             public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
189                 if (mAnimator == null) return;
190                 mAnimator.animateDismissCaptured();
191             }
192 
193             @Override
194             public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
195                     float velX, float velY, boolean wasFlungOut) {
196                 if (mAnimator == null) return;
197                 mAnimator.animateDismissReleased();
198             }
199 
200             @Override
201             public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
202                 dismissMagnetizedObject();
203             }
204         });
205     }
206 }
207