• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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