diff options
4 files changed, 164 insertions, 22 deletions
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 d153bd8eac09..ab1749978653 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 @@ public interface FlagReaderPlugin extends Plugin { 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 08247a824ffb..d4d01c8d97b5 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 @@ public class FeatureFlagReader { 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 0c9e6de4e4d0..e51f90f9c73b 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 @@ package com.android.systemui.flags; 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 @@ import javax.inject.Inject; 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 @@ public class FeatureFlags { 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 @@ public class FeatureFlags { 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 000000000000..1a961787ee79 --- /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(); + + } +} |