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