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

Commit d6a6294f authored by Christian Göllner's avatar Christian Göllner Committed by Christian Göllner
Browse files

[Split shade] Fix QS actions footer border visible on shade expansion

* Also fixes the actions footer fading in too late on split shade

The QS actions footer has its own background and elevation, which makes
it become visible during the split shade expansion.

The fix is to make sure that there different expansion values are used
for the actions footer when on split shade, so that the background of
the actions footer fades in later.

Change-Id: I81dcba67b9473d9a8a4565dcdffd0a62f3817a50
Test: FooterActionsControllerTest.kt
Test: QSFragmentTest.java
Test: Manually
Fixes: 240563302
parent 592f8bf6
Loading
Loading
Loading
Loading
+49 −8
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.qs

import android.content.Intent
import android.content.res.Configuration
import android.os.Handler
import android.os.UserManager
import android.provider.Settings
@@ -38,9 +39,11 @@ import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
import com.android.systemui.qs.dagger.QSScope
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.ViewController
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
@@ -69,18 +72,43 @@ internal class FooterActionsController @Inject constructor(
    private val uiEventLogger: UiEventLogger,
    @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
    private val globalSetting: GlobalSettings,
    private val handler: Handler
    private val handler: Handler,
    private val configurationController: ConfigurationController,
) : ViewController<FooterActionsView>(view) {

    private var globalActionsDialog: GlobalActionsDialogLite? = null

    private var lastExpansion = -1f
    private var listening: Boolean = false
    private var inSplitShade = false

    private val alphaAnimator = TouchAnimator.Builder()
            .addFloat(mView, "alpha", 0f, 1f)
    private val singleShadeAnimator by lazy {
        // In single shade, the actions footer should only appear at the end of the expansion,
        // so that it doesn't overlap with the notifications panel.
        TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).setStartDelay(0.9f).build()
    }

    private val splitShadeAnimator by lazy {
        // The Actions footer view has its own background which is the same color as the qs panel's
        // background.
        // We don't want it to fade in at the same time as the rest of the panel, otherwise it is
        // more opaque than the rest of the panel's background. Only applies to split shade.
        val alphaAnimator = TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).build()
        val bgAlphaAnimator =
            TouchAnimator.Builder()
                .addFloat(mView, "backgroundAlpha", 0f, 1f)
                .setStartDelay(0.9f)
                .build()
        // In split shade, we want the actions footer to fade in exactly at the same time as the
        // rest of the shade, as there is no overlap.
        TouchAnimator.Builder()
            .addFloat(alphaAnimator, "position", 0f, 1f)
            .addFloat(bgAlphaAnimator, "position", 0f, 1f)
            .build()
    }

    private val animators: TouchAnimator
        get() = if (inSplitShade) splitShadeAnimator else singleShadeAnimator

    var visible = true
        set(value) {
@@ -95,9 +123,7 @@ internal class FooterActionsController @Inject constructor(
    private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)

    @VisibleForTesting
    internal val securityFootersSeparator = View(context).apply {
        visibility = View.GONE
    }
    internal val securityFootersSeparator = View(context).apply { visibility = View.GONE }

    private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
        val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
@@ -133,6 +159,17 @@ internal class FooterActionsController @Inject constructor(
        }
    }

    private val configurationListener =
        object : ConfigurationController.ConfigurationListener {
            override fun onConfigChanged(newConfig: Configuration?) {
                updateResources()
            }
        }

    private fun updateResources() {
        inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources)
    }

    override fun onInit() {
        multiUserSwitchController.init()
        securityFooterController.init()
@@ -189,6 +226,9 @@ internal class FooterActionsController @Inject constructor(
        securityFooterController.setOnVisibilityChangedListener(visibilityListener)
        fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)

        configurationController.addCallback(configurationListener)

        updateResources()
        updateView()
    }

@@ -201,6 +241,7 @@ internal class FooterActionsController @Inject constructor(
        globalActionsDialog = null
        setListening(false)
        multiUserSetting.isListening = false
        configurationController.removeCallback(configurationListener)
    }

    fun setListening(listening: Boolean) {
@@ -224,7 +265,7 @@ internal class FooterActionsController @Inject constructor(
    }

    fun setExpansion(headerExpansionFraction: Float) {
        alphaAnimator.setPosition(headerExpansionFraction)
        animators.setPosition(headerExpansionFraction)
    }

    fun setKeyguardShowing(showing: Boolean) {
+15 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.view.MotionEvent
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.annotation.Keep
import com.android.settingslib.Utils
import com.android.settingslib.drawable.UserIconDrawable
import com.android.systemui.R
@@ -45,6 +46,19 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout(
    private var qsDisabled = false
    private var expansionAmount = 0f

    /**
     * Sets the alpha of the background of this view.
     *
     * Used from a [TouchAnimator] in the controller.
     */
    var backgroundAlpha: Float = 1f
        @Keep
        set(value) {
            field = value
            background?.alpha = (value * 255).toInt()
        }
        @Keep get

    override fun onFinishInflate() {
        super.onFinishInflate()
        settingsContainer = findViewById(R.id.settings_button_container)
+5 −3
Original line number Diff line number Diff line
@@ -557,9 +557,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
    public void setQsExpansion(float expansion, float panelExpansionFraction,
            float proposedTranslation, float squishinessFraction) {
        float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation;
        float progress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD
        float alphaProgress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD
                ? mFullShadeProgress : panelExpansionFraction;
        setAlphaAnimationProgress(mInSplitShade ? progress : 1);
        setAlphaAnimationProgress(mInSplitShade ? alphaProgress : 1);
        mContainer.setExpansion(expansion);
        final float translationScaleY = (mInSplitShade
                ? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
@@ -600,7 +600,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
        }
        mQSPanelController.setIsOnKeyguard(onKeyguard);
        mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
        mQSFooterActionController.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
        float footerActionsExpansion =
                onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion;
        mQSFooterActionController.setExpansion(footerActionsExpansion);
        mQSPanelController.setRevealExpansion(expansion);
        mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
        mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
+152 −37
Original line number Diff line number Diff line
@@ -22,13 +22,17 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.utils.leaks.LeakCheckedTest
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import javax.inject.Provider
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -42,47 +46,38 @@ import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import javax.inject.Provider
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations

@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner::class)
class FooterActionsControllerTest : LeakCheckedTest() {
    @Mock
    private lateinit var userManager: UserManager
    @Mock
    private lateinit var userTracker: UserTracker
    @Mock
    private lateinit var activityStarter: ActivityStarter
    @Mock
    private lateinit var deviceProvisionedController: DeviceProvisionedController
    @Mock
    private lateinit var userInfoController: UserInfoController
    @Mock
    private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory
    @Mock
    private lateinit var multiUserSwitchController: MultiUserSwitchController
    @Mock
    private lateinit var globalActionsDialogProvider: Provider<GlobalActionsDialogLite>
    @Mock
    private lateinit var globalActionsDialog: GlobalActionsDialogLite
    @Mock
    private lateinit var uiEventLogger: UiEventLogger
    @Mock
    private lateinit var securityFooterController: QSSecurityFooter
    @Mock
    private lateinit var fgsManagerController: QSFgsManagerFooter

    @get:Rule var expect: Expect = Expect.create()

    @Mock private lateinit var userManager: UserManager
    @Mock private lateinit var userTracker: UserTracker
    @Mock private lateinit var activityStarter: ActivityStarter
    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
    @Mock private lateinit var userInfoController: UserInfoController
    @Mock private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory
    @Mock private lateinit var multiUserSwitchController: MultiUserSwitchController
    @Mock private lateinit var globalActionsDialogProvider: Provider<GlobalActionsDialogLite>
    @Mock private lateinit var globalActionsDialog: GlobalActionsDialogLite
    @Mock private lateinit var uiEventLogger: UiEventLogger
    @Mock private lateinit var securityFooterController: QSSecurityFooter
    @Mock private lateinit var fgsManagerController: QSFgsManagerFooter
    @Captor
    private lateinit var visibilityChangedCaptor:
        ArgumentCaptor<VisibilityChangedDispatcher.OnVisibilityChangedListener>

    private lateinit var controller: FooterActionsController

    private val configurationController = FakeConfigurationController()
    private val metricsLogger: MetricsLogger = FakeMetricsLogger()
    private lateinit var view: FooterActionsView
    private val falsingManager: FalsingManagerFake = FalsingManagerFake()
    private lateinit var view: FooterActionsView
    private lateinit var testableLooper: TestableLooper
    private lateinit var fakeSettings: FakeSettings
    private lateinit var securityFooter: View
@@ -90,6 +85,9 @@ class FooterActionsControllerTest : LeakCheckedTest() {

    @Before
    fun setUp() {
        // We want to make sure testable resources are always used
        context.ensureTestableResources()

        MockitoAnnotations.initMocks(this)
        testableLooper = TestableLooper.get(this)
        fakeSettings = FakeSettings()
@@ -299,6 +297,86 @@ class FooterActionsControllerTest : LeakCheckedTest() {
        assertThat(booleanCaptor.allValues.last()).isTrue()
    }

    @Test
    fun setExpansion_inSplitShade_alphaFollowsExpansion() {
        enableSplitShade()

        controller.setExpansion(0f)
        expect.that(view.alpha).isEqualTo(0f)

        controller.setExpansion(0.25f)
        expect.that(view.alpha).isEqualTo(0.25f)

        controller.setExpansion(0.5f)
        expect.that(view.alpha).isEqualTo(0.5f)

        controller.setExpansion(0.75f)
        expect.that(view.alpha).isEqualTo(0.75f)

        controller.setExpansion(1f)
        expect.that(view.alpha).isEqualTo(1f)
    }

    @Test
    fun setExpansion_inSplitShade_backgroundAlphaFollowsExpansion_with_0_9_delay() {
        enableSplitShade()

        controller.setExpansion(0f)
        expect.that(view.backgroundAlphaFraction).isEqualTo(0f)

        controller.setExpansion(0.5f)
        expect.that(view.backgroundAlphaFraction).isEqualTo(0f)

        controller.setExpansion(0.9f)
        expect.that(view.backgroundAlphaFraction).isEqualTo(0f)

        controller.setExpansion(0.91f)
        expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.1f)

        controller.setExpansion(0.95f)
        expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.5f)

        controller.setExpansion(1f)
        expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
    }

    @Test
    fun setExpansion_inSingleShade_alphaFollowsExpansion_with_0_9_delay() {
        disableSplitShade()

        controller.setExpansion(0f)
        expect.that(view.alpha).isEqualTo(0f)

        controller.setExpansion(0.5f)
        expect.that(view.alpha).isEqualTo(0f)

        controller.setExpansion(0.9f)
        expect.that(view.alpha).isEqualTo(0f)

        controller.setExpansion(0.91f)
        expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.1f)

        controller.setExpansion(0.95f)
        expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.5f)

        controller.setExpansion(1f)
        expect.that(view.alpha).isEqualTo(1f)
    }

    @Test
    fun setExpansion_inSingleShade_backgroundAlphaAlways1() {
        disableSplitShade()

        controller.setExpansion(0f)
        expect.that(view.backgroundAlphaFraction).isEqualTo(1f)

        controller.setExpansion(0.5f)
        expect.that(view.backgroundAlphaFraction).isEqualTo(1f)

        controller.setExpansion(1f)
        expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
    }

    private fun setVisibilities(
        securityFooterVisible: Boolean,
        fgsFooterVisible: Boolean,
@@ -311,15 +389,52 @@ class FooterActionsControllerTest : LeakCheckedTest() {
    }

    private fun inflateView(): FooterActionsView {
        return LayoutInflater.from(context)
                .inflate(R.layout.footer_actions, null) as FooterActionsView
        return LayoutInflater.from(context).inflate(R.layout.footer_actions, null)
            as FooterActionsView
    }

    private fun constructFooterActionsController(view: FooterActionsView): FooterActionsController {
        return FooterActionsController(view, multiUserSwitchControllerFactory,
                activityStarter, userManager, userTracker, userInfoController,
                deviceProvisionedController, securityFooterController, fgsManagerController,
                falsingManager, metricsLogger, globalActionsDialogProvider, uiEventLogger,
                showPMLiteButton = true, fakeSettings, Handler(testableLooper.looper))
        return FooterActionsController(
            view,
            multiUserSwitchControllerFactory,
            activityStarter,
            userManager,
            userTracker,
            userInfoController,
            deviceProvisionedController,
            securityFooterController,
            fgsManagerController,
            falsingManager,
            metricsLogger,
            globalActionsDialogProvider,
            uiEventLogger,
            showPMLiteButton = true,
            fakeSettings,
            Handler(testableLooper.looper),
            configurationController)
    }

    private fun enableSplitShade() {
        setSplitShadeEnabled(true)
    }

    private fun disableSplitShade() {
        setSplitShadeEnabled(false)
    }

    private fun setSplitShadeEnabled(enabled: Boolean) {
        overrideResource(R.bool.config_use_split_notification_shade, enabled)
        configurationController.notifyConfigurationChanged()
    }
}

private const val FLOAT_TOLERANCE = 0.01f

private val View.backgroundAlphaFraction: Float?
    get() {
        return if (background != null) {
            background.alpha / 255f
        } else {
            null
        }
    }
+34 −0
Original line number Diff line number Diff line
@@ -204,6 +204,40 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
        assertThat(mQsFragmentView.getAlpha()).isEqualTo(1);
    }

    @Test
    public void setQsExpansion_inSplitShade_setsFooterActionsExpansion_basedOnPanelExpFraction() {
        // Random test values without any meaning. They just have to be different from each other.
        float expansion = 0.123f;
        float panelExpansionFraction = 0.321f;
        float proposedTranslation = 456f;
        float squishinessFraction = 0.987f;

        QSFragment fragment = resumeAndGetFragment();
        enableSplitShade();

        fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
                squishinessFraction);

        verify(mQSFooterActionController).setExpansion(panelExpansionFraction);
    }

    @Test
    public void setQsExpansion_notInSplitShade_setsFooterActionsExpansion_basedOnExpansion() {
        // Random test values without any meaning. They just have to be different from each other.
        float expansion = 0.123f;
        float panelExpansionFraction = 0.321f;
        float proposedTranslation = 456f;
        float squishinessFraction = 0.987f;

        QSFragment fragment = resumeAndGetFragment();
        disableSplitShade();

        fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
                squishinessFraction);

        verify(mQSFooterActionController).setExpansion(expansion);
    }

    @Test
    public void getQsMinExpansionHeight_notInSplitShade_returnsHeaderHeight() {
        QSFragment fragment = resumeAndGetFragment();