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

Commit bb058689 authored by Behnam Heydarshahi's avatar Behnam Heydarshahi
Browse files

Migrate Reduce Bright Colors Tile

Fixes: 301056371
Flag: aconfig com.android.systemui.qs_new_tiles_future DEVELOPMENT
Test: atest ReduceBrightColorsTileDataInteractorTest
ReduceBrightColorsTileUserActionInteractorTest
ReduceBrightColorsTileMapperTest

Change-Id: I37595417dff72d4a5795d71014bbe160d665c429
parent 8756fdba
Loading
Loading
Loading
Loading
+87 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.qs.tiles.impl.reducebrightness.domain.interactor

import android.os.UserHandle
import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.reduceBrightColorsController
import com.android.systemui.coroutines.collectValues
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toCollection
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class ReduceBrightColorsTileDataInteractorTest : SysuiTestCase() {

    private val isAvailable = true
    private val kosmos = Kosmos()
    private val testScope = kosmos.testScope
    private val reduceBrightColorsController = kosmos.reduceBrightColorsController
    private val underTest: ReduceBrightColorsTileDataInteractor =
        ReduceBrightColorsTileDataInteractor(
            testScope.testScheduler,
            isAvailable,
            reduceBrightColorsController
        )

    @Test
    fun alwaysAvailable() =
        testScope.runTest {
            val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())

            assertThat(availability).hasSize(1)
            assertThat(availability.last()).isEqualTo(isAvailable)
        }

    @Test
    fun dataMatchesTheRepository() =
        testScope.runTest {
            val dataList: List<ReduceBrightColorsTileModel> by
                collectValues(
                    underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
                )
            runCurrent()

            reduceBrightColorsController.isReduceBrightColorsActivated = true
            runCurrent()

            reduceBrightColorsController.isReduceBrightColorsActivated = false
            runCurrent()

            assertThat(dataList).hasSize(3)
            assertThat(dataList.map { it.isEnabled }).isEqualTo(listOf(false, true, false))
        }

    private companion object {
        val TEST_USER = UserHandle.of(1)!!
    }
}
+91 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.qs.tiles.impl.reducebrightness.domain.interactor

import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.reduceBrightColorsController
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class ReduceBrightColorsTileUserActionInteractorTest : SysuiTestCase() {

    private val kosmos = Kosmos()
    private val inputHandler = FakeQSTileIntentUserInputHandler()
    private val controller = kosmos.reduceBrightColorsController

    private val underTest =
        ReduceBrightColorsTileUserActionInteractor(
            inputHandler,
            controller,
        )

    @Test
    fun handleClickWhenEnabled() = runTest {
        val wasEnabled = true
        controller.isReduceBrightColorsActivated = wasEnabled

        underTest.handleInput(QSTileInputTestKtx.click(ReduceBrightColorsTileModel(wasEnabled)))

        assertThat(controller.isReduceBrightColorsActivated).isEqualTo(!wasEnabled)
    }

    @Test
    fun handleClickWhenDisabled() = runTest {
        val wasEnabled = false
        controller.isReduceBrightColorsActivated = wasEnabled

        underTest.handleInput(QSTileInputTestKtx.click(ReduceBrightColorsTileModel(wasEnabled)))

        assertThat(controller.isReduceBrightColorsActivated).isEqualTo(!wasEnabled)
    }

    @Test
    fun handleLongClickWhenDisabled() = runTest {
        val enabled = false

        underTest.handleInput(QSTileInputTestKtx.longClick(ReduceBrightColorsTileModel(enabled)))

        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
            assertThat(it.intent.action).isEqualTo(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
        }
    }

    @Test
    fun handleLongClickWhenEnabled() = runTest {
        val enabled = true

        underTest.handleInput(QSTileInputTestKtx.longClick(ReduceBrightColorsTileModel(enabled)))

        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
            assertThat(it.intent.action).isEqualTo(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
        }
    }
}
+111 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.qs.tiles.impl.reducebrightness.ui

import android.graphics.drawable.TestStubDrawable
import android.service.quicksettings.Tile
import android.widget.Switch
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
import com.android.systemui.qs.tiles.impl.reducebrightness.qsReduceBrightColorsTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class ReduceBrightColorsTileMapperTest : SysuiTestCase() {
    private val kosmos = Kosmos()
    private val config = kosmos.qsReduceBrightColorsTileConfig

    private lateinit var mapper: ReduceBrightColorsTileMapper

    @Before
    fun setup() {
        mapper =
            ReduceBrightColorsTileMapper(
                context.orCreateTestableResources
                    .apply {
                        addOverride(R.drawable.qs_extra_dim_icon_on, TestStubDrawable())
                        addOverride(R.drawable.qs_extra_dim_icon_off, TestStubDrawable())
                    }
                    .resources,
                context.theme
            )
    }

    @Test
    fun disabledModel() {
        val inputModel = ReduceBrightColorsTileModel(false)

        val outputState = mapper.map(config, inputModel)

        val expectedState =
            createReduceBrightColorsTileState(
                QSTileState.ActivationState.INACTIVE,
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    @Test
    fun enabledModel() {
        val inputModel = ReduceBrightColorsTileModel(true)

        val outputState = mapper.map(config, inputModel)

        val expectedState = createReduceBrightColorsTileState(QSTileState.ActivationState.ACTIVE)
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    private fun createReduceBrightColorsTileState(
        activationState: QSTileState.ActivationState,
    ): QSTileState {
        val label =
            context.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
        return QSTileState(
            {
                Icon.Loaded(
                    context.getDrawable(
                        if (activationState == QSTileState.ActivationState.ACTIVE)
                            R.drawable.qs_extra_dim_icon_on
                        else R.drawable.qs_extra_dim_icon_off
                    )!!,
                    null
                )
            },
            label,
            activationState,
            context.resources
                .getStringArray(R.array.tile_states_reduce_brightness)[
                    if (activationState == QSTileState.ActivationState.ACTIVE) Tile.STATE_ACTIVE
                    else Tile.STATE_INACTIVE],
            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
            label,
            null,
            QSTileState.SideViewIcon.None,
            QSTileState.EnabledState.ENABLED,
            Switch::class.qualifiedName
        )
    }
}
+43 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.accessibility.qs

import com.android.systemui.Flags
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -40,9 +41,14 @@ import com.android.systemui.qs.tiles.impl.inversion.domain.ColorInversionTileMap
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileDataInteractor
import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
import com.android.systemui.qs.tiles.impl.reducebrightness.ui.ReduceBrightColorsTileMapper
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
@@ -105,6 +111,7 @@ interface QSAccessibilityModule {
        const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
        const val COLOR_INVERSION_TILE_SPEC = "inversion"
        const val FONT_SCALING_TILE_SPEC = "font_scaling"
        const val REDUCE_BRIGHTNESS_TILE_SPEC = "reduce_brightness"

        @Provides
        @IntoMap
@@ -198,5 +205,41 @@ interface QSAccessibilityModule {
                stateInteractor,
                mapper,
            )

        @Provides
        @IntoMap
        @StringKey(REDUCE_BRIGHTNESS_TILE_SPEC)
        fun provideReduceBrightColorsTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
            QSTileConfig(
                tileSpec = TileSpec.create(REDUCE_BRIGHTNESS_TILE_SPEC),
                uiConfig =
                    QSTileUIConfig.Resource(
                        iconRes = R.drawable.qs_extra_dim_icon_on,
                        labelRes = com.android.internal.R.string.reduce_bright_colors_feature_name,
                    ),
                instanceId = uiEventLogger.getNewInstanceId(),
            )

        /**
         * Inject Reduce Bright Colors Tile into tileViewModelMap in QSModule. The tile is hidden
         * behind a flag.
         */
        @Provides
        @IntoMap
        @StringKey(REDUCE_BRIGHTNESS_TILE_SPEC)
        fun provideReduceBrightColorsTileViewModel(
            factory: QSTileViewModelFactory.Static<ReduceBrightColorsTileModel>,
            mapper: ReduceBrightColorsTileMapper,
            stateInteractor: ReduceBrightColorsTileDataInteractor,
            userActionInteractor: ReduceBrightColorsTileUserActionInteractor
        ): QSTileViewModel =
            if (Flags.qsNewTilesFuture())
                factory.create(
                    TileSpec.create(REDUCE_BRIGHTNESS_TILE_SPEC),
                    userActionInteractor,
                    stateInteractor,
                    mapper,
                )
            else StubQSTileViewModel
    }
}
+6 −109
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 * 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.
@@ -16,124 +16,21 @@

package com.android.systemui.qs;

import android.content.Context;
import android.database.ContentObserver;
import android.hardware.display.ColorDisplayManager;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.provider.Settings;

import androidx.annotation.NonNull;

import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.util.settings.SecureSettings;

import java.util.ArrayList;

import javax.inject.Inject;

/**
 * @hide
 */
@SysUISingleton
public class ReduceBrightColorsController implements
public interface ReduceBrightColorsController extends
        CallbackController<ReduceBrightColorsController.Listener> {
    private final ColorDisplayManager mManager;
    private final UserTracker mUserTracker;
    private UserTracker.Callback mCurrentUserTrackerCallback;
    private final Handler mHandler;
    private final ContentObserver mContentObserver;
    private final SecureSettings mSecureSettings;
    private final ArrayList<ReduceBrightColorsController.Listener> mListeners = new ArrayList<>();

    @Inject
    public ReduceBrightColorsController(UserTracker userTracker,
            @Background Handler handler,
            ColorDisplayManager colorDisplayManager,
            SecureSettings secureSettings) {
        mManager = colorDisplayManager;
        mUserTracker = userTracker;
        mHandler = handler;
        mSecureSettings = secureSettings;
        mContentObserver = new ContentObserver(mHandler) {
            @Override
            public void onChange(boolean selfChange, Uri uri) {
                super.onChange(selfChange, uri);
                final String setting = uri == null ? null : uri.getLastPathSegment();
                synchronized (mListeners) {
                    if (setting != null && mListeners.size() != 0) {
                        if (setting.equals(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) {
                            dispatchOnActivated(mManager.isReduceBrightColorsActivated());
                        }
                    }
                }
            }
        };

        mCurrentUserTrackerCallback = new UserTracker.Callback() {
            @Override
            public void onUserChanged(int newUser, Context userContext) {
                synchronized (mListeners) {
                    if (mListeners.size() > 0) {
                        mSecureSettings.unregisterContentObserver(mContentObserver);
                        mSecureSettings.registerContentObserverForUser(
                                Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
                                false, mContentObserver, newUser);
                    }
                }
            }
        };
        mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler));
    }

    @Override
    public void addCallback(@NonNull Listener listener) {
        synchronized (mListeners) {
            if (!mListeners.contains(listener)) {
                mListeners.add(listener);
                if (mListeners.size() == 1) {
                    mSecureSettings.registerContentObserverForUser(
                            Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
                            false, mContentObserver, mUserTracker.getUserId());
                }
            }
        }
    }

    @Override
    public void removeCallback(@androidx.annotation.NonNull Listener listener) {
        synchronized (mListeners) {
            if (mListeners.remove(listener) && mListeners.size() == 0) {
                mSecureSettings.unregisterContentObserver(mContentObserver);
            }
        }
    }

    /** Returns {@code true} if Reduce Bright Colors is activated */
    public boolean isReduceBrightColorsActivated() {
        return mManager.isReduceBrightColorsActivated();
    }
    boolean isReduceBrightColorsActivated();

    /** Sets the activation state of Reduce Bright Colors */
    public void setReduceBrightColorsActivated(boolean activated) {
        mManager.setReduceBrightColorsActivated(activated);
    }

    private void dispatchOnActivated(boolean activated) {
        ArrayList<Listener> copy = new ArrayList<>(mListeners);
        for (Listener l : copy) {
            l.onActivated(activated);
        }
    }
    void setReduceBrightColorsActivated(boolean activated);

    /**
     * Listener invoked whenever the Reduce Bright Colors settings are changed.
     */
    public interface Listener {
    interface Listener {
        /**
         * Listener invoked when the activated state changes.
         *
Loading