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