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