Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dd8dba7a authored by Catherine Liang's avatar Catherine Liang
Browse files

Better handle icon apply failure and icon theme refresh (1/2)

Detect apply failure cases. Cancel loading and reset preview when apply
fails.

Flag: com.android.systemui.shared.extendible_theme_manager
Bug: 434404436
Test: manually verified apply success and failure cases
Test: added unit tests
Change-Id: I813e12142eebef625e59045394c53874943744b7
parent ef4a21df
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -35,5 +35,11 @@ interface IconStyleRepository {

    val selectedIconStyle: Flow<IconStyle>

    suspend fun setIconStyle(iconStyle: IconStyle)
    /**
     * Sets the selected icon style.
     *
     * @param iconStyle The icon style to set.
     * @return True if the icon style was set successfully and data was updated, false otherwise.
     */
    suspend fun setIconStyle(iconStyle: IconStyle): Boolean
}
+5 −2
Original line number Diff line number Diff line
@@ -184,12 +184,15 @@ constructor(
        }
    }

    override suspend fun setIconStyle(iconStyle: IconStyle) {
    override suspend fun setIconStyle(iconStyle: IconStyle): Boolean {
        themedIconUri.first()?.let {
            val values = ContentValues()
            values.put(COL_ICON_THEMED_VALUE, iconStyle == ThemePickerIconStyle.MONOCHROME)
            val rowsUpdated =
                contentResolver.update(it, values, /* where= */ null, /* selectionArgs= */ null)
            return rowsUpdated > 0
        }
        return false
    }

    companion object {
+2 −1
Original line number Diff line number Diff line
@@ -56,5 +56,6 @@ constructor(

    suspend fun applyShape(shapeKey: String) = shapeRepository.applyShape(shapeKey)

    suspend fun applyIconStyle(iconStyle: IconStyle) = iconStyleRepository.setIconStyle(iconStyle)
    suspend fun applyIconStyle(iconStyle: IconStyle): Boolean =
        iconStyleRepository.setIconStyle(iconStyle)
}
+40 −9
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.wallpaper.customization.ui.viewmodel

import android.content.Context
import android.util.Log
import com.android.customization.model.grid.ShapeOptionModel
import com.android.customization.module.logging.ThemesUserEventLogger
import com.android.customization.picker.grid.ui.viewmodel.ShapeIconViewModel
@@ -34,7 +35,10 @@ import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.scopes.ViewModelScoped
import java.util.Locale
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -48,6 +52,7 @@ import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout

class AppIconPickerViewModel
@AssistedInject
@@ -115,7 +120,7 @@ constructor(
        }

    //// Style
    private val selectedIconStyle = interactor.selectedIconStyle
    val selectedIconStyle = interactor.selectedIconStyle
    private val overridingIconStyle: MutableStateFlow<IconStyle?> = MutableStateFlow(null)
    val previewingIconStyle =
        combine(selectedIconStyle, overridingIconStyle) { selected, overriding ->
@@ -127,9 +132,9 @@ constructor(
            started = SharingStarted.WhileSubscribed(),
            replay = 1,
        )
    val overridingIconStyleModel =
        combine(overridingIconStyle, iconStylesModels) { overridingIconStyle, iconStylesModels ->
            iconStylesModels.find { it.iconStyle == overridingIconStyle }
    val previewingIconStyleModel =
        combine(previewingIconStyle, iconStylesModels) { previewingIconStyle, iconStylesModels ->
            iconStylesModels.find { it.iconStyle == previewingIconStyle }
        }
    val styleOptions: Flow<List<OptionItemViewModel2<IconStyleModel>>> =
        iconStylesModels.map {
@@ -353,13 +358,34 @@ constructor(
                    }
                    if (styleNeedsUpdate) {
                        coroutineScope {
                            launch { overridingIconStyle?.let { interactor.applyIconStyle(it) } }
                            val waitForUpdate = launch {
                                try {
                                    withTimeout(ICON_UPDATE_TIMEOUT) {
                                        // Drop the first emitted icon style since it is the current
                                        // selection. The next emitted icon style signals an update.
                                        selectedIconStyle.drop(1).take(1).collect {
                                            return@collect
                                        }
                            overridingIconStyle?.let {
                                logger.logThemedIconApplied(it.getIsThemedIcon())
                                    }
                                } catch (e: TimeoutCancellationException) {
                                    Log.w(TAG, "Timed out waiting for icon update", e)
                                }
                            }
                            launch {
                                val success =
                                    overridingIconStyle?.let { interactor.applyIconStyle(it) }
                                if (success == true) {
                                    logger.logThemedIconApplied(
                                        overridingIconStyle.getIsThemedIcon()
                                    )
                                } else {
                                    Log.w(TAG, "Apply unsuccessful, no data was updated")
                                    waitForUpdate.cancel()
                                }
                            }
                            try {
                                waitForUpdate.join()
                            } catch (_: CancellationException) {}
                        }
                    }
                }
@@ -447,4 +473,9 @@ constructor(
    interface Factory {
        fun create(viewModelScope: CoroutineScope): AppIconPickerViewModel
    }

    companion object {
        const val TAG = "AppIconPickerViewModel"
        val ICON_UPDATE_TIMEOUT = 1000.milliseconds
    }
}
+8 −2
Original line number Diff line number Diff line
@@ -42,6 +42,9 @@ class FakeIconStyleRepository @Inject constructor() : IconStyleRepository {
    private val _selectedIconStyle = MutableStateFlow<IconStyle>(ThemePickerIconStyle.DEFAULT)
    override val selectedIconStyle = _selectedIconStyle.asStateFlow()

    var shouldApplySuccessfully = true
    var shouldUpdateSuccessfully = true

    override val iconStyleModels: Flow<List<IconStyleModel>> =
        isCustomizationAvailable.map { isThemedIconAvailable ->
            ThemePickerIconStyle.entries
@@ -61,9 +64,12 @@ class FakeIconStyleRepository @Inject constructor() : IconStyleRepository {
        )
    }

    override suspend fun setIconStyle(iconStyle: IconStyle) {
    override suspend fun setIconStyle(iconStyle: IconStyle): Boolean {
        if (shouldApplySuccessfully && shouldUpdateSuccessfully) {
            _selectedIconStyle.value = iconStyle
        }
        return shouldApplySuccessfully
    }

    override suspend fun setThemedIconEnabled(enabled: Boolean) {
        _isThemedIconActivated.value = enabled
Loading