1 /* 2 * 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 fun <T> readFlagValue(name: String, serializer: FlagSerializer<T>): T? { 120 val data = settings.getString(nameToSettingsKey(name)) 121 return serializer.fromSettingsData(data) 122 } 123 124 override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) { 125 synchronized(listeners) { 126 val registerNeeded = listeners.isEmpty() 127 listeners.add(PerFlagListener(flag.name, listener)) 128 if (registerNeeded) { 129 settings.registerContentObserver(SETTINGS_PREFIX, true, settingsObserver) 130 } 131 } 132 } 133 134 override fun removeListener(listener: FlagListenable.Listener) { 135 synchronized(listeners) { 136 if (listeners.isEmpty()) { 137 return 138 } 139 listeners.removeIf { it.listener == listener } 140 if (listeners.isEmpty()) { 141 settings.unregisterContentObserver(settingsObserver) 142 } 143 } 144 } 145 146 private fun createIntent(name: String): Intent { 147 val intent = Intent(ACTION_SET_FLAG) 148 intent.setPackage(RECEIVING_PACKAGE) 149 intent.putExtra(EXTRA_NAME, name) 150 151 return intent 152 } 153 154 fun nameToSettingsKey(name: String): String { 155 return "$SETTINGS_PREFIX/$name" 156 } 157 158 inner class SettingsObserver : ContentObserver(handler) { 159 override fun onChange(selfChange: Boolean, uri: Uri?) { 160 if (uri == null) { 161 return 162 } 163 val parts = uri.pathSegments 164 val name = parts[parts.size - 1] 165 clearCacheAction?.accept(name) 166 dispatchListenersAndMaybeRestart(name, onSettingsChangedAction) 167 } 168 } 169 170 fun dispatchListenersAndMaybeRestart(name: String, restartAction: Consumer<Boolean>?) { 171 val filteredListeners: List<FlagListenable.Listener> = synchronized(listeners) { 172 listeners.mapNotNull { if (it.name == name) it.listener else null } 173 } 174 // If there are no listeners, there's nothing to dispatch to, and nothing to suppress it. 175 if (filteredListeners.isEmpty()) { 176 restartAction?.accept(false) 177 return 178 } 179 // Dispatch to every listener and save whether each one called requestNoRestart. 180 val suppressRestartList: List<Boolean> = filteredListeners.map { listener -> 181 var didRequestNoRestart = false 182 val event = object : FlagListenable.FlagEvent { 183 override val flagName = name 184 override fun requestNoRestart() { 185 didRequestNoRestart = true 186 } 187 } 188 listener.onFlagChanged(event) 189 didRequestNoRestart 190 } 191 // Suppress restart only if ALL listeners request it. 192 val suppressRestart = suppressRestartList.all { it } 193 restartAction?.accept(suppressRestart) 194 } 195 196 private data class PerFlagListener(val name: String, val listener: FlagListenable.Listener) 197 } 198 199 class NoFlagResultsException : Exception( 200 "SystemUI failed to communicate its flags back successfully" 201 ) 202