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