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

Commit f028725d authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Reset support for dark theme." into tm-qpr-dev

parents 62643ed0 17e8bbef
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -59,12 +59,17 @@ public class DarkModeSectionController implements

    private Context mContext;
    private DarkModeSectionView mDarkModeSectionView;
    private final DarkModeSnapshotRestorer mSnapshotRestorer;

    public DarkModeSectionController(Context context, Lifecycle lifecycle) {
    public DarkModeSectionController(
            Context context,
            Lifecycle lifecycle,
            DarkModeSnapshotRestorer snapshotRestorer) {
        mContext = context;
        mLifecycle = lifecycle;
        mPowerManager = context.getSystemService(PowerManager.class);
        mLifecycle.addObserver(this);
        mSnapshotRestorer = snapshotRestorer;
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
@@ -132,6 +137,7 @@ public class DarkModeSectionController implements
                    mDarkModeSectionView.announceForAccessibility(
                            context.getString(R.string.mode_changed));
                    uiModeManager.setNightModeActivated(viewActivated);
                    mSnapshotRestorer.store(viewActivated);
                },
                /* delayMillis= */ shortDelay);
    }
+101 −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.model.mode

import android.app.UiModeManager
import android.content.Context
import android.content.res.Configuration
import androidx.annotation.VisibleForTesting
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
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext

class DarkModeSnapshotRestorer : SnapshotRestorer {

    private val backgroundDispatcher: CoroutineDispatcher
    private val isActive: () -> Boolean
    private val setActive: suspend (Boolean) -> Unit

    private lateinit var store: SnapshotStore

    constructor(
        context: Context,
        manager: UiModeManager,
        backgroundDispatcher: CoroutineDispatcher,
    ) : this(
        backgroundDispatcher = backgroundDispatcher,
        isActive = {
            context.applicationContext.resources.configuration.uiMode and
                Configuration.UI_MODE_NIGHT_YES != 0
        },
        setActive = { isActive -> manager.setNightModeActivated(isActive) },
    )

    @VisibleForTesting
    constructor(
        backgroundDispatcher: CoroutineDispatcher,
        isActive: () -> Boolean,
        setActive: suspend (Boolean) -> Unit,
    ) {
        this.backgroundDispatcher = backgroundDispatcher
        this.isActive = isActive
        this.setActive = setActive
    }

    override suspend fun setUpSnapshotRestorer(store: SnapshotStore): RestorableSnapshot {
        this.store = store
        return snapshot(
            isActivated = isActive(),
        )
    }

    override suspend fun restoreToSnapshot(snapshot: RestorableSnapshot) {
        val isActivated = snapshot.args[KEY]?.toBoolean() == true
        withContext(backgroundDispatcher) { setActive(isActivated) }
    }

    fun store(
        isActivated: Boolean,
    ) {
        store.store(
            snapshot(
                isActivated = isActivated,
            ),
        )
    }

    private fun snapshot(
        isActivated: Boolean,
    ): RestorableSnapshot {
        return RestorableSnapshot(
            args =
                buildMap {
                    put(
                        KEY,
                        isActivated.toString(),
                    )
                }
        )
    }

    companion object {
        private const val KEY = "is_activated"
    }
}
+13 −5
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ import com.android.customization.model.color.ColorSectionController2;
import com.android.customization.model.grid.GridOptionsManager;
import com.android.customization.model.grid.GridSectionController;
import com.android.customization.model.mode.DarkModeSectionController;
import com.android.customization.model.mode.DarkModeSnapshotRestorer;
import com.android.customization.model.themedicon.ThemedIconSectionController;
import com.android.customization.model.themedicon.ThemedIconSwitchProvider;
import com.android.customization.picker.clock.data.repository.ClockRegistryProvider;
@@ -56,6 +57,7 @@ public final class DefaultCustomizationSections implements CustomizationSections
            mClockCarouselViewModelProvider;
    private final PreviewWithClockCarouselSectionController.ClockViewFactoryProvider
            mClockViewFactoryProvider;
    private final DarkModeSnapshotRestorer mDarkModeSnapshotRestorer;

    public DefaultCustomizationSections(
            KeyguardQuickAffordancePickerInteractor keyguardQuickAffordancePickerInteractor,
@@ -65,7 +67,8 @@ public final class DefaultCustomizationSections implements CustomizationSections
            BaseFlags flags,
            ClockRegistryProvider clockRegistryProvider,
            ClockCarouselViewModelProvider clockCarouselViewModelProvider,
            ClockViewFactoryProvider clockViewFactoryProvider) {
            ClockViewFactoryProvider clockViewFactoryProvider,
            DarkModeSnapshotRestorer darkModeSnapshotRestorer) {
        mKeyguardQuickAffordancePickerInteractor = keyguardQuickAffordancePickerInteractor;
        mKeyguardQuickAffordancePickerViewModelFactory =
                keyguardQuickAffordancePickerViewModelFactory;
@@ -74,6 +77,7 @@ public final class DefaultCustomizationSections implements CustomizationSections
        mClockRegistryProvider = clockRegistryProvider;
        mClockCarouselViewModelProvider = clockCarouselViewModelProvider;
        mClockViewFactoryProvider = clockViewFactoryProvider;
        mDarkModeSnapshotRestorer = darkModeSnapshotRestorer;
    }

    @Override
@@ -157,8 +161,10 @@ public final class DefaultCustomizationSections implements CustomizationSections

            case HOME_SCREEN:
                // Dark/Light theme section.
                sectionControllers.add(new DarkModeSectionController(activity,
                        lifecycleOwner.getLifecycle()));
                sectionControllers.add(new DarkModeSectionController(
                        activity,
                        lifecycleOwner.getLifecycle(),
                        mDarkModeSnapshotRestorer));

                // Themed app icon section.
                sectionControllers.add(new ThemedIconSectionController(
@@ -198,8 +204,10 @@ public final class DefaultCustomizationSections implements CustomizationSections
                activity, wallpaperColorsViewModel, lifecycleOwner, savedInstanceState));

        // Dark/Light theme section.
        sectionControllers.add(new DarkModeSectionController(activity,
                lifecycleOwner.getLifecycle()));
        sectionControllers.add(new DarkModeSectionController(
                activity,
                lifecycleOwner.getLifecycle(),
                mDarkModeSnapshotRestorer));

        // Themed app icon section.
        sectionControllers.add(new ThemedIconSectionController(
+21 −2
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.customization.module

import android.app.UiModeManager
import android.content.Context
import android.content.Intent
import android.net.Uri
@@ -22,6 +23,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.android.customization.model.mode.DarkModeSnapshotRestorer
import com.android.customization.model.theme.OverlayManagerCompat
import com.android.customization.model.theme.ThemeBundleProvider
import com.android.customization.model.theme.ThemeManager
@@ -86,6 +88,7 @@ open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInject
    private var notificationSectionViewModelFactory: NotificationSectionViewModel.Factory? = null
    private var colorPickerInteractor: ColorPickerInteractor? = null
    private var colorPickerViewModelFactory: ColorPickerViewModel.Factory? = null
    private var darkModeSnapshotRestorer: DarkModeSnapshotRestorer? = null

    override fun getCustomizationSections(activity: ComponentActivity): CustomizationSections {
        return customizationSections
@@ -112,7 +115,8 @@ open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInject
                                registry = registry,
                            )
                        }
                    }
                    },
                    getDarkModeSnapshotRestorer(activity),
                )
                .also { customizationSections = it }
    }
@@ -173,6 +177,7 @@ open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInject
                getKeyguardQuickAffordanceSnapshotRestorer(context)
            this[KEY_WALLPAPER_SNAPSHOT_RESTORER] = getWallpaperSnapshotRestorer(context)
            this[KEY_NOTIFICATIONS_SNAPSHOT_RESTORER] = getNotificationsSnapshotRestorer(context)
            this[KEY_DARK_MODE_SNAPSHOT_RESTORER] = getDarkModeSnapshotRestorer(context)
        }
    }

@@ -348,6 +353,18 @@ open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInject
                .also { colorPickerViewModelFactory = it }
    }

    protected fun getDarkModeSnapshotRestorer(
        context: Context,
    ): DarkModeSnapshotRestorer {
        return darkModeSnapshotRestorer
            ?: DarkModeSnapshotRestorer(
                    context = context,
                    manager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager,
                    backgroundDispatcher = Dispatchers.IO,
                )
                .also { darkModeSnapshotRestorer = it }
    }

    companion object {
        @JvmStatic
        private val KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER =
@@ -356,11 +373,13 @@ open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInject
        private val KEY_WALLPAPER_SNAPSHOT_RESTORER = KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER + 1
        @JvmStatic
        private val KEY_NOTIFICATIONS_SNAPSHOT_RESTORER = KEY_WALLPAPER_SNAPSHOT_RESTORER + 1
        @JvmStatic
        private val KEY_DARK_MODE_SNAPSHOT_RESTORER = KEY_NOTIFICATIONS_SNAPSHOT_RESTORER + 1

        /**
         * When this injector is overridden, this is the minimal value that should be used by
         * restorers returns in [getSnapshotRestorers].
         */
        @JvmStatic protected val MIN_SNAPSHOT_RESTORER_KEY = KEY_NOTIFICATIONS_SNAPSHOT_RESTORER + 1
        @JvmStatic protected val MIN_SNAPSHOT_RESTORER_KEY = KEY_DARK_MODE_SNAPSHOT_RESTORER + 1
    }
}
+107 −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.model.mode

import androidx.test.filters.SmallTest
import com.android.wallpaper.testing.FakeSnapshotStore
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class DarkModeSnapshotRestorerTest {

    private lateinit var underTest: DarkModeSnapshotRestorer
    private lateinit var testScope: TestScope

    private var isActive = false

    @Before
    fun setUp() {
        val testDispatcher = StandardTestDispatcher()
        testScope = TestScope(testDispatcher)
        underTest =
            DarkModeSnapshotRestorer(
                backgroundDispatcher = testDispatcher,
                isActive = { isActive },
                setActive = { isActive = it },
            )
    }

    @Test
    fun `set up and restore - active`() =
        testScope.runTest {
            isActive = true

            val store = FakeSnapshotStore()
            store.store(underTest.setUpSnapshotRestorer(store = store))
            val storedSnapshot = store.retrieve()

            underTest.restoreToSnapshot(snapshot = storedSnapshot)
            assertThat(isActive).isTrue()
        }

    @Test
    fun `set up and restore - inactive`() =
        testScope.runTest {
            isActive = false

            val store = FakeSnapshotStore()
            store.store(underTest.setUpSnapshotRestorer(store = store))
            val storedSnapshot = store.retrieve()

            underTest.restoreToSnapshot(snapshot = storedSnapshot)
            assertThat(isActive).isFalse()
        }

    @Test
    fun `set up - deactivate - restore to active`() =
        testScope.runTest {
            isActive = true
            val store = FakeSnapshotStore()
            store.store(underTest.setUpSnapshotRestorer(store = store))
            val initialSnapshot = store.retrieve()

            underTest.store(isActivated = false)

            underTest.restoreToSnapshot(snapshot = initialSnapshot)
            assertThat(isActive).isTrue()
        }

    @Test
    fun `set up - activate - restore to inactive`() =
        testScope.runTest {
            isActive = false
            val store = FakeSnapshotStore()
            store.store(underTest.setUpSnapshotRestorer(store = store))
            val initialSnapshot = store.retrieve()

            underTest.store(isActivated = true)

            underTest.restoreToSnapshot(snapshot = initialSnapshot)
            assertThat(isActive).isFalse()
        }
}