From f6db05e59f0fa6684440d5251de5ddb5b9bbbe8e Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Wed, 13 Oct 2021 13:37:50 -0400 Subject: [PATCH] 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 --- packages/SystemUI/AndroidManifest.xml | 3 + .../src/com/android/systemui/flags/Flags.java | 39 +++++++++ .../systemui/flags/FeatureFlagManager.java | 81 +++++++++++++++++-- 3 files changed, 117 insertions(+), 6 deletions(-) diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 9de1c5ea1a3d4..58e3d398553c5 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -183,6 +183,9 @@ + + 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 e983818ef0402..17b13a235805a 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 @@ package com.android.systemui.flags; */ 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> sFlagMap; + static Map> collectFlags() { + if (sFlagMap != null) { + return sFlagMap; + } + Map> 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 b84c7bf0bd5ff..2ed632876b06a 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 [--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 @@ public class FeatureFlagManager implements FlagReader, FlagWriter { } 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 @@ public class FeatureFlagManager implements FlagReader, FlagWriter { } } + 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> 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)); + } + } + }; }