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.launcher3.R 20 import com.android.launcher3.Utilities 21 import com.android.launcher3.anim.Interpolators.LINEAR 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.TaskbarStashInputConsumer 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 [TaskbarStashInputConsumer]. 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 || controllers.taskbarStashController.isStashed) { 112 return false 113 } 114 115 val screenCoordinatesEv = MotionEvent.obtain(ev) 116 screenCoordinatesEv.setLocation(ev.rawX, ev.rawY) 117 if (ev.action == MotionEvent.ACTION_OUTSIDE) { 118 controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true) 119 } else if (controllers.taskbarViewController.isEventOverAnyItem(screenCoordinatesEv)) { 120 swipeDownDetector.onTouchEvent(ev) 121 if (swipeDownDetector.isDraggingState) { 122 return true 123 } 124 } else if (ev.action == MotionEvent.ACTION_DOWN) { 125 if (screenCoordinatesEv.y < gestureHeightYThreshold) { 126 controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true) 127 } 128 } 129 return false 130 } 131 onControllerTouchEventnull132 override fun onControllerTouchEvent(ev: MotionEvent) = swipeDownDetector.onTouchEvent(ev) 133 } 134