Merge "Move contents of FeatureFlags into FeatureFlagManager." into sc-v2-dev

This commit is contained in:
Dave Mankoff
2021-11-10 17:49:54 +00:00
committed by Android (Google) Code Review
6 changed files with 47 additions and 226 deletions

View File

@@ -19,6 +19,11 @@ package com.android.systemui.flags
* Plugin for loading flag values * Plugin for loading flag values
*/ */
interface FlagReader { interface FlagReader {
/** Returns a boolean value for the given flag. */
fun isEnabled(flag: BooleanFlag): Boolean {
return flag.default
}
/** Returns a boolean value for the given flag. */ /** Returns a boolean value for the given flag. */
fun isEnabled(id: Int, def: Boolean): Boolean { fun isEnabled(id: Int, def: Boolean): Boolean {
return def return def

View File

@@ -26,13 +26,16 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import androidx.annotation.BoolRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.android.systemui.Dumpable; import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.settings.SecureSettings;
@@ -62,14 +65,19 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
private final FlagManager mFlagManager; private final FlagManager mFlagManager;
private final SecureSettings mSecureSettings; private final SecureSettings mSecureSettings;
private final Resources mResources;
private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>(); private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>();
@Inject @Inject
public FeatureFlagManager(FlagManager flagManager, public FeatureFlagManager(
SecureSettings secureSettings, Context context, FlagManager flagManager,
Context context,
SecureSettings secureSettings,
@Main Resources resources,
DumpManager dumpManager) { DumpManager dumpManager) {
mFlagManager = flagManager; mFlagManager = flagManager;
mSecureSettings = secureSettings; mSecureSettings = secureSettings;
mResources = resources;
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SET_FLAG); filter.addAction(ACTION_SET_FLAG);
filter.addAction(ACTION_GET_FLAGS); filter.addAction(ACTION_GET_FLAGS);
@@ -77,17 +85,32 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
dumpManager.registerDumpable(TAG, this); dumpManager.registerDumpable(TAG, this);
} }
/** Return a {@link BooleanFlag}'s value. */
@Override @Override
public boolean isEnabled(int id, boolean defaultValue) { public boolean isEnabled(BooleanFlag flag) {
int id = flag.getId();
if (!mBooleanFlagCache.containsKey(id)) { if (!mBooleanFlagCache.containsKey(id)) {
Boolean result = isEnabledInternal(id); boolean def = flag.getDefault();
mBooleanFlagCache.put(id, result == null ? defaultValue : result); if (flag.hasResourceOverride()) {
try {
def = isEnabledInOverlay(flag.getResourceOverride());
} catch (Resources.NotFoundException e) {
// no-op
}
}
mBooleanFlagCache.put(id, isEnabled(id, def));
} }
return mBooleanFlagCache.get(id); return mBooleanFlagCache.get(id);
} }
/** Return a {@link BooleanFlag}'s value. */
@Override
public boolean isEnabled(int id, boolean defaultValue) {
Boolean result = isEnabledInternal(id);
return result == null ? defaultValue : result;
}
/** Returns the stored value or null if not set. */ /** Returns the stored value or null if not set. */
private Boolean isEnabledInternal(int id) { private Boolean isEnabledInternal(int id) {
try { try {
@@ -98,6 +121,10 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
return null; return null;
} }
private boolean isEnabledInOverlay(@BoolRes int resId) {
return mResources.getBoolean(resId);
}
/** Set whether a given {@link BooleanFlag} is enabled or not. */ /** Set whether a given {@link BooleanFlag} is enabled or not. */
@Override @Override
public void setEnabled(int id, boolean value) { public void setEnabled(int id, boolean value) {

View File

@@ -16,7 +16,6 @@
package com.android.systemui.flags; package com.android.systemui.flags;
import android.content.Context;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -24,7 +23,6 @@ import androidx.annotation.NonNull;
import com.android.systemui.Dumpable; import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.settings.SecureSettings;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.PrintWriter; import java.io.PrintWriter;
@@ -41,8 +39,7 @@ import javax.inject.Inject;
public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
SparseBooleanArray mAccessedFlags = new SparseBooleanArray(); SparseBooleanArray mAccessedFlags = new SparseBooleanArray();
@Inject @Inject
public FeatureFlagManager( public FeatureFlagManager(DumpManager dumpManager) {
SecureSettings secureSettings, Context context, DumpManager dumpManager) {
dumpManager.registerDumpable("SysUIFlags", this); dumpManager.registerDumpable("SysUIFlags", this);
} }
@@ -52,6 +49,11 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
@Override @Override
public void removeListener(Listener run) {} public void removeListener(Listener run) {}
@Override
public boolean isEnabled(BooleanFlag flag) {
return isEnabled(flag.getId(), flag.getDefault());
}
@Override @Override
public boolean isEnabled(int key, boolean defaultValue) { public boolean isEnabled(int key, boolean defaultValue) {
mAccessedFlags.append(key, defaultValue); mAccessedFlags.append(key, defaultValue);

View File

@@ -17,22 +17,11 @@
package com.android.systemui.flags; package com.android.systemui.flags;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.util.FeatureFlagUtils; import android.util.FeatureFlagUtils;
import android.util.Log; import android.util.Log;
import android.util.SparseArray;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.BoolRes;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
@@ -43,31 +32,13 @@ import javax.inject.Inject;
*/ */
@SysUISingleton @SysUISingleton
public class FeatureFlags { public class FeatureFlags {
private final Resources mResources;
private final FlagReader mFlagReader; private final FlagReader mFlagReader;
private final Context mContext; private final Context mContext;
private final Map<Integer, Flag<?>> mFlagMap = new HashMap<>();
private final Map<Integer, List<Listener>> mListeners = new HashMap<>();
private final SparseArray<Boolean> mCachedFlags = new SparseArray<>();
@Inject @Inject
public FeatureFlags(@Main Resources resources, FlagReader flagReader, Context context) { public FeatureFlags(FlagReader flagReader, Context context) {
mResources = resources;
mFlagReader = flagReader; mFlagReader = flagReader;
mContext = context; mContext = context;
flagReader.addListener(mListener);
}
private final FlagReader.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);
} }
/** /**
@@ -75,32 +46,7 @@ public class FeatureFlags {
* @return The value of the flag. * @return The value of the flag.
*/ */
public boolean isEnabled(BooleanFlag flag) { public boolean isEnabled(BooleanFlag flag) {
boolean def = flag.getDefault(); return mFlagReader.isEnabled(flag);
if (flag.hasResourceOverride()) {
try {
def = isEnabledInOverlay(flag.getResourceOverride());
} catch (Resources.NotFoundException e) {
// no-op
}
}
return mFlagReader.isEnabled(flag.getId(), def);
}
/**
* @param flag The {@link IntFlag} of interest.
/** 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 void assertLegacyPipelineEnabled() { public void assertLegacyPipelineEnabled() {
@@ -205,20 +151,4 @@ public class FeatureFlags {
public static boolean isProviderModelSettingEnabled(Context context) { public static boolean isProviderModelSettingEnabled(Context context) {
return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL); return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
} }
private boolean isEnabledInOverlay(@BoolRes int resId) {
synchronized (mCachedFlags) {
if (!mCachedFlags.contains(resId)) {
mCachedFlags.put(resId, mResources.getBoolean(resId));
}
return mCachedFlags.get(resId);
}
}
/** Simple interface for beinga alerted when a specific flag changes value. */
public interface Listener {
/** */
void onFlagChanged(Flag<?> flag);
}
} }

View File

@@ -53,8 +53,6 @@ import java.io.StringWriter;
public class FeatureFlagManagerTest extends SysuiTestCase { public class FeatureFlagManagerTest extends SysuiTestCase {
FeatureFlagManager mFeatureFlagManager; FeatureFlagManager mFeatureFlagManager;
@Mock private FlagManager mFlagManager;
@Mock private SecureSettings mSecureSettings;
@Mock private Context mContext; @Mock private Context mContext;
@Mock private DumpManager mDumpManager; @Mock private DumpManager mDumpManager;
@@ -62,14 +60,11 @@ public class FeatureFlagManagerTest extends SysuiTestCase {
public void setup() { public void setup() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mFeatureFlagManager = new FeatureFlagManager(mSecureSettings, mContext, mDumpManager); mFeatureFlagManager = new FeatureFlagManager(mDumpManager);
} }
@After @After
public void onFinished() { public void onFinished() {
// SecureSettings and Context are provided for constructor consistency with the
// debug version of the FeatureFlagManager, but should never be used.
verifyZeroInteractions(mSecureSettings, mContext);
// The dump manager should be registered with even for the release version, but that's it. // The dump manager should be registered with even for the release version, but that's it.
verify(mDumpManager).registerDumpable(anyString(), any()); verify(mDumpManager).registerDumpable(anyString(), any());
verifyNoMoreInteractions(mDumpManager); verifyNoMoreInteractions(mDumpManager);

View File

@@ -1,138 +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.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
@SmallTest
public class FeatureFlagsTest extends SysuiTestCase {
@Mock Resources mResources;
@Mock FlagReader mFeatureFlagReader;
private FeatureFlags mFeatureFlags;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(mFeatureFlagReader.isEnabled(anyInt(), anyBoolean())).thenAnswer(
(Answer<Boolean>) invocation -> invocation.getArgument(1));
mFeatureFlags = new FeatureFlags(mResources, mFeatureFlagReader, getContext());
}
@Test
public void testAddListener() {
Flag<?> flag = new BooleanFlag(1);
mFeatureFlags.addFlag(flag);
// Assert and capture that a plugin listener was added.
ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor =
ArgumentCaptor.forClass(FlagReader.Listener.class);
verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
FlagReader.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<FlagReader.Listener> pluginListenerCaptor =
ArgumentCaptor.forClass(FlagReader.Listener.class);
verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
FlagReader.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();
}
@Test
public void testBooleanDefault() {
BooleanFlag flag = new BooleanFlag(1, true);
mFeatureFlags.addFlag(flag);
assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
}
@Test
public void testBooleanResourceOverlay() {
int resourceId = 12;
BooleanFlag flag = new BooleanFlag(1, false, resourceId);
when(mResources.getBoolean(resourceId)).thenReturn(true);
when(mResources.getResourceEntryName(resourceId)).thenReturn("flag");
mFeatureFlags.addFlag(flag);
assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
}
}