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.res.R 25 import com.android.systemui.animation.Expandable 26 import com.android.systemui.common.shared.model.Icon 27 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState 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 object Handled : OnTriggeredResult() 143 144 /** 145 * Returning this as a result from the [onTriggered] method means that the implementation 146 * has _not_ taken care of the action and the system should start an activity using the 147 * given [Intent]. 148 */ 149 data class StartActivity( 150 val intent: Intent, 151 val canShowWhileLocked: Boolean, 152 ) : OnTriggeredResult() 153 154 /** 155 * Returning this as a result from the [onTriggered] method means that the implementation 156 * has _not_ taken care of the action and the system should show a Dialog using the given 157 * [AlertDialog] and [Expandable]. 158 */ 159 data class ShowDialog( 160 val dialog: AlertDialog, 161 val expandable: Expandable?, 162 ) : OnTriggeredResult() 163 } 164 165 companion object { 166 167 /** 168 * Returns an [Intent] that can be used to start an activity that opens the app store app to 169 * a page showing the app with the passed-in [packageName]. 170 * 171 * If the feature isn't enabled on this device/variant/configuration, a `null` will be 172 * returned. 173 */ appStoreIntentnull174 fun appStoreIntent(context: Context, packageName: String?): Intent? { 175 if (packageName.isNullOrEmpty()) { 176 return null 177 } 178 179 val appStorePackageName = context.getString(R.string.config_appStorePackageName) 180 val linkTemplate = context.getString(R.string.config_appStoreAppLinkTemplate) 181 if (appStorePackageName.isEmpty() || linkTemplate.isEmpty()) { 182 return null 183 } 184 185 check(linkTemplate.contains(APP_PACKAGE_NAME_PLACEHOLDER)) 186 187 return Intent(Intent.ACTION_VIEW).apply { 188 setPackage(appStorePackageName) 189 data = Uri.parse(linkTemplate.replace(APP_PACKAGE_NAME_PLACEHOLDER, packageName)) 190 } 191 } 192 193 private const val APP_PACKAGE_NAME_PLACEHOLDER = "\$packageName" 194 } 195 } 196