• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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