From de960774a08274e68a4021e706f7611f0c0a880e Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Wed, 12 Jul 2023 18:18:39 +0000 Subject: [PATCH] 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 --- core/java/Android.bp | 9 - core/java/android/content/Context.java | 7 - core/java/android/flags/IFeatureFlags.aidl | 65 --- .../android/flags/IFeatureFlagsCallback.aidl | 31 -- core/java/android/flags/SyncableFlag.aidl | 22 - core/java/android/flags/SyncableFlag.java | 99 ----- services/Android.bp | 1 - services/flags/Android.bp | 18 - services/flags/OWNERS | 6 - .../flags/DynamicFlagBinderDelegate.java | 273 ------------- .../server/flags/FeatureFlagsBinder.java | 86 ---- .../server/flags/FeatureFlagsService.java | 59 --- .../com/android/server/flags/FlagCache.java | 103 ----- .../server/flags/FlagOverrideStore.java | 120 ------ .../server/flags/FlagsShellCommand.java | 214 ---------- .../server/flags/GlobalSettingsProxy.java | 68 ---- .../android/server/flags/SettingsProxy.java | 381 ------------------ .../java/com/android/server/SystemServer.java | 7 - services/tests/servicestests/Android.bp | 1 - .../server/flags/FeatureFlagsServiceTest.java | 179 -------- .../android/server/flags/FlagCacheTest.java | 86 ---- .../server/flags/FlagOverrideStoreTest.java | 111 ----- .../src/com/android/server/flags/OWNERS | 1 - 23 files changed, 1947 deletions(-) delete mode 100644 core/java/android/flags/IFeatureFlags.aidl delete mode 100644 core/java/android/flags/IFeatureFlagsCallback.aidl delete mode 100644 core/java/android/flags/SyncableFlag.aidl delete mode 100644 core/java/android/flags/SyncableFlag.java delete mode 100644 services/flags/Android.bp delete mode 100644 services/flags/OWNERS delete mode 100644 services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java delete mode 100644 services/flags/java/com/android/server/flags/FeatureFlagsBinder.java delete mode 100644 services/flags/java/com/android/server/flags/FeatureFlagsService.java delete mode 100644 services/flags/java/com/android/server/flags/FlagCache.java delete mode 100644 services/flags/java/com/android/server/flags/FlagOverrideStore.java delete mode 100644 services/flags/java/com/android/server/flags/FlagsShellCommand.java delete mode 100644 services/flags/java/com/android/server/flags/GlobalSettingsProxy.java delete mode 100644 services/flags/java/com/android/server/flags/SettingsProxy.java delete mode 100644 services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/flags/OWNERS 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/IFeatureFlags.aidl b/core/java/android/flags/IFeatureFlags.aidl deleted file mode 100644 index 1eef47feaa8b3..0000000000000 --- 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 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); -} \ 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/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 54c4c6d5fdf96..0000000000000 --- 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 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 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 322a52bf01cc9..0000000000000 --- 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 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); - } - - 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 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); - } - } - - 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 dc97fde758dcc..0000000000000 --- 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 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 syncFlags(List 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 111fad0ce3b0a..0000000000000 --- 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 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 9866b1c7bb1cd..0000000000000 --- 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. (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> 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 8455b88ae9aff..0000000000000 --- 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 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(); - } - - 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