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