1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.keyguard.domain.interactor
18 
19 import android.content.Context
20 import android.media.AudioManager
21 import android.view.KeyEvent
22 import com.android.systemui.back.domain.interactor.BackActionInteractor
23 import com.android.systemui.dagger.SysUISingleton
24 import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor.Companion.handleAction
25 import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
26 import com.android.systemui.plugins.statusbar.StatusBarStateController
27 import com.android.systemui.shade.ShadeController
28 import com.android.systemui.statusbar.StatusBarState
29 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
30 import javax.inject.Inject
31 
32 /** Handles key events arriving when the keyguard is showing or device is dozing. */
33 @SysUISingleton
34 class KeyguardKeyEventInteractor
35 @Inject
36 constructor(
37     private val context: Context,
38     private val statusBarStateController: StatusBarStateController,
39     private val keyguardInteractor: KeyguardInteractor,
40     private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
41     private val shadeController: ShadeController,
42     private val mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper,
43     private val backActionInteractor: BackActionInteractor,
44 ) {
45 
46     fun dispatchKeyEvent(event: KeyEvent): Boolean {
47         if (statusBarStateController.isDozing) {
48             when (event.keyCode) {
49                 KeyEvent.KEYCODE_VOLUME_DOWN,
50                 KeyEvent.KEYCODE_VOLUME_UP -> return dispatchVolumeKeyEvent(event)
51             }
52         }
53 
54         if (event.handleAction()) {
55             when (event.keyCode) {
56                 KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent()
57                 KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent()
58             }
59         }
60         return false
61     }
62 
63     /**
64      * While IME is active and a BACK event is detected, check with {@link
65      * StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme()} to see if the event should be
66      * handled before routing to IME, in order to prevent the user from having to hit back twice to
67      * exit bouncer.
68      */
69     fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
70         when (event.keyCode) {
71             KeyEvent.KEYCODE_BACK ->
72                 if (
73                     statusBarStateController.state == StatusBarState.KEYGUARD &&
74                         statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()
75                 ) {
76                     return backActionInteractor.onBackRequested()
77                 }
78         }
79         return false
80     }
81 
82     fun interceptMediaKey(event: KeyEvent): Boolean {
83         return statusBarStateController.state == StatusBarState.KEYGUARD &&
84             statusBarKeyguardViewManager.interceptMediaKey(event)
85     }
86 
87     private fun dispatchMenuKeyEvent(): Boolean {
88         val shouldUnlockOnMenuPressed =
89             isDeviceInteractive() &&
90                 (statusBarStateController.state != StatusBarState.SHADE) &&
91                 statusBarKeyguardViewManager.shouldDismissOnMenuPressed()
92         if (shouldUnlockOnMenuPressed) {
93             shadeController.animateCollapseShadeForced()
94             return true
95         }
96         return false
97     }
98 
99     private fun dispatchSpaceEvent(): Boolean {
100         if (isDeviceInteractive() && statusBarStateController.state != StatusBarState.SHADE) {
101             shadeController.animateCollapseShadeForced()
102             return true
103         }
104         return false
105     }
106 
107     private fun dispatchVolumeKeyEvent(event: KeyEvent): Boolean {
108         mediaSessionLegacyHelperWrapper
109             .getHelper(context)
110             .sendVolumeKeyEvent(event, AudioManager.USE_DEFAULT_STREAM_TYPE, true)
111         return true
112     }
113 
114     private fun isDeviceInteractive(): Boolean {
115         return keyguardInteractor.wakefulnessModel.value.isDeviceInteractive()
116     }
117 }
118