[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:
committed by
Christian Göllner
parent
592f8bf611
commit
d6a6294fe4
@@ -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) {
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user