diff --git a/core/java/Android.bp b/core/java/Android.bp index 236d0b5814d74..f8f4cc3523f25 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -33,15 +33,6 @@ filegroup { srcs: ["com/android/internal/os/IBinaryTransparencyService.aidl"], } -filegroup { - name: "feature_flags_aidl", - srcs: [ - "android/flags/IFeatureFlags.aidl", - "android/flags/IFeatureFlagsCallback.aidl", - "android/flags/SyncableFlag.aidl", - ], -} - filegroup { name: "ITracingServiceProxy.aidl", srcs: ["android/tracing/ITracingServiceProxy.aidl"], diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2a6d84b1acc68..6d82922484bc8 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5297,13 +5297,6 @@ public abstract class Context { @SystemApi public static final String APP_PREDICTION_SERVICE = "app_prediction"; - /** - * Used for reading system-wide, overridable flags. - * - * @hide - */ - public static final String FEATURE_FLAGS_SERVICE = "feature_flags"; - /** * Official published name of the search ui service. * diff --git a/core/java/android/flags/BooleanFlag.java b/core/java/android/flags/BooleanFlag.java deleted file mode 100644 index d4a35b25f6236..0000000000000 --- a/core/java/android/flags/BooleanFlag.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -import android.annotation.NonNull; - -/** - * A flag representing a true or false value. - * - * The value will always be the same during the lifetime of the process it is read in. - * - * @hide - */ -public class BooleanFlag extends BooleanFlagBase { - private final boolean mDefault; - - /** - * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}. - * @param name A name for this flag. - * @param defaultValue The value of this flag if no other override is present. - */ - BooleanFlag(String namespace, String name, boolean defaultValue) { - super(namespace, name); - mDefault = defaultValue; - } - - @Override - @NonNull - public Boolean getDefault() { - return mDefault; - } - - @Override - public BooleanFlag defineMetaData(String label, String description, String categoryName) { - super.defineMetaData(label, description, categoryName); - return this; - } -} diff --git a/core/java/android/flags/BooleanFlagBase.java b/core/java/android/flags/BooleanFlagBase.java deleted file mode 100644 index 985dbe3f2f01e..0000000000000 --- a/core/java/android/flags/BooleanFlagBase.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -import android.annotation.NonNull; - -abstract class BooleanFlagBase implements Flag { - - private final String mNamespace; - private final String mName; - private String mLabel; - private String mDescription; - private String mCategoryName; - - /** - * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}. - * @param name A name for this flag. - */ - BooleanFlagBase(String namespace, String name) { - mNamespace = namespace; - mName = name; - mLabel = name; - } - - public abstract Boolean getDefault(); - - @Override - @NonNull - public String getNamespace() { - return mNamespace; - } - - @Override - @NonNull - public String getName() { - return mName; - } - - @Override - public BooleanFlagBase defineMetaData(String label, String description, String categoryName) { - mLabel = label; - mDescription = description; - mCategoryName = categoryName; - return this; - } - - @Override - @NonNull - public String getLabel() { - return mLabel; - } - - @Override - public String getDescription() { - return mDescription; - } - - @Override - public String getCategoryName() { - return mCategoryName; - } - - @Override - @NonNull - public String toString() { - return getNamespace() + "." + getName() + "[" + getDefault() + "]"; - } -} diff --git a/core/java/android/flags/DynamicBooleanFlag.java b/core/java/android/flags/DynamicBooleanFlag.java deleted file mode 100644 index 271a8c5f4d15f..0000000000000 --- a/core/java/android/flags/DynamicBooleanFlag.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -/** - * A flag representing a true or false value. - * - * The value may be different from one read to the next. - * - * @hide - */ -public class DynamicBooleanFlag extends BooleanFlagBase implements DynamicFlag { - - private final boolean mDefault; - - /** - * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}. - * @param name A name for this flag. - * @param defaultValue The value of this flag if no other override is present. - */ - DynamicBooleanFlag(String namespace, String name, boolean defaultValue) { - super(namespace, name); - mDefault = defaultValue; - } - - @Override - public Boolean getDefault() { - return mDefault; - } - - @Override - public DynamicBooleanFlag defineMetaData(String label, String description, String categoryName) { - super.defineMetaData(label, description, categoryName); - return this; - } -} diff --git a/core/java/android/flags/DynamicFlag.java b/core/java/android/flags/DynamicFlag.java deleted file mode 100644 index 68819c58c064d..0000000000000 --- a/core/java/android/flags/DynamicFlag.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -/** - * A flag for which the value may be different from one read to the next. - * - * @param The type of value that this flag stores. E.g. Boolean or String. - * - * @hide - */ -public interface DynamicFlag extends Flag { - @Override - default boolean isDynamic() { - return true; - } -} diff --git a/core/java/android/flags/FeatureFlags.java b/core/java/android/flags/FeatureFlags.java deleted file mode 100644 index 8d3112c35d512..0000000000000 --- a/core/java/android/flags/FeatureFlags.java +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -import android.annotation.NonNull; -import android.content.Context; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.util.ArraySet; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * A class for querying constants from the system - primarily booleans. - * - * Clients using this class can define their flags and their default values in one place, - * can override those values on running devices for debugging and testing purposes, and can control - * what flags are available to be used on release builds. - * - * TODO(b/279054964): A lot. This is skeleton code right now. - * @hide - */ -public class FeatureFlags { - private static final String TAG = "FeatureFlags"; - private static FeatureFlags sInstance; - private static final Object sInstanceLock = new Object(); - - private final Set> mKnownFlags = new ArraySet<>(); - private final Set> mDirtyFlags = new ArraySet<>(); - - private IFeatureFlags mIFeatureFlags; - private final Map> mBooleanOverrides = new HashMap<>(); - private final Set mListeners = new HashSet<>(); - - /** - * Obtain a per-process instance of FeatureFlags. - * @return A singleton instance of {@link FeatureFlags}. - */ - @NonNull - public static FeatureFlags getInstance() { - synchronized (sInstanceLock) { - if (sInstance == null) { - sInstance = new FeatureFlags(); - } - } - - return sInstance; - } - - /** See {@link FeatureFlagsFake}. */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public static void setInstance(FeatureFlags instance) { - synchronized (sInstanceLock) { - sInstance = instance; - } - } - - private final IFeatureFlagsCallback mIFeatureFlagsCallback = new IFeatureFlagsCallback.Stub() { - @Override - public void onFlagChange(SyncableFlag flag) { - for (Flag f : mKnownFlags) { - if (flagEqualsSyncableFlag(f, flag)) { - if (f instanceof DynamicFlag) { - if (f instanceof DynamicBooleanFlag) { - String value = flag.getValue(); - if (value == null) { // Null means any existing overrides were erased. - value = ((DynamicBooleanFlag) f).getDefault().toString(); - } - addBooleanOverride(flag.getNamespace(), flag.getName(), value); - } - FeatureFlags.this.onFlagChange((DynamicFlag) f); - } - break; - } - } - } - }; - - private FeatureFlags() { - this(null); - } - - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public FeatureFlags(IFeatureFlags iFeatureFlags) { - mIFeatureFlags = iFeatureFlags; - - if (mIFeatureFlags != null) { - try { - mIFeatureFlags.registerCallback(mIFeatureFlagsCallback); - } catch (RemoteException e) { - // Shouldn't happen with things passed into tests. - Log.e(TAG, "Could not register callbacks!", e); - } - } - } - - /** - * Construct a new {@link BooleanFlag}. - * - * Use this instead of constructing a {@link BooleanFlag} directly, as it registers the flag - * with the internals of the flagging system. - */ - @NonNull - public static BooleanFlag booleanFlag( - @NonNull String namespace, @NonNull String name, boolean def) { - return getInstance().addFlag(new BooleanFlag(namespace, name, def)); - } - - /** - * Construct a new {@link FusedOffFlag}. - * - * Use this instead of constructing a {@link FusedOffFlag} directly, as it registers the - * flag with the internals of the flagging system. - */ - @NonNull - public static FusedOffFlag fusedOffFlag(@NonNull String namespace, @NonNull String name) { - return getInstance().addFlag(new FusedOffFlag(namespace, name)); - } - - /** - * Construct a new {@link FusedOnFlag}. - * - * Use this instead of constructing a {@link FusedOnFlag} directly, as it registers the flag - * with the internals of the flagging system. - */ - @NonNull - public static FusedOnFlag fusedOnFlag(@NonNull String namespace, @NonNull String name) { - return getInstance().addFlag(new FusedOnFlag(namespace, name)); - } - - /** - * Construct a new {@link DynamicBooleanFlag}. - * - * Use this instead of constructing a {@link DynamicBooleanFlag} directly, as it registers - * the flag with the internals of the flagging system. - */ - @NonNull - public static DynamicBooleanFlag dynamicBooleanFlag( - @NonNull String namespace, @NonNull String name, boolean def) { - return getInstance().addFlag(new DynamicBooleanFlag(namespace, name, def)); - } - - /** - * Add a listener to be alerted when a {@link DynamicFlag} changes. - * - * See also {@link #removeChangeListener(ChangeListener)}. - * - * @param listener The listener to add. - */ - public void addChangeListener(@NonNull ChangeListener listener) { - mListeners.add(listener); - } - - /** - * Remove a listener that was added earlier. - * - * See also {@link #addChangeListener(ChangeListener)}. - * - * @param listener The listener to remove. - */ - public void removeChangeListener(@NonNull ChangeListener listener) { - mListeners.remove(listener); - } - - protected void onFlagChange(@NonNull DynamicFlag flag) { - for (ChangeListener l : mListeners) { - l.onFlagChanged(flag); - } - } - - /** - * Returns whether the supplied flag is true or not. - * - * {@link BooleanFlag} should only be used in debug builds. They do not get optimized out. - * - * The first time a flag is read, its value is cached for the lifetime of the process. - */ - public boolean isEnabled(@NonNull BooleanFlag flag) { - return getBooleanInternal(flag); - } - - /** - * Returns whether the supplied flag is true or not. - * - * Always returns false. - */ - public boolean isEnabled(@NonNull FusedOffFlag flag) { - return false; - } - - /** - * Returns whether the supplied flag is true or not. - * - * Always returns true; - */ - public boolean isEnabled(@NonNull FusedOnFlag flag) { - return true; - } - - /** - * Returns whether the supplied flag is true or not. - * - * Can return a different value for the flag each time it is called if an override comes in. - */ - public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) { - return getBooleanInternal(flag); - } - - private boolean getBooleanInternal(Flag flag) { - sync(); - Map ns = mBooleanOverrides.get(flag.getNamespace()); - Boolean value = null; - if (ns != null) { - value = ns.get(flag.getName()); - } - if (value == null) { - throw new IllegalStateException("Boolean flag being read but was not synced: " + flag); - } - - return value; - } - - private > T addFlag(T flag) { - synchronized (FeatureFlags.class) { - mDirtyFlags.add(flag); - mKnownFlags.add(flag); - } - return flag; - } - - /** - * Sync any known flags that have not yet been synced. - * - * This is called implicitly when any flag is read, and is not generally needed except in - * exceptional circumstances. - */ - public void sync() { - synchronized (FeatureFlags.class) { - if (mDirtyFlags.isEmpty()) { - return; - } - syncInternal(mDirtyFlags); - mDirtyFlags.clear(); - } - } - - /** - * Called when new flags have been declared. Gives the implementation a chance to act on them. - * - * Guaranteed to be called from a synchronized, thread-safe context. - */ - protected void syncInternal(Set> dirtyFlags) { - IFeatureFlags iFeatureFlags = bind(); - List syncableFlags = new ArrayList<>(); - for (Flag f : dirtyFlags) { - syncableFlags.add(flagToSyncableFlag(f)); - } - - List serverFlags = List.of(); // Need to initialize the list with something. - try { - // New values come back from the service. - serverFlags = iFeatureFlags.syncFlags(syncableFlags); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - - for (Flag f : dirtyFlags) { - boolean found = false; - for (SyncableFlag sf : serverFlags) { - if (flagEqualsSyncableFlag(f, sf)) { - if (f instanceof BooleanFlag || f instanceof DynamicBooleanFlag) { - addBooleanOverride(sf.getNamespace(), sf.getName(), sf.getValue()); - } - found = true; - break; - } - } - if (!found) { - if (f instanceof BooleanFlag) { - addBooleanOverride( - f.getNamespace(), - f.getName(), - ((BooleanFlag) f).getDefault() ? "true" : "false"); - } - } - } - } - - private void addBooleanOverride(String namespace, String name, String override) { - Map nsOverrides = mBooleanOverrides.get(namespace); - if (nsOverrides == null) { - nsOverrides = new HashMap<>(); - mBooleanOverrides.put(namespace, nsOverrides); - } - nsOverrides.put(name, parseBoolean(override)); - } - - private SyncableFlag flagToSyncableFlag(Flag f) { - return new SyncableFlag( - f.getNamespace(), - f.getName(), - f.getDefault().toString(), - f instanceof DynamicFlag); - } - - private IFeatureFlags bind() { - if (mIFeatureFlags == null) { - mIFeatureFlags = IFeatureFlags.Stub.asInterface( - ServiceManager.getService(Context.FEATURE_FLAGS_SERVICE)); - try { - mIFeatureFlags.registerCallback(mIFeatureFlagsCallback); - } catch (RemoteException e) { - Log.e(TAG, "Failed to listen for flag changes!"); - } - } - - return mIFeatureFlags; - } - - static boolean parseBoolean(String value) { - // Check for a truish string. - boolean result = value.equalsIgnoreCase("true") - || value.equals("1") - || value.equalsIgnoreCase("t") - || value.equalsIgnoreCase("on"); - if (!result) { // Expect a falsish string, else log an error. - if (!(value.equalsIgnoreCase("false") - || value.equals("0") - || value.equalsIgnoreCase("f") - || value.equalsIgnoreCase("off"))) { - Log.e(TAG, - "Tried parsing " + value + " as boolean but it doesn't look like one. " - + "Value expected to be one of true|false, 1|0, t|f, on|off."); - } - } - return result; - } - - private static boolean flagEqualsSyncableFlag(Flag f, SyncableFlag sf) { - return f.getName().equals(sf.getName()) && f.getNamespace().equals(sf.getNamespace()); - } - - - /** - * A simpler listener that is alerted when a {@link DynamicFlag} changes. - * - * See {@link #addChangeListener(ChangeListener)} - */ - public interface ChangeListener { - /** - * Called when a {@link DynamicFlag} changes. - * - * @param flag The flag that has changed. - */ - void onFlagChanged(DynamicFlag flag); - } -} diff --git a/core/java/android/flags/FeatureFlagsFake.java b/core/java/android/flags/FeatureFlagsFake.java deleted file mode 100644 index daedcdae0b5f5..0000000000000 --- a/core/java/android/flags/FeatureFlagsFake.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -import android.annotation.NonNull; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * An implementation of {@link FeatureFlags} for testing. - * - * Before you read a flag from using this Fake, you must set that flag using - * {@link #setFlagValue(BooleanFlagBase, boolean)}. This ensures that your tests are deterministic. - * - * If you are relying on {@link FeatureFlags#getInstance()} to access FeatureFlags in your code - * under test, (instead of dependency injection), you can pass an instance of this fake to - * {@link FeatureFlags#setInstance(FeatureFlags)}. Be sure to call that method again, passing null, - * to ensure hermetic testing - you don't want static state persisting between your test methods. - * - * @hide - */ -public class FeatureFlagsFake extends FeatureFlags { - private final Map mFlagValues = new HashMap<>(); - private final Set mReadFlags = new HashSet<>(); - - public FeatureFlagsFake(IFeatureFlags iFeatureFlags) { - super(iFeatureFlags); - } - - @Override - public boolean isEnabled(@NonNull BooleanFlag flag) { - return requireFlag(flag); - } - - @Override - public boolean isEnabled(@NonNull FusedOffFlag flag) { - return requireFlag(flag); - } - - @Override - public boolean isEnabled(@NonNull FusedOnFlag flag) { - return requireFlag(flag); - } - - @Override - public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) { - return requireFlag(flag); - } - - @Override - protected void syncInternal(Set> dirtyFlags) { - } - - /** - * Explicitly set a flag's value for reading in tests. - * - * You _must_ call this for every flag your code-under-test will read. Otherwise, an - * {@link IllegalStateException} will be thrown. - * - * You are able to set values for {@link FusedOffFlag} and {@link FusedOnFlag}, despite those - * flags having a fixed value at compile time, since unit tests should still test the state of - * those flags as both true and false. I.e. a flag that is off might be turned on in a future - * build or vice versa. - * - * You can not call this method _after_ a non-dynamic flag has been read. Non-dynamic flags - * are held stable in the system, so changing a value after reading would not match - * real-implementation behavior. - * - * Calling this method will trigger any {@link android.flags.FeatureFlags.ChangeListener}s that - * are registered for the supplied flag if the flag is a {@link DynamicFlag}. - * - * @param flag The BooleanFlag that you want to set a value for. - * @param value The value that the flag should return when accessed. - */ - public void setFlagValue(@NonNull BooleanFlagBase flag, boolean value) { - if (!(flag instanceof DynamicBooleanFlag) && mReadFlags.contains(flag)) { - throw new RuntimeException( - "You can not set the value of a flag after it has been read. Tried to set " - + flag + " to " + value + " but it already " + mFlagValues.get(flag)); - } - mFlagValues.put(flag, value); - if (flag instanceof DynamicBooleanFlag) { - onFlagChange((DynamicFlag) flag); - } - } - - private boolean requireFlag(BooleanFlagBase flag) { - if (!mFlagValues.containsKey(flag)) { - throw new IllegalStateException( - "Tried to access " + flag + " in test but no overrided specified. You must " - + "call #setFlagValue for each flag read in a test."); - } - mReadFlags.add(flag); - - return mFlagValues.get(flag); - } - -} diff --git a/core/java/android/flags/Flag.java b/core/java/android/flags/Flag.java deleted file mode 100644 index b97a4c8a0fe76..0000000000000 --- a/core/java/android/flags/Flag.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -import android.annotation.NonNull; - -/** - * Base class for constants read via {@link android.flags.FeatureFlags}. - * - * @param The type of value that this flag stores. E.g. Boolean or String. - * - * @hide - */ -public interface Flag { - /** The namespace for a flag. Should combine uniquely with its name. */ - @NonNull - String getNamespace(); - - /** The name of the flag. Should combine uniquely with its namespace. */ - @NonNull - String getName(); - - /** The value of this flag if no override has been set. Null values are not supported. */ - @NonNull - T getDefault(); - - /** Returns true if the value of this flag can change at runtime. */ - default boolean isDynamic() { - return false; - } - - /** - * Add human-readable details to the flag. Flag client's are not required to set this. - * - * See {@link #getLabel()}, {@link #getDescription()}, and {@link #getCategoryName()}. - * - * @return Returns `this`, to make a fluent api. - */ - Flag defineMetaData(String label, String description, String categoryName); - - /** - * A human-readable name for the flag. Defaults to {@link #getName()} - * - * See {@link #defineMetaData(String, String, String)} - */ - @NonNull - default String getLabel() { - return getName(); - } - - /** - * A human-readable description for the flag. Defaults to null if unset. - * - * See {@link #defineMetaData(String, String, String)} - */ - default String getDescription() { - return null; - } - - /** - * A human-readable category name for the flag. Defaults to null if unset. - * - * See {@link #defineMetaData(String, String, String)} - */ - default String getCategoryName() { - return null; - } -} diff --git a/core/java/android/flags/FusedOffFlag.java b/core/java/android/flags/FusedOffFlag.java deleted file mode 100644 index 6844b8faafef0..0000000000000 --- a/core/java/android/flags/FusedOffFlag.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -import android.annotation.NonNull; -import android.provider.DeviceConfig; - -/** - * A flag representing a false value. - * - * The flag can never be changed or overridden. It is false at compile time. - * - * @hide - */ -public final class FusedOffFlag extends BooleanFlagBase { - /** - * @param namespace A namespace for this flag. See {@link DeviceConfig}. - * @param name A name for this flag. - */ - FusedOffFlag(String namespace, String name) { - super(namespace, name); - } - - @Override - @NonNull - public Boolean getDefault() { - return false; - } - - @Override - public FusedOffFlag defineMetaData(String label, String description, String categoryName) { - super.defineMetaData(label, description, categoryName); - return this; - } -} diff --git a/core/java/android/flags/FusedOnFlag.java b/core/java/android/flags/FusedOnFlag.java deleted file mode 100644 index e9adba7595c11..0000000000000 --- a/core/java/android/flags/FusedOnFlag.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -import android.annotation.NonNull; -import android.provider.DeviceConfig; - -/** - * A flag representing a true value. - * - * The flag can never be changed or overridden. It is true at compile time. - * - * @hide - */ -public final class FusedOnFlag extends BooleanFlagBase { - /** - * @param namespace A namespace for this flag. See {@link DeviceConfig}. - * @param name A name for this flag. - */ - FusedOnFlag(String namespace, String name) { - super(namespace, name); - } - - @Override - @NonNull - public Boolean getDefault() { - return true; - } - - @Override - public FusedOnFlag defineMetaData(String label, String description, String categoryName) { - super.defineMetaData(label, description, categoryName); - return this; - } -} diff --git a/core/java/android/flags/IFeatureFlags.aidl b/core/java/android/flags/IFeatureFlags.aidl deleted file mode 100644 index 3efcec97fe6d5..0000000000000 --- a/core/java/android/flags/IFeatureFlags.aidl +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -import android.flags.IFeatureFlagsCallback; -import android.flags.SyncableFlag; - -/** - * Binder interface for communicating with {@link com.android.server.flags.FeatureFlagsService}. - * - * This interface is used by {@link android.flags.FeatureFlags} and developers should use that to - * interface with the service. FeatureFlags is the "client" in this documentation. - * - * The methods allow client apps to communicate what flags they care about, and receive back - * current values for those flags. For stable flags, this is the finalized value until the device - * restarts. For {@link DynamicFlag}s, this is the last known value, though it may change in the - * future. Clients can listen for changes to flag values so that it can react accordingly. - * @hide - */ -interface IFeatureFlags { - /** - * Synchronize with the {@link com.android.server.flags.FeatureFlagsService} about flags of - * interest. - * - * The client should pass in a list of flags that it is using as {@link SyncableFlag}s, which - * includes what it thinks the default values of the flags are. - * - * The response will contain a list of matching SyncableFlags, whose values are set to what the - * value of the flags actually are. The client should update its internal state flag data to - * match. - * - * Generally speaking, if a flag that is passed in is new to the FeatureFlagsService, the - * service will cache the passed-in value, and return it back out. If, however, a different - * client has synced that flag with the service previously, FeatureFlagsService will return the - * existing cached value, which may or may not be what the current client passed in. This allows - * FeatureFlagsService to keep clients in agreement with one another. - */ - List syncFlags(in List flagList); - - /** - * Pass in an {@link IFeatureFlagsCallback} that will be called whenever a {@link DymamicFlag} - * changes. - */ - void registerCallback(IFeatureFlagsCallback callback); - - /** - * Remove a {@link IFeatureFlagsCallback} that was previously registered with - * {@link #registerCallback}. - */ - void unregisterCallback(IFeatureFlagsCallback callback); - - /** - * Query the {@link com.android.server.flags.FeatureFlagsService} for flags, but don't - * cache them. See {@link #syncFlags}. - * - * You almost certainly don't want this method. This is intended for the Flag Flipper - * application that needs to query the state of system but doesn't want to affect it by - * doing so. All other clients should use {@link syncFlags}. - */ - List queryFlags(in List flagList); - - /** - * Change a flags value in the system. - * - * This is intended for use by the Flag Flipper application. - */ - void overrideFlag(in SyncableFlag flag); - - /** - * Restore a flag to its default value. - * - * This is intended for use by the Flag Flipper application. - */ - void resetFlag(in SyncableFlag flag); -} \ No newline at end of file diff --git a/core/java/android/flags/IFeatureFlagsCallback.aidl b/core/java/android/flags/IFeatureFlagsCallback.aidl deleted file mode 100644 index f708667ea4c42..0000000000000 --- a/core/java/android/flags/IFeatureFlagsCallback.aidl +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -import android.flags.SyncableFlag; - -/** - * Callback for {@link IFeatureFlags#registerCallback} to get alerts when a {@link DynamicFlag} - * changes. - * - * DynamicFlags can change at run time. Stable flags will never result in a call to this method. - * - * @hide - */ -oneway interface IFeatureFlagsCallback { - void onFlagChange(in SyncableFlag flag); -} \ No newline at end of file diff --git a/core/java/android/flags/OWNERS b/core/java/android/flags/OWNERS deleted file mode 100644 index fa125c4a159cc..0000000000000 --- a/core/java/android/flags/OWNERS +++ /dev/null @@ -1,7 +0,0 @@ -# Bug component: 1306523 - -mankoff@google.com -pixel@google.com - -dsandler@android.com - diff --git a/core/java/android/flags/SyncableFlag.aidl b/core/java/android/flags/SyncableFlag.aidl deleted file mode 100644 index 1526ec148967d..0000000000000 --- a/core/java/android/flags/SyncableFlag.aidl +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -/** - * A parcelable data class for serializing {@link Flag} across a Binder. - */ -parcelable SyncableFlag; \ No newline at end of file diff --git a/core/java/android/flags/SyncableFlag.java b/core/java/android/flags/SyncableFlag.java deleted file mode 100644 index 449bcc3c49f53..0000000000000 --- a/core/java/android/flags/SyncableFlag.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -import android.annotation.NonNull; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * @hide - */ -public final class SyncableFlag implements Parcelable { - private final String mNamespace; - private final String mName; - private final String mValue; - private final boolean mDynamic; - private final boolean mOverridden; - - public SyncableFlag( - @NonNull String namespace, - @NonNull String name, - @NonNull String value, - boolean dynamic) { - this(namespace, name, value, dynamic, false); - } - - public SyncableFlag( - @NonNull String namespace, - @NonNull String name, - @NonNull String value, - boolean dynamic, - boolean overridden - ) { - mNamespace = namespace; - mName = name; - mValue = value; - mDynamic = dynamic; - mOverridden = overridden; - } - - @NonNull - public String getNamespace() { - return mNamespace; - } - - @NonNull - public String getName() { - return mName; - } - - @NonNull - public String getValue() { - return mValue; - } - - public boolean isDynamic() { - return mDynamic; - } - - public boolean isOverridden() { - return mOverridden; - } - - @NonNull - public static final Parcelable.Creator CREATOR = new Parcelable.Creator<>() { - public SyncableFlag createFromParcel(Parcel in) { - return new SyncableFlag( - in.readString(), - in.readString(), - in.readString(), - in.readBoolean(), - in.readBoolean()); - } - - public SyncableFlag[] newArray(int size) { - return new SyncableFlag[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeString(mNamespace); - dest.writeString(mName); - dest.writeString(mValue); - dest.writeBoolean(mDynamic); - dest.writeBoolean(mOverridden); - } - - @Override - public String toString() { - return getNamespace() + "." + getName() + "[" + getValue() + "]"; - } -} diff --git a/core/java/com/android/internal/flags/CoreFlags.java b/core/java/com/android/internal/flags/CoreFlags.java deleted file mode 100644 index f177ef88c38f7..0000000000000 --- a/core/java/com/android/internal/flags/CoreFlags.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2023 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.internal.flags; - -import android.flags.BooleanFlag; -import android.flags.DynamicBooleanFlag; -import android.flags.FeatureFlags; -import android.flags.FusedOffFlag; -import android.flags.FusedOnFlag; -import android.flags.SyncableFlag; - -import java.util.ArrayList; -import java.util.List; - -/** - * Flags defined here are can be read by code in core. - * - * Flags not defined here will throw a security exception if third-party processes attempts to read - * them. - * - * DO NOT define a flag here unless you explicitly intend for that flag to be readable by code that - * runs inside a third party process. - */ -public abstract class CoreFlags { - private static final List sKnownFlags = new ArrayList<>(); - - public static BooleanFlag BOOL_FLAG = booleanFlag("core", "bool_flag", false); - public static FusedOffFlag OFF_FLAG = fusedOffFlag("core", "off_flag"); - public static FusedOnFlag ON_FLAG = fusedOnFlag("core", "on_flag"); - public static DynamicBooleanFlag DYN_FLAG = dynamicBooleanFlag("core", "dyn_flag", true); - - /** Returns true if the passed in flag matches a flag in this class. */ - public static boolean isCoreFlag(SyncableFlag flag) { - for (SyncableFlag knownFlag : sKnownFlags) { - if (knownFlag.getName().equals(flag.getName()) - && knownFlag.getNamespace().equals(flag.getNamespace())) { - return true; - } - } - return false; - } - - public static List getCoreFlags() { - return sKnownFlags; - } - - private static BooleanFlag booleanFlag(String namespace, String name, boolean defaultValue) { - BooleanFlag f = FeatureFlags.booleanFlag(namespace, name, defaultValue); - - sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), false)); - - return f; - } - - private static FusedOffFlag fusedOffFlag(String namespace, String name) { - FusedOffFlag f = FeatureFlags.fusedOffFlag(namespace, name); - - sKnownFlags.add(new SyncableFlag(namespace, name, "false", false)); - - return f; - } - - private static FusedOnFlag fusedOnFlag(String namespace, String name) { - FusedOnFlag f = FeatureFlags.fusedOnFlag(namespace, name); - - sKnownFlags.add(new SyncableFlag(namespace, name, "true", false)); - - return f; - } - - private static DynamicBooleanFlag dynamicBooleanFlag( - String namespace, String name, boolean defaultValue) { - DynamicBooleanFlag f = FeatureFlags.dynamicBooleanFlag(namespace, name, defaultValue); - - sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), true)); - - return f; - } -} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 10cf353bf5e9b..a01c7b6355c14 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7655,24 +7655,6 @@ - - - - - - - diff --git a/core/tests/coretests/src/android/flags/FeatureFlagsTest.java b/core/tests/coretests/src/android/flags/FeatureFlagsTest.java deleted file mode 100644 index 3fc94394d12c2..0000000000000 --- a/core/tests/coretests/src/android/flags/FeatureFlagsTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2020 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 android.flags; - -import static com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; - -import org.junit.Before; -import org.junit.Test; - -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - - -@SmallTest -@Presubmit -public class FeatureFlagsTest { - - IFeatureFlagsFake mIFeatureFlagsFake = new IFeatureFlagsFake(); - FeatureFlags mFeatureFlags = new FeatureFlags(mIFeatureFlagsFake); - - @Before - public void setup() { - FeatureFlags.setInstance(mFeatureFlags); - } - - @Test - public void testFusedOff_Disabled() { - FusedOffFlag flag = FeatureFlags.fusedOffFlag("test", "a"); - assertThat(mFeatureFlags.isEnabled(flag)).isFalse(); - } - - @Test - public void testFusedOn_Enabled() { - FusedOnFlag flag = FeatureFlags.fusedOnFlag("test", "a"); - assertThat(mFeatureFlags.isEnabled(flag)).isTrue(); - } - - @Test - public void testBooleanFlag_DefaultDisabled() { - BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", false); - assertThat(mFeatureFlags.isEnabled(flag)).isFalse(); - } - - @Test - public void testBooleanFlag_DefaultEnabled() { - BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", true); - assertThat(mFeatureFlags.isEnabled(flag)).isTrue(); - } - - @Test - public void testDynamicBooleanFlag_DefaultDisabled() { - DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false); - assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isFalse(); - } - - @Test - public void testDynamicBooleanFlag_DefaultEnabled() { - DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", true); - assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue(); - } - - @Test - public void testBooleanFlag_OverrideBeforeRead() { - BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", false); - SyncableFlag syncableFlag = new SyncableFlag( - flag.getNamespace(), flag.getName(), "true", false); - - mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag)); - - assertThat(mFeatureFlags.isEnabled(flag)).isTrue(); - } - - @Test - public void testFusedOffFlag_OverrideHasNoEffect() { - FusedOffFlag flag = FeatureFlags.fusedOffFlag("test", "a"); - SyncableFlag syncableFlag = new SyncableFlag( - flag.getNamespace(), flag.getName(), "true", false); - - mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag)); - - assertThat(mFeatureFlags.isEnabled(flag)).isFalse(); - } - - @Test - public void testFusedOnFlag_OverrideHasNoEffect() { - FusedOnFlag flag = FeatureFlags.fusedOnFlag("test", "a"); - SyncableFlag syncableFlag = new SyncableFlag( - flag.getNamespace(), flag.getName(), "false", false); - - mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag)); - - assertThat(mFeatureFlags.isEnabled(flag)).isTrue(); - } - - @Test - public void testDynamicFlag_OverrideBeforeRead() { - DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false); - SyncableFlag syncableFlag = new SyncableFlag( - flag.getNamespace(), flag.getName(), "true", true); - - mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag)); - - // Changes to true - assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue(); - } - - @Test - public void testDynamicFlag_OverrideAfterRead() { - DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false); - SyncableFlag syncableFlag = new SyncableFlag( - flag.getNamespace(), flag.getName(), "true", true); - - // Starts false - assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isFalse(); - - mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag)); - - // Changes to true - assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue(); - } - - @Test - public void testDynamicFlag_FiresListener() { - DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false); - AtomicBoolean called = new AtomicBoolean(false); - FeatureFlags.ChangeListener listener = flag1 -> called.set(true); - - mFeatureFlags.addChangeListener(listener); - - SyncableFlag syncableFlag = new SyncableFlag( - flag.getNamespace(), flag.getName(), flag.getDefault().toString(), true); - - mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag)); - - // Fires listener. - assertThat(called.get()).isTrue(); - } -} diff --git a/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java b/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java deleted file mode 100644 index bc5d8aa3ac733..0000000000000 --- a/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2023 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 android.flags; - -import android.os.IBinder; -import android.os.RemoteException; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -class IFeatureFlagsFake implements IFeatureFlags { - - private final Set mCallbacks = new HashSet<>(); - - List mOverrides; - - @Override - public IBinder asBinder() { - return null; - } - - @Override - public List syncFlags(List flagList) { - return mOverrides == null ? flagList : mOverrides; - } - - @Override - public List queryFlags(List flagList) { - return mOverrides == null ? flagList : mOverrides; } - - @Override - public void overrideFlag(SyncableFlag syncableFlag) { - SyncableFlag match = findFlag(syncableFlag); - if (match != null) { - mOverrides.remove(match); - } - - mOverrides.add(syncableFlag); - - for (IFeatureFlagsCallback cb : mCallbacks) { - try { - cb.onFlagChange(syncableFlag); - } catch (RemoteException e) { - // does not happen in fakes. - } - } - } - - @Override - public void resetFlag(SyncableFlag syncableFlag) { - SyncableFlag match = findFlag(syncableFlag); - if (match != null) { - mOverrides.remove(match); - } - - for (IFeatureFlagsCallback cb : mCallbacks) { - try { - cb.onFlagChange(syncableFlag); - } catch (RemoteException e) { - // does not happen in fakes. - } - } - } - - private SyncableFlag findFlag(SyncableFlag syncableFlag) { - SyncableFlag match = null; - for (SyncableFlag sf : mOverrides) { - if (sf.getName().equals(syncableFlag.getName()) - && sf.getNamespace().equals(syncableFlag.getNamespace())) { - match = sf; - break; - } - } - - return match; - } - @Override - public void registerCallback(IFeatureFlagsCallback callback) { - mCallbacks.add(callback); - } - - @Override - public void unregisterCallback(IFeatureFlagsCallback callback) { - mCallbacks.remove(callback); - } - - public void setFlagOverrides(List flagList) { - mOverrides = flagList; - for (SyncableFlag sf : flagList) { - for (IFeatureFlagsCallback cb : mCallbacks) { - try { - cb.onFlagChange(sf); - } catch (RemoteException e) { - // does not happen in fakes. - } - } - } - } -} diff --git a/services/Android.bp b/services/Android.bp index 453f572341452..b0a0e5e44a8cb 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -159,7 +159,6 @@ java_library { "services.coverage", "services.credentials", "services.devicepolicy", - "services.flags", "services.midi", "services.musicsearch", "services.net", diff --git a/services/flags/Android.bp b/services/flags/Android.bp deleted file mode 100644 index 29d2b9cfbb989..0000000000000 --- a/services/flags/Android.bp +++ /dev/null @@ -1,18 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -java_library_static { - name: "services.flags", - defaults: ["platform_service_defaults"], - srcs: [ - "java/**/*.java", - ":feature_flags_aidl", - ], - libs: ["services.core"], -} diff --git a/services/flags/OWNERS b/services/flags/OWNERS deleted file mode 100644 index 3925b5c13c2d2..0000000000000 --- a/services/flags/OWNERS +++ /dev/null @@ -1,6 +0,0 @@ -# Bug component: 1306523 - -mankoff@google.com - -pixel@google.com -dsandler@android.com diff --git a/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java b/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java deleted file mode 100644 index 0db328792cf3c..0000000000000 --- a/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2023 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.server.flags; - -import android.annotation.NonNull; -import android.flags.IFeatureFlagsCallback; -import android.flags.SyncableFlag; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.provider.DeviceConfig; -import android.util.Slog; - -import com.android.internal.os.BackgroundThread; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -/** - * Handles DynamicFlags for {@link FeatureFlagsBinder}. - * - * Dynamic flags are simultaneously simpler and more complicated than process stable flags. We can - * return whatever value is last known for a flag is, without too much worry about the flags - * changing (they are dynamic after all). However, we have to alert all the relevant clients - * about those flag changes, and need to be able to restore to a default value if the flag gets - * reset/erased during runtime. - */ -class DynamicFlagBinderDelegate { - - private final FlagOverrideStore mFlagStore; - private final FlagCache mDynamicFlags = new FlagCache<>(); - private final Map> mCallbacks = new HashMap<>(); - private static final Function> NEW_CALLBACK_SET = - k -> new HashSet<>(); - - private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener = - new DeviceConfig.OnPropertiesChangedListener() { - @Override - public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { - String ns = properties.getNamespace(); - for (String name : properties.getKeyset()) { - // Don't alert for flags we don't care about. - // Don't alert for flags that have been overridden locally. - if (!mDynamicFlags.contains(ns, name) || mFlagStore.contains(ns, name)) { - continue; - } - mFlagChangeCallback.onFlagChanged( - ns, name, properties.getString(name, null)); - } - } - }; - - private final FlagOverrideStore.FlagChangeCallback mFlagChangeCallback = - (namespace, name, value) -> { - // Don't bother with callbacks for non-dynamic flags. - if (!mDynamicFlags.contains(namespace, name)) { - return; - } - - // Don't bother with callbacks if nothing changed. - // Handling erasure (null) is special, as we may be restoring back to a value - // we were already at. - DynamicFlagData data = mDynamicFlags.getOrNull(namespace, name); - if (data == null) { - return; // shouldn't happen, but better safe than sorry. - } - if (value == null) { - if (data.getValue().equals(data.getDefaultValue())) { - return; - } - value = data.getDefaultValue(); - } else if (data.getValue().equals(value)) { - return; - } - data.setValue(value); - - final Set cbCopy; - synchronized (mCallbacks) { - cbCopy = new HashSet<>(); - - for (Integer pid : mCallbacks.keySet()) { - if (data.containsPid(pid)) { - cbCopy.addAll(mCallbacks.get(pid)); - } - } - } - SyncableFlag sFlag = new SyncableFlag(namespace, name, value, true); - cbCopy.forEach(cb -> { - try { - cb.onFlagChange(sFlag); - } catch (RemoteException e) { - Slog.w( - FeatureFlagsService.TAG, - "Failed to communicate flag change to client."); - } - }); - }; - - DynamicFlagBinderDelegate(FlagOverrideStore flagStore) { - mFlagStore = flagStore; - mFlagStore.setChangeCallback(mFlagChangeCallback); - } - - SyncableFlag syncDynamicFlag(int pid, SyncableFlag sf) { - if (!sf.isDynamic()) { - return sf; - } - - String ns = sf.getNamespace(); - String name = sf.getName(); - - // Dynamic flags don't need any special threading or synchronization considerations. - // We simply give them whatever the current value is. - // However, we do need to keep track of dynamic flags, so that we can alert - // about changes coming in from adb, DeviceConfig, or other sources. - // And also so that we can keep flags relatively consistent across processes. - - DynamicFlagData data = mDynamicFlags.getOrNull(ns, name); - String value = getFlagValue(ns, name, sf.getValue()); - // DeviceConfig listeners are per-namespace. - if (!mDynamicFlags.containsNamespace(ns)) { - DeviceConfig.addOnPropertiesChangedListener( - ns, BackgroundThread.getExecutor(), mDeviceConfigListener); - } - data.addClientPid(pid); - data.setValue(value); - // Store the default value so that if an override gets erased, we can restore - // to something. - data.setDefaultValue(sf.getValue()); - - return new SyncableFlag(sf.getNamespace(), sf.getName(), value, true); - } - - - void registerCallback(int pid, IFeatureFlagsCallback callback) { - // Always add callback so that we don't end up with a possible race/leak. - // We remove the callback directly if we fail to call #linkToDeath. - // If we tried to add the callback after we linked, then we could end up in a - // scenario where we link, then the binder dies, firing our BinderGriever which tries - // to remove the callback (which has not yet been added), then finally we add the - // callback, creating a leak. - Set callbacks; - synchronized (mCallbacks) { - callbacks = mCallbacks.computeIfAbsent(pid, NEW_CALLBACK_SET); - callbacks.add(callback); - } - try { - callback.asBinder().linkToDeath(new BinderGriever(pid), 0); - } catch (RemoteException e) { - Slog.e( - FeatureFlagsService.TAG, - "Failed to link to binder death. Callback not registered."); - synchronized (mCallbacks) { - callbacks.remove(callback); - } - } - } - - void unregisterCallback(int pid, IFeatureFlagsCallback callback) { - // No need to unlink, since the BinderGriever will essentially be a no-op. - // We would have to track our BinderGriever's in a map otherwise. - synchronized (mCallbacks) { - Set callbacks = - mCallbacks.computeIfAbsent(pid, NEW_CALLBACK_SET); - callbacks.remove(callback); - } - } - - String getFlagValue(String namespace, String name, String defaultValue) { - // If we already have a value cached, just use that. - String value = null; - DynamicFlagData data = mDynamicFlags.getOrNull(namespace, name); - if (data != null) { - value = data.getValue(); - } else { - // Put the value in the cache for future reference. - data = new DynamicFlagData(namespace, name); - mDynamicFlags.setIfChanged(namespace, name, data); - } - // If we're not in a release build, flags can be overridden locally on device. - if (!Build.IS_USER && value == null) { - value = mFlagStore.get(namespace, name); - } - // If we still don't have a value, maybe DeviceConfig does? - // Fallback to sf.getValue() here as well. - if (value == null) { - value = DeviceConfig.getString(namespace, name, defaultValue); - } - - return value; - } - - private static class DynamicFlagData { - private final String mNamespace; - private final String mName; - private final Set mPids = new HashSet<>(); - private String mValue; - private String mDefaultValue; - - private DynamicFlagData(String namespace, String name) { - mNamespace = namespace; - mName = name; - } - - String getValue() { - return mValue; - } - - void setValue(String value) { - mValue = value; - } - - String getDefaultValue() { - return mDefaultValue; - } - - void setDefaultValue(String value) { - mDefaultValue = value; - } - - void addClientPid(int pid) { - mPids.add(pid); - } - - boolean containsPid(int pid) { - return mPids.contains(pid); - } - - @Override - public boolean equals(Object other) { - if (other == null || !(other instanceof DynamicFlagData)) { - return false; - } - - DynamicFlagData o = (DynamicFlagData) other; - - return mName.equals(o.mName) && mNamespace.equals(o.mNamespace) - && mValue.equals(o.mValue) && mDefaultValue.equals(o.mDefaultValue); - } - - @Override - public int hashCode() { - return mName.hashCode() + mNamespace.hashCode() - + mValue.hashCode() + mDefaultValue.hashCode(); - } - } - - - private class BinderGriever implements IBinder.DeathRecipient { - private final int mPid; - - private BinderGriever(int pid) { - mPid = pid; - } - - @Override - public void binderDied() { - synchronized (mCallbacks) { - mCallbacks.remove(mPid); - } - } - } -} diff --git a/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java b/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java deleted file mode 100644 index 1fa85325aea6a..0000000000000 --- a/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2023 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.server.flags; - -import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.flags.IFeatureFlags; -import android.flags.IFeatureFlagsCallback; -import android.flags.SyncableFlag; -import android.os.Build; -import android.os.ParcelFileDescriptor; - -import com.android.internal.flags.CoreFlags; -import com.android.server.flags.FeatureFlagsService.PermissionsChecker; - -import java.io.FileOutputStream; -import java.util.ArrayList; -import java.util.List; - -class FeatureFlagsBinder extends IFeatureFlags.Stub { - private final FlagOverrideStore mFlagStore; - private final FlagsShellCommand mShellCommand; - private final FlagCache mFlagCache = new FlagCache<>(); - private final DynamicFlagBinderDelegate mDynamicFlagDelegate; - private final PermissionsChecker mPermissionsChecker; - - FeatureFlagsBinder( - FlagOverrideStore flagStore, - FlagsShellCommand shellCommand, - PermissionsChecker permissionsChecker) { - mFlagStore = flagStore; - mShellCommand = shellCommand; - mDynamicFlagDelegate = new DynamicFlagBinderDelegate(flagStore); - mPermissionsChecker = permissionsChecker; - } - - @Override - public void registerCallback(IFeatureFlagsCallback callback) { - mDynamicFlagDelegate.registerCallback(getCallingPid(), callback); - } - - @Override - public void unregisterCallback(IFeatureFlagsCallback callback) { - mDynamicFlagDelegate.unregisterCallback(getCallingPid(), callback); - } - - // Note: The internals of this method should be kept in sync with queryFlags - // as they both should return identical results. The difference is that this method - // caches any values it receives and/or reads, whereas queryFlags does not. - - @Override - public List syncFlags(List incomingFlags) { - int pid = getCallingPid(); - List outputFlags = new ArrayList<>(); - - boolean hasFullSyncPrivileges = false; - SecurityException permissionFailureException = null; - try { - assertSyncPermission(); - hasFullSyncPrivileges = true; - } catch (SecurityException e) { - permissionFailureException = e; - } - - for (SyncableFlag sf : incomingFlags) { - if (!hasFullSyncPrivileges && !CoreFlags.isCoreFlag(sf)) { - throw permissionFailureException; - } - - String ns = sf.getNamespace(); - String name = sf.getName(); - SyncableFlag outFlag; - if (sf.isDynamic()) { - outFlag = mDynamicFlagDelegate.syncDynamicFlag(pid, sf); - } else { - synchronized (mFlagCache) { - String value = mFlagCache.getOrNull(ns, name); - if (value == null) { - String overrideValue = Build.IS_USER ? null : mFlagStore.get(ns, name); - value = overrideValue != null ? overrideValue : sf.getValue(); - mFlagCache.setIfChanged(ns, name, value); - } - outFlag = new SyncableFlag(sf.getNamespace(), sf.getName(), value, false); - } - } - outputFlags.add(outFlag); - } - return outputFlags; - } - - @Override - public void overrideFlag(SyncableFlag flag) { - assertWritePermission(); - mFlagStore.set(flag.getNamespace(), flag.getName(), flag.getValue()); - } - - @Override - public void resetFlag(SyncableFlag flag) { - assertWritePermission(); - mFlagStore.erase(flag.getNamespace(), flag.getName()); - } - - @Override - public List queryFlags(List incomingFlags) { - assertSyncPermission(); - List outputFlags = new ArrayList<>(); - for (SyncableFlag sf : incomingFlags) { - String ns = sf.getNamespace(); - String name = sf.getName(); - String value; - String storeValue = mFlagStore.get(ns, name); - boolean overridden = storeValue != null; - - if (sf.isDynamic()) { - value = mDynamicFlagDelegate.getFlagValue(ns, name, sf.getValue()); - } else { - value = mFlagCache.getOrNull(ns, name); - if (value == null) { - value = Build.IS_USER ? null : storeValue; - if (value == null) { - value = sf.getValue(); - } - } - } - outputFlags.add(new SyncableFlag( - sf.getNamespace(), sf.getName(), value, sf.isDynamic(), overridden)); - } - - return outputFlags; - } - - private void assertSyncPermission() { - mPermissionsChecker.assertSyncPermission(); - clearCallingIdentity(); - } - - private void assertWritePermission() { - mPermissionsChecker.assertWritePermission(); - clearCallingIdentity(); - } - - - @SystemApi - public int handleShellCommand( - @NonNull ParcelFileDescriptor in, - @NonNull ParcelFileDescriptor out, - @NonNull ParcelFileDescriptor err, - @NonNull String[] args) { - FileOutputStream fout = new FileOutputStream(out.getFileDescriptor()); - FileOutputStream ferr = new FileOutputStream(err.getFileDescriptor()); - - return mShellCommand.process(args, fout, ferr); - } -} diff --git a/services/flags/java/com/android/server/flags/FeatureFlagsService.java b/services/flags/java/com/android/server/flags/FeatureFlagsService.java deleted file mode 100644 index 93b9e9e0dc8cf..0000000000000 --- a/services/flags/java/com/android/server/flags/FeatureFlagsService.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2023 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.server.flags; - -import static android.Manifest.permission.SYNC_FLAGS; -import static android.Manifest.permission.WRITE_FLAGS; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.flags.FeatureFlags; -import android.util.Slog; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.SystemService; - -/** - * A service that manages syncing {@link android.flags.FeatureFlags} across processes. - * - * This service holds flags stable for at least the lifetime of a process, meaning that if - * a process comes online with a flag set to true, any other process that connects here and - * tries to read the same flag will also receive the flag as true. The flag will remain stable - * until either all of the interested processes have died, or the device restarts. - * - * TODO(279054964): Add to dumpsys - * @hide - */ -public class FeatureFlagsService extends SystemService { - - static final String TAG = "FeatureFlagsService"; - private final FlagOverrideStore mFlagStore; - private final FlagsShellCommand mShellCommand; - - /** - * Initializes the system service. - * - * @param context The system server context. - */ - public FeatureFlagsService(Context context) { - super(context); - mFlagStore = new FlagOverrideStore( - new GlobalSettingsProxy(context.getContentResolver())); - mShellCommand = new FlagsShellCommand(mFlagStore); - } - - @Override - public void onStart() { - Slog.d(TAG, "Started Feature Flag Service"); - FeatureFlagsBinder service = new FeatureFlagsBinder( - mFlagStore, mShellCommand, new PermissionsChecker(getContext())); - publishBinderService( - Context.FEATURE_FLAGS_SERVICE, service); - publishLocalService(FeatureFlags.class, new FeatureFlags(service)); - } - - @Override - public void onBootPhase(int phase) { - super.onBootPhase(phase); - - if (phase == PHASE_SYSTEM_SERVICES_READY) { - // Immediately sync our core flags so that they get locked in. We don't want third-party - // apps to override them, and syncing immediately is the easiest way to prevent that. - FeatureFlags.getInstance().sync(); - } - } - - /** - * Delegate for checking flag permissions. - */ - @VisibleForTesting - public static class PermissionsChecker { - private final Context mContext; - - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public PermissionsChecker(Context context) { - mContext = context; - } - - /** - * Ensures that the caller has {@link SYNC_FLAGS} permission. - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public void assertSyncPermission() { - if (mContext.checkCallingOrSelfPermission(SYNC_FLAGS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException( - "Non-core flag queried. Requires SYNC_FLAGS permission!"); - } - } - - /** - * Ensures that the caller has {@link WRITE_FLAGS} permission. - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public void assertWritePermission() { - if (mContext.checkCallingPermission(WRITE_FLAGS) != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires WRITE_FLAGS permission!"); - } - } - } -} diff --git a/services/flags/java/com/android/server/flags/FlagCache.java b/services/flags/java/com/android/server/flags/FlagCache.java deleted file mode 100644 index cee1578a5ddec..0000000000000 --- a/services/flags/java/com/android/server/flags/FlagCache.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2023 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.server.flags; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -/** - * Threadsafe cache of values that stores the supplied default on cache miss. - * - * @param The type of value to store. - */ -public class FlagCache { - private final Function> mNewHashMap = k -> new HashMap<>(); - - // Cache is organized first by namespace, then by name. All values are stored as strings. - final Map> mCache = new HashMap<>(); - - FlagCache() { - } - - /** - * Returns true if the namespace exists in the cache already. - */ - boolean containsNamespace(String namespace) { - synchronized (mCache) { - return mCache.containsKey(namespace); - } - } - - /** - * Returns true if the value is stored in the cache. - */ - boolean contains(String namespace, String name) { - synchronized (mCache) { - Map nsCache = mCache.get(namespace); - return nsCache != null && nsCache.containsKey(name); - } - } - - /** - * Sets the value if it is different from what is currently stored. - * - * If the value is not set, or the current value is null, it will store the value and - * return true. - * - * @return True if the value was set. False if the value is the same. - */ - boolean setIfChanged(String namespace, String name, V value) { - synchronized (mCache) { - Map nsCache = mCache.computeIfAbsent(namespace, mNewHashMap); - V curValue = nsCache.get(name); - if (curValue == null || !curValue.equals(value)) { - nsCache.put(name, value); - return true; - } - return false; - } - } - - /** - * Gets the current value from the cache, setting it if it is currently absent. - * - * @return The value that is now in the cache after the call to the method. - */ - V getOrSet(String namespace, String name, V defaultValue) { - synchronized (mCache) { - Map nsCache = mCache.computeIfAbsent(namespace, mNewHashMap); - V value = nsCache.putIfAbsent(name, defaultValue); - return value == null ? defaultValue : value; - } - } - - /** - * Gets the current value from the cache, returning null if not present. - * - * @return The value that is now in the cache if there is one. - */ - V getOrNull(String namespace, String name) { - synchronized (mCache) { - Map nsCache = mCache.get(namespace); - if (nsCache == null) { - return null; - } - return nsCache.get(name); - } - } -} diff --git a/services/flags/java/com/android/server/flags/FlagOverrideStore.java b/services/flags/java/com/android/server/flags/FlagOverrideStore.java deleted file mode 100644 index b1ddc7e67f68c..0000000000000 --- a/services/flags/java/com/android/server/flags/FlagOverrideStore.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2023 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.server.flags; - -import android.database.Cursor; -import android.provider.Settings; - -import com.android.internal.annotations.VisibleForTesting; - -import java.util.HashMap; -import java.util.Map; - -/** - * Persistent storage for the {@link FeatureFlagsService}. - * - * The implementation stores data in Settings. (generally {@link Settings.Global} - * is expected). - */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) -public class FlagOverrideStore { - private static final String KEYNAME_PREFIX = "flag|"; - private static final String NAMESPACE_NAME_SEPARATOR = "."; - - private final SettingsProxy mSettingsProxy; - - private FlagChangeCallback mCallback; - - FlagOverrideStore(SettingsProxy settingsProxy) { - mSettingsProxy = settingsProxy; - } - - void setChangeCallback(FlagChangeCallback callback) { - mCallback = callback; - } - - /** Returns true if a non-null value is in the store. */ - boolean contains(String namespace, String name) { - return get(namespace, name) != null; - } - - /** Put a value in the store. */ - @VisibleForTesting - public void set(String namespace, String name, String value) { - mSettingsProxy.putString(getPropName(namespace, name), value); - mCallback.onFlagChanged(namespace, name, value); - } - - /** Read a value out of the store. */ - @VisibleForTesting - public String get(String namespace, String name) { - return mSettingsProxy.getString(getPropName(namespace, name)); - } - - /** Erase a value from the store. */ - @VisibleForTesting - public void erase(String namespace, String name) { - set(namespace, name, null); - } - - Map> getFlags() { - return getFlagsForNamespace(null); - } - - Map> getFlagsForNamespace(String namespace) { - Cursor c = mSettingsProxy.getContentResolver().query( - Settings.Global.CONTENT_URI, - new String[]{Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE}, - null, // Doesn't support a "LIKE" query - null, - null - ); - - if (c == null) { - return Map.of(); - } - int keynamePrefixLength = KEYNAME_PREFIX.length(); - Map> results = new HashMap<>(); - while (c.moveToNext()) { - String key = c.getString(0); - if (!key.startsWith(KEYNAME_PREFIX) - || key.indexOf(NAMESPACE_NAME_SEPARATOR, keynamePrefixLength) < 0) { - continue; - } - String value = c.getString(1); - if (value == null || value.isEmpty()) { - continue; - } - String ns = key.substring(keynamePrefixLength, key.indexOf(NAMESPACE_NAME_SEPARATOR)); - if (namespace != null && !namespace.equals(ns)) { - continue; - } - String name = key.substring(key.indexOf(NAMESPACE_NAME_SEPARATOR) + 1); - results.putIfAbsent(ns, new HashMap<>()); - results.get(ns).put(name, value); - } - c.close(); - return results; - } - - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - static String getPropName(String namespace, String name) { - return KEYNAME_PREFIX + namespace + NAMESPACE_NAME_SEPARATOR + name; - } - - interface FlagChangeCallback { - void onFlagChanged(String namespace, String name, String value); - } -} diff --git a/services/flags/java/com/android/server/flags/FlagsShellCommand.java b/services/flags/java/com/android/server/flags/FlagsShellCommand.java deleted file mode 100644 index b7896ee187145..0000000000000 --- a/services/flags/java/com/android/server/flags/FlagsShellCommand.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2023 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.server.flags; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.FastPrintWriter; - -import java.io.OutputStream; -import java.io.PrintWriter; -import java.util.Locale; -import java.util.Map; - -/** - * Process command line input for the flags service. - */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) -public class FlagsShellCommand { - private final FlagOverrideStore mFlagStore; - - FlagsShellCommand(FlagOverrideStore flagStore) { - mFlagStore = flagStore; - } - - /** - * Interpret the command supplied in the constructor. - * - * @return Zero on success or non-zero on error. - */ - public int process( - String[] args, - OutputStream out, - OutputStream err) { - PrintWriter outPw = new FastPrintWriter(out); - PrintWriter errPw = new FastPrintWriter(err); - - if (args.length == 0) { - return printHelp(outPw); - } - switch (args[0].toLowerCase(Locale.ROOT)) { - case "help": - return printHelp(outPw); - case "list": - return listCmd(args, outPw, errPw); - case "set": - return setCmd(args, outPw, errPw); - case "get": - return getCmd(args, outPw, errPw); - case "erase": - return eraseCmd(args, outPw, errPw); - default: - return unknownCmd(outPw); - } - } - - private int printHelp(PrintWriter outPw) { - outPw.println("Feature Flags command, allowing listing, setting, getting, and erasing of"); - outPw.println("local flag overrides on a device."); - outPw.println(); - outPw.println("Commands:"); - outPw.println(" list [namespace]"); - outPw.println(" List all flag overrides. Namespace is optional."); - outPw.println(); - outPw.println(" get "); - outPw.println(" Return the string value of a specific flag, or "); - outPw.println(); - outPw.println(" set "); - outPw.println(" Set a specific flag"); - outPw.println(); - outPw.println(" erase "); - outPw.println(" Unset a specific flag"); - outPw.flush(); - return 0; - } - - private int listCmd(String[] args, PrintWriter outPw, PrintWriter errPw) { - if (!validateNumArguments(args, 0, 1, args[0], errPw)) { - errPw.println("Expected `" + args[0] + " [namespace]`"); - errPw.flush(); - return -1; - } - Map> overrides; - if (args.length == 2) { - overrides = mFlagStore.getFlagsForNamespace(args[1]); - } else { - overrides = mFlagStore.getFlags(); - } - if (overrides.isEmpty()) { - outPw.println("No overrides set"); - } else { - int longestNamespaceLen = "namespace".length(); - int longestFlagLen = "flag".length(); - int longestValLen = "value".length(); - for (Map.Entry> namespace : overrides.entrySet()) { - longestNamespaceLen = Math.max(longestNamespaceLen, namespace.getKey().length()); - for (Map.Entry flag : namespace.getValue().entrySet()) { - longestFlagLen = Math.max(longestFlagLen, flag.getKey().length()); - longestValLen = Math.max(longestValLen, flag.getValue().length()); - } - } - outPw.print(String.format("%-" + longestNamespaceLen + "s", "namespace")); - outPw.print(' '); - outPw.print(String.format("%-" + longestFlagLen + "s", "flag")); - outPw.print(' '); - outPw.println("value"); - for (int i = 0; i < longestNamespaceLen; i++) { - outPw.print('='); - } - outPw.print(' '); - for (int i = 0; i < longestFlagLen; i++) { - outPw.print('='); - } - outPw.print(' '); - for (int i = 0; i < longestValLen; i++) { - outPw.print('='); - } - outPw.println(); - for (Map.Entry> namespace : overrides.entrySet()) { - for (Map.Entry flag : namespace.getValue().entrySet()) { - outPw.print( - String.format("%-" + longestNamespaceLen + "s", namespace.getKey())); - outPw.print(' '); - outPw.print(String.format("%-" + longestFlagLen + "s", flag.getKey())); - outPw.print(' '); - outPw.println(flag.getValue()); - } - } - } - outPw.flush(); - return 0; - } - - private int setCmd(String[] args, PrintWriter outPw, PrintWriter errPw) { - if (!validateNumArguments(args, 3, args[0], errPw)) { - errPw.println("Expected `" + args[0] + " `"); - errPw.flush(); - return -1; - } - mFlagStore.set(args[1], args[2], args[3]); - outPw.println("Flag " + args[1] + "." + args[2] + " is now " + args[3]); - outPw.flush(); - return 0; - } - - private int getCmd(String[] args, PrintWriter outPw, PrintWriter errPw) { - if (!validateNumArguments(args, 2, args[0], errPw)) { - errPw.println("Expected `" + args[0] + " `"); - errPw.flush(); - return -1; - } - - String value = mFlagStore.get(args[1], args[2]); - outPw.print(args[1] + "." + args[2] + " is "); - if (value == null || value.isEmpty()) { - outPw.println(""); - } else { - outPw.println("\"" + value.translateEscapes() + "\""); - } - outPw.flush(); - return 0; - } - - private int eraseCmd(String[] args, PrintWriter outPw, PrintWriter errPw) { - if (!validateNumArguments(args, 2, args[0], errPw)) { - errPw.println("Expected `" + args[0] + " `"); - errPw.flush(); - return -1; - } - mFlagStore.erase(args[1], args[2]); - outPw.println("Erased " + args[1] + "." + args[2]); - return 0; - } - - private int unknownCmd(PrintWriter outPw) { - outPw.println("This command is unknown."); - printHelp(outPw); - outPw.flush(); - return -1; - } - - private boolean validateNumArguments( - String[] args, int exactly, String cmdName, PrintWriter errPw) { - return validateNumArguments(args, exactly, exactly, cmdName, errPw); - } - - private boolean validateNumArguments( - String[] args, int min, int max, String cmdName, PrintWriter errPw) { - int len = args.length - 1; // Discount the command itself. - if (len < min) { - errPw.println( - "Less than " + min + " arguments provided for \"" + cmdName + "\" command."); - return false; - } else if (len > max) { - errPw.println( - "More than " + max + " arguments provided for \"" + cmdName + "\" command."); - return false; - } - - return true; - } -} diff --git a/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java b/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java deleted file mode 100644 index acb7bb5a49db5..0000000000000 --- a/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2023 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.server.flags; - -import android.content.ContentResolver; -import android.net.Uri; -import android.provider.Settings; - -class GlobalSettingsProxy implements SettingsProxy { - private final ContentResolver mContentResolver; - - GlobalSettingsProxy(ContentResolver contentResolver) { - mContentResolver = contentResolver; - } - - @Override - public ContentResolver getContentResolver() { - return mContentResolver; - } - - @Override - public Uri getUriFor(String name) { - return Settings.Global.getUriFor(name); - } - - @Override - public String getStringForUser(String name, int userHandle) { - return Settings.Global.getStringForUser(mContentResolver, name, userHandle); - } - - @Override - public boolean putString(String name, String value, boolean overrideableByRestore) { - throw new UnsupportedOperationException( - "This method only exists publicly for Settings.System and Settings.Secure"); - } - - @Override - public boolean putStringForUser(String name, String value, int userHandle) { - return Settings.Global.putStringForUser(mContentResolver, name, value, userHandle); - } - - @Override - public boolean putStringForUser(String name, String value, String tag, boolean makeDefault, - int userHandle, boolean overrideableByRestore) { - return Settings.Global.putStringForUser( - mContentResolver, name, value, tag, makeDefault, userHandle, - overrideableByRestore); - } - - @Override - public boolean putString(String name, String value, String tag, boolean makeDefault) { - return Settings.Global.putString(mContentResolver, name, value, tag, makeDefault); - } -} diff --git a/services/flags/java/com/android/server/flags/SettingsProxy.java b/services/flags/java/com/android/server/flags/SettingsProxy.java deleted file mode 100644 index c6e85d5d1dc8f..0000000000000 --- a/services/flags/java/com/android/server/flags/SettingsProxy.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright (C) 2023 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.server.flags; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.content.ContentResolver; -import android.database.ContentObserver; -import android.net.Uri; -import android.provider.Settings; - -/** - * Wrapper class meant to enable hermetic testing of {@link Settings}. - * - * Implementations of this class are expected to be constructed with a {@link ContentResolver} or, - * otherwise have access to an implicit one. All the proxy methods in this class exclude - * {@link ContentResolver} from their signature and rely on an internally defined one instead. - * - * Most methods in the {@link Settings} classes have default implementations defined. - * Implementations of this interfac need only concern themselves with getting and putting Strings. - * They should also override any methods for a class they are proxying that _are not_ defined, and - * throw an appropriate {@link UnsupportedOperationException}. For instance, {@link Settings.Global} - * does not define {@link #putString(String, String, boolean)}, so an implementation of this - * interface that proxies through to it should throw an exception when that method is called. - * - * This class adds in the following helpers as well: - * - {@link #getBool(String)} - * - {@link #putBool(String, boolean)} - * - {@link #registerContentObserver(Uri, ContentObserver)} - * - * ... and similar variations for all of those. - */ -public interface SettingsProxy { - - /** - * Returns the {@link ContentResolver} this instance uses. - */ - ContentResolver getContentResolver(); - - /** - * Construct the content URI for a particular name/value pair, - * useful for monitoring changes with a ContentObserver. - * @param name to look up in the table - * @return the corresponding content URI, or null if not present - */ - Uri getUriFor(String name); - - /**See {@link Settings.Secure#getString(ContentResolver, String)} */ - String getStringForUser(String name, int userHandle); - - /**See {@link Settings.Secure#putString(ContentResolver, String, String, boolean)} */ - boolean putString(String name, String value, boolean overrideableByRestore); - - /** See {@link Settings.Secure#putStringForUser(ContentResolver, String, String, int)} */ - boolean putStringForUser(String name, String value, int userHandle); - - /** - * See {@link Settings.Secure#putStringForUser(ContentResolver, String, String, String, boolean, - * int, boolean)} - */ - boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag, - boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore); - - /** See {@link Settings.Secure#putString(ContentResolver, String, String, String, boolean)} */ - boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag, - boolean makeDefault); - - /** - * Returns the user id for the associated {@link ContentResolver}. - */ - default int getUserId() { - return getContentResolver().getUserId(); - } - - /** See {@link Settings.Secure#getString(ContentResolver, String)} */ - default String getString(String name) { - return getStringForUser(name, getUserId()); - } - - /** See {@link Settings.Secure#putString(ContentResolver, String, String)} */ - default boolean putString(String name, String value) { - return putStringForUser(name, value, getUserId()); - } - /** See {@link Settings.Secure#getIntForUser(ContentResolver, String, int, int)} */ - default int getIntForUser(String name, int def, int userHandle) { - String v = getStringForUser(name, userHandle); - try { - return v != null ? Integer.parseInt(v) : def; - } catch (NumberFormatException e) { - return def; - } - } - - /** See {@link Settings.Secure#getInt(ContentResolver, String)} */ - default int getInt(String name) throws Settings.SettingNotFoundException { - return getIntForUser(name, getUserId()); - } - - /** See {@link Settings.Secure#getIntForUser(ContentResolver, String, int)} */ - default int getIntForUser(String name, int userHandle) - throws Settings.SettingNotFoundException { - String v = getStringForUser(name, userHandle); - try { - return Integer.parseInt(v); - } catch (NumberFormatException e) { - throw new Settings.SettingNotFoundException(name); - } - } - - /** See {@link Settings.Secure#putInt(ContentResolver, String, int)} */ - default boolean putInt(String name, int value) { - return putIntForUser(name, value, getUserId()); - } - - /** See {@link Settings.Secure#putIntForUser(ContentResolver, String, int, int)} */ - default boolean putIntForUser(String name, int value, int userHandle) { - return putStringForUser(name, Integer.toString(value), userHandle); - } - - /** - * Convenience function for retrieving a single settings value - * as a boolean. Note that internally setting values are always - * stored as strings; this function converts the string to a boolean - * for you. The default value will be returned if the setting is - * not defined or not a boolean. - * - * @param name The name of the setting to retrieve. - * @param def Value to return if the setting is not defined. - * - * @return The setting's current value, or 'def' if it is not defined - * or not a valid boolean. - */ - default boolean getBool(String name, boolean def) { - return getBoolForUser(name, def, getUserId()); - } - - /** See {@link #getBool(String, boolean)}. */ - default boolean getBoolForUser(String name, boolean def, int userHandle) { - return getIntForUser(name, def ? 1 : 0, userHandle) != 0; - } - - /** - * Convenience function for retrieving a single settings value - * as a boolean. Note that internally setting values are always - * stored as strings; this function converts the string to a boolean - * for you. - *

- * This version does not take a default value. If the setting has not - * been set, or the string value is not a number, - * it throws {@link Settings.SettingNotFoundException}. - * - * @param name The name of the setting to retrieve. - * - * @throws Settings.SettingNotFoundException Thrown if a setting by the given - * name can't be found or the setting value is not a boolean. - * - * @return The setting's current value. - */ - default boolean getBool(String name) throws Settings.SettingNotFoundException { - return getBoolForUser(name, getUserId()); - } - - /** See {@link #getBool(String)}. */ - default boolean getBoolForUser(String name, int userHandle) - throws Settings.SettingNotFoundException { - return getIntForUser(name, userHandle) != 0; - } - - /** - * Convenience function for updating a single settings value as a - * boolean. This will either create a new entry in the table if the - * given name does not exist, or modify the value of the existing row - * with that name. Note that internally setting values are always - * stored as strings, so this function converts the given value to a - * string before storing it. - * - * @param name The name of the setting to modify. - * @param value The new value for the setting. - * @return true if the value was set, false on database errors - */ - default boolean putBool(String name, boolean value) { - return putBoolForUser(name, value, getUserId()); - } - - /** See {@link #putBool(String, boolean)}. */ - default boolean putBoolForUser(String name, boolean value, int userHandle) { - return putIntForUser(name, value ? 1 : 0, userHandle); - } - - /** See {@link Settings.Secure#getLong(ContentResolver, String, long)} */ - default long getLong(String name, long def) { - return getLongForUser(name, def, getUserId()); - } - - /** See {@link Settings.Secure#getLongForUser(ContentResolver, String, long, int)} */ - default long getLongForUser(String name, long def, int userHandle) { - String valString = getStringForUser(name, userHandle); - long value; - try { - value = valString != null ? Long.parseLong(valString) : def; - } catch (NumberFormatException e) { - value = def; - } - return value; - } - - /** See {@link Settings.Secure#getLong(ContentResolver, String)} */ - default long getLong(String name) throws Settings.SettingNotFoundException { - return getLongForUser(name, getUserId()); - } - - /** See {@link Settings.Secure#getLongForUser(ContentResolver, String, int)} */ - default long getLongForUser(String name, int userHandle) - throws Settings.SettingNotFoundException { - String valString = getStringForUser(name, userHandle); - try { - return Long.parseLong(valString); - } catch (NumberFormatException e) { - throw new Settings.SettingNotFoundException(name); - } - } - - /** See {@link Settings.Secure#putLong(ContentResolver, String, long)} */ - default boolean putLong(String name, long value) { - return putLongForUser(name, value, getUserId()); - } - - /** See {@link Settings.Secure#putLongForUser(ContentResolver, String, long, int)} */ - default boolean putLongForUser(String name, long value, int userHandle) { - return putStringForUser(name, Long.toString(value), userHandle); - } - - /** See {@link Settings.Secure#getFloat(ContentResolver, String, float)} */ - default float getFloat(String name, float def) { - return getFloatForUser(name, def, getUserId()); - } - - /** See {@link Settings.Secure#getFloatForUser(ContentResolver, String, int)} */ - default float getFloatForUser(String name, float def, int userHandle) { - String v = getStringForUser(name, userHandle); - try { - return v != null ? Float.parseFloat(v) : def; - } catch (NumberFormatException e) { - return def; - } - } - - - /** See {@link Settings.Secure#getFloat(ContentResolver, String)} */ - default float getFloat(String name) throws Settings.SettingNotFoundException { - return getFloatForUser(name, getUserId()); - } - - /** See {@link Settings.Secure#getFloatForUser(ContentResolver, String, int)} */ - default float getFloatForUser(String name, int userHandle) - throws Settings.SettingNotFoundException { - String v = getStringForUser(name, userHandle); - if (v == null) { - throw new Settings.SettingNotFoundException(name); - } - try { - return Float.parseFloat(v); - } catch (NumberFormatException e) { - throw new Settings.SettingNotFoundException(name); - } - } - - /** See {@link Settings.Secure#putFloat(ContentResolver, String, float)} */ - default boolean putFloat(String name, float value) { - return putFloatForUser(name, value, getUserId()); - } - - /** See {@link Settings.Secure#putFloatForUser(ContentResolver, String, float, int)} */ - default boolean putFloatForUser(String name, float value, int userHandle) { - return putStringForUser(name, Float.toString(value), userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - * - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserver(String name, ContentObserver settingsObserver) { - registerContentObserver(getUriFor(name), settingsObserver); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - */ - default void registerContentObserver(Uri uri, ContentObserver settingsObserver) { - registerContentObserverForUser(uri, settingsObserver, getUserId()); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}. - * - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserver(String name, boolean notifyForDescendants, - ContentObserver settingsObserver) { - registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - */ - default void registerContentObserver(Uri uri, boolean notifyForDescendants, - ContentObserver settingsObserver) { - registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId()); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - * - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserverForUser( - String name, ContentObserver settingsObserver, int userHandle) { - registerContentObserverForUser( - getUriFor(name), settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - */ - default void registerContentObserverForUser( - Uri uri, ContentObserver settingsObserver, int userHandle) { - registerContentObserverForUser( - uri, false, settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - * - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserverForUser( - String name, boolean notifyForDescendants, ContentObserver settingsObserver, - int userHandle) { - registerContentObserverForUser( - getUriFor(name), notifyForDescendants, settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - */ - default void registerContentObserverForUser( - Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver, - int userHandle) { - getContentResolver().registerContentObserver( - uri, notifyForDescendants, settingsObserver, userHandle); - } - - /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */ - default void unregisterContentObserver(ContentObserver settingsObserver) { - getContentResolver().unregisterContentObserver(settingsObserver); - } -} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 7de12790e935f..991248aceb4be 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -132,7 +132,6 @@ import com.android.server.display.DisplayManagerService; import com.android.server.display.color.ColorDisplayService; import com.android.server.dreams.DreamManagerService; import com.android.server.emergency.EmergencyAffordanceService; -import com.android.server.flags.FeatureFlagsService; import com.android.server.gpu.GpuService; import com.android.server.grammaticalinflection.GrammaticalInflectionService; import com.android.server.graphics.fonts.FontManagerService; @@ -1112,12 +1111,6 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(DeviceIdentifiersPolicyService.class); t.traceEnd(); - // Starts a service for reading runtime flag overrides, and keeping processes - // in sync with one another. - t.traceBegin("StartFeatureFlagsService"); - mSystemServiceManager.startService(FeatureFlagsService.class); - t.traceEnd(); - // Uri Grants Manager. t.traceBegin("UriGrantsManagerService"); mSystemServiceManager.startService(UriGrantsManagerService.Lifecycle.class); diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 0530f892a48e8..ee3a773580a37 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -34,7 +34,6 @@ android_test { "services.core", "services.credentials", "services.devicepolicy", - "services.flags", "services.net", "services.people", "services.usage", diff --git a/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java b/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java deleted file mode 100644 index df4731fb0bb71..0000000000000 --- a/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2023 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.server.flags; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; - -import static org.junit.Assert.fail; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.flags.IFeatureFlagsCallback; -import android.flags.SyncableFlag; -import android.os.IBinder; -import android.os.RemoteException; -import android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import java.util.List; - -@Presubmit -@SmallTest -public class FeatureFlagsServiceTest { - private static final String NS = "ns"; - private static final String NAME = "name"; - private static final String PROP_NAME = FlagOverrideStore.getPropName(NS, NAME); - - @Rule - public final MockitoRule mockito = MockitoJUnit.rule(); - - @Mock - private FlagOverrideStore mFlagStore; - @Mock - private FlagsShellCommand mFlagCommand; - @Mock - private IFeatureFlagsCallback mIFeatureFlagsCallback; - @Mock - private IBinder mIFeatureFlagsCallbackAsBinder; - @Mock - private FeatureFlagsService.PermissionsChecker mPermissionsChecker; - - private FeatureFlagsBinder mFeatureFlagsService; - - @Before - public void setup() { - when(mIFeatureFlagsCallback.asBinder()).thenReturn(mIFeatureFlagsCallbackAsBinder); - mFeatureFlagsService = new FeatureFlagsBinder( - mFlagStore, mFlagCommand, mPermissionsChecker); - } - - @Test - public void testRegisterCallback() { - mFeatureFlagsService.registerCallback(mIFeatureFlagsCallback); - try { - verify(mIFeatureFlagsCallbackAsBinder).linkToDeath(any(), eq(0)); - } catch (RemoteException e) { - fail("Our mock threw a Remote Exception?"); - } - } - - @Test - public void testOverrideFlag_requiresWritePermission() { - SecurityException exc = new SecurityException("not allowed"); - doThrow(exc).when(mPermissionsChecker).assertWritePermission(); - - SyncableFlag f = new SyncableFlag(NS, "a", "false", false); - - try { - mFeatureFlagsService.overrideFlag(f); - fail("Should have thrown exception"); - } catch (SecurityException e) { - assertThat(exc).isEqualTo(e); - } catch (Exception e) { - fail("should have thrown a security exception"); - } - } - - @Test - public void testResetFlag_requiresWritePermission() { - SecurityException exc = new SecurityException("not allowed"); - doThrow(exc).when(mPermissionsChecker).assertWritePermission(); - - SyncableFlag f = new SyncableFlag(NS, "a", "false", false); - - try { - mFeatureFlagsService.resetFlag(f); - fail("Should have thrown exception"); - } catch (SecurityException e) { - assertThat(exc).isEqualTo(e); - } catch (Exception e) { - fail("should have thrown a security exception"); - } - } - - @Test - public void testSyncFlags_noOverrides() { - List inputFlags = List.of( - new SyncableFlag(NS, "a", "false", false), - new SyncableFlag(NS, "b", "true", false), - new SyncableFlag(NS, "c", "false", false) - ); - - List outputFlags = mFeatureFlagsService.syncFlags(inputFlags); - - assertThat(inputFlags.size()).isEqualTo(outputFlags.size()); - - for (SyncableFlag inpF: inputFlags) { - boolean found = false; - for (SyncableFlag outF : outputFlags) { - if (compareSyncableFlagsNames(inpF, outF)) { - found = true; - break; - } - } - assertWithMessage("Failed to find input flag " + inpF + " in the output") - .that(found).isTrue(); - } - } - - @Test - public void testSyncFlags_withSomeOverrides() { - List inputFlags = List.of( - new SyncableFlag(NS, "a", "false", false), - new SyncableFlag(NS, "b", "true", false), - new SyncableFlag(NS, "c", "false", false) - ); - - assertThat(mFlagStore).isNotNull(); - when(mFlagStore.get(NS, "c")).thenReturn("true"); - List outputFlags = mFeatureFlagsService.syncFlags(inputFlags); - - assertThat(inputFlags.size()).isEqualTo(outputFlags.size()); - - for (SyncableFlag inpF: inputFlags) { - boolean found = false; - for (SyncableFlag outF : outputFlags) { - if (compareSyncableFlagsNames(inpF, outF)) { - found = true; - - // Once we've found "c", do an extra check - if (outF.getName().equals("c")) { - assertWithMessage("Flag " + outF + "was not returned with an override") - .that(outF.getValue()).isEqualTo("true"); - } - break; - } - } - assertWithMessage("Failed to find input flag " + inpF + " in the output") - .that(found).isTrue(); - } - } - - @Test - public void testSyncFlags_twoCallsWithDifferentDefaults() { - List inputFlagsFirst = List.of( - new SyncableFlag(NS, "a", "false", false) - ); - List inputFlagsSecond = List.of( - new SyncableFlag(NS, "a", "true", false), - new SyncableFlag(NS, "b", "false", false) - ); - - List outputFlagsFirst = mFeatureFlagsService.syncFlags(inputFlagsFirst); - List outputFlagsSecond = mFeatureFlagsService.syncFlags(inputFlagsSecond); - - assertThat(inputFlagsFirst.size()).isEqualTo(outputFlagsFirst.size()); - assertThat(inputFlagsSecond.size()).isEqualTo(outputFlagsSecond.size()); - - // This test only cares that the "a" flag passed in the second time came out with the - // same value that was passed in the first time. - - boolean found = false; - for (SyncableFlag second : outputFlagsSecond) { - if (compareSyncableFlagsNames(second, inputFlagsFirst.get(0))) { - found = true; - assertThat(second.getValue()).isEqualTo(inputFlagsFirst.get(0).getValue()); - break; - } - } - - assertWithMessage( - "Failed to find flag " + inputFlagsFirst.get(0) + " in the second calls output") - .that(found).isTrue(); - } - - @Test - public void testQueryFlags_onlyOnce() { - List inputFlags = List.of( - new SyncableFlag(NS, "a", "false", false), - new SyncableFlag(NS, "b", "true", false), - new SyncableFlag(NS, "c", "false", false) - ); - - List outputFlags = mFeatureFlagsService.queryFlags(inputFlags); - - assertThat(inputFlags.size()).isEqualTo(outputFlags.size()); - - for (SyncableFlag inpF: inputFlags) { - boolean found = false; - for (SyncableFlag outF : outputFlags) { - if (compareSyncableFlagsNames(inpF, outF)) { - found = true; - break; - } - } - assertWithMessage("Failed to find input flag " + inpF + " in the output") - .that(found).isTrue(); - } - } - - @Test - public void testQueryFlags_twoCallsWithDifferentDefaults() { - List inputFlagsFirst = List.of( - new SyncableFlag(NS, "a", "false", false) - ); - List inputFlagsSecond = List.of( - new SyncableFlag(NS, "a", "true", false), - new SyncableFlag(NS, "b", "false", false) - ); - - List outputFlagsFirst = mFeatureFlagsService.queryFlags(inputFlagsFirst); - List outputFlagsSecond = mFeatureFlagsService.queryFlags(inputFlagsSecond); - - assertThat(inputFlagsFirst.size()).isEqualTo(outputFlagsFirst.size()); - assertThat(inputFlagsSecond.size()).isEqualTo(outputFlagsSecond.size()); - - // This test only cares that the "a" flag passed in the second time came out with the - // same value that was passed in (i.e. it wasn't cached). - - boolean found = false; - for (SyncableFlag second : outputFlagsSecond) { - if (compareSyncableFlagsNames(second, inputFlagsSecond.get(0))) { - found = true; - assertThat(second.getValue()).isEqualTo(inputFlagsSecond.get(0).getValue()); - break; - } - } - - assertWithMessage( - "Failed to find flag " + inputFlagsSecond.get(0) + " in the second calls output") - .that(found).isTrue(); - } - - @Test - public void testOverrideFlag() { - SyncableFlag f = new SyncableFlag(NS, "a", "false", false); - - mFeatureFlagsService.overrideFlag(f); - - verify(mFlagStore).set(f.getNamespace(), f.getName(), f.getValue()); - } - - @Test - public void testResetFlag() { - SyncableFlag f = new SyncableFlag(NS, "a", "false", false); - - mFeatureFlagsService.resetFlag(f); - - verify(mFlagStore).erase(f.getNamespace(), f.getName()); - } - - - private static boolean compareSyncableFlagsNames(SyncableFlag a, SyncableFlag b) { - return a.getNamespace().equals(b.getNamespace()) - && a.getName().equals(b.getName()) - && a.isDynamic() == b.isDynamic(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java b/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java deleted file mode 100644 index c2cf540d1d625..0000000000000 --- a/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2023 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.server.flags; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class FlagCacheTest { - private static final String NS = "ns"; - private static final String NAME = "name"; - - FlagCache mFlagCache = new FlagCache(); - - @Test - public void testGetOrNull_unset() { - assertThat(mFlagCache.getOrNull(NS, NAME)).isNull(); - } - - @Test - public void testGetOrSet_unset() { - assertThat(mFlagCache.getOrSet(NS, NAME, "value")).isEqualTo("value"); - } - - @Test - public void testGetOrSet_alreadySet() { - mFlagCache.setIfChanged(NS, NAME, "value"); - assertThat(mFlagCache.getOrSet(NS, NAME, "newvalue")).isEqualTo("value"); - } - - @Test - public void testSetIfChanged_unset() { - assertThat(mFlagCache.setIfChanged(NS, NAME, "value")).isTrue(); - } - - @Test - public void testSetIfChanged_noChange() { - mFlagCache.setIfChanged(NS, NAME, "value"); - assertThat(mFlagCache.setIfChanged(NS, NAME, "value")).isFalse(); - } - - @Test - public void testSetIfChanged_changing() { - mFlagCache.setIfChanged(NS, NAME, "value"); - assertThat(mFlagCache.setIfChanged(NS, NAME, "newvalue")).isTrue(); - } - - @Test - public void testContainsNamespace_unset() { - assertThat(mFlagCache.containsNamespace(NS)).isFalse(); - } - - @Test - public void testContainsNamespace_set() { - mFlagCache.setIfChanged(NS, NAME, "value"); - assertThat(mFlagCache.containsNamespace(NS)).isTrue(); - } - - @Test - public void testContains_unset() { - assertThat(mFlagCache.contains(NS, NAME)).isFalse(); - } - - @Test - public void testContains_set() { - mFlagCache.setIfChanged(NS, NAME, "value"); - assertThat(mFlagCache.contains(NS, NAME)).isTrue(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java b/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java deleted file mode 100644 index 6cc3acfb6125a..0000000000000 --- a/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2023 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.server.flags; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -@Presubmit -@SmallTest -public class FlagOverrideStoreTest { - private static final String NS = "ns"; - private static final String NAME = "name"; - private static final String PROP_NAME = FlagOverrideStore.getPropName(NS, NAME); - - @Rule - public final MockitoRule mockito = MockitoJUnit.rule(); - - @Mock - private SettingsProxy mSettingsProxy; - @Mock - private FlagOverrideStore.FlagChangeCallback mCallback; - - private FlagOverrideStore mFlagStore; - - @Before - public void setup() { - mFlagStore = new FlagOverrideStore(mSettingsProxy); - mFlagStore.setChangeCallback(mCallback); - } - - @Test - public void testSet_unset() { - mFlagStore.set(NS, NAME, "value"); - verify(mSettingsProxy).putString(PROP_NAME, "value"); - } - - @Test - public void testSet_setTwice() { - mFlagStore.set(NS, NAME, "value"); - mFlagStore.set(NS, NAME, "newvalue"); - verify(mSettingsProxy).putString(PROP_NAME, "value"); - verify(mSettingsProxy).putString(PROP_NAME, "newvalue"); - } - - @Test - public void testGet_unset() { - assertThat(mFlagStore.get(NS, NAME)).isNull(); - } - - @Test - public void testGet_set() { - when(mSettingsProxy.getString(PROP_NAME)).thenReturn("value"); - assertThat(mFlagStore.get(NS, NAME)).isEqualTo("value"); - } - - @Test - public void testErase() { - mFlagStore.erase(NS, NAME); - verify(mSettingsProxy).putString(PROP_NAME, null); - } - - @Test - public void testContains_unset() { - assertThat(mFlagStore.contains(NS, NAME)).isFalse(); - } - - @Test - public void testContains_set() { - when(mSettingsProxy.getString(PROP_NAME)).thenReturn("value"); - assertThat(mFlagStore.contains(NS, NAME)).isTrue(); - } - - @Test - public void testCallback_onSet() { - mFlagStore.set(NS, NAME, "value"); - verify(mCallback).onFlagChanged(NS, NAME, "value"); - } - - @Test - public void testCallback_onErase() { - mFlagStore.erase(NS, NAME); - verify(mCallback).onFlagChanged(NS, NAME, null); - } -} diff --git a/services/tests/servicestests/src/com/android/server/flags/OWNERS b/services/tests/servicestests/src/com/android/server/flags/OWNERS deleted file mode 100644 index 7ed369e371060..0000000000000 --- a/services/tests/servicestests/src/com/android/server/flags/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /services/flags/OWNERS