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