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