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));
+            }
+        }
+    };
 }