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.SysUIKeyEventHandler.Companion.handleAction
25 import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
26 import com.android.systemui.plugins.statusbar.StatusBarStateController
27 import com.android.systemui.power.domain.interactor.PowerInteractor
28 import com.android.systemui.shade.ShadeController
29 import com.android.systemui.statusbar.StatusBarState
30 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
31 import javax.inject.Inject
32 import kotlinx.coroutines.ExperimentalCoroutinesApi
33 
34 /** Handles key events arriving when the keyguard is showing or device is dozing. */
35 @ExperimentalCoroutinesApi
36 @SysUISingleton
37 class KeyguardKeyEventInteractor
38 @Inject
39 constructor(
40     private val context: Context,
41     private val statusBarStateController: StatusBarStateController,
42     private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
43     private val shadeController: ShadeController,
44     private val mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper,
45     private val backActionInteractor: BackActionInteractor,
46     private val powerInteractor: PowerInteractor,
47 ) {
48 
dispatchKeyEventnull49     fun dispatchKeyEvent(event: KeyEvent): Boolean {
50         if (statusBarStateController.isDozing) {
51             when (event.keyCode) {
52                 KeyEvent.KEYCODE_VOLUME_DOWN,
53                 KeyEvent.KEYCODE_VOLUME_UP -> return dispatchVolumeKeyEvent(event)
54             }
55         }
56 
57         if (event.handleAction()) {
58             if (KeyEvent.isConfirmKey(event.keyCode)) {
59                 if (isDeviceAwake()) {
60                     return collapseShadeLockedOrShowPrimaryBouncer()
61                 }
62             }
63             when (event.keyCode) {
64                 KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent()
65             }
66         }
67         return false
68     }
69 
70     /**
71      * While IME is active and a BACK event is detected, check with {@link
72      * StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme()} to see if the event should be
73      * handled before routing to IME, in order to prevent the user from having to hit back twice to
74      * exit bouncer.
75      */
dispatchKeyEventPreImenull76     fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
77         when (event.action) {
78             KeyEvent.ACTION_DOWN -> {
79                 val device = event.getDevice()
80                 if (device != null && device.isFullKeyboard() && device.isExternal()) {
81                     powerInteractor.onUserTouch()
82                 }
83             }
84         }
85         when (event.keyCode) {
86             KeyEvent.KEYCODE_BACK ->
87                 if (
88                     statusBarStateController.state == StatusBarState.KEYGUARD &&
89                         statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()
90                 ) {
91                     return backActionInteractor.onBackRequested()
92                 }
93         }
94         return false
95     }
96 
interceptMediaKeynull97     fun interceptMediaKey(event: KeyEvent): Boolean {
98         return statusBarStateController.state == StatusBarState.KEYGUARD &&
99             statusBarKeyguardViewManager.interceptMediaKey(event)
100     }
101 
dispatchMenuKeyEventnull102     private fun dispatchMenuKeyEvent(): Boolean {
103         val shouldUnlockOnMenuPressed =
104             isDeviceAwake() &&
105                 (statusBarStateController.state != StatusBarState.SHADE) &&
106                 statusBarKeyguardViewManager.shouldDismissOnMenuPressed()
107         if (shouldUnlockOnMenuPressed) {
108             shadeController.animateCollapseShadeForced()
109             return true
110         }
111         return false
112     }
113 
collapseShadeLockedOrShowPrimaryBouncernull114     private fun collapseShadeLockedOrShowPrimaryBouncer(): Boolean {
115         when (statusBarStateController.state) {
116             StatusBarState.SHADE -> return false
117             StatusBarState.SHADE_LOCKED -> {
118                 shadeController.animateCollapseShadeForced()
119                 return true
120             }
121             StatusBarState.KEYGUARD -> {
122                 statusBarKeyguardViewManager.showPrimaryBouncer(true)
123                 return true
124             }
125         }
126         return false
127     }
128 
dispatchVolumeKeyEventnull129     private fun dispatchVolumeKeyEvent(event: KeyEvent): Boolean {
130         mediaSessionLegacyHelperWrapper
131             .getHelper(context)
132             .sendVolumeKeyEvent(event, AudioManager.USE_DEFAULT_STREAM_TYPE, true)
133         return true
134     }
135 
isDeviceAwakenull136     private fun isDeviceAwake(): Boolean {
137         return powerInteractor.detailedWakefulness.value.isAwake()
138     }
139 }
140