From 9f6ddda4dfbd71f0f78230c145e559dea8e8460d Mon Sep 17 00:00:00 2001 From: Caitlin Cassidy Date: Wed, 2 Jun 2021 20:37:32 +0000 Subject: [PATCH] [Device Controls] Handle taps correctly when the tile is in an inactive state. Test: Manual + new unit tests Fixes: 189446872 Change-Id: Ia688a002c3d893bd61678f7572c0cb0f60e46105 --- .../systemui/qs/tiles/DeviceControlsTile.kt | 39 +++++---- .../qs/tiles/DeviceControlsTileTest.kt | 84 ++++++++++++++++--- 2 files changed, 99 insertions(+), 24 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt index 2ab5a3a15205b..6d3190ffa7256 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -96,21 +96,32 @@ class DeviceControlsTile @Inject constructor( } override fun handleClick(view: View?) { - if (state.state == Tile.STATE_ACTIVE) { - mUiHandler.post { - val i = Intent().apply { - component = ComponentName(mContext, ControlsActivity::class.java) - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) - putExtra(ControlsUiController.EXTRA_ANIMATE, true) - } - if (keyguardStateController.isUnlocked()) { - val animationController = view?.let { - ActivityLaunchAnimator.Controller.fromView(it) - } - mActivityStarter.startActivity(i, true /* dismissShade */, animationController) - } else { + if (state.state == Tile.STATE_UNAVAILABLE) { + return + } + + val intent = Intent().apply { + component = ComponentName(mContext, ControlsActivity::class.java) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + putExtra(ControlsUiController.EXTRA_ANIMATE, true) + } + val animationController = view?.let { + ActivityLaunchAnimator.Controller.fromView(it) + } + + mUiHandler.post { + if (keyguardStateController.isUnlocked) { + mActivityStarter.startActivity( + intent, true /* dismissShade */, animationController) + } else { + if (state.state == Tile.STATE_ACTIVE) { mHost.collapsePanels() - mContext.startActivity(i) + // With an active tile, don't use ActivityStarter so that the activity is + // started without prompting keyguard unlock. + mContext.startActivity(intent) + } else { + mActivityStarter.postStartActivityDismissingKeyguard( + intent, 0 /* delay */, animationController) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt index 580cd35459b9c..6d1bbd9708eac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt @@ -17,9 +17,9 @@ package com.android.systemui.qs.tiles import android.content.ComponentName -import android.os.Handler import android.content.Context import android.content.Intent +import android.os.Handler import android.provider.Settings import android.service.quicksettings.Tile import android.testing.AndroidTestingRunner @@ -52,14 +52,17 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.doNothing import org.mockito.Mockito.never +import org.mockito.Mockito.nullable import org.mockito.Mockito.spy import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations import java.util.Optional @@ -95,6 +98,8 @@ class DeviceControlsTileTest : SysuiTestCase() { @Captor private lateinit var listingCallbackCaptor: ArgumentCaptor + @Captor + private lateinit var intentCaptor: ArgumentCaptor private lateinit var testableLooper: TestableLooper private lateinit var tile: DeviceControlsTile @@ -259,21 +264,21 @@ class DeviceControlsTileTest : SysuiTestCase() { } @Test - fun testNoDialogWhenUnavailable() { + fun handleClick_unavailable_noActivityStarted() { tile.click(null /* view */) testableLooper.processAllMessages() - verify(activityStarter, never()).startActivity(any(), anyBoolean(), - any()) + verifyZeroInteractions(activityStarter) } @Test - fun testDialogShowWhenAvailable() { + fun handleClick_availableAndLocked_activityStarted() { verify(controlsListingController).observe( any(LifecycleOwner::class.java), capture(listingCallbackCaptor) ) `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE) + `when`(keyguardStateController.isUnlocked).thenReturn(false) listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo)) testableLooper.processAllMessages() @@ -281,18 +286,44 @@ class DeviceControlsTileTest : SysuiTestCase() { tile.click(null /* view */) testableLooper.processAllMessages() - verify(activityStarter).startActivity(any(), eq(true) /* dismissShade */, - eq(null) as ActivityLaunchAnimator.Controller?) + // The activity should be started right away and not require a keyguard dismiss. + verifyZeroInteractions(activityStarter) + verify(spiedContext).startActivity(intentCaptor.capture()) + assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME) } @Test - fun testNoDialogWhenInactive() { + fun handleClick_availableAndUnlocked_activityStarted() { + verify(controlsListingController).observe( + any(LifecycleOwner::class.java), + capture(listingCallbackCaptor) + ) + `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE) + `when`(keyguardStateController.isUnlocked).thenReturn(true) + + listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo)) + testableLooper.processAllMessages() + + tile.click(null /* view */) + testableLooper.processAllMessages() + + verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt()) + verify(activityStarter).startActivity( + intentCaptor.capture(), + eq(true) /* dismissShade */, + nullable(ActivityLaunchAnimator.Controller::class.java)) + assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME) + } + + @Test + fun handleClick_availableAfterUnlockAndIsLocked_keyguardDismissRequired() { verify(controlsListingController).observe( any(LifecycleOwner::class.java), capture(listingCallbackCaptor) ) `when`(controlsComponent.getVisibility()) .thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK) + `when`(keyguardStateController.isUnlocked).thenReturn(false) listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo)) testableLooper.processAllMessages() @@ -300,8 +331,39 @@ class DeviceControlsTileTest : SysuiTestCase() { tile.click(null /* view */) testableLooper.processAllMessages() - verify(activityStarter, never()).startActivity(any(), anyBoolean(), - any()) + verify(activityStarter, never()).startActivity( + any(), + anyBoolean() /* dismissShade */, + nullable(ActivityLaunchAnimator.Controller::class.java)) + verify(activityStarter).postStartActivityDismissingKeyguard( + intentCaptor.capture(), + anyInt(), + nullable(ActivityLaunchAnimator.Controller::class.java)) + assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME) + } + + @Test + fun handleClick_availableAfterUnlockAndIsUnlocked_activityStarted() { + verify(controlsListingController).observe( + any(LifecycleOwner::class.java), + capture(listingCallbackCaptor) + ) + `when`(controlsComponent.getVisibility()) + .thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK) + `when`(keyguardStateController.isUnlocked).thenReturn(true) + + listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo)) + testableLooper.processAllMessages() + + tile.click(null /* view */) + testableLooper.processAllMessages() + + verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt()) + verify(activityStarter).startActivity( + intentCaptor.capture(), + eq(true) /* dismissShade */, + nullable(ActivityLaunchAnimator.Controller::class.java)) + assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME) } private fun createTile(): DeviceControlsTile { @@ -319,3 +381,5 @@ class DeviceControlsTileTest : SysuiTestCase() { ) } } + +private const val CONTROLS_ACTIVITY_CLASS_NAME = "com.android.systemui.controls.ui.ControlsActivity"