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 ) { 37 sealed class MotionEventModel { 38 object Other : MotionEventModel() 39 40 data class Down( 41 val x: Int, 42 val y: Int, 43 ) : MotionEventModel() 44 45 data class Move( 46 val distanceMoved: Float, 47 ) : MotionEventModel() 48 49 data class Up( 50 val distanceMoved: Float, 51 val gestureDuration: Long, 52 ) : MotionEventModel() 53 54 object Cancel : MotionEventModel() 55 } 56 57 var isLongPressHandlingEnabled: Boolean = false 58 var scheduledLongPressHandle: DisposableHandle? = null 59 60 fun onTouchEvent(event: MotionEventModel?): Boolean { 61 if (!isLongPressHandlingEnabled) { 62 return false 63 } 64 65 return when (event) { 66 is MotionEventModel.Down -> { 67 scheduleLongPress(event.x, event.y) 68 true 69 } 70 is MotionEventModel.Move -> { 71 if (event.distanceMoved > ViewConfiguration.getTouchSlop()) { 72 cancelScheduledLongPress() 73 } 74 false 75 } 76 is MotionEventModel.Up -> { 77 cancelScheduledLongPress() 78 if ( 79 event.distanceMoved <= ViewConfiguration.getTouchSlop() && 80 event.gestureDuration < ViewConfiguration.getLongPressTimeout() 81 ) { 82 dispatchSingleTap() 83 } 84 false 85 } 86 is MotionEventModel.Cancel -> { 87 cancelScheduledLongPress() 88 false 89 } 90 else -> false 91 } 92 } 93 94 private fun scheduleLongPress( 95 x: Int, 96 y: Int, 97 ) { 98 scheduledLongPressHandle = 99 postDelayed( 100 { 101 dispatchLongPress( 102 x = x, 103 y = y, 104 ) 105 }, 106 ViewConfiguration.getLongPressTimeout().toLong(), 107 ) 108 } 109 110 private fun dispatchLongPress( 111 x: Int, 112 y: Int, 113 ) { 114 if (!isAttachedToWindow()) { 115 return 116 } 117 118 onLongPressDetected(x, y) 119 } 120 121 private fun cancelScheduledLongPress() { 122 scheduledLongPressHandle?.dispose() 123 } 124 125 private fun dispatchSingleTap() { 126 if (!isAttachedToWindow()) { 127 return 128 } 129 130 onSingleTapDetected() 131 } 132 } 133