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 -