1 /*
2  * Copyright (C) 2021 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 
17 package com.android.systemui.statusbar.gesture
18 
19 import android.content.Context
20 import android.view.InputEvent
21 import android.view.MotionEvent
22 import android.view.MotionEvent.ACTION_CANCEL
23 import android.view.MotionEvent.ACTION_DOWN
24 import android.view.MotionEvent.ACTION_MOVE
25 import android.view.MotionEvent.ACTION_UP
26 import com.android.systemui.dagger.SysUISingleton
27 import com.android.systemui.settings.DisplayTracker
28 
29 /**
30  * A class to detect a generic "swipe up" gesture. To be notified when the swipe up gesture is
31  * detected, add a callback via [addOnGestureDetectedCallback].
32  */
33 @SysUISingleton
34 abstract class SwipeUpGestureHandler(
35     context: Context,
36     displayTracker: DisplayTracker,
37     private val logger: SwipeUpGestureLogger,
38     private val loggerTag: String,
39 ) : GenericGestureDetector(
40         SwipeUpGestureHandler::class.simpleName!!,
41         displayTracker.defaultDisplayId
42 ) {
43 
44     private var startY: Float = 0f
45     private var startTime: Long = 0L
46     private var monitoringCurrentTouch: Boolean = false
47 
48     private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize(
49         com.android.internal.R.dimen.system_gestures_distance_threshold
50     )
51 
onInputEventnull52     override fun onInputEvent(ev: InputEvent) {
53         if (ev !is MotionEvent) {
54             return
55         }
56 
57         when (ev.actionMasked) {
58             ACTION_DOWN -> {
59                 if (
60                     startOfGestureIsWithinBounds(ev)
61                 ) {
62                     logger.logGestureDetectionStarted(loggerTag, ev.y.toInt())
63                     startY = ev.y
64                     startTime = ev.eventTime
65                     monitoringCurrentTouch = true
66                 } else {
67                     monitoringCurrentTouch = false
68                 }
69             }
70             ACTION_MOVE -> {
71                 if (!monitoringCurrentTouch) {
72                     return
73                 }
74                 if (
75                     // Gesture is up
76                     ev.y < startY &&
77                     // Gesture went far enough
78                     (startY - ev.y) >= swipeDistanceThreshold &&
79                     // Gesture completed quickly enough
80                     (ev.eventTime - startTime) < SWIPE_TIMEOUT_MS
81                 ) {
82                     monitoringCurrentTouch = false
83                     logger.logGestureDetected(loggerTag, ev.y.toInt())
84                     onGestureDetected(ev)
85                 }
86             }
87             ACTION_CANCEL, ACTION_UP -> {
88                 if (monitoringCurrentTouch) {
89                     logger.logGestureDetectionEndedWithoutTriggering(loggerTag, ev.y.toInt())
90                 }
91                 monitoringCurrentTouch = false
92             }
93         }
94     }
95 
96     /**
97      * Returns true if the [ACTION_DOWN] event falls within bounds for this specific swipe-up
98      * gesture.
99      *
100      * Implementations must override this method to specify what part(s) of the screen are valid
101      * locations for the swipe up gesture to start at.
102      */
startOfGestureIsWithinBoundsnull103     abstract fun startOfGestureIsWithinBounds(ev: MotionEvent): Boolean
104 
105     override fun startGestureListening() {
106         super.startGestureListening()
107         logger.logInputListeningStarted(loggerTag)
108     }
109 
stopGestureListeningnull110     override fun stopGestureListening() {
111         super.stopGestureListening()
112         logger.logInputListeningStopped(loggerTag)
113     }
114 }
115 
116 private const val SWIPE_TIMEOUT_MS: Long = 500
117