blob: 98c7274aeba6c2dc660715b174440f6402e7a942 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.qs.tiles
import android.content.ComponentName
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
import android.testing.TestableLooper
import androidx.lifecycle.LifecycleOwner
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
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.nullable
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
import java.util.Optional
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class DeviceControlsTileTest : SysuiTestCase() {
@Mock
private lateinit var qsHost: QSHost
@Mock
private lateinit var metricsLogger: MetricsLogger
@Mock
private lateinit var statusBarStateController: StatusBarStateController
@Mock
private lateinit var activityStarter: ActivityStarter
@Mock
private lateinit var qsLogger: QSLogger
@Mock
private lateinit var controlsComponent: ControlsComponent
@Mock
private lateinit var controlsUiController: ControlsUiController
@Mock
private lateinit var controlsListingController: ControlsListingController
@Mock
private lateinit var controlsController: ControlsController
@Mock
private lateinit var serviceInfo: ControlsServiceInfo
@Mock
private lateinit var uiEventLogger: UiEventLogger
@Mock
private lateinit var keyguardStateController: KeyguardStateController
@Captor
private lateinit var listingCallbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@Captor
private lateinit var intentCaptor: ArgumentCaptor<Intent>
private lateinit var testableLooper: TestableLooper
private lateinit var tile: DeviceControlsTile
private lateinit var secureSettings: SecureSettings
private lateinit var spiedContext: Context
private var featureEnabled = true
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
secureSettings = FakeSettings()
spiedContext = spy(mContext)
doNothing().`when`(spiedContext).startActivity(any(Intent::class.java))
`when`(qsHost.context).thenReturn(spiedContext)
`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
`when`(controlsComponent.isEnabled()).thenReturn(true)
`when`(keyguardStateController.isUnlocked()).thenReturn(true)
`when`(controlsController.getPreferredStructure())
.thenReturn(StructureInfo(ComponentName("pkg", "cls"), "structure", listOf()))
secureSettings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 1)
setupControlsComponent()
tile = createTile()
}
private fun setupControlsComponent() {
`when`(controlsComponent.getControlsController()).thenAnswer {
if (featureEnabled) {
Optional.of(controlsController)
} else {
Optional.empty()
}
}
`when`(controlsComponent.getControlsListingController()).thenAnswer {
if (featureEnabled) {
Optional.of(controlsListingController)
} else {
Optional.empty()
}
}
`when`(controlsComponent.getControlsUiController()).thenAnswer {
if (featureEnabled) {
Optional.of(controlsUiController)
} else {
Optional.empty()
}
}
}
@Test
fun testAvailable() {
assertThat(tile.isAvailable).isTrue()
}
@Test
fun testNotAvailableControls() {
featureEnabled = false
tile = createTile()
assertThat(tile.isAvailable).isFalse()
}
@Test
fun testObservingCallback() {
verify(controlsListingController).observe(
any(LifecycleOwner::class.java),
any(ControlsListingController.ControlsListingCallback::class.java)
)
}
@Test
fun testLongClickIntent() {
assertThat(tile.longClickIntent).isNull()
}
@Test
fun testDoesNotHandleLongClick() {
assertThat(tile.state.handlesLongClick).isFalse()
}
@Test
fun testUnavailableByDefault() {
assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
}
@Test
fun testStateUnavailableIfNoListings() {
verify(controlsListingController).observe(
any(LifecycleOwner::class.java),
capture(listingCallbackCaptor)
)
listingCallbackCaptor.value.onServicesUpdated(emptyList())
testableLooper.processAllMessages()
assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
}
@Test
fun testStateUnavailableIfNotEnabled() {
verify(controlsListingController).observe(
any(LifecycleOwner::class.java),
capture(listingCallbackCaptor)
)
`when`(controlsComponent.isEnabled()).thenReturn(false)
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
testableLooper.processAllMessages()
assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
}
@Test
fun testStateAvailableIfListings() {
verify(controlsListingController).observe(
any(LifecycleOwner::class.java),
capture(listingCallbackCaptor)
)
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
testableLooper.processAllMessages()
assertThat(tile.state.state).isEqualTo(Tile.STATE_ACTIVE)
}
@Test
fun testStateInactiveIfLocked() {
verify(controlsListingController).observe(
any(LifecycleOwner::class.java),
capture(listingCallbackCaptor)
)
`when`(controlsComponent.getVisibility())
.thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
testableLooper.processAllMessages()
assertThat(tile.state.state).isEqualTo(Tile.STATE_INACTIVE)
}
@Test
fun testMoveBetweenStates() {
verify(controlsListingController).observe(
any(LifecycleOwner::class.java),
capture(listingCallbackCaptor)
)
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
testableLooper.processAllMessages()
listingCallbackCaptor.value.onServicesUpdated(emptyList())
testableLooper.processAllMessages()
assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
}
@Test
fun handleClick_unavailable_noActivityStarted() {
tile.click(null /* view */)
testableLooper.processAllMessages()
verifyZeroInteractions(activityStarter)
}
@Test
fun handleClick_available_shownOverLockscreenWhenLocked() {
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).startActivity(
intentCaptor.capture(),
eq(true) /* dismissShade */,
nullable(ActivityLaunchAnimator.Controller::class.java),
eq(true) /* showOverLockscreenWhenLocked */)
assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
}
@Test
fun handleClick_availableAfterUnlock_notShownOverLockscreenWhenLocked() {
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()
tile.click(null /* view */)
testableLooper.processAllMessages()
verify(activityStarter).startActivity(
intentCaptor.capture(),
anyBoolean() /* dismissShade */,
nullable(ActivityLaunchAnimator.Controller::class.java),
eq(false) /* showOverLockscreenWhenLocked */)
assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
}
private fun createTile(): DeviceControlsTile {
return DeviceControlsTile(
qsHost,
testableLooper.looper,
Handler(testableLooper.looper),
FalsingManagerFake(),
metricsLogger,
statusBarStateController,
activityStarter,
qsLogger,
controlsComponent,
keyguardStateController
).also {
it.initialize()
testableLooper.processAllMessages()
}
}
}
private const val CONTROLS_ACTIVITY_CLASS_NAME = "com.android.systemui.controls.ui.ControlsActivity"