1 /*
<lambda>null2 * 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.annotation.SuppressLint
21 import android.content.Context
22 import android.util.AttributeSet
23 import android.view.MotionEvent
24 import android.view.View
25 import android.view.ViewConfiguration
26 import com.android.systemui.shade.TouchLogger
27 import kotlin.math.pow
28 import kotlin.math.sqrt
29 import kotlinx.coroutines.DisposableHandle
30
31 /**
32 * View designed to handle long-presses.
33 *
34 * The view will not handle any long pressed by default. To set it up, set up a listener and, when
35 * ready to start consuming long-presses, set [setLongPressHandlingEnabled] to `true`.
36 */
37 class LongPressHandlingView(
38 context: Context,
39 attrs: AttributeSet?,
40 longPressDuration: () -> Long,
41 ) :
42 View(
43 context,
44 attrs,
45 ) {
46
47 constructor(
48 context: Context,
49 attrs: AttributeSet?,
50 ) : this(context, attrs, { ViewConfiguration.getLongPressTimeout().toLong() })
51
52 interface Listener {
53 /** Notifies that a long-press has been detected by the given view. */
54 fun onLongPressDetected(
55 view: View,
56 x: Int,
57 y: Int,
58 )
59
60 /** Notifies that the gesture was too short for a long press, it is actually a click. */
61 fun onSingleTapDetected(view: View) = Unit
62 }
63
64 var listener: Listener? = null
65
66 private val interactionHandler: LongPressHandlingViewInteractionHandler by lazy {
67 LongPressHandlingViewInteractionHandler(
68 postDelayed = { block, timeoutMs ->
69 val dispatchToken = Any()
70
71 handler.postDelayed(
72 block,
73 dispatchToken,
74 timeoutMs,
75 )
76
77 DisposableHandle { handler.removeCallbacksAndMessages(dispatchToken) }
78 },
79 isAttachedToWindow = ::isAttachedToWindow,
80 onLongPressDetected = { x, y ->
81 listener?.onLongPressDetected(
82 view = this,
83 x = x,
84 y = y,
85 )
86 },
87 onSingleTapDetected = { listener?.onSingleTapDetected(this@LongPressHandlingView) },
88 longPressDuration = longPressDuration,
89 )
90 }
91
92 var longPressDuration: () -> Long
93 get() = interactionHandler.longPressDuration
94 set(longPressDuration) {
95 interactionHandler.longPressDuration = longPressDuration
96 }
97
98 fun setLongPressHandlingEnabled(isEnabled: Boolean) {
99 interactionHandler.isLongPressHandlingEnabled = isEnabled
100 }
101
102 override fun dispatchTouchEvent(event: MotionEvent): Boolean {
103 return TouchLogger.logDispatchTouch("long_press", event, super.dispatchTouchEvent(event))
104 }
105
106 @SuppressLint("ClickableViewAccessibility")
107 override fun onTouchEvent(event: MotionEvent?): Boolean {
108 return interactionHandler.onTouchEvent(event?.toModel())
109 }
110 }
111
toModelnull112 private fun MotionEvent.toModel(): LongPressHandlingViewInteractionHandler.MotionEventModel {
113 return when (actionMasked) {
114 MotionEvent.ACTION_DOWN ->
115 LongPressHandlingViewInteractionHandler.MotionEventModel.Down(
116 x = x.toInt(),
117 y = y.toInt(),
118 )
119 MotionEvent.ACTION_MOVE ->
120 LongPressHandlingViewInteractionHandler.MotionEventModel.Move(
121 distanceMoved = distanceMoved(),
122 )
123 MotionEvent.ACTION_UP ->
124 LongPressHandlingViewInteractionHandler.MotionEventModel.Up(
125 distanceMoved = distanceMoved(),
126 gestureDuration = gestureDuration(),
127 )
128 MotionEvent.ACTION_CANCEL -> LongPressHandlingViewInteractionHandler.MotionEventModel.Cancel
129 else -> LongPressHandlingViewInteractionHandler.MotionEventModel.Other
130 }
131 }
132
MotionEventnull133 private fun MotionEvent.distanceMoved(): Float {
134 return if (historySize > 0) {
135 sqrt((x - getHistoricalX(0)).pow(2) + (y - getHistoricalY(0)).pow(2))
136 } else {
137 0f
138 }
139 }
140
gestureDurationnull141 private fun MotionEvent.gestureDuration(): Long {
142 return eventTime - downTime
143 }
144