Merge "[TP] Clock color updates" into tm-qpr-dev
diff --git a/src/com/android/customization/model/grid/GridOptionsManager.java b/src/com/android/customization/model/grid/GridOptionsManager.java
index bff7933..b7ee37f 100644
--- a/src/com/android/customization/model/grid/GridOptionsManager.java
+++ b/src/com/android/customization/model/grid/GridOptionsManager.java
@@ -49,6 +49,7 @@
 
     private final LauncherGridOptionsProvider mProvider;
     private final ThemesUserEventLogger mEventLogger;
+    private int mGridOptionSize = -1;
 
     /** Returns the {@link GridOptionsManager} instance. */
     public static GridOptionsManager getInstance(Context context) {
@@ -73,16 +74,17 @@
 
     @Override
     public boolean isAvailable() {
-        int gridOptionSize = 0;
-        try {
-            gridOptionSize = sExecutorService.submit(() -> {
-                List<GridOption> gridOptions = mProvider.fetch(/* reload= */true);
-                return gridOptions == null ? 0 : gridOptions.size();
-            }).get();
-        } catch (InterruptedException | ExecutionException e) {
-            Log.w(TAG, "could not get gridOptionSize", e);
+        if (mGridOptionSize < 0) {
+            try {
+                mGridOptionSize = sExecutorService.submit(() -> {
+                    List<GridOption> gridOptions = mProvider.fetch(/* reload= */true);
+                    return gridOptions == null ? 0 : gridOptions.size();
+                }).get();
+            } catch (InterruptedException | ExecutionException e) {
+                Log.w(TAG, "could not get gridOptionSize", e);
+            }
         }
-        return gridOptionSize > 1 && mProvider.areGridsAvailable();
+        return mGridOptionSize > 1 && mProvider.areGridsAvailable();
     }
 
     @Override
diff --git a/src/com/android/customization/model/grid/GridSectionController.java b/src/com/android/customization/model/grid/GridSectionController.java
index 3e5dba0..c50bfcc 100644
--- a/src/com/android/customization/model/grid/GridSectionController.java
+++ b/src/com/android/customization/model/grid/GridSectionController.java
@@ -96,7 +96,7 @@
 
     @Override
     public void release() {
-        if (mIsRevampedUiEnabled) {
+        if (mIsRevampedUiEnabled && mGridOptionsManager.isAvailable()) {
             mGridOptionsManager.getOptionChangeObservable(/* handler= */ null).removeObserver(
                     mOptionChangeObserver
             );
diff --git a/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java b/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java
index 5ae283a..4e775c6 100644
--- a/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java
+++ b/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java
@@ -126,6 +126,7 @@
 
     /**
      * Returns an observable that receives a new value each time that the grid options are changed.
+     * Do not call if {@link #areGridsAvailable()} returns false
      */
     public LiveData<Object> getOptionChangeObservable(
             @Nullable Handler handler) {
diff --git a/src/com/android/customization/model/grid/data/repository/GridRepository.kt b/src/com/android/customization/model/grid/data/repository/GridRepository.kt
index 7c84aec..9a3be0c 100644
--- a/src/com/android/customization/model/grid/data/repository/GridRepository.kt
+++ b/src/com/android/customization/model/grid/data/repository/GridRepository.kt
@@ -35,7 +35,8 @@
 import kotlinx.coroutines.withContext
 
 interface GridRepository {
-    val optionChanges: Flow<Unit>
+    suspend fun isAvailable(): Boolean
+    fun getOptionChanges(): Flow<Unit>
     suspend fun getOptions(): GridOptionItemsModel
 }
 
@@ -45,7 +46,11 @@
     private val backgroundDispatcher: CoroutineDispatcher,
 ) : GridRepository {
 
-    override val optionChanges: Flow<Unit> =
+    override suspend fun isAvailable(): Boolean {
+        return withContext(backgroundDispatcher) { manager.isAvailable }
+    }
+
+    override fun getOptionChanges(): Flow<Unit> =
         manager.getOptionChangeObservable(/* handler= */ null).asFlow().map {}
 
     private val selectedOption = MutableStateFlow<GridOption?>(null)
diff --git a/src/com/android/customization/model/grid/domain/interactor/GridInteractor.kt b/src/com/android/customization/model/grid/domain/interactor/GridInteractor.kt
index 5ab9e1f..cdb679d 100644
--- a/src/com/android/customization/model/grid/domain/interactor/GridInteractor.kt
+++ b/src/com/android/customization/model/grid/domain/interactor/GridInteractor.kt
@@ -24,6 +24,9 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.shareIn
@@ -34,22 +37,30 @@
     private val snapshotRestorer: Provider<GridSnapshotRestorer>,
 ) {
     val options: Flow<GridOptionItemsModel> =
-        // this upstream flow tells us each time the options are changed.
-        repository.optionChanges
-            // when we start, we pretend the options _just_ changed. This way, we load something as
-            // soon as possible into the flow so it's ready by the time the first observer starts to
-            // observe.
-            .onStart { emit(Unit) }
-            // each time the options changed, we load them.
-            .map { reload() }
-            // we place the loaded options in a SharedFlow so downstream observers all
-            // share the same flow and don't trigger a new one each time they want to start
-            // observing.
-            .shareIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                replay = 1,
-            )
+        flow { emit(repository.isAvailable()) }
+            .flatMapLatest { isAvailable ->
+                if (isAvailable) {
+                    // this upstream flow tells us each time the options are changed.
+                    repository
+                        .getOptionChanges()
+                        // when we start, we pretend the options _just_ changed. This way, we load
+                        // something as soon as possible into the flow so it's ready by the time the
+                        // first observer starts to observe.
+                        .onStart { emit(Unit) }
+                        // each time the options changed, we load them.
+                        .map { reload() }
+                        // we place the loaded options in a SharedFlow so downstream observers all
+                        // share the same flow and don't trigger a new one each time they want to
+                        // start observing.
+                        .shareIn(
+                            scope = applicationScope,
+                            started = SharingStarted.WhileSubscribed(),
+                            replay = 1,
+                        )
+                } else {
+                    emptyFlow()
+                }
+            }
 
     suspend fun setSelectedOption(model: GridOptionItemModel) {
         model.onSelected.invoke()
diff --git a/tests/src/com/android/customization/model/grid/data/repository/FakeGridRepository.kt b/tests/src/com/android/customization/model/grid/data/repository/FakeGridRepository.kt
index 6291c21..5953937 100644
--- a/tests/src/com/android/customization/model/grid/data/repository/FakeGridRepository.kt
+++ b/tests/src/com/android/customization/model/grid/data/repository/FakeGridRepository.kt
@@ -32,13 +32,17 @@
 class FakeGridRepository(
     private val scope: CoroutineScope,
     initialOptionCount: Int,
+    var available: Boolean = true
 ) : GridRepository {
     private val _optionChanges =
         MutableSharedFlow<Unit>(
             replay = 1,
             onBufferOverflow = BufferOverflow.DROP_OLDEST,
         )
-    override val optionChanges: Flow<Unit> = _optionChanges.asSharedFlow()
+
+    override suspend fun isAvailable(): Boolean = available
+
+    override fun getOptionChanges(): Flow<Unit> = _optionChanges.asSharedFlow()
 
     private val selectedOptionIndex = MutableStateFlow(0)
     private var options: GridOptionItemsModel = createOptions(count = initialOptionCount)
diff --git a/tests/src/com/android/customization/model/grid/domain/interactor/GridInteractorTest.kt b/tests/src/com/android/customization/model/grid/domain/interactor/GridInteractorTest.kt
index 20dd300..f73d5a3 100644
--- a/tests/src/com/android/customization/model/grid/domain/interactor/GridInteractorTest.kt
+++ b/tests/src/com/android/customization/model/grid/domain/interactor/GridInteractorTest.kt
@@ -135,4 +135,12 @@
             // External updates do not record a new snapshot with the undo system.
             assertThat(store.retrieve()).isEqualTo(storedSnapshot)
         }
+
+    @Test
+    fun unavailableRepository_emptyOptions() =
+        testScope.runTest {
+            repository.available = false
+            val options = collectLastValue(underTest.options)
+            assertThat(options()).isNull()
+        }
 }