diff options
5 files changed, 616 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt new file mode 100644 index 000000000000..3f2f67dbba37 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 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.keyevent.domain.interactor + +import android.view.KeyEvent +import com.android.systemui.back.domain.interactor.BackActionInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor +import javax.inject.Inject + +/** + * Sends key events to the appropriate interactors and then acts upon key events that haven't + * already been handled but should be handled by SystemUI. + */ +@SysUISingleton +class KeyEventInteractor +@Inject +constructor( + private val backActionInteractor: BackActionInteractor, + private val keyguardKeyEventInteractor: KeyguardKeyEventInteractor, +) { + fun dispatchKeyEvent(event: KeyEvent): Boolean { + if (keyguardKeyEventInteractor.dispatchKeyEvent(event)) { + return true + } + + when (event.keyCode) { + KeyEvent.KEYCODE_BACK -> { + if (event.handleAction()) { + backActionInteractor.onBackRequested() + } + return true + } + } + return false + } + + fun interceptMediaKey(event: KeyEvent): Boolean { + return keyguardKeyEventInteractor.interceptMediaKey(event) + } + + fun dispatchKeyEventPreIme(event: KeyEvent): Boolean { + return keyguardKeyEventInteractor.dispatchKeyEventPreIme(event) + } + + companion object { + // Most actions shouldn't be handled on the down event and instead handled on subsequent + // key events like ACTION_UP. + fun KeyEvent.handleAction(): Boolean { + return action != KeyEvent.ACTION_DOWN + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt new file mode 100644 index 000000000000..635961b0ea01 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 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.keyguard.domain.interactor + +import android.content.Context +import android.media.AudioManager +import android.view.KeyEvent +import com.android.systemui.back.domain.interactor.BackActionInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor.Companion.handleAction +import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeController +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import javax.inject.Inject + +/** Handles key events arriving when the keyguard is showing or device is dozing. */ +@SysUISingleton +class KeyguardKeyEventInteractor +@Inject +constructor( + private val context: Context, + private val statusBarStateController: StatusBarStateController, + private val keyguardInteractor: KeyguardInteractor, + private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, + private val shadeController: ShadeController, + private val mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper, + private val backActionInteractor: BackActionInteractor, +) { + + fun dispatchKeyEvent(event: KeyEvent): Boolean { + if (statusBarStateController.isDozing) { + when (event.keyCode) { + KeyEvent.KEYCODE_VOLUME_DOWN, + KeyEvent.KEYCODE_VOLUME_UP -> return dispatchVolumeKeyEvent(event) + } + } + + if (event.handleAction()) { + when (event.keyCode) { + KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent() + KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent() + } + } + return false + } + + /** + * While IME is active and a BACK event is detected, check with {@link + * StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme()} to see if the event should be + * handled before routing to IME, in order to prevent the user from having to hit back twice to + * exit bouncer. + */ + fun dispatchKeyEventPreIme(event: KeyEvent): Boolean { + when (event.keyCode) { + KeyEvent.KEYCODE_BACK -> + if ( + statusBarStateController.state == StatusBarState.KEYGUARD && + statusBarKeyguardViewManager.dispatchBackKeyEventPreIme() + ) { + return backActionInteractor.onBackRequested() + } + } + return false + } + + fun interceptMediaKey(event: KeyEvent): Boolean { + return statusBarStateController.state == StatusBarState.KEYGUARD && + statusBarKeyguardViewManager.interceptMediaKey(event) + } + + private fun dispatchMenuKeyEvent(): Boolean { + val shouldUnlockOnMenuPressed = + isDeviceInteractive() && + (statusBarStateController.state != StatusBarState.SHADE) && + statusBarKeyguardViewManager.shouldDismissOnMenuPressed() + if (shouldUnlockOnMenuPressed) { + shadeController.animateCollapseShadeForced() + return true + } + return false + } + + private fun dispatchSpaceEvent(): Boolean { + if (isDeviceInteractive() && statusBarStateController.state != StatusBarState.SHADE) { + shadeController.animateCollapseShadeForced() + return true + } + return false + } + + private fun dispatchVolumeKeyEvent(event: KeyEvent): Boolean { + mediaSessionLegacyHelperWrapper + .getHelper(context) + .sendVolumeKeyEvent(event, AudioManager.USE_DEFAULT_STREAM_TYPE, true) + return true + } + + private fun isDeviceInteractive(): Boolean { + return keyguardInteractor.wakefulnessModel.value.isDeviceInteractive() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt new file mode 100644 index 000000000000..99243696a4e0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.media.controls.util + +import android.content.Context +import android.media.session.MediaSessionLegacyHelper +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** Injectable wrapper around `MediaSessionLegacyHelper` functions */ +@SysUISingleton +class MediaSessionLegacyHelperWrapper @Inject constructor() { + fun getHelper(context: Context): MediaSessionLegacyHelper { + return MediaSessionLegacyHelper.getHelper(context) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt new file mode 100644 index 000000000000..632d149c9520 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2023 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.keyevent.domain.interactor + +import android.view.KeyEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.back.domain.interactor.BackActionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidJUnit4::class) +class KeyEventInteractorTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + + private lateinit var keyguardInteractorWithDependencies: + KeyguardInteractorFactory.WithDependencies + @Mock private lateinit var keyguardKeyEventInteractor: KeyguardKeyEventInteractor + @Mock private lateinit var backActionInteractor: BackActionInteractor + + private lateinit var underTest: KeyEventInteractor + + @Before + fun setup() { + keyguardInteractorWithDependencies = KeyguardInteractorFactory.create() + underTest = + KeyEventInteractor( + backActionInteractor, + keyguardKeyEventInteractor, + ) + } + + @Test + fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() { + val backKeyEventActionDown = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK) + val backKeyEventActionUp = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK) + + // GIVEN back key ACTION_DOWN and ACTION_UP aren't handled by the keyguardKeyEventInteractor + whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionDown)) + .thenReturn(false) + whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionUp)) + .thenReturn(false) + + // WHEN back key event ACTION_DOWN, the event is handled even though back isn't requested + assertThat(underTest.dispatchKeyEvent(backKeyEventActionDown)).isTrue() + // THEN back event isn't handled on ACTION_DOWN + verify(backActionInteractor, never()).onBackRequested() + + // WHEN back key event ACTION_UP + assertThat(underTest.dispatchKeyEvent(backKeyEventActionUp)).isTrue() + // THEN back event is handled on ACTION_UP + verify(backActionInteractor).onBackRequested() + } + + @Test + fun dispatchKeyEvent_isNotHandledByKeyguardKeyEventInteractor() { + val keyEvent = + KeyEvent( + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SPACE, + ) + whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(false) + assertThat(underTest.dispatchKeyEvent(keyEvent)).isFalse() + } + + @Test + fun dispatchKeyEvent_handledByKeyguardKeyEventInteractor() { + val keyEvent = + KeyEvent( + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SPACE, + ) + whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(true) + assertThat(underTest.dispatchKeyEvent(keyEvent)).isTrue() + } + + @Test + fun interceptMediaKey_isNotHandledByKeyguardKeyEventInteractor() { + val keyEvent = + KeyEvent( + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SPACE, + ) + whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(false) + assertThat(underTest.interceptMediaKey(keyEvent)).isFalse() + } + + @Test + fun interceptMediaKey_handledByKeyguardKeyEventInteractor() { + val keyEvent = + KeyEvent( + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SPACE, + ) + whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(true) + assertThat(underTest.interceptMediaKey(keyEvent)).isTrue() + } + + @Test + fun dispatchKeyEventPreIme_isNotHandledByKeyguardKeyEventInteractor() { + val keyEvent = + KeyEvent( + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SPACE, + ) + whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(false) + assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isFalse() + } + + @Test + fun dispatchKeyEventPreIme_handledByKeyguardKeyEventInteractor() { + val keyEvent = + KeyEvent( + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SPACE, + ) + whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(true) + assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt new file mode 100644 index 000000000000..a3f7fc5fc8cf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2023 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.keyguard.domain.interactor + +import android.media.AudioManager +import android.media.session.MediaSessionLegacyHelper +import android.view.KeyEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.back.domain.interactor.BackActionInteractor +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeController +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidJUnit4::class) +class KeyguardKeyEventInteractorTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + + private val actionDownVolumeDownKeyEvent = + KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN) + private val actionDownVolumeUpKeyEvent = + KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP) + private val backKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK) + private val awakeWakefulnessMode = + WakefulnessModel(WakefulnessState.AWAKE, WakeSleepReason.OTHER, WakeSleepReason.OTHER) + private val asleepWakefulnessMode = + WakefulnessModel(WakefulnessState.ASLEEP, WakeSleepReason.OTHER, WakeSleepReason.OTHER) + + private lateinit var keyguardInteractorWithDependencies: + KeyguardInteractorFactory.WithDependencies + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager + @Mock private lateinit var shadeController: ShadeController + @Mock private lateinit var mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper + @Mock private lateinit var mediaSessionLegacyHelper: MediaSessionLegacyHelper + @Mock private lateinit var backActionInteractor: BackActionInteractor + + private lateinit var underTest: KeyguardKeyEventInteractor + + @Before + fun setup() { + whenever(mediaSessionLegacyHelperWrapper.getHelper(any())) + .thenReturn(mediaSessionLegacyHelper) + keyguardInteractorWithDependencies = KeyguardInteractorFactory.create() + underTest = + KeyguardKeyEventInteractor( + context, + statusBarStateController, + keyguardInteractorWithDependencies.keyguardInteractor, + statusBarKeyguardViewManager, + shadeController, + mediaSessionLegacyHelperWrapper, + backActionInteractor, + ) + } + + @Test + fun dispatchKeyEvent_volumeKey_dozing_handlesEvents() { + whenever(statusBarStateController.isDozing).thenReturn(true) + + assertThat(underTest.dispatchKeyEvent(actionDownVolumeDownKeyEvent)).isTrue() + verify(mediaSessionLegacyHelper) + .sendVolumeKeyEvent( + eq(actionDownVolumeDownKeyEvent), + eq(AudioManager.USE_DEFAULT_STREAM_TYPE), + eq(true) + ) + + assertThat(underTest.dispatchKeyEvent(actionDownVolumeUpKeyEvent)).isTrue() + verify(mediaSessionLegacyHelper) + .sendVolumeKeyEvent( + eq(actionDownVolumeUpKeyEvent), + eq(AudioManager.USE_DEFAULT_STREAM_TYPE), + eq(true) + ) + } + + @Test + fun dispatchKeyEvent_volumeKey_notDozing_doesNotHandleEvents() { + whenever(statusBarStateController.isDozing).thenReturn(false) + + assertThat(underTest.dispatchKeyEvent(actionDownVolumeDownKeyEvent)).isFalse() + verify(mediaSessionLegacyHelper, never()) + .sendVolumeKeyEvent( + eq(actionDownVolumeDownKeyEvent), + eq(AudioManager.USE_DEFAULT_STREAM_TYPE), + eq(true) + ) + + assertThat(underTest.dispatchKeyEvent(actionDownVolumeUpKeyEvent)).isFalse() + verify(mediaSessionLegacyHelper, never()) + .sendVolumeKeyEvent( + eq(actionDownVolumeUpKeyEvent), + eq(AudioManager.USE_DEFAULT_STREAM_TYPE), + eq(true) + ) + } + + @Test + fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() { + keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode) + whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) + + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() + } + + @Test + fun dispatchKeyEvent_menuActionUp_interactiveShadeLocked_collapsesShade() { + keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode) + whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) + whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) + + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() + + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() + } + + @Test + fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() { + keyguardInteractorWithDependencies.repository.setWakefulnessModel(asleepWakefulnessMode) + whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) + + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() + } + + @Test + fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() { + keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode) + whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() + + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() + } + + @Test + fun dispatchKeyEventPreIme_back_keyguard_onBackRequested() { + whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(true) + + whenever(backActionInteractor.onBackRequested()).thenReturn(false) + assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse() + verify(backActionInteractor).onBackRequested() + clearInvocations(backActionInteractor) + + whenever(backActionInteractor.onBackRequested()).thenReturn(true) + assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isTrue() + verify(backActionInteractor).onBackRequested() + } + + @Test + fun dispatchKeyEventPreIme_back_keyguard_SBKVMdoesNotHandle_neverOnBackRequested() { + whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(false) + whenever(backActionInteractor.onBackRequested()).thenReturn(true) + + assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse() + verify(backActionInteractor, never()).onBackRequested() + } + + @Test + fun dispatchKeyEventPreIme_back_shade_neverOnBackRequested() { + whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) + whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(true) + whenever(backActionInteractor.onBackRequested()).thenReturn(true) + + assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse() + verify(backActionInteractor, never()).onBackRequested() + } + + @Test + fun interceptMediaKey_keyguard_SBKVMdoesNotHandle_doesNotHandleMediaKey() { + val keyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP) + whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(statusBarKeyguardViewManager.interceptMediaKey(eq(keyEvent))).thenReturn(false) + + assertThat(underTest.interceptMediaKey(keyEvent)).isFalse() + verify(statusBarKeyguardViewManager).interceptMediaKey(eq(keyEvent)) + } + + @Test + fun interceptMediaKey_keyguard_handleMediaKey() { + val keyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP) + whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(statusBarKeyguardViewManager.interceptMediaKey(eq(keyEvent))).thenReturn(true) + + assertThat(underTest.interceptMediaKey(keyEvent)).isTrue() + verify(statusBarKeyguardViewManager).interceptMediaKey(eq(keyEvent)) + } + + @Test + fun interceptMediaKey_shade_doesNotHandleMediaKey() { + whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) + + assertThat( + underTest.interceptMediaKey( + KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP) + ) + ) + .isFalse() + verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any()) + } +} |