Add timeouts to awaitDeferredWatchFace* and fix watchfaceOverlayStyle NPE
This patch adds a very conservative 10s timeout to
awaitDeferredWatchFaceImplThenRunOnUiThreadBlocking and
awaitDeferredWatchFaceAndComplicationManagerThenRunOnBinderThread.
In normal circumstances this shouldn't timeout including in OOBE
and DirectBoot scenarios where the CPU load is high. We've also
fixed a NPE if getWatchfaceOverlayStyle is called after close().
Test: Added a test for the NPE
Bug: 233574644
Change-Id: Idf71a26e966e164ab9827ddf108f6e6bd8fa0ab7
(cherry picked from commit d02de8fd4df50ab634bf0ab41d4381ac62dd20e2)
Merged-In: Idf71a26e966e164ab9827ddf108f6e6bd8fa0ab7
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
index 53fe878..d41679a 100644
--- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
@@ -1566,6 +1566,37 @@
}
@Test
+ fun watchfaceOverlayStyle_after_close() {
+ val wallpaperService = TestWatchfaceOverlayStyleWatchFaceService(
+ context,
+ surfaceHolder,
+ WatchFace.OverlayStyle(Color.valueOf(Color.RED), Color.valueOf(Color.BLACK))
+ )
+ val deferredInteractiveInstance = handlerCoroutineScope.async {
+ service.getOrCreateInteractiveWatchFaceClient(
+ "testId",
+ deviceConfig,
+ systemState,
+ null,
+ complications
+ )
+ }
+
+ // Create the engine which triggers creation of the interactive instance.
+ handler.post {
+ engine = wallpaperService.onCreateEngine() as WatchFaceService.EngineWrapper
+ }
+
+ // Wait for the instance to be created.
+ val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+
+ interactiveInstance.close()
+
+ assertThat(interactiveInstance.overlayStyle.backgroundColor).isNull()
+ assertThat(interactiveInstance.overlayStyle.foregroundColor).isNull()
+ }
+
+ @Test
fun computeUserStyleSchemaDigestHash() {
val headlessInstance1 = service.createHeadlessWatchFaceClient(
"id",
diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
index 9be90c8..1a3718d 100644
--- a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
+++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
@@ -332,15 +332,12 @@
override val overlayStyle: OverlayStyle
get() {
- return if (iInteractiveWatchFace.apiVersion >= 4) {
- val wireFormat = iInteractiveWatchFace.watchFaceOverlayStyle
- OverlayStyle(
- wireFormat.backgroundColor,
- wireFormat.foregroundColor
- )
- } else {
- OverlayStyle(null, null)
+ if (iInteractiveWatchFace.apiVersion >= 4) {
+ iInteractiveWatchFace.watchFaceOverlayStyle?.let {
+ return OverlayStyle(it.backgroundColor, it.foregroundColor)
+ }
}
+ return OverlayStyle(null, null)
}
override fun updateWatchFaceInstance(newInstanceId: String, userStyle: UserStyle) = TraceEvent(
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index cf14c70..c4ade27 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -98,6 +98,7 @@
import java.time.Instant
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeout
/** The wire format for [ComplicationData]. */
internal typealias WireComplicationData = android.support.wearable.complications.ComplicationData
@@ -317,6 +318,12 @@
/** The maximum permitted duration of [WatchFaceService.createWatchFace]. */
public const val MAX_CREATE_WATCHFACE_TIME_MILLIS: Int = 5000
+ /**
+ * The maximum delay for [awaitDeferredWatchFaceImplThenRunOnUiThreadBlocking] and
+ * [awaitDeferredWatchFaceAndComplicationManagerThenRunOnBinderThread] in milliseconds.
+ */
+ private const val AWAIT_DEFERRED_TIMEOUT = 10000L
+
/** The maximum reasonable wire size for a [UserStyleSchema] in bytes. */
internal const val MAX_REASONABLE_SCHEMA_WIRE_SIZE_BYTES = 50000
@@ -341,9 +348,11 @@
}
runBlocking {
try {
- val watchFaceImpl = engine.deferredWatchFaceImpl.await()
- withContext(engine.uiThreadCoroutineScope.coroutineContext) {
- task(watchFaceImpl)
+ withTimeout(AWAIT_DEFERRED_TIMEOUT) {
+ val watchFaceImpl = engine.deferredWatchFaceImpl.await()
+ withContext(engine.uiThreadCoroutineScope.coroutineContext) {
+ task(watchFaceImpl)
+ }
}
} catch (e: Exception) {
Log.e(HeadlessWatchFaceImpl.TAG, "Operation failed", e)
@@ -352,7 +361,7 @@
}
}
- internal fun <R> deferredWatchFaceAndComplicationManagerThenRunOnBinderThread(
+ internal fun <R> awaitDeferredWatchFaceAndComplicationManagerThenRunOnBinderThread(
engine: WatchFaceService.EngineWrapper?,
traceName: String,
task: (watchFaceInitDetails: WatchFaceService.WatchFaceInitDetails) -> R
@@ -363,7 +372,9 @@
}
runBlocking {
try {
- task(engine.watchFaceInitDetails.await())
+ withTimeout(AWAIT_DEFERRED_TIMEOUT) {
+ task(engine.watchFaceInitDetails.await())
+ }
} catch (e: Exception) {
Log.e(HeadlessWatchFaceImpl.TAG, "Operation failed", e)
throw e
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt
index 4320488..40dfec1 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt
@@ -98,7 +98,7 @@
) { watchFaceImpl -> watchFaceImpl.renderComplicationToBitmap(params) }
override fun getUserStyleSchema() =
- WatchFaceService.deferredWatchFaceAndComplicationManagerThenRunOnBinderThread(
+ WatchFaceService.awaitDeferredWatchFaceAndComplicationManagerThenRunOnBinderThread(
engine,
"HeadlessWatchFaceImpl.getUserStyleSchema"
) { watchFaceInitDetails ->
@@ -106,7 +106,7 @@
}
override fun computeUserStyleSchemaDigestHash() =
- WatchFaceService.deferredWatchFaceAndComplicationManagerThenRunOnBinderThread(
+ WatchFaceService.awaitDeferredWatchFaceAndComplicationManagerThenRunOnBinderThread(
engine,
"HeadlessWatchFaceImpl.computeUserStyleSchemaDigestHash"
) { watchFaceInitDetails ->
@@ -115,7 +115,7 @@
@OptIn(WatchFaceFlavorsExperimental::class)
override fun getUserStyleFlavors() =
- WatchFaceService.deferredWatchFaceAndComplicationManagerThenRunOnBinderThread(
+ WatchFaceService.awaitDeferredWatchFaceAndComplicationManagerThenRunOnBinderThread(
engine,
"HeadlessWatchFaceImpl.getUserStyleFlavors"
) { watchFaceInitDetails ->
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
index 17282bf..48adaeb 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
@@ -33,6 +33,8 @@
import androidx.wear.watchface.style.data.UserStyleWireFormat
import kotlinx.coroutines.launch
import java.time.Instant
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
/** An interactive watch face instance with SysUI and WCS facing interfaces.*/
internal class InteractiveWatchFaceImpl(
@@ -65,7 +67,7 @@
override fun unused18() {}
override fun getWatchFaceOverlayStyle(): WatchFaceOverlayStyleWireFormat? =
- WatchFaceService.deferredWatchFaceAndComplicationManagerThenRunOnBinderThread(
+ WatchFaceService.awaitDeferredWatchFaceAndComplicationManagerThenRunOnBinderThread(
engine,
"InteractiveWatchFaceImpl.getWatchFaceOverlayStyle"
) { watchFaceInitDetails ->
@@ -113,15 +115,19 @@
}
override fun release(): Unit = TraceEvent("InteractiveWatchFaceImpl.release").use {
- uiThreadCoroutineScope.launch {
- engine?.let {
- try {
- it.deferredWatchFaceImpl.await()
- } catch (e: Exception) {
- // deferredWatchFaceImpl may have completed with an exception. This will
- // have already been reported so we can ignore it.
+ // Note this is a one way method called on a binder thread, so it shouldn't matter if we
+ // block.
+ runBlocking {
+ withContext(uiThreadCoroutineScope.coroutineContext) {
+ engine?.let {
+ try {
+ it.deferredWatchFaceImpl.await()
+ } catch (e: Exception) {
+ // deferredWatchFaceImpl may have completed with an exception. This will
+ // have already been reported so we can ignore it.
+ }
+ InteractiveInstanceManager.releaseInstance(instanceId)
}
- InteractiveInstanceManager.releaseInstance(instanceId)
}
}
}
@@ -177,8 +183,11 @@
}
fun onDestroy() {
- uiThreadCoroutineScope.launch {
- engine = null
+ // Note this is almost certainly called on the ui thread, from release() above.
+ runBlocking {
+ withContext(uiThreadCoroutineScope.coroutineContext) {
+ engine = null
+ }
}
}
}