Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ce5fbaed authored by Beverly's avatar Beverly Committed by Beverly Tai
Browse files

Add interactors for KeyEvents

This is in preparation to swap over
to using the KeyEventInteractor instead of
calling methods on CentralSurfaces. However, this is NOT and ideal
way for the interactors themselves to be structured. In the future
the interactors should be updated to reflect true modern
architecture patterns.

The logic in these interactors match logic from
NotificationShadeWindowViewController and CentralSurfacesImpl.

Test: atest KeyEventInteractorTest
Test: atest KeyguardKeyEventInteractorTest
Bug: 295927655
Change-Id: Id5c5659a4a19a0b0feb45d6ee391930c2063a9b1
parent 44a5fb63
Loading
Loading
Loading
Loading
+67 −0
Original line number Diff line number Diff line
/*
 * 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
        }
    }
}
+117 −0
Original line number Diff line number Diff line
/*
 * 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()
    }
}
+30 −0
Original line number Diff line number Diff line
/*
 * 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)
    }
}
+147 −0
Original line number Diff line number Diff line
/*
 * 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()
    }
}
+255 −0

File added.

Preview size limit exceeded, changes collapsed.