1 /* 2 * Copyright (C) 2022 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 package com.android.systemui.notetask 17 18 import android.app.role.OnRoleHoldersChangedListener 19 import android.app.role.RoleManager 20 import android.content.Context 21 import android.content.pm.UserInfo 22 import android.os.UserHandle 23 import android.view.KeyEvent 24 import android.view.KeyEvent.KEYCODE_N 25 import android.view.KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL 26 import android.view.ViewConfiguration 27 import com.android.keyguard.KeyguardUpdateMonitor 28 import com.android.keyguard.KeyguardUpdateMonitorCallback 29 import com.android.systemui.dagger.qualifiers.Background 30 import com.android.systemui.notetask.NoteTaskEntryPoint.KEYBOARD_SHORTCUT 31 import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON 32 import com.android.systemui.settings.UserTracker 33 import com.android.systemui.statusbar.CommandQueue 34 import com.android.wm.shell.bubbles.Bubbles 35 import java.util.Optional 36 import java.util.concurrent.Executor 37 import javax.inject.Inject 38 39 /** Class responsible to "glue" all note task dependencies. */ 40 internal class NoteTaskInitializer 41 @Inject 42 constructor( 43 private val controller: NoteTaskController, 44 private val roleManager: RoleManager, 45 private val commandQueue: CommandQueue, 46 private val optionalBubbles: Optional<Bubbles>, 47 private val userTracker: UserTracker, 48 private val keyguardUpdateMonitor: KeyguardUpdateMonitor, 49 @Background private val backgroundExecutor: Executor, 50 @NoteTaskEnabledKey private val isEnabled: Boolean, 51 ) { 52 53 /** Initializes note task related features and glue it with other parts of the SystemUI. */ 54 fun initialize() { 55 // Guard against feature not being enabled or mandatory dependencies aren't available. 56 if (!isEnabled || optionalBubbles.isEmpty) return 57 58 initializeHandleSystemKey() 59 initializeOnRoleHoldersChanged() 60 initializeOnUserUnlocked() 61 initializeUserTracker() 62 } 63 64 /** 65 * Initializes a callback for [CommandQueue] which will redirect [KeyEvent] from a Stylus to 66 * [NoteTaskController], ensure custom actions can be triggered (i.e., keyboard shortcut). 67 */ 68 private fun initializeHandleSystemKey() { 69 commandQueue.addCallback(callbacks) 70 } 71 72 /** 73 * Initializes the [RoleManager] role holder changed listener to ensure [NoteTaskController] 74 * will always update whenever the role holder app changes. Keep in mind that a role may change 75 * by direct user interaction (i.e., user goes to settings and change it) or by indirect 76 * interaction (i.e., the current role holder app is uninstalled). 77 */ 78 private fun initializeOnRoleHoldersChanged() { 79 roleManager.addOnRoleHoldersChangedListenerAsUser( 80 backgroundExecutor, 81 callbacks, 82 UserHandle.ALL, 83 ) 84 } 85 86 /** 87 * Initializes a [KeyguardUpdateMonitor] listener that will ensure [NoteTaskController] is in 88 * correct state during system initialization (after a direct boot user unlocked event). 89 * 90 * Once the system is unlocked, we will force trigger [NoteTaskController.onRoleHoldersChanged] 91 * with a hardcoded [RoleManager.ROLE_NOTES] for the current user. 92 */ 93 private fun initializeOnUserUnlocked() { 94 if (keyguardUpdateMonitor.isUserUnlocked(userTracker.userId)) { 95 controller.updateNoteTaskForCurrentUserAndManagedProfiles() 96 } 97 keyguardUpdateMonitor.registerCallback(callbacks) 98 } 99 100 private fun initializeUserTracker() { 101 userTracker.addCallback(callbacks, backgroundExecutor) 102 } 103 104 // Some callbacks use a weak reference, so we play safe and keep a hard reference to them all. 105 private val callbacks = 106 object : 107 KeyguardUpdateMonitorCallback(), 108 CommandQueue.Callbacks, 109 UserTracker.Callback, 110 OnRoleHoldersChangedListener { 111 112 override fun handleSystemKey(key: KeyEvent) { 113 key.toNoteTaskEntryPointOrNull()?.let(controller::showNoteTask) 114 } 115 116 override fun onRoleHoldersChanged(roleName: String, user: UserHandle) { 117 controller.onRoleHoldersChanged(roleName, user) 118 } 119 120 override fun onUserUnlocked() { 121 controller.updateNoteTaskForCurrentUserAndManagedProfiles() 122 } 123 124 override fun onUserChanged(newUser: Int, userContext: Context) { 125 controller.updateNoteTaskForCurrentUserAndManagedProfiles() 126 } 127 128 override fun onProfilesChanged(profiles: List<UserInfo>) { 129 controller.updateNoteTaskForCurrentUserAndManagedProfiles() 130 } 131 } 132 133 /** 134 * Tracks a [KeyEvent], and determines if it should trigger an action to show the note task. 135 * Returns a [NoteTaskEntryPoint] if an action should be taken, and null otherwise. 136 */ 137 private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? = 138 when { 139 keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON 140 keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT 141 else -> null 142 } 143 144 private var lastStylusButtonTailUpEventTime: Long = -MULTI_PRESS_TIMEOUT 145 146 /** 147 * Perform gesture detection for the stylus tail button to make sure we only show the note task 148 * when there is a single press. Long presses and multi-presses are ignored for now. 149 */ 150 private fun KeyEvent.isTailButtonNotesGesture(): Boolean { 151 if (keyCode != KEYCODE_STYLUS_BUTTON_TAIL || action != KeyEvent.ACTION_UP) { 152 return false 153 } 154 155 val isMultiPress = (downTime - lastStylusButtonTailUpEventTime) < MULTI_PRESS_TIMEOUT 156 val isLongPress = (eventTime - downTime) >= LONG_PRESS_TIMEOUT 157 lastStylusButtonTailUpEventTime = eventTime 158 // For now, trigger action immediately on UP of a single press, without waiting for 159 // the multi-press timeout to expire. 160 return !isMultiPress && !isLongPress 161 } 162 163 companion object { 164 val MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout().toLong() 165 val LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout().toLong() 166 } 167 } 168