blob: 323070a848633ebfa05459b0f71f2d448b983bb6 [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.systemui.common.ui.view
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.android.systemui.shade.TouchLogger
import kotlin.math.pow
import kotlin.math.sqrt
import kotlinx.coroutines.DisposableHandle
/**
* View designed to handle long-presses.
*
* The view will not handle any long pressed by default. To set it up, set up a listener and, when
* ready to start consuming long-presses, set [setLongPressHandlingEnabled] to `true`.
*/
class LongPressHandlingView(
context: Context,
attrs: AttributeSet?,
) :
View(
context,
attrs,
) {
interface Listener {
/** Notifies that a long-press has been detected by the given view. */
fun onLongPressDetected(
view: View,
x: Int,
y: Int,
)
/** Notifies that the gesture was too short for a long press, it is actually a click. */
fun onSingleTapDetected(view: View) = Unit
}
var listener: Listener? = null
private val interactionHandler: LongPressHandlingViewInteractionHandler by lazy {
LongPressHandlingViewInteractionHandler(
postDelayed = { block, timeoutMs ->
val dispatchToken = Any()
handler.postDelayed(
block,
dispatchToken,
timeoutMs,
)
DisposableHandle { handler.removeCallbacksAndMessages(dispatchToken) }
},
isAttachedToWindow = ::isAttachedToWindow,
onLongPressDetected = { x, y ->
listener?.onLongPressDetected(
view = this,
x = x,
y = y,
)
},
onSingleTapDetected = { listener?.onSingleTapDetected(this@LongPressHandlingView) },
)
}
fun setLongPressHandlingEnabled(isEnabled: Boolean) {
interactionHandler.isLongPressHandlingEnabled = isEnabled
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
return TouchLogger.logDispatchTouch("long_press", event, super.dispatchTouchEvent(event))
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
return interactionHandler.onTouchEvent(event?.toModel())
}
}
private fun MotionEvent.toModel(): LongPressHandlingViewInteractionHandler.MotionEventModel {
return when (actionMasked) {
MotionEvent.ACTION_DOWN ->
LongPressHandlingViewInteractionHandler.MotionEventModel.Down(
x = x.toInt(),
y = y.toInt(),
)
MotionEvent.ACTION_MOVE ->
LongPressHandlingViewInteractionHandler.MotionEventModel.Move(
distanceMoved = distanceMoved(),
)
MotionEvent.ACTION_UP ->
LongPressHandlingViewInteractionHandler.MotionEventModel.Up(
distanceMoved = distanceMoved(),
gestureDuration = gestureDuration(),
)
MotionEvent.ACTION_CANCEL -> LongPressHandlingViewInteractionHandler.MotionEventModel.Cancel
else -> LongPressHandlingViewInteractionHandler.MotionEventModel.Other
}
}
private fun MotionEvent.distanceMoved(): Float {
return if (historySize > 0) {
sqrt((x - getHistoricalX(0)).pow(2) + (y - getHistoricalY(0)).pow(2))
} else {
0f
}
}
private fun MotionEvent.gestureDuration(): Long {
return eventTime - downTime
}