Merge "Add flag listeners." into sc-v2-dev am: 64eea77e08 am: 61065aada9

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15405312

Change-Id: I31244e87f82a601816bc5d4444a4077d4682a722
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
index d153bd8..ab17499 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
@@ -56,4 +56,16 @@
     default double getValue(int id, double def) {
         return def;
     }
+
+    /** Add a listener to be alerted when any flag changes. */
+    default void addListener(Listener listener) {}
+
+    /** Remove a listener to be alerted when any flag changes. */
+    default void removeListener(Listener listener) {}
+
+    /** A simple listener to be alerted when a flag changes. */
+    interface Listener {
+        /** */
+        void onFlagChanged(int id);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
index 08247a8..d4d01c8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
@@ -114,6 +114,14 @@
         return mPlugin.getValue(flag.getId(), flag.getDefault());
     }
 
+    void addListener(FlagReaderPlugin.Listener listener) {
+        mPlugin.addListener(listener);
+    }
+
+    void removeListener(FlagReaderPlugin.Listener listener) {
+        mPlugin.removeListener(listener);
+    }
+
     /**
      * Returns true if the specified feature flag has been enabled.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index 0c9e6de..e51f90f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -19,11 +19,14 @@
 import android.content.Context;
 import android.util.FeatureFlagUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.plugins.FlagReaderPlugin;
 
-import java.lang.reflect.Field;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.inject.Inject;
@@ -37,11 +40,26 @@
 public class FeatureFlags {
     private final FeatureFlagReader mFlagReader;
     private final Context mContext;
+    private final Map<Integer, Flag<?>> mFlagMap = new HashMap<>();
+    private final Map<Integer, List<Listener>> mListeners = new HashMap<>();
 
     @Inject
     public FeatureFlags(FeatureFlagReader flagReader, Context context) {
         mFlagReader = flagReader;
         mContext = context;
+
+        flagReader.addListener(mListener);
+    }
+
+    private final FlagReaderPlugin.Listener mListener = id -> {
+        if (mListeners.containsKey(id) && mFlagMap.containsKey(id)) {
+            mListeners.get(id).forEach(listener -> listener.onFlagChanged(mFlagMap.get(id)));
+        }
+    };
+
+    @VisibleForTesting
+    void addFlag(Flag flag) {
+        mFlagMap.put(flag.getId(), flag);
     }
 
     /**
@@ -92,6 +110,20 @@
         return mFlagReader.getValue(flag);
     }
 
+    /** Add a listener for a specific flag. */
+    public void addFlagListener(Flag<?> flag, Listener listener) {
+        mListeners.putIfAbsent(flag.getId(), new ArrayList<>());
+        mListeners.get(flag.getId()).add(listener);
+        mFlagMap.putIfAbsent(flag.getId(), flag);
+    }
+
+    /** Remove a listener for a specific flag. */
+    public void removeFlagListener(Flag<?> flag, Listener listener) {
+        if (mListeners.containsKey(flag.getId())) {
+            mListeners.get(flag.getId()).remove(listener);
+        }
+    }
+
     public boolean isNewNotifPipelineEnabled() {
         return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2);
     }
@@ -160,27 +192,6 @@
         return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
     }
 
-    private Map<Integer, Flag<?>> collectFlags() {
-        Map<Integer, Flag<?>> flags = new HashMap<>();
-
-        Field[] fields = this.getClass().getFields();
-
-        for (Field field : fields) {
-            Class<?> t = field.getType();
-            if (Flag.class.isAssignableFrom(t)) {
-                try {
-                    //flags.add((Flag<?>) field.get(null));
-                    Flag flag = (Flag) field.get(null);
-                    flags.put(flag.getId(), flag);
-                } catch (IllegalAccessException e) {
-                    // no-op
-                }
-            }
-        }
-
-        return flags;
-    }
-
     /** Simple interface for beinga alerted when a specific flag changes value. */
     public interface Listener {
         /** */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
new file mode 100644
index 0000000..1a96178
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.FlagReaderPlugin;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class FeatureFlagsTest extends SysuiTestCase {
+
+    @Mock FeatureFlagReader mFeatureFlagReader;
+
+    private FeatureFlags mFeatureFlags;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mFeatureFlags = new FeatureFlags(mFeatureFlagReader, getContext());
+    }
+
+    @Test
+    public void testAddListener() {
+        Flag<?> flag = new BooleanFlag(1);
+        mFeatureFlags.addFlag(flag);
+
+        // Assert and capture that a plugin listener was added.
+        ArgumentCaptor<FlagReaderPlugin.Listener> pluginListenerCaptor =
+                ArgumentCaptor.forClass(FlagReaderPlugin.Listener.class);
+
+        verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
+        FlagReaderPlugin.Listener pluginListener = pluginListenerCaptor.getValue();
+
+        // Signal a change. No listeners, so no real effect.
+        pluginListener.onFlagChanged(flag.getId());
+
+        // Add a listener for the flag
+        final Flag<?>[] changedFlag = {null};
+        FeatureFlags.Listener listener = f -> changedFlag[0] = f;
+        mFeatureFlags.addFlagListener(flag, listener);
+
+        // No changes seen yet.
+        assertThat(changedFlag[0]).isNull();
+
+        // Signal a change.
+        pluginListener.onFlagChanged(flag.getId());
+
+        // Assert that the change was for the correct flag.
+        assertThat(changedFlag[0]).isEqualTo(flag);
+    }
+
+    @Test
+    public void testRemoveListener() {
+        Flag<?> flag = new BooleanFlag(1);
+        mFeatureFlags.addFlag(flag);
+
+        // Assert and capture that a plugin listener was added.
+        ArgumentCaptor<FlagReaderPlugin.Listener> pluginListenerCaptor =
+                ArgumentCaptor.forClass(FlagReaderPlugin.Listener.class);
+
+        verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
+        FlagReaderPlugin.Listener pluginListener = pluginListenerCaptor.getValue();
+
+        // Add a listener for the flag
+        final Flag<?>[] changedFlag = {null};
+        FeatureFlags.Listener listener = f -> changedFlag[0] = f;
+        mFeatureFlags.addFlagListener(flag, listener);
+
+        // Signal a change.
+        pluginListener.onFlagChanged(flag.getId());
+
+        // Assert that the change was for the correct flag.
+        assertThat(changedFlag[0]).isEqualTo(flag);
+
+        changedFlag[0] = null;
+
+        // Now remove the listener.
+        mFeatureFlags.removeFlagListener(flag, listener);
+        // Signal a change.
+        pluginListener.onFlagChanged(flag.getId());
+        // Assert that the change was not triggered
+        assertThat(changedFlag[0]).isNull();
+
+    }
+}