1 /* 2 * 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.app.AlertDialog 21 import android.content.Context 22 import android.content.Intent 23 import android.net.Uri 24 import com.android.systemui.animation.Expandable 25 import com.android.systemui.common.shared.model.Icon 26 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState 27 import com.android.systemui.res.R 28 import kotlinx.coroutines.flow.Flow 29 30 /** Defines interface that can act as data source for a single quick affordance model. */ 31 interface KeyguardQuickAffordanceConfig { 32 33 /** Unique identifier for this quick affordance. It must be globally unique. */ 34 val key: String 35 36 val pickerIconResourceId: Int 37 38 /** 39 * The ever-changing state of the affordance. 40 * 41 * Used to populate the lock screen. 42 */ 43 val lockScreenState: Flow<LockScreenState> 44 45 /** 46 * Returns a user-visible [String] that should be shown as the name for the option in the 47 * wallpaper picker / settings app to select this quick affordance. 48 */ pickerNamenull49 fun pickerName(): String 50 51 /** 52 * Returns the [PickerScreenState] representing the affordance in the settings or selector 53 * experience. 54 */ 55 suspend fun getPickerScreenState(): PickerScreenState = PickerScreenState.Default() 56 57 /** 58 * Notifies that the affordance was clicked by the user. 59 * 60 * @param expandable An [Expandable] to use when animating dialogs or activities 61 * @return An [OnTriggeredResult] telling the caller what to do next 62 */ 63 fun onTriggered(expandable: Expandable?): OnTriggeredResult 64 65 /** 66 * Encapsulates the state of a quick affordance within the context of the settings or selector 67 * experience. 68 */ 69 sealed class PickerScreenState { 70 71 /** The picker shows the item for selecting this affordance as it normally would. */ 72 data class Default( 73 /** Optional [Intent] to use to start an activity to configure this affordance. */ 74 val configureIntent: Intent? = null 75 ) : PickerScreenState() 76 77 /** 78 * The picker does not show an item for selecting this affordance as it is not supported on 79 * the device at all. For example, missing hardware requirements. 80 */ 81 object UnavailableOnDevice : PickerScreenState() 82 83 /** 84 * The picker shows the item for selecting this affordance as disabled. Clicking on it will 85 * show the given instructions to the user. If [actionText] and [actionIntent] are provided 86 * (optional) a button will be shown to open an activity to help the user complete the steps 87 * described in the instructions. 88 */ 89 data class Disabled( 90 /** Human-readable explanation as to why the quick affordance is current disabled. */ 91 val explanation: String, 92 /** 93 * Optional text to display on a button that the user can click to start a flow to go 94 * and set up the quick affordance and make it enabled. 95 */ 96 val actionText: String? = null, 97 /** 98 * Optional [Intent] that opens an `Activity` for the user to be able to set up the 99 * quick affordance and make it enabled. 100 */ 101 val actionIntent: Intent? = null, 102 ) : PickerScreenState() { 103 init { 104 check(explanation.isNotEmpty()) { "Explanation must not be empty!" } 105 check( 106 (actionText.isNullOrEmpty() && actionIntent == null) || 107 (!actionText.isNullOrEmpty() && actionIntent != null) 108 ) { 109 """ 110 actionText and actionIntent must either both be null/empty or both be 111 non-null and non-empty! 112 """ 113 .trimIndent() 114 } 115 } 116 } 117 } 118 119 /** 120 * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a 121 * button on the lock-screen). 122 */ 123 sealed class LockScreenState { 124 125 /** No affordance should show up. */ 126 object Hidden : LockScreenState() 127 128 /** An affordance is visible. */ 129 data class Visible( 130 /** An icon for the affordance. */ 131 val icon: Icon, 132 /** The activation state of the affordance. */ 133 val activationState: ActivationState = ActivationState.NotSupported, 134 ) : LockScreenState() 135 } 136 137 sealed class OnTriggeredResult() { 138 /** 139 * Returning this as a result from the [onTriggered] method means that the implementation 140 * has taken care of the action, the system will do nothing. 141 * 142 * @param[actionLaunched] Whether the implementation handled the action by launching a 143 * dialog or an activity. 144 */ 145 data class Handled(val actionLaunched: Boolean) : OnTriggeredResult() 146 147 /** 148 * Returning this as a result from the [onTriggered] method means that the implementation 149 * has _not_ taken care of the action and the system should start an activity using the 150 * given [Intent]. 151 */ 152 data class StartActivity(val intent: Intent, val canShowWhileLocked: Boolean) : 153 OnTriggeredResult() 154 155 /** 156 * Returning this as a result from the [onTriggered] method means that the implementation 157 * has _not_ taken care of the action and the system should show a Dialog using the given 158 * [AlertDialog] and [Expandable]. 159 */ 160 data class ShowDialog(val dialog: AlertDialog, val expandable: Expandable?) : 161 OnTriggeredResult() 162 } 163 164 /** 165 * Models an [OnTriggeredResult] that did or did not launch a dialog or activity for a given 166 * config key. 167 */ 168 data class LaunchingFromTriggeredResult(val launched: Boolean, val configKey: String) 169 170 companion object { 171 172 /** 173 * Returns an [Intent] that can be used to start an activity that opens the app store app to 174 * a page showing the app with the passed-in [packageName]. 175 * 176 * If the feature isn't enabled on this device/variant/configuration, a `null` will be 177 * returned. 178 */ appStoreIntentnull179 fun appStoreIntent(context: Context, packageName: String?): Intent? { 180 if (packageName.isNullOrEmpty()) { 181 return null 182 } 183 184 val appStorePackageName = context.getString(R.string.config_appStorePackageName) 185 val linkTemplate = context.getString(R.string.config_appStoreAppLinkTemplate) 186 if (appStorePackageName.isEmpty() || linkTemplate.isEmpty()) { 187 return null 188 } 189 190 check(linkTemplate.contains(APP_PACKAGE_NAME_PLACEHOLDER)) 191 192 return Intent(Intent.ACTION_VIEW).apply { 193 setPackage(appStorePackageName) 194 data = Uri.parse(linkTemplate.replace(APP_PACKAGE_NAME_PLACEHOLDER, packageName)) 195 } 196 } 197 198 private const val APP_PACKAGE_NAME_PLACEHOLDER = "\$packageName" 199 } 200 } 201