Loading packages/SystemUI/AndroidManifest.xml +3 −0 Original line number Diff line number Diff line Loading @@ -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" /> Loading packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java +39 −0 Original line number Diff line number Diff line Loading @@ -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. * Loading @@ -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<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; } // | . . . . . . . . . . . . . . . . . . . | // | | // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/ } packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java +75 −6 Original line number Diff line number Diff line Loading @@ -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; } Loading @@ -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) {} Loading @@ -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<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)); } } }; } Loading
packages/SystemUI/AndroidManifest.xml +3 −0 Original line number Diff line number Diff line Loading @@ -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" /> Loading
packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java +39 −0 Original line number Diff line number Diff line Loading @@ -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. * Loading @@ -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<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; } // | . . . . . . . . . . . . . . . . . . . | // | | // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/ }
packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java +75 −6 Original line number Diff line number Diff line Loading @@ -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; } Loading @@ -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) {} Loading @@ -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<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)); } } }; }