[go: nahoru, domu]

Get rid of pendingInitialComplications

Now we update complications regardless of the initialization state.
setComplicationDataList yields new value to the stream which is monitored by internal coroutine.
InteractiveWatchFaceClient.updateComplicationData doesn't wait UI thread anymore.

Bug: 258210649
Change-Id: I1f25c390a26df375d49c18942b23ca53e27e10fc
Test: existing tests + manually (complications work)
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 5e8f737..0e94efe 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
@@ -47,6 +47,7 @@
 import android.view.SurfaceHolder
 import android.view.WindowInsets
 import android.view.accessibility.AccessibilityManager
+import androidx.annotation.AnyThread
 import androidx.annotation.Px
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
@@ -102,6 +103,8 @@
 import kotlinx.coroutines.Runnable
 import kotlinx.coroutines.android.asCoroutineDispatcher
 import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
@@ -1264,9 +1267,9 @@
 
         private var asyncWatchFaceConstructionPending = false
 
-        // Stores the initial ComplicationSlots which could get updated before they're applied.
-        internal var pendingInitialComplications: List<IdAndComplicationDataWireFormat>? = null
-            private set
+        @VisibleForTesting
+        internal val complicationsFlow =
+            MutableStateFlow<List<IdAndComplicationDataWireFormat>>(emptyList())
 
         private var initialUserStyle: UserStyleWireFormat? = null
         internal lateinit var interactiveInstanceId: String
@@ -1524,61 +1527,46 @@
             }
         }
 
-        @UiThread
+        @AnyThread
         internal fun setComplicationDataList(
             complicationDataWireFormats: List<IdAndComplicationDataWireFormat>
         ): Unit = TraceEvent("EngineWrapper.setComplicationDataList").use {
-            val earlyInitDetails = getEarlyInitDetailsOrNull()
-            if (earlyInitDetails != null) {
-                applyComplications(
-                    earlyInitDetails.complicationSlotsManager,
-                    complicationDataWireFormats
-                )
-            } else {
-                setPendingInitialComplications(complicationDataWireFormats)
-            }
-        }
-
-        @UiThread
-        internal fun applyComplications(
-            complicationSlotsManager: ComplicationSlotsManager,
-            complicationDataWireFormats: List<IdAndComplicationDataWireFormat>
-        ) {
-            val now = Instant.ofEpochMilli(systemTimeProvider.getSystemTimeMillis())
-            for (idAndComplicationData in complicationDataWireFormats) {
-                complicationSlotsManager.onComplicationDataUpdate(
-                    idAndComplicationData.id,
-                    idAndComplicationData.complicationData.toApiComplicationData(),
-                    now
-                )
-            }
-            complicationSlotsManager.onComplicationsUpdated()
-            invalidate()
-            scheduleWriteComplicationDataCache()
-        }
-
-        @UiThread
-        internal fun setPendingInitialComplications(
-            complicationDataWireFormats: List<IdAndComplicationDataWireFormat>
-        ) {
-            // If the watchface hasn't been created yet, update pendingInitialComplications so
-            // it can be applied later.
-            if (pendingInitialComplications == null) {
-                pendingInitialComplications = complicationDataWireFormats
-            } else {
+            complicationsFlow.update { base ->
                 // We need to merge the updates.
-                val complicationUpdateMap = pendingInitialComplications!!.associate {
+                val complicationUpdateMap = base.associate {
                     Pair(it.id, it.complicationData)
                 }.toMutableMap()
                 for (data in complicationDataWireFormats) {
                     complicationUpdateMap[data.id] = data.complicationData
                 }
-                pendingInitialComplications = complicationUpdateMap.map {
+                complicationUpdateMap.map {
                     IdAndComplicationDataWireFormat(it.key, it.value)
                 }
             }
         }
 
+        @WorkerThread
+        private fun listenForComplicationChanges(
+            complicationSlotsManager: ComplicationSlotsManager
+        ) {
+            // Add a listener so we can track changes and automatically apply them on the UIThread
+            uiThreadCoroutineScope.launch {
+                complicationsFlow.collect { complicationDataWireFormats ->
+                    val now = Instant.ofEpochMilli(systemTimeProvider.getSystemTimeMillis())
+                    for (idAndComplicationData in complicationDataWireFormats) {
+                        complicationSlotsManager.onComplicationDataUpdate(
+                            idAndComplicationData.id,
+                            idAndComplicationData.complicationData.toApiComplicationData(),
+                            now
+                        )
+                    }
+                    complicationSlotsManager.onComplicationsUpdated()
+                    invalidate()
+                    scheduleWriteComplicationDataCache()
+                }
+            }
+        }
+
         @UiThread
         internal suspend fun updateInstance(newInstanceId: String) {
             val watchFaceImpl = deferredWatchFaceImpl.await()
@@ -1999,10 +1987,12 @@
 
             // Store the initial complications, this could be modified by new data before being
             // applied.
-            pendingInitialComplications = params.idAndComplicationDataWireFormats
-
-            if (pendingInitialComplications == null || pendingInitialComplications!!.isEmpty()) {
-                pendingInitialComplications = readComplicationDataCache(_context, params.instanceId)
+            var initialComplications = params.idAndComplicationDataWireFormats
+            if (initialComplications.isNullOrEmpty()) {
+                initialComplications = readComplicationDataCache(_context, params.instanceId)
+            }
+            if (!initialComplications.isNullOrEmpty()) {
+                setComplicationDataList(initialComplications)
             }
 
             createWatchFaceInternal(
@@ -2052,6 +2042,7 @@
                     }
                 complicationSlotsManager.watchState = watchState
                 complicationSlotsManager.listenForStyleChanges(uiThreadCoroutineScope)
+                listenForComplicationChanges(complicationSlotsManager)
 
                 val userStyleFlavors =
                     TraceEvent("WatchFaceService.createUserStyleFlavors").use {
@@ -2085,16 +2076,6 @@
                         )
                     )
 
-                    withContext(uiThreadCoroutineScope.coroutineContext) {
-                        // Apply any pendingInitialComplications, this must be done after
-                        // deferredEarlyInitDetails has completed or there's a window in which complication
-                        // updates get lost.
-                        pendingInitialComplications?.let {
-                            applyComplications(complicationSlotsManager, it)
-                        }
-                        pendingInitialComplications = null
-                    }
-
                     val watchFace = TraceEvent("WatchFaceService.createWatchFace").use {
                         // Note by awaiting deferredSurfaceHolder we ensure onSurfaceChanged has
                         // been called and we're passing the correct updated surface holder. This is
@@ -2722,7 +2703,7 @@
             writer.println("destroyed=$destroyed")
             writer.println("surfaceDestroyed=$surfaceDestroyed")
             writer.println(
-                "pendingInitialComplications=" + pendingInitialComplications?.joinToString()
+                "lastComplications=" + complicationsFlow.value.joinToString()
             )
 
             synchronized(lock) {
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 1eae0b4..3134a78 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
@@ -160,9 +160,7 @@
 
     override fun updateComplicationData(
         complicationDatumWireFormats: MutableList<IdAndComplicationDataWireFormat>
-    ): Unit = uiThreadCoroutineScope.runBlockingWithTracing(
-        "InteractiveWatchFaceImpl.updateComplicationData"
-    ) {
+    ): Unit = TraceEvent("InteractiveWatchFaceImpl.updateComplicationData").use {
         if ("user" != Build.TYPE) {
             Log.d(TAG, "updateComplicationData " + complicationDatumWireFormats.joinToString())
         }
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index 97ca016..520e72f 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -5801,7 +5801,7 @@
     }
 
     @Test
-    public fun setPendingInitialComplications() {
+    public fun setComplicationDataListMergesCorrectly() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -5829,15 +5829,17 @@
                 .build()
         )
 
-        engineWrapper.setPendingInitialComplications(listOf(left1))
-        assertThat(engineWrapper.pendingInitialComplications).containsExactly(left1)
+        engineWrapper.setComplicationDataList(listOf(left1))
+        // In initEngine we fill initial complication data using
+        // setComplicationViaWallpaperCommand, that's why lastComplications initially is not empty
+        assertThat(engineWrapper.complicationsFlow.value).contains(left1)
 
         // Check merges are working as expected.
-        engineWrapper.setPendingInitialComplications(listOf(right))
-        assertThat(engineWrapper.pendingInitialComplications).containsExactly(left1, right)
+        engineWrapper.setComplicationDataList(listOf(right))
+        assertThat(engineWrapper.complicationsFlow.value).containsExactly(left1, right)
 
-        engineWrapper.setPendingInitialComplications(listOf(left2))
-        assertThat(engineWrapper.pendingInitialComplications).containsExactly(left2, right)
+        engineWrapper.setComplicationDataList(listOf(left2))
+        assertThat(engineWrapper.complicationsFlow.value).containsExactly(left2, right)
     }
 
     @Test