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