summaryrefslogtreecommitdiff
path: root/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt
blob: 1cc667211c02d0b43853acb31a4b6cfef5a3a913 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.launcher3.taskbar

import android.view.MotionEvent
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.anim.Interpolators.LINEAR
import com.android.launcher3.testing.shared.ResourceUtils
import com.android.launcher3.touch.SingleAxisSwipeDetector
import com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE
import com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.TouchController
import com.android.quickstep.inputconsumers.TaskbarUnstashInputConsumer

/**
 * A helper [TouchController] for [TaskbarDragLayerController], specifically to handle touch events
 * to stash Transient Taskbar. There are two cases to handle:
 * - A touch outside of Transient Taskbar bounds will immediately stash on [MotionEvent.ACTION_DOWN]
 *   or [MotionEvent.ACTION_OUTSIDE].
 * - Touches inside Transient Taskbar bounds will stash if it is detected as a swipe down gesture.
 *
 * Note: touches to *unstash* Taskbar are handled by [TaskbarUnstashInputConsumer].
 */
class TaskbarStashViaTouchController(val controllers: TaskbarControllers) : TouchController {

    private val activity: TaskbarActivityContext = controllers.taskbarActivityContext
    private val enabled = DisplayController.isTransientTaskbar(activity)
    private val swipeDownDetector: SingleAxisSwipeDetector
    private val translationCallback = controllers.taskbarTranslationController.transitionCallback
    /** Interpolator to apply resistance as user swipes down to the bottom of the screen. */
    private val displacementInterpolator = LINEAR
    /** How far we can translate the TaskbarView before it's offscreen. */
    private val maxVisualDisplacement =
        activity.resources.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin).toFloat()
    /** How far the swipe could go, if user swiped from the very top of TaskbarView. */
    private val maxTouchDisplacement = maxVisualDisplacement + activity.deviceProfile.taskbarHeight
    private val touchDisplacementToStash =
        activity.resources.getDimensionPixelSize(R.dimen.taskbar_to_nav_threshold).toFloat()

    /** The height of the system gesture region, so we don't stash when touching down there. */
    private var gestureHeightYThreshold = 0f

    init {
        updateGestureHeight()
        swipeDownDetector = SingleAxisSwipeDetector(activity, createSwipeListener(), VERTICAL)
        swipeDownDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false)
    }

    fun updateGestureHeight() {
        if (!enabled) return

        val gestureHeight: Int =
            ResourceUtils.getNavbarSize(
                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
                activity.resources
            )
        gestureHeightYThreshold = (activity.deviceProfile.heightPx - gestureHeight).toFloat()
    }

    private fun createSwipeListener() =
        object : SingleAxisSwipeDetector.Listener {
            private var lastDisplacement = 0f

            override fun onDragStart(start: Boolean, startDisplacement: Float) {}

            override fun onDrag(displacement: Float): Boolean {
                lastDisplacement = displacement
                if (displacement < 0) return false
                // Apply resistance so that the visual displacement doesn't go beyond the screen.
                translationCallback.onActionMove(
                    Utilities.mapToRange(
                        displacement,
                        0f,
                        maxTouchDisplacement,
                        0f,
                        maxVisualDisplacement,
                        displacementInterpolator
                    )
                )
                return false
            }

            override fun onDragEnd(velocity: Float) {
                val isFlingDown = swipeDownDetector.isFling(velocity) && velocity > 0
                val isSignificantDistance = lastDisplacement > touchDisplacementToStash
                if (isFlingDown || isSignificantDistance) {
                    // Successfully triggered stash.
                    controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
                }
                translationCallback.onActionEnd()
                swipeDownDetector.finishedScrolling()
            }
        }

    override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
        if (!enabled || controllers.taskbarStashController.isStashed) {
            return false
        }

        val screenCoordinatesEv = MotionEvent.obtain(ev)
        screenCoordinatesEv.setLocation(ev.rawX, ev.rawY)
        if (ev.action == MotionEvent.ACTION_OUTSIDE) {
            controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
        } else if (controllers.taskbarViewController.isEventOverAnyItem(screenCoordinatesEv)) {
            swipeDownDetector.onTouchEvent(ev)
            if (swipeDownDetector.isDraggingState) {
                return true
            }
        } else if (ev.action == MotionEvent.ACTION_DOWN) {
            if (screenCoordinatesEv.y < gestureHeightYThreshold) {
                controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
            }
        }
        return false
    }

    override fun onControllerTouchEvent(ev: MotionEvent) = swipeDownDetector.onTouchEvent(ev)
}