• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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 
17 package com.android.wm.shell.bubbles.bar
18 
19 import android.annotation.SuppressLint
20 import android.content.Context
21 import android.view.MotionEvent
22 import android.view.View
23 import androidx.annotation.VisibleForTesting
24 import com.android.wm.shell.R
25 import com.android.wm.shell.bubbles.BubblePositioner
26 import com.android.wm.shell.shared.bubbles.BubbleBarLocation
27 import com.android.wm.shell.shared.bubbles.DismissView
28 import com.android.wm.shell.shared.bubbles.DragZoneFactory
29 import com.android.wm.shell.shared.bubbles.DraggedObject
30 import com.android.wm.shell.shared.bubbles.DropTargetManager
31 import com.android.wm.shell.shared.bubbles.RelativeTouchListener
32 import com.android.wm.shell.shared.magnetictarget.MagnetizedObject
33 
34 /** Controller for handling drag interactions with [BubbleBarExpandedView] */
35 @SuppressLint("ClickableViewAccessibility")
36 class BubbleBarExpandedViewDragController(
37     private val context: Context,
38     private val expandedView: BubbleBarExpandedView,
39     private val dismissView: DismissView,
40     private val animationHelper: BubbleBarAnimationHelper,
41     private val bubblePositioner: BubblePositioner,
42     private val pinController: BubbleExpandedViewPinController,
43     private val dropTargetManager: DropTargetManager?,
44     private val dragZoneFactory: DragZoneFactory?,
45     @get:VisibleForTesting val dragListener: DragListener,
46 ) {
47 
48     var isStuckToDismiss: Boolean = false
49         private set
50 
51     var isDragged: Boolean = false
52         private set
53 
54     private var expandedViewInitialTranslationX = 0f
55     private var expandedViewInitialTranslationY = 0f
56     private val magnetizedExpandedView: MagnetizedObject<BubbleBarExpandedView> =
57         MagnetizedObject.magnetizeView(expandedView)
58     private val magnetizedDismissTarget: MagnetizedObject.MagneticTarget
59 
60     private val draggedBubbleElevation: Float
61 
62     init {
63         magnetizedExpandedView.magnetListener = MagnetListener()
64         magnetizedExpandedView.animateStuckToTarget =
65             {
66                 target: MagnetizedObject.MagneticTarget,
67                 _: Float,
68                 _: Float,
69                 _: Boolean,
70                 after: (() -> Unit)? ->
71                 animationHelper.animateIntoTarget(target, after)
72             }
73 
74         magnetizedDismissTarget =
75             MagnetizedObject.MagneticTarget(dismissView.circle, dismissView.circle.width)
76         magnetizedExpandedView.addTarget(magnetizedDismissTarget)
77 
78         draggedBubbleElevation = context.resources.getDimension(
79             R.dimen.dragged_bubble_elevation)
80         val dragMotionEventHandler = HandleDragListener()
81 
82         expandedView.handleView.setOnTouchListener { view, event ->
83             if (event.actionMasked == MotionEvent.ACTION_DOWN) {
84                 expandedViewInitialTranslationX = expandedView.translationX
85                 expandedViewInitialTranslationY = expandedView.translationY
86             }
87             val magnetConsumed = magnetizedExpandedView.maybeConsumeMotionEvent(event)
88             // Move events can be consumed by the magnetized object
89             if (event.actionMasked == MotionEvent.ACTION_MOVE && magnetConsumed) {
90                 return@setOnTouchListener true
91             }
92             return@setOnTouchListener dragMotionEventHandler.onTouch(view, event) || magnetConsumed
93         }
94     }
95 
96     /** Listener to get notified about drag events */
97     interface DragListener {
98         /**
99          * Bubble bar was released
100          *
101          * @param inDismiss `true` if view was release in dismiss target
102          */
103         fun onReleased(inDismiss: Boolean)
104     }
105 
106     private inner class HandleDragListener : RelativeTouchListener() {
107 
108         private var isMoving = false
109 
110         override fun onDown(v: View, ev: MotionEvent): Boolean {
111             // While animating, don't allow new touch events
112             if (expandedView.isAnimating) return false
113             expandedView.z = draggedBubbleElevation
114             if (dropTargetManager != null && dragZoneFactory != null) {
115                 val draggedObject = DraggedObject.ExpandedView(
116                     if (bubblePositioner.isBubbleBarOnLeft) {
117                         BubbleBarLocation.LEFT
118                     } else {
119                         BubbleBarLocation.RIGHT
120                     }
121                 )
122                 dropTargetManager.onDragStarted(
123                     draggedObject,
124                     dragZoneFactory.createSortedDragZones(draggedObject)
125                 )
126             } else {
127                 pinController.onDragStart(bubblePositioner.isBubbleBarOnLeft)
128             }
129             isDragged = true
130             return true
131         }
132 
133         override fun onMove(
134             v: View,
135             ev: MotionEvent,
136             viewInitialX: Float,
137             viewInitialY: Float,
138             dx: Float,
139             dy: Float,
140         ) {
141             if (!isMoving) {
142                 isMoving = true
143                 animationHelper.animateStartDrag()
144             }
145             expandedView.translationX = expandedViewInitialTranslationX + dx
146             expandedView.translationY = expandedViewInitialTranslationY + dy
147             dismissView.show()
148             if (dropTargetManager != null) {
149                 dropTargetManager.onDragUpdated(ev.rawX.toInt(), ev.rawY.toInt())
150             } else {
151                 pinController.onDragUpdate(ev.rawX, ev.rawY)
152             }
153         }
154 
155         override fun onUp(
156             v: View,
157             ev: MotionEvent,
158             viewInitialX: Float,
159             viewInitialY: Float,
160             dx: Float,
161             dy: Float,
162             velX: Float,
163             velY: Float,
164         ) {
165             v.translationZ = 0f
166             finishDrag()
167         }
168 
169         override fun onCancel(v: View, ev: MotionEvent, viewInitialX: Float, viewInitialY: Float) {
170             isStuckToDismiss = false
171             v.translationZ = 0f
172             finishDrag()
173         }
174 
175         private fun finishDrag() {
176             if (!isStuckToDismiss) {
177                 if (dropTargetManager != null) {
178                     dropTargetManager.onDragEnded()
179                 } else {
180                     pinController.onDragEnd()
181                 }
182                 dragListener.onReleased(inDismiss = false)
183                 animationHelper.animateToRestPosition()
184                 dismissView.hide()
185             }
186             isMoving = false
187             isDragged = false
188         }
189     }
190 
191     private inner class MagnetListener : MagnetizedObject.MagnetListener {
192         override fun onStuckToTarget(
193             target: MagnetizedObject.MagneticTarget,
194             draggedObject: MagnetizedObject<*>,
195         ) {
196             isStuckToDismiss = true
197             pinController.onStuckToDismissTarget()
198         }
199 
200         override fun onUnstuckFromTarget(
201             target: MagnetizedObject.MagneticTarget,
202             draggedObject: MagnetizedObject<*>,
203             velX: Float,
204             velY: Float,
205             wasFlungOut: Boolean,
206         ) {
207             isStuckToDismiss = false
208             animationHelper.animateUnstuckFromDismissView(target)
209         }
210 
211         override fun onReleasedInTarget(
212             target: MagnetizedObject.MagneticTarget,
213             draggedObject: MagnetizedObject<*>,
214         ) {
215             dragListener.onReleased(inDismiss = true)
216             if (dropTargetManager != null) {
217                 dropTargetManager.onDragEnded()
218             } else {
219                 pinController.onDragEnd()
220             }
221             dismissView.hide()
222         }
223     }
224 }
225