• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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