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