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

Commit 09128377 authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Re-inflate on interesting config changes

Also, pass configuration changes to the view.

Test: manual: change themes, density, font size
Test: atest QSSceneAdapterImplTest
Fixes: 317249272
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT

Change-Id: I9957bd698e5cb1a48b1774cc5139321d42079e80
parent 622fc57e
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;

import androidx.annotation.NonNull;

/**
 * A class for applying config changes and determing if doing so resulting in any "interesting"
 * changes.
@@ -48,8 +50,15 @@ public class InterestingConfigChanges {
     */
    @SuppressLint("NewApi")
    public boolean applyNewConfig(Resources res) {
        return applyNewConfig(res.getConfiguration());
    }

    /**
     * Applies the given config change and returns whether an "interesting" change happened.
     */
    public boolean applyNewConfig(@NonNull Configuration configuration) {
        int configChanges = mLastConfiguration.updateFrom(
                Configuration.generateDelta(mLastConfiguration, res.getConfiguration()));
                Configuration.generateDelta(mLastConfiguration, configuration));
        return (configChanges & (mFlags)) != 0;
    }
}
+92 −0
Original line number Diff line number Diff line
@@ -16,12 +16,16 @@

package com.android.systemui.qs.ui.adapter

import android.content.res.Configuration
import android.os.Bundle
import android.view.Surface
import android.view.View
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.QSImpl
import com.android.systemui.qs.dagger.QSComponent
@@ -34,6 +38,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.Locale
import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -81,11 +86,17 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
                    .also { components.add(it) }
            }
        }
    private val configuration = Configuration(context.resources.configuration)

    private val fakeConfigurationRepository =
        FakeConfigurationRepository().apply { onConfigurationChange(configuration) }
    private val configurationInteractor = ConfigurationInteractor(fakeConfigurationRepository)

    private val mockAsyncLayoutInflater =
        mock<AsyncLayoutInflater>() {
            whenever(inflate(anyInt(), nullable(), any())).then { invocation ->
                val mockView = mock<View>()
                whenever(mockView.context).thenReturn(context)
                invocation
                    .getArgument<AsyncLayoutInflater.OnInflateFinishedListener>(2)
                    .onInflateFinished(
@@ -102,6 +113,7 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
            qsImplProvider,
            testDispatcher,
            testScope.backgroundScope,
            configurationInteractor,
            { mockAsyncLayoutInflater },
        )

@@ -297,6 +309,9 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
    @Test
    fun reinflation_previousStateDestroyed() =
        testScope.runTest {
            // Run all flows... In particular, initial configuration propagation that could cause
            // QSImpl to re-inflate.
            runCurrent()
            val qsImpl by collectLastValue(underTest.qsImpl)

            underTest.inflate(context)
@@ -322,4 +337,81 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
                    bundleArgCaptor.value,
                )
        }

    @Test
    fun changeInLocale_reinflation() =
        testScope.runTest {
            val qsImpl by collectLastValue(underTest.qsImpl)

            underTest.inflate(context)
            runCurrent()

            val oldQsImpl = qsImpl!!

            val newLocale =
                if (configuration.locales[0] == Locale("en-US")) {
                    Locale("es-UY")
                } else {
                    Locale("en-US")
                }
            configuration.setLocale(newLocale)
            fakeConfigurationRepository.onConfigurationChange(configuration)
            runCurrent()

            assertThat(oldQsImpl).isNotSameInstanceAs(qsImpl!!)
        }

    @Test
    fun changeInFontSize_reinflation() =
        testScope.runTest {
            val qsImpl by collectLastValue(underTest.qsImpl)

            underTest.inflate(context)
            runCurrent()

            val oldQsImpl = qsImpl!!

            configuration.fontScale *= 2
            fakeConfigurationRepository.onConfigurationChange(configuration)
            runCurrent()

            assertThat(oldQsImpl).isNotSameInstanceAs(qsImpl!!)
        }

    @Test
    fun changeInAssetPath_reinflation() =
        testScope.runTest {
            val qsImpl by collectLastValue(underTest.qsImpl)

            underTest.inflate(context)
            runCurrent()

            val oldQsImpl = qsImpl!!

            configuration.assetsSeq += 1
            fakeConfigurationRepository.onConfigurationChange(configuration)
            runCurrent()

            assertThat(oldQsImpl).isNotSameInstanceAs(qsImpl!!)
        }

    @Test
    fun otherChangesInConfiguration_noReinflation_configurationChangeDispatched() =
        testScope.runTest {
            val qsImpl by collectLastValue(underTest.qsImpl)

            underTest.inflate(context)
            runCurrent()

            val oldQsImpl = qsImpl!!
            configuration.densityDpi *= 2
            configuration.windowConfiguration.maxBounds.scale(2f)
            configuration.windowConfiguration.rotation = Surface.ROTATION_270
            fakeConfigurationRepository.onConfigurationChange(configuration)
            runCurrent()

            assertThat(oldQsImpl).isSameInstanceAs(qsImpl!!)
            verify(qsImpl!!).onConfigurationChanged(configuration)
            verify(qsImpl!!.view).dispatchConfigurationChanged(configuration)
        }
}
+3 −0
Original line number Diff line number Diff line
@@ -72,6 +72,9 @@ class ConfigurationInteractor @Inject constructor(private val repository: Config
    val onAnyConfigurationChange: Flow<Unit> =
        repository.onAnyConfigurationChange.onStart { emit(Unit) }

    /** Emits the new configuration on any configuration change */
    val configurationValues: Flow<Configuration> = repository.configurationValues

    /** Emits the current resolution scaling factor */
    val scaleForResolution: Flow<Float> = repository.scaleForResolution
}
+41 −7
Original line number Diff line number Diff line
@@ -17,10 +17,13 @@
package com.android.systemui.qs.ui.adapter

import android.content.Context
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import com.android.settingslib.applications.InterestingConfigChanges
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -58,7 +61,7 @@ interface QSSceneAdapter {

    /**
     * Inflate an instance of [QSImpl] for this context. Once inflated, it will be available in
     * [qsView]
     * [qsView]. Re-inflations due to configuration changes will use the last used [context].
     */
    suspend fun inflate(context: Context)

@@ -90,6 +93,7 @@ constructor(
    private val qsImplProvider: Provider<QSImpl>,
    @Main private val mainDispatcher: CoroutineDispatcher,
    @Application applicationScope: CoroutineScope,
    private val configurationInteractor: ConfigurationInteractor,
    private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater,
) : QSContainerController, QSSceneAdapter {

@@ -99,7 +103,15 @@ constructor(
        qsImplProvider: Provider<QSImpl>,
        @Main dispatcher: CoroutineDispatcher,
        @Application scope: CoroutineScope,
    ) : this(qsSceneComponentFactory, qsImplProvider, dispatcher, scope, ::AsyncLayoutInflater)
        configurationInteractor: ConfigurationInteractor,
    ) : this(
        qsSceneComponentFactory,
        qsImplProvider,
        dispatcher,
        scope,
        configurationInteractor,
        ::AsyncLayoutInflater,
    )

    private val state = MutableStateFlow<QSSceneAdapter.State>(QSSceneAdapter.State.CLOSED)
    private val _isCustomizing: MutableStateFlow<Boolean> = MutableStateFlow(false)
@@ -109,8 +121,17 @@ constructor(
    val qsImpl = _qsImpl.asStateFlow()
    override val qsView: Flow<View> = _qsImpl.map { it?.view }.filterNotNull()

    // Same config changes as in FragmentHostManager
    private val interestingChanges =
        InterestingConfigChanges(
            ActivityInfo.CONFIG_FONT_SCALE or
                ActivityInfo.CONFIG_LOCALE or
                ActivityInfo.CONFIG_ASSETS_PATHS
        )

    init {
        applicationScope.launch {
            launch {
                state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
                    _qsImpl.value?.apply {
                        if (state != QSSceneAdapter.State.QS && customizing) {
@@ -120,6 +141,19 @@ constructor(
                    }
                }
            }
            launch {
                configurationInteractor.configurationValues.collect { config ->
                    if (interestingChanges.applyNewConfig(config)) {
                        // Assumption: The context is always the same and with the same theme.
                        // If colors change they will be reflected as attributes in the theme.
                        qsImpl.value?.view?.let { inflate(it.context) }
                    } else {
                        qsImpl.value?.onConfigurationChanged(config)
                        qsImpl.value?.view?.dispatchConfigurationChanged(config)
                    }
                }
            }
        }
    }

    override fun setCustomizerAnimating(animating: Boolean) {}