1 /*
<lambda>null2 * 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.notetask.quickaffordance
18
19 import android.app.role.OnRoleHoldersChangedListener
20 import android.app.role.RoleManager
21 import android.content.Context
22 import android.content.Intent
23 import android.hardware.input.InputSettings
24 import android.os.Build
25 import android.os.UserHandle
26 import android.os.UserManager
27 import android.util.Log
28 import com.android.keyguard.KeyguardUpdateMonitor
29 import com.android.keyguard.KeyguardUpdateMonitorCallback
30 import com.android.systemui.animation.Expandable
31 import com.android.systemui.common.shared.model.ContentDescription
32 import com.android.systemui.common.shared.model.Icon
33 import com.android.systemui.dagger.qualifiers.Background
34 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
35 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
36 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
37 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
38 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState
39 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
40 import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
41 import com.android.systemui.notetask.NoteTaskController
42 import com.android.systemui.notetask.NoteTaskEnabledKey
43 import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
44 import com.android.systemui.notetask.NoteTaskInfoResolver
45 import com.android.systemui.res.R
46 import com.android.systemui.stylus.StylusManager
47 import dagger.Lazy
48 import java.util.concurrent.Executor
49 import javax.inject.Inject
50 import kotlinx.coroutines.channels.awaitClose
51 import kotlinx.coroutines.channels.trySendBlocking
52 import kotlinx.coroutines.flow.callbackFlow
53 import kotlinx.coroutines.flow.combine
54 import kotlinx.coroutines.flow.map
55 import kotlinx.coroutines.flow.onEach
56
57 class NoteTaskQuickAffordanceConfig
58 @Inject
59 constructor(
60 private val context: Context,
61 private val controller: NoteTaskController,
62 private val noteTaskInfoResolver: NoteTaskInfoResolver,
63 private val stylusManager: StylusManager,
64 private val roleManager: RoleManager,
65 private val keyguardMonitor: KeyguardUpdateMonitor,
66 private val userManager: UserManager,
67 private val lazyRepository: Lazy<KeyguardQuickAffordanceRepository>,
68 @NoteTaskEnabledKey private val isEnabled: Boolean,
69 @Background private val backgroundExecutor: Executor,
70 ) : KeyguardQuickAffordanceConfig {
71
72 override val key = BuiltInKeyguardQuickAffordanceKeys.CREATE_NOTE
73
74 private val pickerNameResourceId = R.string.note_task_button_label
75
76 override fun pickerName(): String = context.getString(pickerNameResourceId)
77
78 override val pickerIconResourceId = R.drawable.ic_note_task_shortcut_keyguard
79
80 // Due to a dependency cycle with KeyguardQuickAffordanceRepository, we need to lazily access
81 // the repository when lockScreenState is accessed for the first time.
82 override val lockScreenState by lazy {
83 val repository = lazyRepository.get()
84 val configSelectedFlow = repository.createConfigSelectedFlow(key)
85 val stylusEverUsedFlow = stylusManager.createStylusEverUsedFlow(context)
86 val userUnlockedFlow = userManager.createUserUnlockedFlow(keyguardMonitor)
87 val defaultNotesAppFlow =
88 roleManager.createNotesRoleFlow(backgroundExecutor, controller, noteTaskInfoResolver)
89 combine(userUnlockedFlow, stylusEverUsedFlow, configSelectedFlow, defaultNotesAppFlow) {
90 isUserUnlocked,
91 isStylusEverUsed,
92 isConfigSelected,
93 isDefaultNotesAppSet ->
94 logDebug { "lockScreenState:isUserUnlocked=$isUserUnlocked" }
95 logDebug { "lockScreenState:isStylusEverUsed=$isStylusEverUsed" }
96 logDebug { "lockScreenState:isConfigSelected=$isConfigSelected" }
97 logDebug { "lockScreenState:isDefaultNotesAppSet=$isDefaultNotesAppSet" }
98
99 val isCustomLockScreenShortcutEnabled =
100 context.resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)
101 val isShortcutSelectedOrDefaultEnabled =
102 if (isCustomLockScreenShortcutEnabled) {
103 isConfigSelected
104 } else {
105 isStylusEverUsed
106 }
107 logDebug {
108 "lockScreenState:isCustomLockScreenShortcutEnabled=" +
109 isCustomLockScreenShortcutEnabled
110 }
111 logDebug {
112 "lockScreenState:isShortcutSelectedOrDefaultEnabled=" +
113 isShortcutSelectedOrDefaultEnabled
114 }
115 if (
116 isEnabled &&
117 isUserUnlocked &&
118 isDefaultNotesAppSet &&
119 isShortcutSelectedOrDefaultEnabled
120 ) {
121 val contentDescription = ContentDescription.Resource(pickerNameResourceId)
122 val icon = Icon.Resource(pickerIconResourceId, contentDescription)
123 LockScreenState.Visible(icon)
124 } else {
125 LockScreenState.Hidden
126 }
127 }
128 .onEach { state -> logDebug { "lockScreenState=$state" } }
129 }
130
131 override suspend fun getPickerScreenState(): PickerScreenState {
132 val isDefaultNotesAppSet =
133 noteTaskInfoResolver.resolveInfo(
134 QUICK_AFFORDANCE,
135 user = controller.getUserForHandlingNotesTaking(QUICK_AFFORDANCE),
136 ) != null
137 return when {
138 isEnabled && isDefaultNotesAppSet -> PickerScreenState.Default()
139 isEnabled -> {
140 PickerScreenState.Disabled(
141 explanation =
142 context.getString(
143 R.string.notes_app_quick_affordance_unavailable_explanation
144 ),
145 actionText =
146 context.getString(
147 R.string.keyguard_affordance_enablement_dialog_notes_app_action
148 ),
149 actionIntent =
150 Intent(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE).apply {
151 setPackage(context.packageName)
152 },
153 )
154 }
155 else -> PickerScreenState.UnavailableOnDevice
156 }
157 }
158
159 override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
160 controller.showNoteTask(entryPoint = QUICK_AFFORDANCE)
161 return OnTriggeredResult.Handled(true)
162 }
163 }
164
createUserUnlockedFlownull165 private fun UserManager.createUserUnlockedFlow(monitor: KeyguardUpdateMonitor) = callbackFlow {
166 trySendBlocking(isUserUnlocked)
167 val callback =
168 object : KeyguardUpdateMonitorCallback() {
169 override fun onUserUnlocked() {
170 trySendBlocking(isUserUnlocked)
171 }
172 }
173 monitor.registerCallback(callback)
174 awaitClose { monitor.removeCallback(callback) }
175 }
176
<lambda>null177 private fun StylusManager.createStylusEverUsedFlow(context: Context) = callbackFlow {
178 trySendBlocking(InputSettings.isStylusEverUsed(context))
179 val callback =
180 object : StylusManager.StylusCallback {
181 override fun onStylusFirstUsed() {
182 trySendBlocking(InputSettings.isStylusEverUsed(context))
183 }
184 }
185 registerCallback(callback)
186 awaitClose { unregisterCallback(callback) }
187 }
188
createNotesRoleFlownull189 private fun RoleManager.createNotesRoleFlow(
190 executor: Executor,
191 noteTaskController: NoteTaskController,
192 noteTaskInfoResolver: NoteTaskInfoResolver,
193 ) = callbackFlow {
194 fun isDefaultNotesAppSetForUser() =
195 noteTaskInfoResolver.resolveInfo(
196 QUICK_AFFORDANCE,
197 user = noteTaskController.getUserForHandlingNotesTaking(QUICK_AFFORDANCE),
198 ) != null
199
200 trySendBlocking(isDefaultNotesAppSetForUser())
201 val callback = OnRoleHoldersChangedListener { roleName, _ ->
202 if (roleName == RoleManager.ROLE_NOTES) {
203 trySendBlocking(isDefaultNotesAppSetForUser())
204 }
205 }
206 addOnRoleHoldersChangedListenerAsUser(executor, callback, UserHandle.ALL)
207 awaitClose { removeOnRoleHoldersChangedListenerAsUser(callback, UserHandle.ALL) }
208 }
209
createConfigSelectedFlownull210 private fun KeyguardQuickAffordanceRepository.createConfigSelectedFlow(key: String) =
211 selections.map { selected ->
212 selected.values.flatten().any { selectedConfig -> selectedConfig.key == key }
213 }
214
logDebugnull215 private inline fun Any.logDebug(message: () -> String) {
216 if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName, message())
217 }
218