1 /*
<lambda>null2  * Copyright (C) 2021 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 package com.android.systemui.flags
18 
19 import android.app.Activity
20 import android.content.pm.PackageManager
21 import android.content.BroadcastReceiver
22 import android.content.Context
23 import android.content.Intent
24 import android.database.ContentObserver
25 import android.net.Uri
26 import android.os.Bundle
27 import android.os.Handler
28 import androidx.concurrent.futures.CallbackToFutureAdapter
29 import com.google.common.util.concurrent.ListenableFuture
30 import java.util.function.Consumer
31 
32 class FlagManager constructor(
33     private val context: Context,
34     private val settings: FlagSettingsHelper,
35     private val handler: Handler
36 ) : FlagListenable {
37     companion object {
38         const val RECEIVING_PACKAGE = "com.android.systemui"
39         const val RECEIVING_PACKAGE_WATCH = "com.google.android.apps.wearable.systemui"
40         const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG"
41         const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS"
42         const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"
43         const val ACTION_SYSUI_STARTED = "com.android.systemui.STARTED"
44         const val EXTRA_NAME = "name"
45         const val EXTRA_VALUE = "value"
46         const val EXTRA_FLAGS = "flags"
47         private const val SETTINGS_PREFIX = "systemui/flags"
48     }
49 
50     constructor(context: Context, handler: Handler) : this(
51         context,
52         FlagSettingsHelper(context.contentResolver),
53         handler
54     )
55 
56     /**
57      * An action called on restart which takes as an argument whether the listeners requested
58      * that the restart be suppressed
59      */
60     var onSettingsChangedAction: Consumer<Boolean>? = null
61     var clearCacheAction: Consumer<String>? = null
62     private val listeners: MutableSet<PerFlagListener> = mutableSetOf()
63     private val settingsObserver: ContentObserver = SettingsObserver()
64 
65     fun getFlagsFuture(): ListenableFuture<Collection<Flag<*>>> {
66         val intent = Intent(ACTION_GET_FLAGS)
67         intent.setPackage(if (isWatch()) RECEIVING_PACKAGE_WATCH else RECEIVING_PACKAGE)
68 
69         return CallbackToFutureAdapter.getFuture {
70                 completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> ->
71             context.sendOrderedBroadcast(
72                 intent,
73                 null,
74                 object : BroadcastReceiver() {
75                     override fun onReceive(context: Context, intent: Intent) {
76                         val extras: Bundle? = getResultExtras(false)
77                         val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? =
78                             extras?.getParcelableArrayList(
79                                 EXTRA_FLAGS, ParcelableFlag::class.java
80                             )
81                         if (listOfFlags != null) {
82                             completer.set(listOfFlags)
83                         } else {
84                             completer.setException(NoFlagResultsException())
85                         }
86                     }
87                 },
88                 null,
89                 Activity.RESULT_OK,
90                 "extra data",
91                 null
92             )
93             "QueryingFlags"
94         }
95     }
96 
97     /**
98      * Returns the stored value or null if not set.
99      * This API is used by TheFlippinApp.
100      */
101     fun isEnabled(name: String): Boolean? = readFlagValue(name, BooleanFlagSerializer)
102 
103     /**
104      * Sets the value of a boolean flag.
105      * This API is used by TheFlippinApp.
106      */
107     fun setFlagValue(name: String, enabled: Boolean) {
108         val intent = createIntent(name)
109         intent.putExtra(EXTRA_VALUE, enabled)
110 
111         context.sendBroadcast(intent)
112     }
113 
114     fun eraseFlag(name: String) {
115         val intent = createIntent(name)
116 
117         context.sendBroadcast(intent)
118     }
119 
120     /** Returns the stored value or null if not set.  */
121     fun <T> readFlagValue(name: String, serializer: FlagSerializer<T>): T? {
122         val data = settings.getString(nameToSettingsKey(name))
123         return serializer.fromSettingsData(data)
124     }
125 
126     override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
127         synchronized(listeners) {
128             val registerNeeded = listeners.isEmpty()
129             listeners.add(PerFlagListener(flag.name, listener))
130             if (registerNeeded) {
131                 settings.registerContentObserver(SETTINGS_PREFIX, true, settingsObserver)
132             }
133         }
134     }
135 
136     override fun removeListener(listener: FlagListenable.Listener) {
137         synchronized(listeners) {
138             if (listeners.isEmpty()) {
139                 return
140             }
141             listeners.removeIf { it.listener == listener }
142             if (listeners.isEmpty()) {
143                 settings.unregisterContentObserver(settingsObserver)
144             }
145         }
146     }
147 
148     private fun createIntent(name: String): Intent {
149         val intent = Intent(ACTION_SET_FLAG)
150         intent.setPackage(RECEIVING_PACKAGE)
151         intent.putExtra(EXTRA_NAME, name)
152 
153         return intent
154     }
155 
156     fun nameToSettingsKey(name: String): String {
157         return "$SETTINGS_PREFIX/$name"
158     }
159 
160     inner class SettingsObserver : ContentObserver(handler) {
161         override fun onChange(selfChange: Boolean, uri: Uri?) {
162             if (uri == null) {
163                 return
164             }
165             val parts = uri.pathSegments
166             val name = parts[parts.size - 1]
167             clearCacheAction?.accept(name)
168             dispatchListenersAndMaybeRestart(name, onSettingsChangedAction)
169         }
170     }
171 
172     fun dispatchListenersAndMaybeRestart(name: String, restartAction: Consumer<Boolean>?) {
173         val filteredListeners: List<FlagListenable.Listener> = synchronized(listeners) {
174             listeners.mapNotNull { if (it.name == name) it.listener else null }
175         }
176         // If there are no listeners, there's nothing to dispatch to, and nothing to suppress it.
177         if (filteredListeners.isEmpty()) {
178             restartAction?.accept(false)
179             return
180         }
181         // Dispatch to every listener and save whether each one called requestNoRestart.
182         val suppressRestartList: List<Boolean> = filteredListeners.map { listener ->
183             var didRequestNoRestart = false
184             val event = object : FlagListenable.FlagEvent {
185                 override val flagName = name
186                 override fun requestNoRestart() {
187                     didRequestNoRestart = true
188                 }
189             }
190             listener.onFlagChanged(event)
191             didRequestNoRestart
192         }
193         // Suppress restart only if ALL listeners request it.
194         val suppressRestart = suppressRestartList.all { it }
195         restartAction?.accept(suppressRestart)
196     }
197 
198     private fun isWatch(): Boolean {
199         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
200     }
201 
202     private data class PerFlagListener(val name: String, val listener: FlagListenable.Listener)
203 }
204 
205 class NoFlagResultsException : Exception(
206     "SystemUI failed to communicate its flags back successfully"
207 )
208