[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
This commit is contained in:
Christian Göllner
2022-08-10 15:56:26 +02:00
committed by Christian Göllner
parent 592f8bf611
commit d6a6294fe4
5 changed files with 255 additions and 49 deletions

View File

@@ -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) {

View File

@@ -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)"
get() = "($id): ($x,$y)"

View File

@@ -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);

View File

@@ -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
}
}
}

View File

@@ -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();