Communicate flags from systemui async

Flags are no longer statically compiled into the shared library.

A "GET_FLAGS" api has been added to SystemUI that returns a list
of Flag objects, as defined in SystemUI.

Communication happens via a simple "ordered" broadcast.

FlagManager#getFlagsFuture() returns a ListenableFuture pointing at
the list of flags. Be sure not to call Future#get on the main
thread - the call will block indefinitely as the broadcast receiver
is handled on the main thread.

Instead, add a listener to the future or call #get on a separate
thread.

Bug: 203548827
Test: manual
Change-Id: I8720e0905662a6371e7aabf04341df46b8943a9c
This commit is contained in:
Dave Mankoff
2021-11-05 14:26:41 -04:00
parent 7c1c6a691c
commit d1d488687b
4 changed files with 171 additions and 21 deletions

View File

@@ -62,6 +62,7 @@ java_library {
srcs: [
"src/com/android/systemui/flags/Flag.kt",
],
include_srcs: true,
static_kotlin_stdlib: false,
java_version: "1.8",
min_sdk_version: "current",
@@ -74,11 +75,11 @@ java_library {
],
static_kotlin_stdlib: false,
libs: [
"SystemUI-flags",
"androidx.concurrent_concurrent-futures",
],
static_libs: [
"SystemUI-flag-types",
"SystemUI-flags",
],
java_version: "1.8",
min_sdk_version: "current",

View File

@@ -16,37 +16,157 @@
package com.android.systemui.flags
interface Flag<T> {
import android.os.Parcel
import android.os.Parcelable
interface Flag<T> : Parcelable {
val id: Int
val default: T
override fun describeContents() = 0
}
// Consider using the "parcelize" kotlin library.
data class BooleanFlag @JvmOverloads constructor(
override val id: Int,
override val default: Boolean = false
) : Flag<Boolean>
) : Flag<Boolean> {
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<BooleanFlag> {
override fun createFromParcel(parcel: Parcel) = BooleanFlag(parcel)
override fun newArray(size: Int) = arrayOfNulls<BooleanFlag>(size)
}
}
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
default = parcel.readBoolean()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeBoolean(default)
}
}
data class StringFlag @JvmOverloads constructor(
override val id: Int,
override val default: String = ""
) : Flag<String>
) : Flag<String> {
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<StringFlag> {
override fun createFromParcel(parcel: Parcel) = StringFlag(parcel)
override fun newArray(size: Int) = arrayOfNulls<StringFlag>(size)
}
}
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
default = parcel.readString() ?: ""
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeString(default)
}
}
data class IntFlag @JvmOverloads constructor(
override val id: Int,
override val default: Int = 0
) : Flag<Int>
) : Flag<Int> {
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<IntFlag> {
override fun createFromParcel(parcel: Parcel) = IntFlag(parcel)
override fun newArray(size: Int) = arrayOfNulls<IntFlag>(size)
}
}
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
default = parcel.readInt()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeInt(default)
}
}
data class LongFlag @JvmOverloads constructor(
override val id: Int,
override val default: Long = 0
) : Flag<Long>
) : Flag<Long> {
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<LongFlag> {
override fun createFromParcel(parcel: Parcel) = LongFlag(parcel)
override fun newArray(size: Int) = arrayOfNulls<LongFlag>(size)
}
}
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
default = parcel.readLong()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeLong(default)
}
}
data class FloatFlag @JvmOverloads constructor(
override val id: Int,
override val default: Float = 0f
) : Flag<Float>
) : Flag<Float> {
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<FloatFlag> {
override fun createFromParcel(parcel: Parcel) = FloatFlag(parcel)
override fun newArray(size: Int) = arrayOfNulls<FloatFlag>(size)
}
}
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
default = parcel.readFloat()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeFloat(default)
}
}
data class DoubleFlag @JvmOverloads constructor(
override val id: Int,
override val default: Double = 0.0
) : Flag<Double>
) : Flag<Double> {
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<DoubleFlag> {
override fun createFromParcel(parcel: Parcel) = DoubleFlag(parcel)
override fun newArray(size: Int) = arrayOfNulls<DoubleFlag>(size)
}
}
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
default = parcel.readDouble()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeDouble(default)
}
}

View File

@@ -16,10 +16,13 @@
package com.android.systemui.flags
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.database.ContentObserver
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.provider.Settings
import androidx.concurrent.futures.CallbackToFutureAdapter
@@ -34,10 +37,12 @@ class FlagManager constructor(
companion object {
const val RECEIVING_PACKAGE = "com.android.systemui"
const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG"
const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS"
const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"
const val FIELD_ID = "id"
const val FIELD_VALUE = "value"
const val FIELD_TYPE = "type"
const val FIELD_FLAGS = "flags"
const val TYPE_BOOLEAN = "boolean"
private const val SETTINGS_PREFIX = "systemui/flags"
}
@@ -46,14 +51,26 @@ class FlagManager constructor(
private val settingsObserver: ContentObserver = SettingsObserver()
fun getFlagsFuture(): ListenableFuture<Collection<Flag<*>>> {
val knownFlagMap = Flags.collectFlags()
// Possible todo in the future: query systemui async to actually get the known flag ids.
return CallbackToFutureAdapter.getFuture(
CallbackToFutureAdapter.Resolver {
completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> ->
completer.set(knownFlagMap.values as Collection<Flag<*>>)
"Retrieving Flags"
})
val intent = Intent(ACTION_GET_FLAGS)
intent.setPackage(RECEIVING_PACKAGE)
return CallbackToFutureAdapter.getFuture {
completer: CallbackToFutureAdapter.Completer<Any?> ->
context.sendOrderedBroadcast(intent, null,
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val extras: Bundle? = getResultExtras(false)
val listOfFlags: java.util.ArrayList<Flag<*>>? =
extras?.getParcelableArrayList(FIELD_FLAGS)
if (listOfFlags != null) {
completer.set(listOfFlags)
} else {
completer.setException(NoFlagResultsException())
}
}
}, null, Activity.RESULT_OK, "extra data", null)
"QueryingFlags"
} as ListenableFuture<Collection<Flag<*>>>
}
fun setFlagValue(id: Int, enabled: Boolean) {
@@ -149,4 +166,7 @@ class FlagManager constructor(
}
}
class InvalidFlagStorageException : Exception("Data found but is invalid")
class InvalidFlagStorageException : Exception("Data found but is invalid")
class NoFlagResultsException : Exception(
"SystemUI failed to communicate its flags back successfully")

View File

@@ -16,10 +16,11 @@
package com.android.systemui.flags;
import static com.android.systemui.flags.FlagManager.ACTION_GET_FLAGS;
import static com.android.systemui.flags.FlagManager.ACTION_SET_FLAG;
import static com.android.systemui.flags.FlagManager.FIELD_FLAGS;
import static com.android.systemui.flags.FlagManager.FIELD_ID;
import static com.android.systemui.flags.FlagManager.FIELD_VALUE;
import static com.android.systemui.flags.FlagManager.FLAGS_PERMISSION;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -69,8 +70,10 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
DumpManager dumpManager) {
mFlagManager = flagManager;
mSecureSettings = secureSettings;
IntentFilter filter = new IntentFilter(ACTION_SET_FLAG);
context.registerReceiver(mReceiver, filter, FLAGS_PERMISSION, null);
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SET_FLAG);
filter.addAction(ACTION_GET_FLAGS);
context.registerReceiver(mReceiver, filter, null, null);
dumpManager.registerDumpable(TAG, this);
}
@@ -151,9 +154,15 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
if (action == null) {
return;
}
if (ACTION_SET_FLAG.equals(action)) {
handleSetFlag(intent.getExtras());
} else if (ACTION_GET_FLAGS.equals(action)) {
Map<Integer, Flag<?>> knownFlagMap = Flags.collectFlags();
ArrayList<Flag<?>> flags = new ArrayList<>(knownFlagMap.values());
Bundle extras = getResultExtras(true);
if (extras != null) {
extras.putParcelableArrayList(FIELD_FLAGS, flags);
}
}
}