mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
fix(android): stop canceled work from surfacing failures (#105047)
* fix(android): propagate request cancellation * chore: keep release notes in PR body
This commit is contained in:
committed by
GitHub
parent
ae3008c70a
commit
46355cbdcf
@@ -7,6 +7,7 @@ import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.LocationManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
@@ -160,6 +161,8 @@ class LocationHandler private constructor(
|
||||
code = "LOCATION_TIMEOUT",
|
||||
message = "LOCATION_TIMEOUT: no fix in time",
|
||||
)
|
||||
} catch (err: CancellationException) {
|
||||
throw err
|
||||
} catch (err: Throwable) {
|
||||
val message = err.message ?: "LOCATION_UNAVAILABLE: no fix"
|
||||
return GatewaySession.InvokeResult.error(code = "LOCATION_UNAVAILABLE", message = message)
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import android.os.SystemClock
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.InternalCoroutinesApi
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
@@ -317,6 +318,8 @@ class MotionHandler private constructor(
|
||||
)
|
||||
} catch (err: IllegalArgumentException) {
|
||||
GatewaySession.InvokeResult.error(code = "MOTION_UNAVAILABLE", message = err.message ?: "MOTION_UNAVAILABLE")
|
||||
} catch (err: CancellationException) {
|
||||
throw err
|
||||
} catch (err: Throwable) {
|
||||
GatewaySession.InvokeResult.error(
|
||||
code = "MOTION_UNAVAILABLE",
|
||||
@@ -353,6 +356,8 @@ class MotionHandler private constructor(
|
||||
)
|
||||
} catch (err: IllegalArgumentException) {
|
||||
GatewaySession.InvokeResult.error(code = "MOTION_UNAVAILABLE", message = err.message ?: "MOTION_UNAVAILABLE")
|
||||
} catch (err: CancellationException) {
|
||||
throw err
|
||||
} catch (err: Throwable) {
|
||||
GatewaySession.InvokeResult.error(
|
||||
code = "MOTION_UNAVAILABLE",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ai.openclaw.app.voice
|
||||
|
||||
import ai.openclaw.app.gateway.GatewaySession
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
@@ -58,6 +59,8 @@ internal class TalkSpeakClient(
|
||||
paramsJson = json.encodeToString(TalkSpeakRequest.from(text = text, directive = directive)),
|
||||
timeoutMs = 45_000,
|
||||
)
|
||||
} catch (err: CancellationException) {
|
||||
throw err
|
||||
} catch (err: Throwable) {
|
||||
return TalkSpeakResult.Failure(err.message ?: "talk.speak request failed")
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ package ai.openclaw.app.node
|
||||
import ai.openclaw.app.LocationMode
|
||||
import android.content.Context
|
||||
import android.location.LocationManager
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Test
|
||||
|
||||
class LocationHandlerTest : NodeHandlerRobolectricTest() {
|
||||
@@ -205,6 +207,28 @@ class LocationHandlerTest : NodeHandlerRobolectricTest() {
|
||||
assertEquals("LOCATION_UNAVAILABLE", result.error?.code)
|
||||
assertEquals("gps offline", result.error?.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleLocationGet_propagatesParentCancellation() =
|
||||
runTest {
|
||||
val handler =
|
||||
LocationHandler.forTesting(
|
||||
appContext = appContext(),
|
||||
dataSource =
|
||||
FakeLocationDataSource(
|
||||
fineGranted = true,
|
||||
coarseGranted = true,
|
||||
failure = CancellationException("request retired"),
|
||||
),
|
||||
)
|
||||
|
||||
try {
|
||||
handler.handleLocationGet(null)
|
||||
fail("expected cancellation to propagate")
|
||||
} catch (err: CancellationException) {
|
||||
assertEquals("request retired", err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeLocationDataSource(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ai.openclaw.app.node
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
@@ -9,6 +10,7 @@ import kotlinx.serialization.json.jsonPrimitive
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Test
|
||||
|
||||
class MotionHandlerTest : NodeHandlerRobolectricTest() {
|
||||
@@ -89,6 +91,46 @@ class MotionHandlerTest : NodeHandlerRobolectricTest() {
|
||||
assertEquals("MOTION_UNAVAILABLE", result.error?.code)
|
||||
assertTrue(result.error?.message?.contains("PEDOMETER_RANGE_UNAVAILABLE") == true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleMotionActivity_propagatesParentCancellation() =
|
||||
runTest {
|
||||
val handler =
|
||||
MotionHandler.forTesting(
|
||||
appContext(),
|
||||
FakeMotionDataSource(
|
||||
hasPermission = true,
|
||||
activityError = CancellationException("invoke retired"),
|
||||
),
|
||||
)
|
||||
|
||||
try {
|
||||
handler.handleMotionActivity(null)
|
||||
fail("expected cancellation to propagate")
|
||||
} catch (err: CancellationException) {
|
||||
assertEquals("invoke retired", err.message)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleMotionPedometer_propagatesParentCancellation() =
|
||||
runTest {
|
||||
val handler =
|
||||
MotionHandler.forTesting(
|
||||
appContext(),
|
||||
FakeMotionDataSource(
|
||||
hasPermission = true,
|
||||
pedometerError = CancellationException("invoke retired"),
|
||||
),
|
||||
)
|
||||
|
||||
try {
|
||||
handler.handleMotionPedometer(null)
|
||||
fail("expected cancellation to propagate")
|
||||
} catch (err: CancellationException) {
|
||||
assertEquals("invoke retired", err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeMotionDataSource(
|
||||
|
||||
@@ -2,9 +2,11 @@ package ai.openclaw.app.voice
|
||||
|
||||
import ai.openclaw.app.gateway.GatewayConnectErrorDetails
|
||||
import ai.openclaw.app.gateway.GatewaySession
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Test
|
||||
|
||||
class TalkSpeakClientTest {
|
||||
@@ -128,4 +130,20 @@ class TalkSpeakClientTest {
|
||||
val result = client.synthesize(text = "Hello", directive = null)
|
||||
assertTrue(result is TalkSpeakResult.FallbackToLocal)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun propagatesRequestCancellation() =
|
||||
runTest {
|
||||
val client =
|
||||
TalkSpeakClient(
|
||||
requestDetailed = { _, _, _ -> throw CancellationException("talk stopped") },
|
||||
)
|
||||
|
||||
try {
|
||||
client.synthesize(text = "Hello", directive = null)
|
||||
fail("expected cancellation to propagate")
|
||||
} catch (err: CancellationException) {
|
||||
assertEquals("talk stopped", err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user