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