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

Commit 2d780174 authored by Austin Wang's avatar Austin Wang Committed by Automerger Merge Worker
Browse files

Merge "Make Clock options resettable (1/3)" into udc-dev am: 5fa9b6a6

parents e77abe7d 5fa9b6a6
Loading
Loading
Loading
Loading
+65 −41
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import com.android.customization.model.themedicon.domain.interactor.ThemedIconSn
import com.android.customization.picker.clock.data.repository.ClockPickerRepositoryImpl
import com.android.customization.picker.clock.data.repository.ClockRegistryProvider
import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
import com.android.customization.picker.clock.domain.interactor.ClockPickerSnapshotRestorer
import com.android.customization.picker.clock.ui.view.ClockViewFactory
import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel
@@ -107,6 +108,7 @@ open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInject
    private var clockSectionViewModel: ClockSectionViewModel? = null
    private var clockCarouselViewModelFactory: ClockCarouselViewModel.Factory? = null
    private var clockViewFactories: MutableMap<Int, ClockViewFactory> = HashMap()
    private var clockPickerSnapshotRestorer: ClockPickerSnapshotRestorer? = null
    private var notificationsInteractor: NotificationsInteractor? = null
    private var notificationSectionViewModelFactory: NotificationSectionViewModel.Factory? = null
    private var colorPickerInteractor: ColorPickerInteractor? = null
@@ -195,17 +197,25 @@ open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInject
        return fragmentFactory ?: ThemePickerFragmentFactory().also { fragmentFactory }
    }

    override fun getSnapshotRestorers(context: Context): Map<Int, SnapshotRestorer> {
        return super<WallpaperPicker2Injector>.getSnapshotRestorers(context).toMutableMap().apply {
    override fun getSnapshotRestorers(
        context: Context,
        lifecycleOwner: LifecycleOwner
    ): Map<Int, SnapshotRestorer> {
        return super<WallpaperPicker2Injector>.getSnapshotRestorers(context, lifecycleOwner)
            .toMutableMap()
            .apply {
                this[KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER] =
                    getKeyguardQuickAffordanceSnapshotRestorer(context)
                this[KEY_WALLPAPER_SNAPSHOT_RESTORER] = getWallpaperSnapshotRestorer(context)
            this[KEY_NOTIFICATIONS_SNAPSHOT_RESTORER] = getNotificationsSnapshotRestorer(context)
                this[KEY_NOTIFICATIONS_SNAPSHOT_RESTORER] =
                    getNotificationsSnapshotRestorer(context)
                this[KEY_DARK_MODE_SNAPSHOT_RESTORER] = getDarkModeSnapshotRestorer(context)
                this[KEY_THEMED_ICON_SNAPSHOT_RESTORER] = getThemedIconSnapshotRestorer(context)
                this[KEY_APP_GRID_SNAPSHOT_RESTORER] = getGridSnapshotRestorer(context)
                this[KEY_COLOR_PICKER_SNAPSHOT_RESTORER] =
                    getColorPickerSnapshotRestorer(context, getWallpaperColorsViewModel())
                this[KEY_CLOCKS_SNAPSHOT_RESTORER] =
                    getClockPickerSnapshotRestorer(context, lifecycleOwner)
            }
    }

@@ -264,16 +274,6 @@ open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInject
                .also { keyguardQuickAffordancePickerViewModelFactory = it }
    }

    fun getNotificationSectionViewModelFactory(
        context: Context,
    ): NotificationSectionViewModel.Factory {
        return notificationSectionViewModelFactory
            ?: NotificationSectionViewModel.Factory(
                    interactor = getNotificationsInteractor(context),
                )
                .also { notificationSectionViewModelFactory = it }
    }

    private fun getKeyguardQuickAffordancePickerInteractorImpl(
        context: Context
    ): KeyguardQuickAffordancePickerInteractor {
@@ -306,6 +306,32 @@ open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInject
                .also { keyguardQuickAffordanceSnapshotRestorer = it }
    }

    fun getNotificationSectionViewModelFactory(
        context: Context,
    ): NotificationSectionViewModel.Factory {
        return notificationSectionViewModelFactory
            ?: NotificationSectionViewModel.Factory(
                    interactor = getNotificationsInteractor(context),
                )
                .also { notificationSectionViewModelFactory = it }
    }

    private fun getNotificationsInteractor(
        context: Context,
    ): NotificationsInteractor {
        return notificationsInteractor
            ?: NotificationsInteractor(
                    repository =
                        NotificationsRepository(
                            scope = getApplicationCoroutineScope(),
                            backgroundDispatcher = Dispatchers.IO,
                            secureSettingsRepository = getSecureSettingsRepository(context),
                        ),
                    snapshotRestorer = { getNotificationsSnapshotRestorer(context) },
                )
                .also { notificationsInteractor = it }
    }

    private fun getNotificationsSnapshotRestorer(context: Context): NotificationsSnapshotRestorer {
        return notificationsSnapshotRestorer
            ?: NotificationsSnapshotRestorer(
@@ -346,11 +372,14 @@ open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInject
    ): ClockPickerInteractor {
        return clockPickerInteractor
            ?: ClockPickerInteractor(
                    repository =
                        ClockPickerRepositoryImpl(
                            secureSettingsRepository = getSecureSettingsRepository(context),
                            registry = getClockRegistry(context, lifecycleOwner),
                            scope = getApplicationCoroutineScope(),
                            mainDispatcher = Dispatchers.Main,
                        ),
                    snapshotRestorer = { getClockPickerSnapshotRestorer(context, lifecycleOwner) },
                )
                .also { clockPickerInteractor = it }
    }
@@ -396,20 +425,14 @@ open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInject
                }
    }

    private fun getNotificationsInteractor(
    private fun getClockPickerSnapshotRestorer(
        context: Context,
    ): NotificationsInteractor {
        return notificationsInteractor
            ?: NotificationsInteractor(
                    repository =
                        NotificationsRepository(
                            scope = getApplicationCoroutineScope(),
                            backgroundDispatcher = Dispatchers.IO,
                            secureSettingsRepository = getSecureSettingsRepository(context),
                        ),
                    snapshotRestorer = { getNotificationsSnapshotRestorer(context) },
                )
                .also { notificationsInteractor = it }
        lifecycleOwner: LifecycleOwner
    ): ClockPickerSnapshotRestorer {
        return clockPickerSnapshotRestorer
            ?: ClockPickerSnapshotRestorer(getClockPickerInteractor(context, lifecycleOwner)).also {
                clockPickerSnapshotRestorer = it
            }
    }

    override fun getColorPickerInteractor(
@@ -570,6 +593,7 @@ open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInject
        private val KEY_APP_GRID_SNAPSHOT_RESTORER = KEY_THEMED_ICON_SNAPSHOT_RESTORER + 1
        @JvmStatic
        private val KEY_COLOR_PICKER_SNAPSHOT_RESTORER = KEY_APP_GRID_SNAPSHOT_RESTORER + 1
        @JvmStatic private val KEY_CLOCKS_SNAPSHOT_RESTORER = KEY_COLOR_PICKER_SNAPSHOT_RESTORER + 1

        /**
         * When this injector is overridden, this is the minimal value that should be used by
@@ -577,6 +601,6 @@ open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInject
         *
         * It should always be greater than the biggest restorer key.
         */
        @JvmStatic protected val MIN_SNAPSHOT_RESTORER_KEY = KEY_COLOR_PICKER_SNAPSHOT_RESTORER + 1
        @JvmStatic protected val MIN_SNAPSHOT_RESTORER_KEY = KEY_CLOCKS_SNAPSHOT_RESTORER + 1
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -42,7 +42,7 @@ interface ClockPickerRepository {
     * @param colorToneProgress color tone from 0 to 100 to apply to the selected color
     * @param seedColor the actual clock color after blending the selected color and color tone
     */
    fun setClockColor(
    suspend fun setClockColor(
        selectedColorId: String?,
        @IntRange(from = 0, to = 100) colorToneProgress: Int,
        @ColorInt seedColor: Int?,
+6 −1
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import com.android.customization.picker.clock.shared.model.ClockMetadataModel
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.wallpaper.settings.data.repository.SecureSettingsRepository
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
@@ -32,6 +33,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull
@@ -43,6 +45,7 @@ class ClockPickerRepositoryImpl(
    private val secureSettingsRepository: SecureSettingsRepository,
    private val registry: ClockRegistry,
    scope: CoroutineScope,
    mainDispatcher: CoroutineDispatcher,
) : ClockPickerRepository {

    @OptIn(ExperimentalCoroutinesApi::class)
@@ -67,6 +70,7 @@ class ClockPickerRepositoryImpl(
                send()
                awaitClose { registry.unregisterClockChangeListener(listener) }
            }
            .flowOn(mainDispatcher)
            .mapLatest { allClocks ->
                // Loading list of clock plugins can cause many consecutive calls of
                // onAvailableClocksChanged(). We only care about the final fully-initiated clock
@@ -108,6 +112,7 @@ class ClockPickerRepositoryImpl(
                send()
                awaitClose { registry.unregisterClockChangeListener(listener) }
            }
            .flowOn(mainDispatcher)
            .mapNotNull { it }

    override suspend fun setSelectedClock(clockId: String) {
@@ -118,7 +123,7 @@ class ClockPickerRepositoryImpl(
        }
    }

    override fun setClockColor(
    override suspend fun setClockColor(
        selectedColorId: String?,
        @IntRange(from = 0, to = 100) colorToneProgress: Int,
        @ColorInt seedColor: Int?,
+61 −5
Original line number Diff line number Diff line
@@ -22,15 +22,21 @@ import androidx.annotation.IntRange
import com.android.customization.picker.clock.data.repository.ClockPickerRepository
import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.clock.shared.model.ClockMetadataModel
import com.android.customization.picker.clock.shared.model.ClockSnapshotModel
import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map

/**
 * Interactor for accessing application clock settings, as well as selecting and configuring custom
 * clocks.
 */
class ClockPickerInteractor(private val repository: ClockPickerRepository) {
class ClockPickerInteractor(
    private val repository: ClockPickerRepository,
    private val snapshotRestorer: Provider<ClockPickerSnapshotRestorer>,
) {

    val allClocks: Flow<List<ClockMetadataModel>> = repository.allClocks

@@ -48,18 +54,68 @@ class ClockPickerInteractor(private val repository: ClockPickerRepository) {
    val selectedClockSize: Flow<ClockSize> = repository.selectedClockSize

    suspend fun setSelectedClock(clockId: String) {
        repository.setSelectedClock(clockId)
        // Use the [clockId] to override saved clock id, since it might not be updated in time
        setClockOption(ClockSnapshotModel(clockId = clockId))
    }

    fun setClockColor(
    suspend fun setClockColor(
        selectedColorId: String?,
        @IntRange(from = 0, to = 100) colorToneProgress: Int,
        @ColorInt seedColor: Int?,
    ) {
        repository.setClockColor(selectedColorId, colorToneProgress, seedColor)
        // Use the color to override saved color, since it might not be updated in time
        setClockOption(
            ClockSnapshotModel(
                selectedColorId = selectedColorId,
                colorToneProgress = colorToneProgress,
                seedColor = seedColor,
            )
        )
    }

    suspend fun setClockSize(size: ClockSize) {
        repository.setClockSize(size)
        // Use the [ClockSize] to override saved clock size, since it might not be updated in time
        setClockOption(ClockSnapshotModel(clockSize = size))
    }

    suspend fun setClockOption(clockSnapshotModel: ClockSnapshotModel) {
        // [ClockCarouselViewModel] is monitoring the [ClockPickerInteractor.setSelectedClock] job,
        // so it needs to finish last.
        storeCurrentClockOption(clockSnapshotModel)

        clockSnapshotModel.clockSize?.let { repository.setClockSize(it) }
        clockSnapshotModel.colorToneProgress?.let {
            repository.setClockColor(
                selectedColorId = clockSnapshotModel.selectedColorId,
                colorToneProgress = clockSnapshotModel.colorToneProgress,
                seedColor = clockSnapshotModel.seedColor
            )
        }
        clockSnapshotModel.clockId?.let { repository.setSelectedClock(it) }
    }

    /**
     * Gets the [ClockSnapshotModel] from the storage and override with [latestOption].
     *
     * The storage might be in the middle of a write, and not reflecting the user's options, always
     * pass in a [ClockSnapshotModel] if we know it's the latest option from a user's point of view.
     *
     * [selectedColorId] and [seedColor] have null state collide with nullable type, but we know
     * they are presented whenever there's a [colorToneProgress].
     */
    suspend fun getCurrentClockToRestore(latestOption: ClockSnapshotModel? = null) =
        ClockSnapshotModel(
            clockId = latestOption?.clockId ?: selectedClockId.firstOrNull(),
            clockSize = latestOption?.clockSize ?: selectedClockSize.firstOrNull(),
            colorToneProgress = latestOption?.colorToneProgress ?: colorToneProgress.firstOrNull(),
            selectedColorId = latestOption?.colorToneProgress?.let { latestOption.selectedColorId }
                    ?: selectedColorId.firstOrNull(),
            seedColor = latestOption?.colorToneProgress?.let { latestOption.seedColor }
                    ?: seedColor.firstOrNull(),
        )

    private suspend fun storeCurrentClockOption(clockSnapshotModel: ClockSnapshotModel) {
        val option = getCurrentClockToRestore(clockSnapshotModel)
        snapshotRestorer.get().storeSnapshot(option)
    }
}
+94 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.clock.domain.interactor

import android.text.TextUtils
import android.util.Log
import com.android.customization.picker.clock.shared.model.ClockSnapshotModel
import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
import com.android.wallpaper.picker.undo.domain.interactor.SnapshotStore
import com.android.wallpaper.picker.undo.shared.model.RestorableSnapshot

/** Handles state restoration for clocks. */
class ClockPickerSnapshotRestorer(private val interactor: ClockPickerInteractor) :
    SnapshotRestorer {
    private var snapshotStore: SnapshotStore = SnapshotStore.NOOP
    private var originalOption: ClockSnapshotModel? = null

    override suspend fun setUpSnapshotRestorer(
        store: SnapshotStore,
    ): RestorableSnapshot {
        snapshotStore = store
        originalOption = interactor.getCurrentClockToRestore()
        return snapshot(originalOption)
    }

    override suspend fun restoreToSnapshot(snapshot: RestorableSnapshot) {
        originalOption?.let { optionToRestore ->
            if (
                TextUtils.isEmpty(optionToRestore.clockId) ||
                    optionToRestore.clockId != snapshot.args[KEY_CLOCK_ID] ||
                    optionToRestore.clockSize?.toString() != snapshot.args[KEY_CLOCK_SIZE] ||
                    optionToRestore.colorToneProgress?.toString() !=
                        snapshot.args[KEY_COLOR_TONE_PROGRESS] ||
                    optionToRestore.seedColor?.toString() != snapshot.args[KEY_SEED_COLOR] ||
                    optionToRestore.selectedColorId != snapshot.args[KEY_COLOR_ID]
            ) {
                Log.wtf(
                    TAG,
                    """ Original clock option does not match snapshot option to restore to. The
                        | current implementation doesn't support undo, only a reset back to the
                        | original clock option."""
                        .trimMargin(),
                )
            }

            interactor.setClockOption(optionToRestore)
        }
    }

    fun storeSnapshot(clockSnapshotModel: ClockSnapshotModel) {
        snapshotStore.store(snapshot(clockSnapshotModel))
    }

    private fun snapshot(clockSnapshotModel: ClockSnapshotModel? = null): RestorableSnapshot {
        val options =
            if (clockSnapshotModel == null) emptyMap()
            else
                buildMap {
                    clockSnapshotModel.clockId?.let { put(KEY_CLOCK_ID, it) }
                    clockSnapshotModel.clockSize?.let { put(KEY_CLOCK_SIZE, it.toString()) }
                    clockSnapshotModel.selectedColorId?.let { put(KEY_COLOR_ID, it) }
                    clockSnapshotModel.colorToneProgress?.let {
                        put(KEY_COLOR_TONE_PROGRESS, it.toString())
                    }
                    clockSnapshotModel.seedColor?.let { put(KEY_SEED_COLOR, it.toString()) }
                }

        return RestorableSnapshot(options)
    }

    companion object {
        private const val TAG = "ClockPickerSnapshotRestorer"
        private const val KEY_CLOCK_ID = "clock_id"
        private const val KEY_CLOCK_SIZE = "clock_size"
        private const val KEY_COLOR_ID = "color_id"
        private const val KEY_COLOR_TONE_PROGRESS = "color_tone_progress"
        private const val KEY_SEED_COLOR = "seed_color"
    }
}
Loading