diff --git a/core/common/actions/src/main/java/com/buzbuz/smartautoclicker/core/common/actions/gesture/GestureExecutor.kt b/core/common/actions/src/main/java/com/buzbuz/smartautoclicker/core/common/actions/gesture/GestureExecutor.kt
index 85d7d1126..4364b4244 100644
--- a/core/common/actions/src/main/java/com/buzbuz/smartautoclicker/core/common/actions/gesture/GestureExecutor.kt
+++ b/core/common/actions/src/main/java/com/buzbuz/smartautoclicker/core/common/actions/gesture/GestureExecutor.kt
@@ -23,13 +23,15 @@ import android.util.Log
import com.buzbuz.smartautoclicker.core.base.Dumpable
import com.buzbuz.smartautoclicker.core.base.addDumpTabulationLvl
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withTimeoutOrNull
import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
+import kotlin.time.Duration.Companion.milliseconds
@Singleton
@@ -60,17 +62,29 @@ internal class GestureExecutor @Inject constructor() : Dumpable {
}
resultCallback = resultCallback ?: newGestureResultCallback()
- return suspendCoroutine { continuation ->
- currentContinuation = continuation
- try {
- service.dispatchGesture(gesture, resultCallback, null)
- } catch (rEx: RuntimeException) {
- Log.w(TAG, "System is not responsive, the user might be spamming gesture too quickly", rEx)
- errorGestures++
- resumeExecution(gestureError = true)
+ val timeoutMs = gesture.gestureDurationMs() * GESTURE_CALLBACK_TIMEOUT_DURATION_RATIO_MS
+ val result = withTimeoutOrNull(timeoutMs.milliseconds) {
+ suspendCancellableCoroutine { continuation ->
+ currentContinuation = continuation
+
+ try {
+ service.dispatchGesture(gesture, resultCallback, null)
+ } catch (rEx: RuntimeException) {
+ Log.w(TAG, "System is not responsive, the user might be spamming gesture too quickly", rEx)
+ errorGestures++
+ resumeExecution(gestureError = true)
+ }
}
}
+
+ if (result == null) {
+ Log.w(TAG, "Gesture timed out after ${timeoutMs}ms, no callback received")
+ errorGestures++
+ currentContinuation = null
+ }
+
+ return result ?: false
}
private fun resumeExecution(gestureError: Boolean) {
@@ -109,4 +123,14 @@ internal class GestureExecutor @Inject constructor() : Dumpable {
}
}
+private fun GestureDescription.gestureDurationMs(): Long {
+ var maxEndTime = 0L
+ for (i in 0 until strokeCount) {
+ val stroke = getStroke(i)
+ maxEndTime = maxOf(maxEndTime, stroke.startTime + stroke.duration)
+ }
+ return maxEndTime
+}
+
+private const val GESTURE_CALLBACK_TIMEOUT_DURATION_RATIO_MS = 1.25
private const val TAG = "GestureExecutor"
\ No newline at end of file
diff --git a/core/common/overlays/src/main/java/com/buzbuz/smartautoclicker/core/common/overlays/menu/implementation/common/OverlayMenuAnimations.kt b/core/common/overlays/src/main/java/com/buzbuz/smartautoclicker/core/common/overlays/menu/implementation/common/OverlayMenuAnimations.kt
index d6a446c62..e91dbce44 100644
--- a/core/common/overlays/src/main/java/com/buzbuz/smartautoclicker/core/common/overlays/menu/implementation/common/OverlayMenuAnimations.kt
+++ b/core/common/overlays/src/main/java/com/buzbuz/smartautoclicker/core/common/overlays/menu/implementation/common/OverlayMenuAnimations.kt
@@ -60,13 +60,23 @@ internal class OverlayMenuAnimations : Dumpable {
fun startShowAnimation(view: View, overlayView: View? = null, onAnimationEnded: () -> Unit) {
if (showAnimationIsRunning) return
+ showAnimationIsRunning = true
Log.d(TAG, "Start show animation on view ${view} with visibility ${view.visibility}")
- showAnimationIsRunning = true
+ val timeoutCallback = Runnable {
+ Log.w(TAG, "Show animation timeout !")
+ showAnimationIsRunning = false
+ showOverlayMenuAnimation.cancel()
+ showOverlayViewAnimation.cancel()
+ onAnimationEnded()
+ }
+ view.postDelayed(timeoutCallback, 1_000)
+
showOverlayMenuAnimation.setListener(
end = {
Log.d(TAG, "Show animation ended")
+ view.removeCallbacks(timeoutCallback)
showAnimationIsRunning = false
onAnimationEnded()
}
@@ -88,13 +98,23 @@ internal class OverlayMenuAnimations : Dumpable {
fun startHideAnimation(view: View, overlayView: View? = null, onAnimationEnded: () -> Unit) {
if (hideAnimationIsRunning) return
+ hideAnimationIsRunning = true
Log.d(TAG, "Start hide animation")
- hideAnimationIsRunning = true
+ val timeoutCallback = Runnable {
+ Log.w(TAG, "Hide animation timeout !")
+ hideAnimationIsRunning = false
+ hideOverlayMenuAnimation.cancel()
+ hideOverlayViewAnimation.cancel()
+ onAnimationEnded()
+ }
+
+ view.postDelayed(timeoutCallback, 1_000)
hideOverlayMenuAnimation.setListener(
end = {
Log.d(TAG, "Hide animation ended")
+ view.removeCallbacks(timeoutCallback)
hideAnimationIsRunning = false
onAnimationEnded()
}
diff --git a/core/common/ui/src/main/res/values/styles.xml b/core/common/ui/src/main/res/values/styles.xml
index fed2cb7a8..3c7fb22a8 100644
--- a/core/common/ui/src/main/res/values/styles.xml
+++ b/core/common/ui/src/main/res/values/styles.xml
@@ -73,7 +73,7 @@
- ?attr/colorOutlineVariant
-