1 /*
<lambda>null2  * 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  */
17 
18 package com.android.systemui.keyguard.data.quickaffordance
19 
20 import android.content.ComponentName
21 import android.content.Context
22 import android.content.Intent
23 import androidx.annotation.DrawableRes
24 import com.android.systemui.res.R
25 import com.android.systemui.animation.Expandable
26 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
27 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
28 import com.android.systemui.common.shared.model.ContentDescription
29 import com.android.systemui.common.shared.model.Icon
30 import com.android.systemui.controls.ControlsServiceInfo
31 import com.android.systemui.controls.controller.StructureInfo
32 import com.android.systemui.controls.dagger.ControlsComponent
33 import com.android.systemui.controls.management.ControlsListingController
34 import com.android.systemui.controls.ui.ControlsActivity
35 import com.android.systemui.controls.ui.ControlsUiController
36 import com.android.systemui.dagger.SysUISingleton
37 import com.android.systemui.dagger.qualifiers.Application
38 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.Companion.appStoreIntent
39 import com.android.systemui.util.kotlin.getOrNull
40 import javax.inject.Inject
41 import kotlinx.coroutines.channels.awaitClose
42 import kotlinx.coroutines.flow.Flow
43 import kotlinx.coroutines.flow.flatMapLatest
44 import kotlinx.coroutines.flow.flowOf
45 
46 /** Home controls quick affordance data source. */
47 @SysUISingleton
48 class HomeControlsKeyguardQuickAffordanceConfig
49 @Inject
50 constructor(
51     @Application private val context: Context,
52     private val component: ControlsComponent,
53 ) : KeyguardQuickAffordanceConfig {
54 
55     private val appContext = context.applicationContext
56 
57     override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
58 
59     override fun pickerName(): String = context.getString(component.getTileTitleId())
60 
61     override val pickerIconResourceId: Int by lazy { component.getTileImageId() }
62 
63     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
64         component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
65             if (canShowWhileLocked) {
66                 stateInternal(component.getControlsListingController().getOrNull())
67             } else {
68                 flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
69             }
70         }
71 
72     override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
73         if (!component.isEnabled()) {
74             return KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
75         }
76 
77         val currentServices =
78             component.getControlsListingController().getOrNull()?.getCurrentServices()
79         val hasFavorites =
80             component.getControlsController().getOrNull()?.getFavorites()?.isNotEmpty() == true
81         val hasPanels = currentServices?.any { it.panelActivity != null } == true
82         val componentPackageName = component.getPackageName()
83         when {
84             currentServices.isNullOrEmpty() && !componentPackageName.isNullOrEmpty() -> {
85                 // No home app installed but we know which app we want to install.
86                 return disabledPickerState(
87                     explanation =
88                         context.getString(
89                             R.string.home_quick_affordance_unavailable_install_the_app
90                         ),
91                     actionText = context.getString(R.string.install_app),
92                     actionIntent = appStoreIntent(context, componentPackageName),
93                 )
94             }
95             currentServices.isNullOrEmpty() && componentPackageName.isNullOrEmpty() -> {
96                 // No home app installed and we don't know which app we want to install.
97                 return disabledPickerState(
98                     explanation =
99                         context.getString(
100                             R.string.home_quick_affordance_unavailable_install_the_app
101                         ),
102                 )
103             }
104             !hasFavorites && !hasPanels -> {
105                 // Home app installed but no favorites selected or panel activities available.
106                 val activityClass = component.getControlsUiController().get().resolveActivity()
107                 return disabledPickerState(
108                     explanation =
109                         context.getString(
110                             R.string.home_quick_affordance_unavailable_configure_the_app
111                         ),
112                     actionText = context.getString(R.string.controls_open_app),
113                     actionIntent =
114                         Intent().apply {
115                             component = ComponentName(context, activityClass)
116                             putExtra(ControlsUiController.EXTRA_ANIMATE, true)
117                         },
118                 )
119             }
120         }
121 
122         return KeyguardQuickAffordanceConfig.PickerScreenState.Default()
123     }
124 
125     override fun onTriggered(
126         expandable: Expandable?,
127     ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
128         return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
129             intent =
130                 Intent(appContext, ControlsActivity::class.java)
131                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
132                     .putExtra(
133                         ControlsUiController.EXTRA_ANIMATE,
134                         true,
135                     ),
136             canShowWhileLocked = component.canShowWhileLockedSetting.value,
137         )
138     }
139 
140     private fun stateInternal(
141         listingController: ControlsListingController?,
142     ): Flow<KeyguardQuickAffordanceConfig.LockScreenState> {
143         if (listingController == null) {
144             return flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
145         }
146 
147         return conflatedCallbackFlow {
148             val callback =
149                 object : ControlsListingController.ControlsListingCallback {
150                     override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
151                         val favorites: List<StructureInfo>? =
152                             component.getControlsController().getOrNull()?.getFavorites()
153 
154                         trySendWithFailureLogging(
155                             state(
156                                 isFeatureEnabled = component.isEnabled(),
157                                 hasFavorites = favorites?.isNotEmpty() == true,
158                                 hasPanels = serviceInfos.any { it.panelActivity != null },
159                                 hasServiceInfos = serviceInfos.isNotEmpty(),
160                                 iconResourceId = component.getTileImageId(),
161                                 visibility = component.getVisibility(),
162                             ),
163                             TAG,
164                         )
165                     }
166                 }
167 
168             listingController.addCallback(callback)
169 
170             awaitClose { listingController.removeCallback(callback) }
171         }
172     }
173 
174     private fun state(
175         isFeatureEnabled: Boolean,
176         hasFavorites: Boolean,
177         hasPanels: Boolean,
178         hasServiceInfos: Boolean,
179         visibility: ControlsComponent.Visibility,
180         @DrawableRes iconResourceId: Int?,
181     ): KeyguardQuickAffordanceConfig.LockScreenState {
182         return if (
183             isFeatureEnabled &&
184                 (hasFavorites || hasPanels) &&
185                 hasServiceInfos &&
186                 iconResourceId != null &&
187                 visibility == ControlsComponent.Visibility.AVAILABLE
188         ) {
189             KeyguardQuickAffordanceConfig.LockScreenState.Visible(
190                 icon =
191                     Icon.Resource(
192                         res = iconResourceId,
193                         contentDescription =
194                             ContentDescription.Resource(
195                                 res = component.getTileTitleId(),
196                             ),
197                     ),
198             )
199         } else {
200             KeyguardQuickAffordanceConfig.LockScreenState.Hidden
201         }
202     }
203 
204     private fun disabledPickerState(
205         explanation: String,
206         actionText: String? = null,
207         actionIntent: Intent? = null,
208     ): KeyguardQuickAffordanceConfig.PickerScreenState.Disabled {
209         check(actionIntent == null || actionText != null)
210 
211         return KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
212             explanation = explanation,
213             actionText = actionText,
214             actionIntent = actionIntent,
215         )
216     }
217 
218     companion object {
219         private const val TAG = "HomeControlsKeyguardQuickAffordanceConfig"
220     }
221 }
222