diff options
| author | 2022-08-11 14:08:28 +0000 | |
|---|---|---|
| committer | 2022-08-11 14:08:28 +0000 | |
| commit | 910d009002d30c36a6b85284af4ae6c796018f72 (patch) | |
| tree | 53009962153424d6ad8ba6180fe7ce6d99aaa32c | |
| parent | 6ac23a11f99f1785ac3128a9c919afd7f482d867 (diff) | |
| parent | d6a6294fe422bf901ea3551bc0aa985a569f99f0 (diff) | |
Merge "[Split shade] Fix QS actions footer border visible on shade expansion" into tm-qpr-dev
5 files changed, 255 insertions, 49 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index d701f33c4c66..c790cfe7b7b7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -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) - .setStartDelay(0.9f) + 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) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt index 05038b7b1b1d..309ac2a66e6b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt @@ -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) @@ -117,4 +131,4 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( private const val TAG = "FooterActionsView" private val VERBOSE = Log.isLoggable(TAG, Log.VERBOSE) private val MotionEvent.string - get() = "($id): ($x,$y)"
\ No newline at end of file + get() = "($id): ($x,$y)" diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 8c5e6cc63147..139fb8b0bc14 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -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); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt index 642e29b364d3..2ba8782c6d02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt @@ -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,12 +85,15 @@ 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() whenever(multiUserSwitchControllerFactory.create(any())) - .thenReturn(multiUserSwitchController) + .thenReturn(multiUserSwitchController) whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog) securityFooter = View(mContext) @@ -135,7 +133,7 @@ class FooterActionsControllerTest : LeakCheckedTest() { view.findViewById<View>(R.id.pm_lite).performClick() // Verify clicks are logged verify(uiEventLogger, Mockito.times(1)) - .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS) + .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS) } @Test @@ -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 + } } -}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 32c66d25d4aa..10f6ce8c0ec9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -205,6 +205,40 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { } @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(); disableSplitShade(); |