Merge "Add ability to toggle themed icons (1/3)" into main
diff --git a/res/layout/customization_option_entry_themed_icons.xml b/res/layout/customization_option_entry_themed_icons.xml
index 06461dc..8710f8f 100644
--- a/res/layout/customization_option_entry_themed_icons.xml
+++ b/res/layout/customization_option_entry_themed_icons.xml
@@ -54,7 +54,6 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:focusable="false"
-        android:clickable="false"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
diff --git a/src/com/android/customization/picker/themedicon/data/repository/ThemedIconRepository.kt b/src/com/android/customization/picker/themedicon/data/repository/ThemedIconRepository.kt
new file mode 100644
index 0000000..6e86ac8
--- /dev/null
+++ b/src/com/android/customization/picker/themedicon/data/repository/ThemedIconRepository.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.customization.picker.themedicon.data.repository
+
+import kotlinx.coroutines.flow.Flow
+
+interface ThemedIconRepository {
+
+    val isAvailable: Flow<Boolean>
+
+    val isActivated: Flow<Boolean>
+
+    suspend fun setThemedIconEnabled(enabled: Boolean)
+}
diff --git a/src/com/android/customization/picker/themedicon/data/repository/ThemedIconRepositoryImpl.kt b/src/com/android/customization/picker/themedicon/data/repository/ThemedIconRepositoryImpl.kt
new file mode 100644
index 0000000..ab1cdb3
--- /dev/null
+++ b/src/com/android/customization/picker/themedicon/data/repository/ThemedIconRepositoryImpl.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.customization.picker.themedicon.data.repository
+
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.database.ContentObserver
+import android.net.Uri
+import com.android.customization.module.CustomizationPreferences
+import com.android.themepicker.R
+import com.android.wallpaper.module.InjectorProvider
+import com.android.wallpaper.picker.di.modules.BackgroundDispatcher
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+@Singleton
+class ThemedIconRepositoryImpl
+@Inject
+constructor(
+    @ApplicationContext private val appContext: Context,
+    private val contentResolver: ContentResolver,
+    packageManager: PackageManager,
+    @BackgroundDispatcher private val backgroundScope: CoroutineScope,
+) : ThemedIconRepository {
+    private val uri: MutableStateFlow<Uri?> = MutableStateFlow(null)
+    private var getUriJob: Job =
+        backgroundScope.launch {
+            val homeIntent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+            val resolveInfo =
+                packageManager.resolveActivity(
+                    homeIntent,
+                    PackageManager.MATCH_DEFAULT_ONLY or PackageManager.GET_META_DATA,
+                )
+            val providerAuthority =
+                resolveInfo
+                    ?.activityInfo
+                    ?.metaData
+                    ?.getString(appContext.getString(R.string.themed_icon_metadata_key))
+            val providerInfo =
+                providerAuthority?.let { authority ->
+                    val info = packageManager.resolveContentProvider(authority, 0)
+                    val hasPermission =
+                        info?.readPermission?.let {
+                            if (it.isNotEmpty()) {
+                                appContext.checkSelfPermission(it) ==
+                                    PackageManager.PERMISSION_GRANTED
+                            } else true
+                        } ?: true
+                    if (!hasPermission) {
+                        null
+                    } else {
+                        info
+                    }
+                }
+            uri.value =
+                providerInfo?.let {
+                    Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(providerInfo.authority)
+                        .appendPath(ICON_THEMED)
+                        .build()
+                }
+        }
+
+    override val isAvailable: Flow<Boolean> =
+        uri.map { it != null }
+            .stateIn(
+                scope = backgroundScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    override val isActivated: Flow<Boolean> =
+        callbackFlow {
+                var disposableHandle: DisposableHandle? = null
+                launch {
+                    uri.collect {
+                        disposableHandle?.dispose()
+                        if (it != null) {
+                            val contentObserver =
+                                object : ContentObserver(null) {
+                                    override fun onChange(selfChange: Boolean) {
+                                        trySend(getThemedIconEnabled(it))
+                                    }
+                                }
+                            contentResolver.registerContentObserver(
+                                it,
+                                /* notifyForDescendants= */ true,
+                                contentObserver,
+                            )
+
+                            trySend(getThemedIconEnabled(it))
+
+                            disposableHandle = DisposableHandle {
+                                contentResolver.unregisterContentObserver(contentObserver)
+                            }
+                        }
+                    }
+                }
+                awaitClose { disposableHandle?.dispose() }
+            }
+            .stateIn(
+                scope = backgroundScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    private fun getThemedIconEnabled(uri: Uri): Boolean {
+        val cursor =
+            contentResolver.query(
+                uri,
+                /* projection= */ null,
+                /* selection= */ null,
+                /* selectionArgs= */ null,
+                /* sortOrder= */ null,
+            )
+        var isEnabled = false
+        if (cursor != null && cursor.moveToNext()) {
+            isEnabled = (cursor.getInt(cursor.getColumnIndex(COL_ICON_THEMED_VALUE)) == ENABLED)
+            val preferences =
+                InjectorProvider.getInjector().getPreferences(appContext)
+                    as CustomizationPreferences
+            if (preferences.getThemedIconEnabled() != isEnabled) {
+                preferences.setThemedIconEnabled(isEnabled)
+            }
+        }
+        cursor?.close()
+        return isEnabled
+    }
+
+    override suspend fun setThemedIconEnabled(enabled: Boolean) {
+        getUriJob.join()
+        uri.value?.let {
+            val values = ContentValues()
+            values.put(COL_ICON_THEMED_VALUE, enabled)
+            contentResolver.update(it, values, /* where= */ null, /* selectionArgs= */ null)
+        }
+    }
+
+    companion object {
+        private const val ICON_THEMED = "icon_themed"
+        private const val COL_ICON_THEMED_VALUE = "boolean_value"
+        private const val ENABLED = 1
+    }
+}
diff --git a/src/com/android/customization/picker/themedicon/domain/interactor/ThemedIconInteractor.kt b/src/com/android/customization/picker/themedicon/domain/interactor/ThemedIconInteractor.kt
new file mode 100644
index 0000000..8a33f96
--- /dev/null
+++ b/src/com/android/customization/picker/themedicon/domain/interactor/ThemedIconInteractor.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.customization.picker.themedicon.domain.interactor
+
+import com.android.customization.picker.themedicon.data.repository.ThemedIconRepository
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlinx.coroutines.flow.Flow
+
+@Singleton
+class ThemedIconInteractor @Inject constructor(private val repository: ThemedIconRepository) {
+    val isAvailable: Flow<Boolean> = repository.isAvailable
+    val isActivated: Flow<Boolean> = repository.isActivated
+
+    suspend fun setThemedIconEnabled(enabled: Boolean) = repository.setThemedIconEnabled(enabled)
+}
diff --git a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
index 60925bf..c71f860 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
@@ -20,6 +20,7 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
+import android.widget.Switch
 import android.widget.TextView
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.core.content.ContextCompat
@@ -141,6 +142,13 @@
         val optionColorContrastIcon: ImageView =
             optionColorContrast.requireViewById(R.id.option_entry_color_contrast_icon)
 
+        val optionThemedIcons =
+            homeScreenCustomizationOptionEntries
+                .find { it.first == ThemePickerHomeCustomizationOption.THEMED_ICONS }
+                ?.second
+        val optionThemedIconsSwitch =
+            optionThemedIcons?.findViewById<Switch>(R.id.option_entry_themed_icons_switch)
+
         val optionsViewModel =
             viewModel.customizationOptionsViewModel as ThemePickerCustomizationOptionsViewModel
         lifecycleOwner.lifecycleScope.launch {
@@ -243,6 +251,28 @@
                         }
                     }
                 }
+
+                if (optionThemedIconsSwitch != null) {
+                    launch {
+                        optionsViewModel.themedIconViewModel.isAvailable.collect { isAvailable ->
+                            optionThemedIconsSwitch.isEnabled = isAvailable
+                        }
+                    }
+
+                    launch {
+                        optionsViewModel.themedIconViewModel.isActivated.collect {
+                            optionThemedIconsSwitch.isChecked = it
+                        }
+                    }
+
+                    launch {
+                        optionsViewModel.themedIconViewModel.toggleThemedIcon.collect {
+                            optionThemedIconsSwitch.setOnCheckedChangeListener { _, _ ->
+                                launch { it.invoke() }
+                            }
+                        }
+                    }
+                }
             }
         }
 
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
index 4832e1d..29b8b6a 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
@@ -48,6 +48,7 @@
     shapeGridPickerViewModelFactory: ShapeGridPickerViewModel.Factory,
     val colorContrastSectionViewModel: ColorContrastSectionViewModel2,
     val darkModeViewModel: DarkModeViewModel,
+    val themedIconViewModel: ThemedIconViewModel,
     @Assisted private val viewModelScope: CoroutineScope,
 ) : CustomizationOptionsViewModel {
 
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ThemedIconViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ThemedIconViewModel.kt
new file mode 100644
index 0000000..ac1ebdc
--- /dev/null
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ThemedIconViewModel.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wallpaper.customization.ui.viewmodel
+
+import com.android.customization.module.logging.ThemesUserEventLogger
+import com.android.customization.picker.themedicon.domain.interactor.ThemedIconInteractor
+import dagger.hilt.android.scopes.ViewModelScoped
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+@ViewModelScoped
+class ThemedIconViewModel
+@Inject
+constructor(
+    private val interactor: ThemedIconInteractor,
+    private val logger: ThemesUserEventLogger,
+) {
+    val isAvailable: Flow<Boolean> = interactor.isAvailable
+    val isActivated: Flow<Boolean> = interactor.isActivated
+
+    val toggleThemedIcon: Flow<suspend () -> Unit> =
+        isActivated.map {
+            {
+                val newValue = !it
+                interactor.setThemedIconEnabled(newValue)
+                logger.logThemedIconApplied(newValue)
+            }
+        }
+}
diff --git a/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt b/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt
index 1f6938f..9fc59c7 100644
--- a/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt
+++ b/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt
@@ -222,6 +222,12 @@
                                 workspaceCallback.sendMessage(MESSAGE_ID_UPDATE_COLOR, bundle)
                             }
                     }
+
+                    launch {
+                        viewModel.themedIconViewModel.isActivated.collect {
+                            workspaceCallback.sendMessage(MESSAGE_ID_UPDATE_COLOR, Bundle.EMPTY)
+                        }
+                    }
                 }
         }
     }
diff --git a/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt b/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt
index a2989bb..5f4da33 100644
--- a/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt
+++ b/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt
@@ -30,6 +30,8 @@
 import com.android.customization.picker.color.data.repository.ColorPickerRepository2
 import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl
 import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl2
+import com.android.customization.picker.themedicon.data.repository.ThemedIconRepository
+import com.android.customization.picker.themedicon.data.repository.ThemedIconRepositoryImpl
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
 import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl
@@ -99,6 +101,10 @@
 
     @Binds
     @Singleton
+    abstract fun bindThemedIconRepository(impl: ThemedIconRepositoryImpl): ThemedIconRepository
+
+    @Binds
+    @Singleton
     abstract fun bindCreativeCategoryInteractor(
         impl: CreativeCategoryInteractorImpl
     ): CreativeCategoryInteractor
diff --git a/tests/common/src/com/android/customization/module/logging/TestThemesUserEventLogger.kt b/tests/common/src/com/android/customization/module/logging/TestThemesUserEventLogger.kt
index 05c95b0..2bb88d8 100644
--- a/tests/common/src/com/android/customization/module/logging/TestThemesUserEventLogger.kt
+++ b/tests/common/src/com/android/customization/module/logging/TestThemesUserEventLogger.kt
@@ -43,6 +43,9 @@
     var useDarkTheme: Boolean = false
         private set
 
+    var useThemedIcon: Boolean = false
+        private set
+
     override fun logThemeColorApplied(@ColorSource source: Int, style: Int, seedColor: Int) {
         this.themeColorSource = source
         this.themeColorStyle = style
@@ -59,7 +62,9 @@
         this.clockSize = clockSize
     }
 
-    override fun logThemedIconApplied(useThemeIcon: Boolean) {}
+    override fun logThemedIconApplied(useThemeIcon: Boolean) {
+        this.useThemedIcon = useThemeIcon
+    }
 
     override fun logLockScreenNotificationApplied(showLockScreenNotifications: Boolean) {}
 
diff --git a/tests/common/src/com/android/customization/picker/themedicon/data/repository/FakeThemedIconRepository.kt b/tests/common/src/com/android/customization/picker/themedicon/data/repository/FakeThemedIconRepository.kt
new file mode 100644
index 0000000..0cae929
--- /dev/null
+++ b/tests/common/src/com/android/customization/picker/themedicon/data/repository/FakeThemedIconRepository.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.customization.picker.themedicon.data.repository
+
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@Singleton
+class FakeThemedIconRepository @Inject constructor() : ThemedIconRepository {
+    override val isAvailable = MutableStateFlow(true).asStateFlow()
+
+    private val _isActivated = MutableStateFlow(false)
+    override val isActivated = _isActivated.asStateFlow()
+
+    override suspend fun setThemedIconEnabled(enabled: Boolean) {
+        _isActivated.value = enabled
+    }
+}
diff --git a/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt b/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt
index 2eb5c10..25a6d65 100644
--- a/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt
+++ b/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt
@@ -30,6 +30,8 @@
 import com.android.customization.picker.color.data.repository.ColorPickerRepository2
 import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl
 import com.android.customization.picker.color.data.repository.FakeColorPickerRepository2
+import com.android.customization.picker.themedicon.data.repository.FakeThemedIconRepository
+import com.android.customization.picker.themedicon.data.repository.ThemedIconRepository
 import com.android.customization.testing.TestCustomizationInjector
 import com.android.customization.testing.TestDefaultCustomizationPreferences
 import com.android.systemui.shared.clocks.ClockRegistry
@@ -100,6 +102,10 @@
 
     @Binds
     @Singleton
+    abstract fun bindThemedIconRepository(impl: FakeThemedIconRepository): ThemedIconRepository
+
+    @Binds
+    @Singleton
     abstract fun bindCustomizationInjector(impl: TestCustomizationInjector): CustomizationInjector
 
     @Binds
diff --git a/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ThemedIconViewModelTest.kt b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ThemedIconViewModelTest.kt
new file mode 100644
index 0000000..1fb4bed
--- /dev/null
+++ b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ThemedIconViewModelTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wallpaper.customization.ui.viewmodel
+
+import com.android.customization.module.logging.TestThemesUserEventLogger
+import com.android.customization.picker.themedicon.domain.interactor.ThemedIconInteractor
+import com.android.wallpaper.testing.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import dagger.hilt.android.testing.HiltAndroidRule
+import dagger.hilt.android.testing.HiltAndroidTest
+import javax.inject.Inject
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@HiltAndroidTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(RobolectricTestRunner::class)
+class ThemedIconViewModelTest {
+    @get:Rule var hiltRule = HiltAndroidRule(this)
+
+    @Inject lateinit var logger: TestThemesUserEventLogger
+    @Inject lateinit var themedIconInteractor: ThemedIconInteractor
+    private lateinit var themedIconViewModel: ThemedIconViewModel
+
+    @Inject lateinit var testDispatcher: TestDispatcher
+    @Inject lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        hiltRule.inject()
+        Dispatchers.setMain(testDispatcher)
+
+        themedIconViewModel = ThemedIconViewModel(themedIconInteractor, logger)
+    }
+
+    @Test
+    fun toggleThemedIcon() {
+        testScope.runTest {
+            val isActivated = collectLastValue(themedIconViewModel.isActivated)
+            val toggleThemedIcon = collectLastValue(themedIconViewModel.toggleThemedIcon)
+            assertThat(isActivated()).isFalse()
+
+            toggleThemedIcon()?.invoke()
+
+            assertThat(isActivated()).isTrue()
+
+            toggleThemedIcon()?.invoke()
+
+            assertThat(isActivated()).isFalse()
+        }
+    }
+
+    @Test
+    fun toggleThemedIcon_shouldLogThemedIcon() {
+        testScope.runTest {
+            val toggleThemedIcon = collectLastValue(themedIconViewModel.toggleThemedIcon)
+            assertThat(logger.useThemedIcon).isFalse()
+
+            toggleThemedIcon()?.invoke()
+
+            assertThat(logger.useThemedIcon).isTrue()
+        }
+    }
+}