Revert "Define FeatureFlagsService."
Revert submission 23727772-b279054964-flag-api-udc-qpr-dev
Reason for revert: b/290929382
Reverted changes: /q/submissionid:23727772-b279054964-flag-api-udc-qpr-dev
Change-Id: I144a92ee478b76d98dc1a2a47533cb524c5fc056
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 236d0b5..f8f4cc3 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -34,15 +34,6 @@
}
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 2a6d84b..6d82922 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5298,13 +5298,6 @@
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.
*
* <p><b>NOTE: </b> this service is optional; callers of
diff --git a/core/java/android/flags/IFeatureFlags.aidl b/core/java/android/flags/IFeatureFlags.aidl
deleted file mode 100644
index 1eef47f..0000000
--- a/core/java/android/flags/IFeatureFlags.aidl
+++ /dev/null
@@ -1,65 +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<SyncableFlag> syncFlags(in List<SyncableFlag> 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);
-}
\ 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 f708667..0000000
--- 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/SyncableFlag.aidl b/core/java/android/flags/SyncableFlag.aidl
deleted file mode 100644
index 1526ec1..0000000
--- 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 54c4c6d..0000000
--- a/core/java/android/flags/SyncableFlag.java
+++ /dev/null
@@ -1,99 +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;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * @hide
- */
-public final class SyncableFlag implements Parcelable {
- private final String mNamespace;
- private final String mName;
- private String mValue;
- private final boolean mDynamic;
-
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public SyncableFlag(
- @NonNull String namespace,
- @NonNull String name,
- @NonNull String value,
- boolean dynamic) {
- mNamespace = namespace;
- mName = name;
- mValue = value;
- mDynamic = dynamic;
- }
-
- public void setValue(@NonNull String value) {
- mValue = value;
- }
-
- @NonNull
- public String getNamespace() {
- return mNamespace;
- }
-
- @NonNull
- public String getName() {
- return mName;
- }
-
- @NonNull
- public String getValue() {
- return mValue;
- }
-
- @NonNull
- public boolean isDynamic() {
- return mDynamic;
- }
-
- @NonNull
- public static final Parcelable.Creator<SyncableFlag> CREATOR = new Parcelable.Creator<>() {
- public SyncableFlag createFromParcel(Parcel in) {
- return new SyncableFlag(
- in.readString(), in.readString(), in.readString(), 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);
- }
-
- @Override
- public String toString() {
- return getNamespace() + "." + getName() + "[" + getValue() + "]";
- }
-}
diff --git a/services/Android.bp b/services/Android.bp
index 453f572..b0a0e5e 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -159,7 +159,6 @@
"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 29d2b9c..0000000
--- 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 3925b5c..0000000
--- 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 322a52bf..0000000
--- a/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java
+++ /dev/null
@@ -1,273 +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<DynamicFlagData> mDynamicFlags = new FlagCache<>();
- private final Map<Integer, Set<IFeatureFlagsCallback>> mCallbacks = new HashMap<>();
- private static final Function<Integer, Set<IFeatureFlagsCallback>> 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<IFeatureFlagsCallback> 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);
- }
-
- void syncDynamicFlag(int pid, SyncableFlag sf) {
- if (!sf.isDynamic()) {
- return;
- }
-
- 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.
-
- // If we already have a value cached, just use that.
- String value = null;
- DynamicFlagData data = mDynamicFlags.getOrNull(ns, name);
- if (data != null) {
- value = data.getValue();
- } else {
- // Put the value in the cache for future reference.
- data = new DynamicFlagData(ns, name);
- mDynamicFlags.setIfChanged(ns, 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(ns, 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(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());
-
- sf.setValue(value);
- }
-
-
- 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<IFeatureFlagsCallback> 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<IFeatureFlagsCallback> callbacks =
- mCallbacks.computeIfAbsent(pid, NEW_CALLBACK_SET);
- callbacks.remove(callback);
- }
- }
-
- private static class DynamicFlagData {
- private final String mNamespace;
- private final String mName;
- private final Set<Integer> 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 dc97fde..0000000
--- a/services/flags/java/com/android/server/flags/FeatureFlagsBinder.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 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 java.io.FileOutputStream;
-import java.util.List;
-
-class FeatureFlagsBinder extends IFeatureFlags.Stub {
- private final FlagOverrideStore mFlagStore;
- private final FlagsShellCommand mShellCommand;
- private final FlagCache<String> mFlagCache = new FlagCache<>();
- private final DynamicFlagBinderDelegate mDynamicFlagDelegate;
-
- FeatureFlagsBinder(FlagOverrideStore flagStore, FlagsShellCommand shellCommand) {
- mFlagStore = flagStore;
- mShellCommand = shellCommand;
- mDynamicFlagDelegate = new DynamicFlagBinderDelegate(flagStore);
- }
-
- @Override
- public void registerCallback(IFeatureFlagsCallback callback) {
- mDynamicFlagDelegate.registerCallback(getCallingPid(), callback);
- }
-
- @Override
- public void unregisterCallback(IFeatureFlagsCallback callback) {
- mDynamicFlagDelegate.unregisterCallback(getCallingPid(), callback);
- }
-
- @Override
- public List<SyncableFlag> syncFlags(List<SyncableFlag> incomingFlags) {
- int pid = getCallingPid();
- for (SyncableFlag sf : incomingFlags) {
- String ns = sf.getNamespace();
- String name = sf.getName();
- if (sf.isDynamic()) {
- 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);
- }
- sf.setValue(value);
- }
- }
- }
- return incomingFlags;
- }
-
- @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 111fad0..0000000
--- a/services/flags/java/com/android/server/flags/FeatureFlagsService.java
+++ /dev/null
@@ -1,59 +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.Context;
-import android.util.Slog;
-
-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");
- publishBinderService(
- Context.FEATURE_FLAGS_SERVICE, new FeatureFlagsBinder(mFlagStore, mShellCommand));
- }
-
-}
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 cee1578..0000000
--- 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 <V> The type of value to store.
- */
-public class FlagCache<V> {
- private final Function<String, HashMap<String, V>> mNewHashMap = k -> new HashMap<>();
-
- // Cache is organized first by namespace, then by name. All values are stored as strings.
- final Map<String, Map<String, V>> 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<String, V> 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<String, V> 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<String, V> 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<String, V> 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 9866b1c..0000000
--- a/services/flags/java/com/android/server/flags/FlagOverrideStore.java
+++ /dev/null
@@ -1,120 +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.<store> (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. */
- 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. */
- void erase(String namespace, String name) {
- set(namespace, name, null);
- }
-
- Map<String, Map<String, String>> getFlags() {
- return getFlagsForNamespace(null);
- }
-
- Map<String, Map<String, String>> 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<String, Map<String, String>> 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 b7896ee..0000000
--- 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 <namespace> <name>");
- outPw.println(" Return the string value of a specific flag, or <unset>");
- outPw.println();
- outPw.println(" set <namespace> <name> <value>");
- outPw.println(" Set a specific flag");
- outPw.println();
- outPw.println(" erase <namespace> <name>");
- 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<String, Map<String, String>> 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<String, Map<String, String>> namespace : overrides.entrySet()) {
- longestNamespaceLen = Math.max(longestNamespaceLen, namespace.getKey().length());
- for (Map.Entry<String, String> 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<String, Map<String, String>> namespace : overrides.entrySet()) {
- for (Map.Entry<String, String> 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] + " <namespace> <name> <value>`");
- 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] + " <namespace> <name>`");
- 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("<unset>");
- } 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] + " <namespace> <name>`");
- 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 acb7bb5..0000000
--- 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 c6e85d5..0000000
--- 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.
- * <p>
- * 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 7de1279..991248a 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.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 @@
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 0530f89..ee3a773 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -34,7 +34,6 @@
"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 8455b88..0000000
--- a/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java
+++ /dev/null
@@ -1,179 +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.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;
-
- private FeatureFlagsBinder mFeatureFlagsService;
-
- @Before
- public void setup() {
- when(mIFeatureFlagsCallback.asBinder()).thenReturn(mIFeatureFlagsCallbackAsBinder);
- mFeatureFlagsService = new FeatureFlagsBinder(mFlagStore, mFlagCommand);
- }
-
- @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 testSyncFlags_noOverrides() {
- List<SyncableFlag> inputFlags = List.of(
- new SyncableFlag(NS, "a", "false", false),
- new SyncableFlag(NS, "b", "true", false),
- new SyncableFlag(NS, "c", "false", false)
- );
-
- List<SyncableFlag> 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<SyncableFlag> 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<SyncableFlag> 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<SyncableFlag> inputFlagsFirst = List.of(
- new SyncableFlag(NS, "a", "false", false)
- );
- List<SyncableFlag> inputFlagsSecond = List.of(
- new SyncableFlag(NS, "a", "true", false),
- new SyncableFlag(NS, "b", "false", false)
- );
-
- List<SyncableFlag> outputFlagsFirst = mFeatureFlagsService.syncFlags(inputFlagsFirst);
- List<SyncableFlag> 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();
- }
-
- 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 c2cf540..0000000
--- 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 6cc3acf..0000000
--- 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 7ed369e..0000000
--- a/services/tests/servicestests/src/com/android/server/flags/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/flags/OWNERS