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 17 18 import android.view.MotionEvent 19 import com.android.app.animation.Interpolators.LINEAR 20 import com.android.launcher3.R 21 import com.android.launcher3.Utilities 22 import com.android.launcher3.testing.shared.ResourceUtils 23 import com.android.launcher3.touch.SingleAxisSwipeDetector 24 import com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE 25 import com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL 26 import com.android.launcher3.util.TouchController 27 import com.android.quickstep.inputconsumers.TaskbarUnstashInputConsumer 28 29 /** 30 * A helper [TouchController] for [TaskbarDragLayerController], specifically to handle touch events 31 * to stash Transient Taskbar. There are two cases to handle: 32 * - A touch outside of Transient Taskbar bounds will immediately stash on [MotionEvent.ACTION_DOWN] 33 * or [MotionEvent.ACTION_OUTSIDE]. 34 * - Touches inside Transient Taskbar bounds will stash if it is detected as a swipe down gesture. 35 * 36 * Note: touches to *unstash* Taskbar are handled by [TaskbarUnstashInputConsumer]. 37 */ 38 class TaskbarStashViaTouchController(val controllers: TaskbarControllers) : TouchController { 39 40 private val activity: TaskbarActivityContext = controllers.taskbarActivityContext 41 private val enabled = activity.isTransientTaskbar 42 private val swipeDownDetector: SingleAxisSwipeDetector 43 private val translationCallback = controllers.taskbarTranslationController.transitionCallback 44 /** Interpolator to apply resistance as user swipes down to the bottom of the screen. */ 45 private val displacementInterpolator = LINEAR 46 /** How far we can translate the TaskbarView before it's offscreen. */ 47 private val maxVisualDisplacement = 48 activity.resources.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin).toFloat() 49 /** How far the swipe could go, if user swiped from the very top of TaskbarView. */ 50 private val maxTouchDisplacement = maxVisualDisplacement + activity.deviceProfile.taskbarHeight 51 private val touchDisplacementToStash = 52 activity.resources.getDimensionPixelSize(R.dimen.taskbar_to_nav_threshold).toFloat() 53 54 /** The height of the system gesture region, so we don't stash when touching down there. */ 55 private var gestureHeightYThreshold = 0f 56 57 init { 58 updateGestureHeight() 59 swipeDownDetector = SingleAxisSwipeDetector(activity, createSwipeListener(), VERTICAL) 60 swipeDownDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false) 61 } 62 updateGestureHeightnull63 fun updateGestureHeight() { 64 if (!enabled) return 65 66 val gestureHeight: Int = 67 ResourceUtils.getNavbarSize( 68 ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, 69 activity.resources, 70 ) 71 gestureHeightYThreshold = (activity.deviceProfile.heightPx - gestureHeight).toFloat() 72 } 73 createSwipeListenernull74 private fun createSwipeListener() = 75 object : SingleAxisSwipeDetector.Listener { 76 private var lastDisplacement = 0f 77 78 override fun onDragStart(start: Boolean, startDisplacement: Float) {} 79 80 override fun onDrag(displacement: Float): Boolean { 81 lastDisplacement = displacement 82 if (displacement < 0) return false 83 // Apply resistance so that the visual displacement doesn't go beyond the screen. 84 translationCallback.onActionMove( 85 Utilities.mapToRange( 86 displacement, 87 0f, 88 maxTouchDisplacement, 89 0f, 90 maxVisualDisplacement, 91 displacementInterpolator, 92 ) 93 ) 94 return false 95 } 96 97 override fun onDragEnd(velocity: Float) { 98 val isFlingDown = swipeDownDetector.isFling(velocity) && velocity > 0 99 val isSignificantDistance = lastDisplacement > touchDisplacementToStash 100 if (isFlingDown || isSignificantDistance) { 101 // Successfully triggered stash. 102 controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true) 103 } 104 translationCallback.onActionEnd() 105 swipeDownDetector.finishedScrolling() 106 } 107 } 108 onControllerInterceptTouchEventnull109 override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean { 110 if (!enabled) { 111 return false 112 } 113 val bubbleControllers = controllers.bubbleControllers.orElse(null) 114 if (bubbleControllers != null && bubbleControllers.bubbleBarViewController.isExpanded) { 115 return false 116 } 117 if ( 118 (bubbleControllers == null || bubbleControllers.bubbleStashController.isStashed) && 119 controllers.taskbarStashController.isStashed 120 ) { 121 return false 122 } 123 124 val screenCoordinatesEv = MotionEvent.obtain(ev) 125 screenCoordinatesEv.setLocation(ev.rawX, ev.rawY) 126 if (ev.action == MotionEvent.ACTION_OUTSIDE) { 127 controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true) 128 } else if (controllers.taskbarViewController.isEventOverAnyItem(screenCoordinatesEv)) { 129 swipeDownDetector.onTouchEvent(ev) 130 if (swipeDownDetector.isDraggingState) { 131 return true 132 } 133 } else if (ev.action == MotionEvent.ACTION_DOWN) { 134 val isDownOnBubbleBar = 135 (bubbleControllers != null && 136 bubbleControllers.bubbleBarViewController.isEventOverAnyItem( 137 screenCoordinatesEv 138 )) 139 if (!isDownOnBubbleBar && screenCoordinatesEv.y < gestureHeightYThreshold) { 140 controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true) 141 } 142 } 143 return false 144 } 145 onControllerTouchEventnull146 override fun onControllerTouchEvent(ev: MotionEvent) = swipeDownDetector.onTouchEvent(ev) 147 } 148