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 */ 17 18 package com.android.systemui.common.ui.view 19 20 import android.view.ViewConfiguration 21 import kotlinx.coroutines.DisposableHandle 22 23 /** Encapsulates logic to handle complex touch interactions with a [LongPressHandlingView]. */ 24 class LongPressHandlingViewInteractionHandler( 25 /** 26 * Callback to run the given [Runnable] with the given delay, returning a [DisposableHandle] 27 * allowing the delayed runnable to be canceled before it is run. 28 */ 29 private val postDelayed: (block: Runnable, delayMs: Long) -> DisposableHandle, 30 /** Callback to be queried to check if the view is attached to its window. */ 31 private val isAttachedToWindow: () -> Boolean, 32 /** Callback reporting the a long-press gesture was detected at the given coordinates. */ 33 private val onLongPressDetected: (x: Int, y: Int) -> Unit, 34 /** Callback reporting the a single tap gesture was detected at the given coordinates. */ 35 private val onSingleTapDetected: () -> Unit, 36 /** Time for the touch to be considered a long-press in ms */ 37 var longPressDuration: () -> Long, 38 ) { 39 sealed class MotionEventModel { 40 object Other : MotionEventModel() 41 42 data class Down( 43 val x: Int, 44 val y: Int, 45 ) : MotionEventModel() 46 47 data class Move( 48 val distanceMoved: Float, 49 ) : MotionEventModel() 50 51 data class Up( 52 val distanceMoved: Float, 53 val gestureDuration: Long, 54 ) : MotionEventModel() 55 56 object Cancel : MotionEventModel() 57 } 58 59 var isLongPressHandlingEnabled: Boolean = false 60 var scheduledLongPressHandle: DisposableHandle? = null 61 onTouchEventnull62 fun onTouchEvent(event: MotionEventModel?): Boolean { 63 if (!isLongPressHandlingEnabled) { 64 return false 65 } 66 67 return when (event) { 68 is MotionEventModel.Down -> { 69 scheduleLongPress(event.x, event.y) 70 true 71 } 72 is MotionEventModel.Move -> { 73 if (event.distanceMoved > ViewConfiguration.getTouchSlop()) { 74 cancelScheduledLongPress() 75 } 76 false 77 } 78 is MotionEventModel.Up -> { 79 cancelScheduledLongPress() 80 if ( 81 event.distanceMoved <= ViewConfiguration.getTouchSlop() && 82 event.gestureDuration < longPressDuration() 83 ) { 84 dispatchSingleTap() 85 } 86 false 87 } 88 is MotionEventModel.Cancel -> { 89 cancelScheduledLongPress() 90 false 91 } 92 else -> false 93 } 94 } 95 scheduleLongPressnull96 private fun scheduleLongPress( 97 x: Int, 98 y: Int, 99 ) { 100 scheduledLongPressHandle = 101 postDelayed( 102 { 103 dispatchLongPress( 104 x = x, 105 y = y, 106 ) 107 }, 108 longPressDuration(), 109 ) 110 } 111 dispatchLongPressnull112 private fun dispatchLongPress( 113 x: Int, 114 y: Int, 115 ) { 116 if (!isAttachedToWindow()) { 117 return 118 } 119 120 onLongPressDetected(x, y) 121 } 122 cancelScheduledLongPressnull123 private fun cancelScheduledLongPress() { 124 scheduledLongPressHandle?.dispose() 125 } 126 dispatchSingleTapnull127 private fun dispatchSingleTap() { 128 if (!isAttachedToWindow()) { 129 return 130 } 131 132 onSingleTapDetected() 133 } 134 } 135