Add ability to set/erase flags via intents.
A flag can be set with a command like the following:
adb shell am broadcast -a com.android.systemui.action.SET_FLAG \
--ei id 1 --ez value 1
A flag can be "erased" with the same command, but leaving the value
unset.
Bug: 202860494
Test: manual
Change-Id: If1097e230692df49a6de7cbbb71ed37b0499c663
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9de1c5e..58e3d39 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -183,6 +183,9 @@
<permission android:name="com.android.systemui.permission.PLUGIN"
android:protectionLevel="signature" />
+ <permission android:name="com.android.systemui.permission.FLAGS"
+ android:protectionLevel="signature" />
+
<!-- Adding Quick Settings tiles -->
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
index e983818..17b13a2 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
@@ -16,6 +16,10 @@
package com.android.systemui.flags;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* List of {@link Flag} objects for use in SystemUI.
*
@@ -26,4 +30,39 @@
*/
public class Flags {
public static final BooleanFlag THE_FIRST_FLAG = new BooleanFlag(1, false);
+
+
+ // Pay no attention to the reflection behind the curtain.
+ // ========================== Curtain ==========================
+ // | |
+ // | . . . . . . . . . . . . . . . . . . . |
+ private static Map<Integer, Flag<?>> sFlagMap;
+ static Map<Integer, Flag<?>> collectFlags() {
+ if (sFlagMap != null) {
+ return sFlagMap;
+ }
+ Map<Integer, Flag<?>> flags = new HashMap<>();
+
+ Field[] fields = Flags.class.getFields();
+
+ for (Field field : fields) {
+ Class<?> t = field.getType();
+ if (Flag.class.isAssignableFrom(t)) {
+ try {
+ Flag<?> flag = (Flag<?>) field.get(null);
+ flags.put(flag.getId(), flag);
+ } catch (IllegalAccessException e) {
+ // no-op
+ }
+ }
+ }
+
+ sFlagMap = flags;
+
+ return sFlagMap;
+ }
+ // | . . . . . . . . . . . . . . . . . . . |
+ // | |
+ // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
+
}
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
index b84c7bf..2ed6328 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
@@ -16,32 +16,55 @@
package com.android.systemui.flags;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+
import com.android.systemui.dagger.SysUISingleton;
import org.json.JSONException;
import org.json.JSONObject;
+import java.util.Map;
+
import javax.inject.Inject;
/**
* Concrete implementation of the a Flag manager that returns default values for debug builds
+ *
+ * Flags can be set (or unset) via the following adb command:
+ *
+ * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
+ *
+ * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
*/
@SysUISingleton
public class FeatureFlagManager implements FlagReader, FlagWriter {
+ private static final String TAG = "SysUIFlags";
+
private static final String SYSPROP_PREFIX = "persist.systemui.flag_";
private static final String FIELD_TYPE = "type";
+ private static final String FIELD_ID = "id";
private static final String FIELD_VALUE = "value";
private static final String TYPE_BOOLEAN = "boolean";
+ private static final String ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG";
+ private static final String FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS";
private final SystemPropertiesHelper mSystemPropertiesHelper;
@Inject
- public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper) {
+ public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context) {
mSystemPropertiesHelper = systemPropertiesHelper;
+
+ IntentFilter filter = new IntentFilter(ACTION_SET_FLAG);
+ context.registerReceiver(mReceiver, filter, FLAGS_PERMISSION, null);
}
/** Return a {@link BooleanFlag}'s value. */
- public boolean isEnabled(int key, boolean defaultValue) {
- String data = mSystemPropertiesHelper.get(keyToSysPropKey(key));
+ public boolean isEnabled(int id, boolean defaultValue) {
+ String data = mSystemPropertiesHelper.get(keyToSysPropKey(id));
if (data.isEmpty()) {
return defaultValue;
}
@@ -53,22 +76,31 @@
}
return json.getBoolean(FIELD_VALUE);
} catch (JSONException e) {
- // TODO: delete the property
+ eraseFlag(id);
return defaultValue;
}
}
- public void setEnabled(int key, boolean value) {
+ /** Set whether a given {@link BooleanFlag} is enabled or not. */
+ public void setEnabled(int id, boolean value) {
JSONObject json = new JSONObject();
try {
json.put(FIELD_TYPE, TYPE_BOOLEAN);
json.put(FIELD_VALUE, value);
- mSystemPropertiesHelper.set(keyToSysPropKey(key), json.toString());
+ mSystemPropertiesHelper.set(keyToSysPropKey(id), json.toString());
+ Log.i(TAG, "Set id " + id + " to " + value);
} catch (JSONException e) {
// no-op
}
}
+ /** Erase a flag's overridden value if there is one. */
+ public void eraseFlag(int id) {
+ // We can't actually "erase" things from sysprops, but we can set them to empty!
+ mSystemPropertiesHelper.set(keyToSysPropKey(id), "");
+ Log.i(TAG, "Erase id " + id);
+ }
+
public void addListener(Listener run) {}
public void removeListener(Listener run) {}
@@ -85,4 +117,41 @@
}
}
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+
+ if (ACTION_SET_FLAG.equals(action)) {
+ handleSetFlag(intent.getExtras());
+ }
+ }
+
+ private void handleSetFlag(Bundle extras) {
+ int id = extras.getInt(FIELD_ID);
+ if (id <= 0) {
+ Log.w(TAG, "ID not set or less than or equal to 0: " + id);
+ return;
+ }
+
+ Map<Integer, Flag<?>> flagMap = Flags.collectFlags();
+ if (!flagMap.containsKey(id)) {
+ Log.w(TAG, "Tried to set unknown id: " + id);
+ return;
+ }
+ Flag<?> flag = flagMap.get(id);
+
+ if (!extras.containsKey(FIELD_VALUE)) {
+ eraseFlag(id);
+ return;
+ }
+
+ if (flag instanceof BooleanFlag) {
+ setEnabled(id, extras.getBoolean(FIELD_VALUE));
+ }
+ }
+ };
}