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 if (
100 isEnabled &&
101 isUserUnlocked &&
102 isDefaultNotesAppSet &&
103 isConfigSelected &&
104 isStylusEverUsed
105 ) {
106 val contentDescription = ContentDescription.Resource(pickerNameResourceId)
107 val icon = Icon.Resource(pickerIconResourceId, contentDescription)
108 LockScreenState.Visible(icon)
109 } else {
110 LockScreenState.Hidden
111 }
112 }
113 .onEach { state -> logDebug { "lockScreenState=$state" } }
114 }
115
116 override suspend fun getPickerScreenState(): PickerScreenState {
117 val isDefaultNotesAppSet =
118 noteTaskInfoResolver.resolveInfo(
119 QUICK_AFFORDANCE,
120 user = controller.getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
121 ) != null
122 return when {
123 isEnabled && isDefaultNotesAppSet -> PickerScreenState.Default()
124 isEnabled -> {
125 PickerScreenState.Disabled(
126 explanation =
127 context.getString(
128 R.string.notes_app_quick_affordance_unavailable_explanation
129 ),
130 actionText =
131 context.getString(
132 R.string.keyguard_affordance_enablement_dialog_notes_app_action
133 ),
134 actionIntent =
135 Intent(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE).apply {
136 setPackage(context.packageName)
137 },
138 )
139 }
140 else -> PickerScreenState.UnavailableOnDevice
141 }
142 }
143
144 override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
145 controller.showNoteTask(entryPoint = QUICK_AFFORDANCE)
146 return OnTriggeredResult.Handled
147 }
148 }
149
createUserUnlockedFlownull150 private fun UserManager.createUserUnlockedFlow(monitor: KeyguardUpdateMonitor) = callbackFlow {
151 trySendBlocking(isUserUnlocked)
152 val callback =
153 object : KeyguardUpdateMonitorCallback() {
154 override fun onUserUnlocked() {
155 trySendBlocking(isUserUnlocked)
156 }
157 }
158 monitor.registerCallback(callback)
159 awaitClose { monitor.removeCallback(callback) }
160 }
161
<lambda>null162 private fun StylusManager.createStylusEverUsedFlow(context: Context) = callbackFlow {
163 trySendBlocking(InputSettings.isStylusEverUsed(context))
164 val callback =
165 object : StylusManager.StylusCallback {
166 override fun onStylusFirstUsed() {
167 trySendBlocking(InputSettings.isStylusEverUsed(context))
168 }
169 }
170 registerCallback(callback)
171 awaitClose { unregisterCallback(callback) }
172 }
173
createNotesRoleFlownull174 private fun RoleManager.createNotesRoleFlow(
175 executor: Executor,
176 noteTaskController: NoteTaskController,
177 noteTaskInfoResolver: NoteTaskInfoResolver,
178 ) = callbackFlow {
179 fun isDefaultNotesAppSetForUser() =
180 noteTaskInfoResolver.resolveInfo(
181 QUICK_AFFORDANCE,
182 user = noteTaskController.getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
183 ) != null
184
185 trySendBlocking(isDefaultNotesAppSetForUser())
186 val callback = OnRoleHoldersChangedListener { roleName, _ ->
187 if (roleName == RoleManager.ROLE_NOTES) {
188 trySendBlocking(isDefaultNotesAppSetForUser())
189 }
190 }
191 addOnRoleHoldersChangedListenerAsUser(executor, callback, UserHandle.ALL)
192 awaitClose { removeOnRoleHoldersChangedListenerAsUser(callback, UserHandle.ALL) }
193 }
194
createConfigSelectedFlownull195 private fun KeyguardQuickAffordanceRepository.createConfigSelectedFlow(key: String) =
196 selections.map { selected ->
197 selected.values.flatten().any { selectedConfig -> selectedConfig.key == key }
198 }
199
logDebugnull200 private inline fun Any.logDebug(message: () -> String) {
201 if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName, message())
202 }
203