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