Merge "Include all flags in our flag list." into tm-qpr-dev
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 540ca04..70a464e 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -115,6 +115,7 @@
         "androidx-constraintlayout_constraintlayout",
         "androidx.exifinterface_exifinterface",
         "com.google.android.material_material",
+        "kotlin-reflect",
         "kotlinx_coroutines_android",
         "kotlinx_coroutines",
         "iconloader_base",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index f7049cf..933e586 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -89,7 +89,7 @@
  *
  * It can be changed or overridden in debug builds but not in release builds.
  */
-data class UnreleasedFlag @JvmOverloads constructor(
+data class UnreleasedFlag constructor(
     override val id: Int,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
@@ -100,7 +100,7 @@
  *
  * It can be changed or overridden in any build, meaning it can be turned off if needed.
  */
-data class ReleasedFlag @JvmOverloads constructor(
+data class ReleasedFlag constructor(
     override val id: Int,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
@@ -111,7 +111,7 @@
  *
  * Prefer [UnreleasedFlag] and [ReleasedFlag].
  */
-data class ResourceBooleanFlag @JvmOverloads constructor(
+data class ResourceBooleanFlag constructor(
     override val id: Int,
     @BoolRes override val resourceId: Int,
     override val teamfood: Boolean = false
@@ -124,7 +124,7 @@
  *
  * Prefer [UnreleasedFlag] and [ReleasedFlag].
  */
-data class DeviceConfigBooleanFlag @JvmOverloads constructor(
+data class DeviceConfigBooleanFlag constructor(
     override val id: Int,
     override val name: String,
     override val namespace: String,
@@ -139,7 +139,7 @@
  *
  * Prefer [UnreleasedFlag] and [ReleasedFlag].
  */
-data class SysPropBooleanFlag @JvmOverloads constructor(
+data class SysPropBooleanFlag constructor(
     override val id: Int,
     override val name: String,
     override val default: Boolean = false
@@ -148,7 +148,7 @@
     override val teamfood: Boolean = false
 }
 
-data class StringFlag @JvmOverloads constructor(
+data class StringFlag constructor(
     override val id: Int,
     override val default: String = "",
     override val teamfood: Boolean = false,
@@ -173,13 +173,13 @@
     }
 }
 
-data class ResourceStringFlag @JvmOverloads constructor(
+data class ResourceStringFlag constructor(
     override val id: Int,
     @StringRes override val resourceId: Int,
     override val teamfood: Boolean = false
 ) : ResourceFlag<String>
 
-data class IntFlag @JvmOverloads constructor(
+data class IntFlag constructor(
     override val id: Int,
     override val default: Int = 0,
     override val teamfood: Boolean = false,
@@ -205,13 +205,13 @@
     }
 }
 
-data class ResourceIntFlag @JvmOverloads constructor(
+data class ResourceIntFlag constructor(
     override val id: Int,
     @IntegerRes override val resourceId: Int,
     override val teamfood: Boolean = false
 ) : ResourceFlag<Int>
 
-data class LongFlag @JvmOverloads constructor(
+data class LongFlag constructor(
     override val id: Int,
     override val default: Long = 0,
     override val teamfood: Boolean = false,
@@ -237,7 +237,7 @@
     }
 }
 
-data class FloatFlag @JvmOverloads constructor(
+data class FloatFlag constructor(
     override val id: Int,
     override val default: Float = 0f,
     override val teamfood: Boolean = false,
@@ -263,13 +263,13 @@
     }
 }
 
-data class ResourceFloatFlag @JvmOverloads constructor(
+data class ResourceFloatFlag constructor(
     override val id: Int,
     override val resourceId: Int,
     override val teamfood: Boolean = false
 ) : ResourceFlag<Int>
 
-data class DoubleFlag @JvmOverloads constructor(
+data class DoubleFlag constructor(
     override val id: Int,
     override val default: Double = 0.0,
     override val teamfood: Boolean = false,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index 2a1e3cd..ad4b87d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -23,7 +23,6 @@
 import com.android.systemui.statusbar.commandline.Command;
 
 import java.io.PrintWriter;
-import java.lang.reflect.Field;
 import java.util.List;
 import java.util.Map;
 
@@ -230,33 +229,22 @@
     }
 
     private int flagNameToId(String flagName) {
-        List<Field> fields = Flags.getFlagFields();
-        for (Field field : fields) {
-            if (flagName.equals(field.getName())) {
-                return fieldToId(field);
+        Map<String, Flag<?>> flagFields = Flags.getFlagFields();
+        for (String fieldName : flagFields.keySet()) {
+            if (flagName.equals(fieldName)) {
+                return flagFields.get(fieldName).getId();
             }
         }
 
         return 0;
     }
 
-    private int fieldToId(Field field) {
-        try {
-            Flag<?> flag = (Flag<?>) field.get(null);
-            return flag.getId();
-        } catch (IllegalAccessException e) {
-            // no-op
-        }
-
-        return 0;
-    }
-
     private void printKnownFlags(PrintWriter pw) {
-        List<Field> fields = Flags.getFlagFields();
+        Map<String, Flag<?>> fields = Flags.getFlagFields();
 
         int longestFieldName = 0;
-        for (Field field : fields) {
-            longestFieldName = Math.max(longestFieldName, field.getName().length());
+        for (String fieldName : fields.keySet()) {
+            longestFieldName = Math.max(longestFieldName, fieldName.length());
         }
 
         pw.println("Known Flags:");
@@ -268,16 +256,15 @@
         for (int i = 0; i < longestFieldName; i++) {
             pw.print("=");
         }
-        pw.println(" ==== =====");
-        for (Field field : fields) {
-            int id = fieldToId(field);
-            Flag<?> flag = mAllFlags.get(id);
-
+        pw.println(" ==== ========");
+        for (String fieldName : fields.keySet()) {
+            Flag<?> flag = fields.get(fieldName);
+            int id = flag.getId();
             if (id == 0 || !mAllFlags.containsKey(id)) {
                 continue;
             }
-            pw.print(field.getName());
-            int fieldWidth = field.getName().length();
+            pw.print(fieldName);
+            int fieldWidth = fieldName.length();
             for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
                 pw.print(" ");
             }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 0258b23d..9d4a3a9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -17,8 +17,12 @@
 
 import android.provider.DeviceConfig
 import com.android.internal.annotations.Keep
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.R
-import java.lang.reflect.Field
+import kotlin.reflect.KClass
+import kotlin.reflect.full.declaredMembers
+import kotlin.reflect.full.isSubclassOf
+import kotlin.reflect.full.staticProperties
 
 /**
  * List of [Flag] objects for use in SystemUI.
@@ -63,6 +67,7 @@
     @JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true)
     val STABILITY_INDEX_FIX = UnreleasedFlag(114, teamfood = true)
     val SEMI_STABLE_SORT = UnreleasedFlag(115, teamfood = true)
+
     @JvmField val NOTIFICATION_GROUP_CORNER = UnreleasedFlag(116, teamfood = true)
     // next id: 117
 
@@ -148,6 +153,7 @@
     // TODO(b/254512321): Tracking Bug
     @JvmField val COMBINED_QS_HEADERS = UnreleasedFlag(501, teamfood = true)
     val PEOPLE_TILE = ResourceBooleanFlag(502, R.bool.flag_conversations)
+
     @JvmField
     val QS_USER_DETAIL_SHORTCUT =
         ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut)
@@ -244,14 +250,14 @@
     @JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
 
     // 1100 - windowing
-    @JvmField
     @Keep
+    @JvmField
     val WM_ENABLE_SHELL_TRANSITIONS =
         SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false)
 
     // TODO(b/254513207): Tracking Bug
-    @JvmField
     @Keep
+    @JvmField
     val WM_ENABLE_PARTIAL_SCREEN_SHARING =
         DeviceConfigBooleanFlag(
             1102,
@@ -262,30 +268,30 @@
         )
 
     // TODO(b/254512674): Tracking Bug
-    @JvmField
     @Keep
+    @JvmField
     val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false)
 
-    @JvmField
     @Keep
+    @JvmField
     val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false)
 
-    @JvmField
     @Keep
+    @JvmField
     val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false)
 
-    @JvmField
     @Keep
+    @JvmField
     val ENABLE_FLING_TO_DISMISS_BUBBLE =
         SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true)
 
-    @JvmField
     @Keep
+    @JvmField
     val ENABLE_FLING_TO_DISMISS_PIP =
         SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true)
 
-    @JvmField
     @Keep
+    @JvmField
     val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
         SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false)
 
@@ -293,18 +299,18 @@
     @JvmField @Keep val WM_BUBBLE_BAR = UnreleasedFlag(1111)
 
     // 1200 - predictive back
-    @JvmField
     @Keep
+    @JvmField
     val WM_ENABLE_PREDICTIVE_BACK =
         SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true)
 
-    @JvmField
     @Keep
+    @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_ANIM =
         SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false)
 
-    @JvmField
     @Keep
+    @JvmField
     val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
         SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false)
 
@@ -313,7 +319,7 @@
 
     // 1300 - screenshots
     // TODO(b/254512719): Tracking Bug
-    @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300, true)
+    @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300, teamfood = true)
 
     // TODO(b/254513155): Tracking Bug
     @JvmField val SCREENSHOT_WORK_PROFILE_POLICY = UnreleasedFlag(1301)
@@ -327,7 +333,7 @@
     val CHOOSER_UNBUNDLED = UnreleasedFlag(1500, teamfood = true)
 
     // 1700 - clipboard
-    @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, true)
+    @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, teamfood = true)
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = UnreleasedFlag(1701)
 
     // 1800 - shade container
@@ -337,7 +343,7 @@
     @JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
 
     // 2000 - device controls
-    @Keep val USE_APP_PANELS = UnreleasedFlag(2000, true)
+    @Keep @JvmField val USE_APP_PANELS = UnreleasedFlag(2000, teamfood = true)
 
     // 2100 - Falsing Manager
     @JvmField val FALSING_FOR_LONG_TAPS = ReleasedFlag(2100)
@@ -348,24 +354,48 @@
     // |  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  |
     @JvmStatic
     fun collectFlags(): Map<Int, Flag<*>> {
-        return flagFields
-            .map { field ->
-                // field[null] returns the current value of the field.
-                // See java.lang.Field#get
-                val flag = field[null] as Flag<*>
-                flag.id to flag
-            }
-            .toMap()
+        return flagFields.mapKeys { field -> field.value.id }
     }
 
     // |  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  |
     @JvmStatic
-    val flagFields: List<Field>
-        get() {
-            return Flags::class.java.fields.filter { f ->
-                Flag::class.java.isAssignableFrom(f.type)
+    val flagFields: Map<String, Flag<*>>
+        get() = collectFlagsInClass(Flags)
+
+    @VisibleForTesting
+    fun collectFlagsInClass(instance: Any): Map<String, Flag<*>> {
+        val cls = instance::class
+        val javaPropNames = cls.java.fields.map { it.name }
+        val props = cls.declaredMembers
+        val staticProps = cls.staticProperties
+        val staticPropNames = staticProps.map { it.name }
+        return props
+            .mapNotNull { property ->
+                if ((property.returnType.classifier as KClass<*>).isSubclassOf(Flag::class)) {
+                    // Fields with @JvmStatic should be accessed via java mechanisms
+                    if (javaPropNames.contains(property.name)) {
+                        property.name to cls.java.getField(property.name)[null] as Flag<*>
+                        // Fields with @Keep but not @JvmField. Don't do this.
+                    } else if (staticPropNames.contains(property.name)) {
+                        // The below code causes access violation exceptions. I don't know why.
+                        // property.name to (property.call() as Flag<*>)
+                        // property.name to (staticProps.find { it.name == property.name }!!
+                        // .getter.call() as Flag<*>)
+                        throw java.lang.RuntimeException(
+                            "The {$property.name} flag needs @JvmField"
+                        )
+                        // Everything else. Skip the `get` prefixed fields that kotlin adds.
+                    } else if (property.name.subSequence(0, 3) != "get") {
+                        property.name to (property.call(instance) as Flag<*>)
+                    } else {
+                        null
+                    }
+                } else {
+                    null
+                }
             }
-        }
+            .toMap()
+    }
     // |                                                           |
     // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java
deleted file mode 100644
index 250cc48..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.flags;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.util.Pair;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Test;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-@SmallTest
-public class FlagsTest extends SysuiTestCase {
-
-    @Test
-    public void testDuplicateFlagIdCheckWorks() {
-        List<Pair<String, Flag<?>>> flags = collectFlags(DuplicateFlagContainer.class);
-        Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags);
-
-        assertWithMessage(generateAssertionMessage(duplicates))
-                .that(duplicates.size()).isEqualTo(2);
-    }
-
-    @Test
-    public void testNoDuplicateFlagIds() {
-        List<Pair<String, Flag<?>>> flags = collectFlags(Flags.class);
-        Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags);
-
-        assertWithMessage(generateAssertionMessage(duplicates))
-                .that(duplicates.size()).isEqualTo(0);
-    }
-
-    private String generateAssertionMessage(Map<Integer, List<String>> duplicates) {
-        StringBuilder stringBuilder = new StringBuilder();
-        stringBuilder.append("Duplicate flag keys found: {");
-        for (int id : duplicates.keySet()) {
-            stringBuilder
-                    .append(" ")
-                    .append(id)
-                    .append(": [")
-                    .append(String.join(", ", duplicates.get(id)))
-                    .append("]");
-        }
-        stringBuilder.append(" }");
-
-        return stringBuilder.toString();
-    }
-
-    private List<Pair<String, Flag<?>>> collectFlags(Class<?> clz) {
-        List<Pair<String, Flag<?>>> flags = new ArrayList<>();
-
-        Field[] fields = clz.getFields();
-
-        for (Field field : fields) {
-            Class<?> t = field.getType();
-            if (Flag.class.isAssignableFrom(t)) {
-                try {
-                    flags.add(Pair.create(field.getName(), (Flag<?>) field.get(null)));
-                } catch (IllegalAccessException e) {
-                    // no-op
-                }
-            }
-        }
-
-        return flags;
-    }
-
-    private Map<Integer, List<String>> groupDuplicateFlags(List<Pair<String, Flag<?>>> flags) {
-        Map<Integer, List<String>> grouping = new HashMap<>();
-
-        for (Pair<String, Flag<?>> flag : flags) {
-            grouping.putIfAbsent(flag.second.getId(), new ArrayList<>());
-            grouping.get(flag.second.getId()).add(flag.first);
-        }
-
-        Map<Integer, List<String>> result = new HashMap<>();
-        for (Integer id : grouping.keySet()) {
-            if (grouping.get(id).size() > 1) {
-                result.put(id, grouping.get(id));
-            }
-        }
-
-        return result;
-    }
-
-    private static class DuplicateFlagContainer {
-        public static final BooleanFlag A_FLAG = new UnreleasedFlag(0);
-        public static final BooleanFlag B_FLAG = new UnreleasedFlag(0);
-        public static final StringFlag C_FLAG = new StringFlag(0);
-
-        public static final BooleanFlag D_FLAG = new UnreleasedFlag(1);
-
-        public static final DoubleFlag E_FLAG = new DoubleFlag(3);
-        public static final DoubleFlag F_FLAG = new DoubleFlag(3);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.kt
new file mode 100644
index 0000000..2b556f1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth
+import java.lang.StringBuilder
+import java.util.ArrayList
+import java.util.HashMap
+import org.junit.Test
+
+@SmallTest
+class FlagsTest : SysuiTestCase() {
+    @Test
+    fun testDuplicateFlagIdCheckWorks() {
+        val flags = Flags.collectFlagsInClass(DuplicateFlagContainer)
+        val duplicates = groupDuplicateFlags(flags)
+        Truth.assertWithMessage(generateAssertionMessage(duplicates))
+            .that(duplicates.size)
+            .isEqualTo(2)
+    }
+
+    @Test
+    fun testNoDuplicateFlagIds() {
+        val flags = Flags.collectFlagsInClass(Flags)
+        val duplicates = groupDuplicateFlags(flags)
+        Truth.assertWithMessage(generateAssertionMessage(duplicates))
+            .that(duplicates.size)
+            .isEqualTo(0)
+    }
+
+    private fun generateAssertionMessage(duplicates: Map<Int, List<String>>): String {
+        val stringBuilder = StringBuilder()
+        stringBuilder.append("Duplicate flag keys found: {")
+        for (id in duplicates.keys) {
+            stringBuilder
+                .append(" ")
+                .append(id)
+                .append(": [")
+                .append(java.lang.String.join(", ", duplicates[id]))
+                .append("]")
+        }
+        stringBuilder.append(" }")
+        return stringBuilder.toString()
+    }
+
+    private fun groupDuplicateFlags(flags: Map<String, Flag<*>>): Map<Int, List<String>> {
+        val grouping: MutableMap<Int, MutableList<String>> = HashMap()
+        for (flag in flags) {
+            grouping.putIfAbsent(flag.value.id, ArrayList())
+            grouping[flag.value.id]!!.add(flag.key)
+        }
+        val result: MutableMap<Int, List<String>> = HashMap()
+        for (id in grouping.keys) {
+            if (grouping[id]!!.size > 1) {
+                result[id] = grouping[id]!!
+            }
+        }
+        return result
+    }
+
+    private object DuplicateFlagContainer {
+        val A_FLAG: BooleanFlag = UnreleasedFlag(0)
+        val B_FLAG: BooleanFlag = UnreleasedFlag(0)
+        val C_FLAG = StringFlag(0)
+        val D_FLAG: BooleanFlag = UnreleasedFlag(1)
+        val E_FLAG = DoubleFlag(3)
+        val F_FLAG = DoubleFlag(3)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index c132537..a35427f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -27,9 +27,8 @@
     private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>()
 
     init {
-        Flags.flagFields.forEach { field ->
-            val flag: Flag<*> = field.get(null) as Flag<*>
-            knownFlagNames[flag.id] = field.name
+        Flags.flagFields.forEach { entry: Map.Entry<String, Flag<*>> ->
+            knownFlagNames[entry.value.id] = entry.key
         }
     }