From 1a4363ad668ffba26de816b3e24179c35cf38208 Mon Sep 17 00:00:00 2001 From: Hassan Ali Date: Thu, 16 Mar 2023 20:03:06 +0000 Subject: Add SYNC_DISABLED_* constants to the API This was missed when the APIs were moving to the module. Bug: 271870457 Test: presubmit Change-Id: Ic1484520c224c542b2fc20ae9560cef907080bd6 --- framework/api/system-current.txt | 3 ++ framework/java/android/provider/DeviceConfig.java | 43 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 2e05d20..e118610 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -95,6 +95,9 @@ package android.provider { field public static final String NAMESPACE_UWB = "uwb"; field public static final String NAMESPACE_WEARABLE_SENSING = "wearable_sensing"; field public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot"; + field public static final int SYNC_DISABLED_MODE_NONE = 0; // 0x0 + field public static final int SYNC_DISABLED_MODE_PERSISTENT = 1; // 0x1 + field public static final int SYNC_DISABLED_MODE_UNTIL_REBOOT = 2; // 0x2 } public static class DeviceConfig.BadConfigException extends java.lang.Exception { diff --git a/framework/java/android/provider/DeviceConfig.java b/framework/java/android/provider/DeviceConfig.java index 454f577..4de7f40 100644 --- a/framework/java/android/provider/DeviceConfig.java +++ b/framework/java/android/provider/DeviceConfig.java @@ -23,6 +23,7 @@ import static android.Manifest.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -37,6 +38,11 @@ import android.util.Pair; import com.android.internal.annotations.GuardedBy; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -925,6 +931,43 @@ public final class DeviceConfig { @SystemApi public static final String NAMESPACE_REMOTE_AUTH = "remote_auth"; + /** + * The modes that can be used when disabling syncs to the 'config' settings. + * @hide + */ + @IntDef(prefix = "SYNC_DISABLED_MODE_", + value = { SYNC_DISABLED_MODE_NONE, SYNC_DISABLED_MODE_PERSISTENT, + SYNC_DISABLED_MODE_UNTIL_REBOOT }) + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + public @interface SyncDisabledMode {} + + /** + * Sync is not disabled. + * + * @hide + */ + @SystemApi + public static final int SYNC_DISABLED_MODE_NONE = 0; + + /** + * Disabling of Config bulk update / syncing is persistent, i.e. it survives a device + * reboot. + * + * @hide + */ + @SystemApi + public static final int SYNC_DISABLED_MODE_PERSISTENT = 1; + + /** + * Disabling of Config bulk update / syncing is not persistent, i.e. it will + * not survive a device reboot. + * + * @hide + */ + @SystemApi + public static final int SYNC_DISABLED_MODE_UNTIL_REBOOT = 2; + private static final Object sLock = new Object(); @GuardedBy("sLock") private static ArrayMap> sListeners = -- cgit v1.2.3 From 2062623b8e9b085add3e25aeb297f598d3fdae98 Mon Sep 17 00:00:00 2001 From: Pedro Loureiro Date: Fri, 30 Dec 2022 12:20:53 +0000 Subject: Extract Settings.Config logic and hide behind a generic data store This will make migrating to alternatives easier in the future. Test: no new logic; current tests should cover the new classes Bug: 263955152 Change-Id: Ibc5038f19d31de7a2e04758b4f2ebff4b80f3003 --- framework/java/android/provider/DeviceConfig.java | 35 +++++--- .../android/provider/DeviceConfigDataStore.java | 59 ++++++++++++++ .../android/provider/SettingsConfigDataStore.java | 95 ++++++++++++++++++++++ 3 files changed, 176 insertions(+), 13 deletions(-) create mode 100644 framework/java/android/provider/DeviceConfigDataStore.java create mode 100644 framework/java/android/provider/SettingsConfigDataStore.java diff --git a/framework/java/android/provider/DeviceConfig.java b/framework/java/android/provider/DeviceConfig.java index 88ff1f1..6b89e05 100644 --- a/framework/java/android/provider/DeviceConfig.java +++ b/framework/java/android/provider/DeviceConfig.java @@ -915,6 +915,8 @@ public final class DeviceConfig { private static Map> sNamespaces = new HashMap<>(); private static final String TAG = "DeviceConfig"; + private static final DeviceConfigDataStore sDataStore = new SettingsConfigDataStore(); + /** * Interface for monitoring callback functions. * @@ -991,8 +993,7 @@ public final class DeviceConfig { @NonNull @RequiresPermission(READ_DEVICE_CONFIG) public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) { - return new Properties(namespace, - Settings.Config.getStrings(namespace, Arrays.asList(names))); + return sDataStore.getProperties(namespace, names); } /** @@ -1132,7 +1133,7 @@ public final class DeviceConfig { @RequiresPermission(WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String namespace, @NonNull String name, @Nullable String value, boolean makeDefault) { - return Settings.Config.putString(namespace, name, value, makeDefault); + return sDataStore.setProperty(namespace, name, value, makeDefault); } /** @@ -1153,8 +1154,7 @@ public final class DeviceConfig { @SystemApi @RequiresPermission(WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull Properties properties) throws BadConfigException { - return Settings.Config.setStrings(properties.getNamespace(), - properties.mMap); + return sDataStore.setProperties(properties); } /** @@ -1169,7 +1169,7 @@ public final class DeviceConfig { @SystemApi @RequiresPermission(WRITE_DEVICE_CONFIG) public static boolean deleteProperty(@NonNull String namespace, @NonNull String name) { - return Settings.Config.deleteString(namespace, name); + return sDataStore.deleteProperty(namespace, name); } /** @@ -1200,7 +1200,7 @@ public final class DeviceConfig { @SystemApi @RequiresPermission(WRITE_DEVICE_CONFIG) public static void resetToDefaults(int resetMode, @Nullable String namespace) { - Settings.Config.resetToDefaults(resetMode, namespace); + sDataStore.resetToDefaults(resetMode, namespace); } /** @@ -1218,7 +1218,7 @@ public final class DeviceConfig { @SystemApi @RequiresPermission(WRITE_DEVICE_CONFIG) public static void setSyncDisabledMode(int syncDisabledMode) { - Settings.Config.setSyncDisabledMode(syncDisabledMode); + sDataStore.setSyncDisabledMode(syncDisabledMode); } /** @@ -1230,7 +1230,7 @@ public final class DeviceConfig { @SystemApi @RequiresPermission(WRITE_DEVICE_CONFIG) public static int getSyncDisabledMode() { - return Settings.Config.getSyncDisabledMode(); + return sDataStore.getSyncDisabledMode(); } /** @@ -1304,7 +1304,7 @@ public final class DeviceConfig { @NonNull ContentResolver resolver, @NonNull @CallbackExecutor Executor executor, @NonNull MonitorCallback callback) { - Settings.Config.setMonitorCallback(resolver, executor, callback); + sDataStore.setMonitorCallback(resolver, executor, callback); } /** @@ -1316,7 +1316,7 @@ public final class DeviceConfig { @SystemApi @RequiresPermission(Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS) public static void clearMonitorCallback(@NonNull ContentResolver resolver) { - Settings.Config.clearMonitorCallback(resolver); + sDataStore.clearMonitorCallback(resolver); } /** @@ -1342,7 +1342,7 @@ public final class DeviceConfig { } } }; - Settings.Config + sDataStore .registerContentObserver(namespace, true, contentObserver); sNamespaces.put(namespace, new Pair<>(contentObserver, 1)); } @@ -1366,7 +1366,7 @@ public final class DeviceConfig { sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second - 1)); } else { // Decrementing a namespace to zero means we no longer need its ContentObserver. - Settings.Config.unregisterContentObserver(namespaceCount.first); + sDataStore.unregisterContentObserver(namespaceCount.first); sNamespaces.remove(namespace); } } @@ -1585,6 +1585,15 @@ public final class DeviceConfig { } } + /** + * Returns a map with the underlying property values defined by this object + * + * @hide + */ + public @NonNull Map getPropertyValues() { + return new HashMap<>(mMap); + } + /** * Builder class for the construction of {@link Properties} objects. */ diff --git a/framework/java/android/provider/DeviceConfigDataStore.java b/framework/java/android/provider/DeviceConfigDataStore.java new file mode 100644 index 0000000..d74d833 --- /dev/null +++ b/framework/java/android/provider/DeviceConfigDataStore.java @@ -0,0 +1,59 @@ +/* + * 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.provider; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.provider.DeviceConfig; + +import java.util.concurrent.Executor; + +/** + * @hide + */ +public interface DeviceConfigDataStore { + + @NonNull DeviceConfig.Properties getProperties(@NonNull String namespace, @NonNull String ... names); + + boolean setProperties(@NonNull DeviceConfig.Properties properties) throws + DeviceConfig.BadConfigException; + + boolean setProperty(@NonNull String namespace, @NonNull String name, + @Nullable String value, boolean makeDefault); + + boolean deleteProperty(@NonNull String namespace, @NonNull String name); + + void resetToDefaults(int resetMode, @Nullable String namespace); + + void setSyncDisabledMode(int syncDisabledMode); + int getSyncDisabledMode(); + + void setMonitorCallback( + @NonNull ContentResolver resolver, + @NonNull @CallbackExecutor Executor executor, + @NonNull DeviceConfig.MonitorCallback callback); + + void clearMonitorCallback(@NonNull ContentResolver resolver); + + void registerContentObserver(@NonNull String namespace, boolean notifyForescendants, + ContentObserver contentObserver); + + void unregisterContentObserver(@NonNull ContentObserver contentObserver); +} diff --git a/framework/java/android/provider/SettingsConfigDataStore.java b/framework/java/android/provider/SettingsConfigDataStore.java new file mode 100644 index 0000000..f48017e --- /dev/null +++ b/framework/java/android/provider/SettingsConfigDataStore.java @@ -0,0 +1,95 @@ +/* + * 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.provider; + +import android.content.ContentResolver; +import android.database.ContentObserver; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.util.Arrays; +import java.util.concurrent.Executor; + +/** + * TODO: want to change the package of this class + * + * @hide + */ +public class SettingsConfigDataStore implements DeviceConfigDataStore { + @Override + public @NonNull DeviceConfig.Properties getProperties(@NonNull String namespace, + @NonNull String... names) { + return new DeviceConfig.Properties(namespace, + Settings.Config.getStrings(namespace, Arrays.asList(names))); + } + + @Override + public boolean setProperties(@NonNull DeviceConfig.Properties properties) + throws DeviceConfig.BadConfigException { + return Settings.Config.setStrings(properties.getNamespace(), + properties.getPropertyValues()); + } + + @Override + public boolean setProperty(@NonNull String namespace, @NonNull String name, + @Nullable String value, boolean makeDefault) { + return Settings.Config.putString(namespace, name, value, makeDefault); + } + + @Override + public boolean deleteProperty(@NonNull String namespace, @NonNull String name) { + return Settings.Config.deleteString(namespace, name); + } + + @Override + public void resetToDefaults(int resetMode, @Nullable String namespace) { + Settings.Config.resetToDefaults(resetMode, namespace); + } + + @Override + public void setSyncDisabledMode(int syncDisabledMode) { + Settings.Config.setSyncDisabledMode(syncDisabledMode); + } + + @Override + public int getSyncDisabledMode() { + return Settings.Config.getSyncDisabledMode(); + } + + @Override + public void setMonitorCallback(@NonNull ContentResolver resolver, @NonNull Executor executor, + @NonNull DeviceConfig.MonitorCallback callback) { + Settings.Config.setMonitorCallback(resolver, executor, callback); + } + + @Override + public void clearMonitorCallback(@NonNull ContentResolver resolver) { + Settings.Config.clearMonitorCallback(resolver); + } + + @Override + public void registerContentObserver(@NonNull String namespace, boolean notifyForescendants, + ContentObserver contentObserver) { + Settings.Config.registerContentObserver(namespace, true, contentObserver); + } + + @Override + public void unregisterContentObserver(@NonNull ContentObserver contentObserver) { + Settings.Config.unregisterContentObserver(contentObserver); + } +} -- cgit v1.2.3 From f28e3b712ee6ec944a3fa4674443153bc08baa56 Mon Sep 17 00:00:00 2001 From: Pedro Loureiro Date: Fri, 30 Dec 2022 19:29:41 +0000 Subject: Add device config database Includes a DataStore, DbAdapter, DbHelper and DeviceConfigService Test: follow-up Bug: 263955152 Change-Id: I89ceaa0692043a424b5e76844725a49fd3bb6a0e --- .../deviceconfig/DeviceConfigServiceImpl.java | 52 ++++++++ .../deviceconfig/db/DeviceConfigDbAdapter.java | 145 +++++++++++++++++++++ .../deviceconfig/db/DeviceConfigDbHelper.java | 84 ++++++++++++ 3 files changed, 281 insertions(+) create mode 100644 service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java create mode 100644 service/java/com/android/server/deviceconfig/db/DeviceConfigDbAdapter.java create mode 100644 service/java/com/android/server/deviceconfig/db/DeviceConfigDbHelper.java diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java b/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java new file mode 100644 index 0000000..7b51b6a --- /dev/null +++ b/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java @@ -0,0 +1,52 @@ +/* + * 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.deviceconfig; + +import android.content.Context; +import android.os.RemoteException; + +import com.android.server.deviceconfig.db.DeviceConfigDbAdapter; +import com.android.server.deviceconfig.db.DeviceConfigDbHelper; + +import java.util.Map; + +/** + * DeviceConfig Service implementation (updatable via Mainline) that uses a SQLite database as a storage mechanism + * for the configuration values. + * + * @hide + */ +public class DeviceConfigServiceImpl { + private final DeviceConfigDbAdapter mDbAdapter; + + public DeviceConfigServiceImpl(Context context) { + DeviceConfigDbHelper dbHelper = new DeviceConfigDbHelper(context); + mDbAdapter = new DeviceConfigDbAdapter(dbHelper.getWritableDatabase()); + } + + public Map getProperties(String namespace, String[] names) throws RemoteException { + return mDbAdapter.getValuesForNamespace(namespace, names); + } + + public boolean setProperties(String namespace, Map values) { + return mDbAdapter.setValues(namespace, values); + } + + public boolean setProperty(String namespace, String key, String value, boolean makeDefault) { + return mDbAdapter.setValue(namespace, key, value, makeDefault); + } +} diff --git a/service/java/com/android/server/deviceconfig/db/DeviceConfigDbAdapter.java b/service/java/com/android/server/deviceconfig/db/DeviceConfigDbAdapter.java new file mode 100644 index 0000000..2688a93 --- /dev/null +++ b/service/java/com/android/server/deviceconfig/db/DeviceConfigDbAdapter.java @@ -0,0 +1,145 @@ +/* + * 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.deviceconfig.db; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.text.TextUtils; + +import com.android.server.deviceconfig.db.DeviceConfigDbHelper.Contract.DeviceConfigEntry; + +import java.util.HashMap; +import java.util.Map; + +/** + * @hide + */ +public class DeviceConfigDbAdapter { + + private final SQLiteDatabase mDb; + + public DeviceConfigDbAdapter(SQLiteDatabase db) { + mDb = db; + } + + public Map getValuesForNamespace(String namespace, String... keys) { + + String[] projection = { + DeviceConfigEntry.COLUMN_NAME_KEY, + DeviceConfigEntry.COLUMN_NAME_VALUE + }; + + String selection; + String[] selectionArgs; + if (keys != null && keys.length > 0) { + selection = DeviceConfigEntry.COLUMN_NAME_NAMESPACE + " = ? " + + "and " + DeviceConfigEntry.COLUMN_NAME_KEY + " in ( ? ) "; + String keySelection = TextUtils.join(",", keys); + selectionArgs = new String[]{namespace, keySelection}; + } else { + selection = DeviceConfigEntry.COLUMN_NAME_NAMESPACE + " = ?"; + selectionArgs = new String[]{namespace}; + } + Cursor cursor = mDb.query( + DeviceConfigEntry.TABLE_NAME, + projection, + selection, + selectionArgs, + null, + null, + null + ); + + Map map = new HashMap<>(cursor.getCount()); + while (cursor.moveToNext()) { + String key = cursor.getString( + cursor.getColumnIndexOrThrow(DeviceConfigEntry.COLUMN_NAME_KEY)); + String value = cursor.getString( + cursor.getColumnIndexOrThrow(DeviceConfigEntry.COLUMN_NAME_VALUE)); + map.put(key, value); + } + cursor.close(); + return map; + } + + /** + * + * @return true if the data was inserted or updated in the database + */ + private boolean insertOrUpdateValue_inTransaction(String namespace, String key, String value) { + // TODO(b/265948914): see if this is the most performant way to either insert or update a record + ContentValues values = new ContentValues(); + values.put(DeviceConfigEntry.COLUMN_NAME_NAMESPACE, namespace); + values.put(DeviceConfigEntry.COLUMN_NAME_KEY, key); + values.put(DeviceConfigEntry.COLUMN_NAME_VALUE, value); + + String where = DeviceConfigEntry.COLUMN_NAME_NAMESPACE + " = ? " + + "and " + DeviceConfigEntry.COLUMN_NAME_VALUE + " = ? "; + + String[] whereArgs = {namespace, key}; + int updatedRows = mDb.update(DeviceConfigEntry.TABLE_NAME, values, where, whereArgs); + if (updatedRows == 0) { + // this is a new row, we need to insert it + long id = mDb.insert(DeviceConfigEntry.TABLE_NAME, null, values); + return id != -1; + } + return updatedRows > 0; + } + + /** + * Set or update the values in the map into the namespace. + * + * @return true if all values were set. Returns true if the map is empty. + */ + public boolean setValues(String namespace, Map map) { + if (map.size() == 0) { + return true; + } + boolean allSucceeded = true; + try { + mDb.beginTransaction(); + for (Map.Entry entry : map.entrySet()) { + // TODO(b/265948914) probably should call yieldIfContendedSafely in this loop + allSucceeded &= insertOrUpdateValue_inTransaction(namespace, entry.getKey(), + entry.getValue()); + } + mDb.setTransactionSuccessful(); + } finally { + mDb.endTransaction(); + } + return allSucceeded; + } + + /** + * + * @return true if the value was set + */ + public boolean setValue(String namespace, String key, String value, boolean makeDefault) { + HashMap map = new HashMap<>(); + map.put(key, value); + return setValues(namespace, map); + // TODO(b/265948914) implement make default! + } + + public void deleteValue(String namespace, String key) { + String where = DeviceConfigEntry.COLUMN_NAME_NAMESPACE + " = ? " + + "and " + DeviceConfigEntry.COLUMN_NAME_KEY + " = ? "; + String[] whereArgs = { namespace, key }; + mDb.delete(DeviceConfigEntry.TABLE_NAME, where, whereArgs); + } +} diff --git a/service/java/com/android/server/deviceconfig/db/DeviceConfigDbHelper.java b/service/java/com/android/server/deviceconfig/db/DeviceConfigDbHelper.java new file mode 100644 index 0000000..d7c90cc --- /dev/null +++ b/service/java/com/android/server/deviceconfig/db/DeviceConfigDbHelper.java @@ -0,0 +1,84 @@ +/* + * 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.deviceconfig.db; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.provider.BaseColumns; + +/** + * @hide + */ +public class DeviceConfigDbHelper extends SQLiteOpenHelper { + public static final int DATABASE_VERSION = 1; + public static final String DATABASE_NAME = "config_infrastructure.db"; + + /** + * TODO(b/265948914) / to consider: + * + * - enforce uniqueness of (namespace, key) pairs + * - synchronize calls that modify the db (maybe reads too?) + * - probably use a read/write lock + * - per-process caching of results so we don't go to the db every time + * - test the sql commands to make sure they work well (e.g. where clauses are + * written properly) + * - check the performance of the sql commands and look for optimizations + * - write a test for adapter.setProperties that has some but not all + * preexisting properties + * - Settings.Config has a concept "makeDefault" which is not implemented here + * - ensure that any sql exceptions are not thrown to the callers (where methods + * can return + * false) + * - see what happens if a caller starts observing changes before the database + * is loaded/ready (early in the boot process) + * - I've seen strict mode alerts about doing I/O in the main thread after a + * device boots. Maybe we can't avoid it but double check. + * - finish API implementation in DatabaseDataStore + */ + + interface Contract { + class DeviceConfigEntry implements BaseColumns { + public static final String TABLE_NAME = "config"; + public static final String COLUMN_NAME_NAMESPACE = "namespace"; + public static final String COLUMN_NAME_KEY = "config_key"; + public static final String COLUMN_NAME_VALUE = "config_value"; + } + } + + private static final String SQL_CREATE_ENTRIES = + "CREATE TABLE " + Contract.DeviceConfigEntry.TABLE_NAME + " (" + + Contract.DeviceConfigEntry._ID + " INTEGER PRIMARY KEY," + + Contract.DeviceConfigEntry.COLUMN_NAME_NAMESPACE + " TEXT," + + Contract.DeviceConfigEntry.COLUMN_NAME_KEY + " TEXT," + + Contract.DeviceConfigEntry.COLUMN_NAME_VALUE + " TEXT)"; + + public DeviceConfigDbHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(SQL_CREATE_ENTRIES); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // no op for now + } + +} -- cgit v1.2.3 From bd65c4ef7d0255f3c29dccffa2e7aca5a3e76061 Mon Sep 17 00:00:00 2001 From: Pedro Loureiro Date: Fri, 13 Jan 2023 18:18:50 +0000 Subject: Implement device config db and service Test: manual Bug: 265948913 Change-Id: I6225411bce87454529556be8cd17aacbdac6e25e --- apex/Android.bp | 10 ++ framework/Android.bp | 10 +- framework/java/android/provider/DeviceConfig.java | 17 ++- .../java/android/provider/DeviceConfigManager.java | 68 +++++++++ .../provider/DeviceConfigServiceDataStore.java | 159 +++++++++++++++++++++ .../provider/aidl/IDeviceConfigManager.aidl | 16 +++ .../server/deviceconfig/DeviceConfigInit.java | 19 ++- .../deviceconfig/DeviceConfigServiceImpl.java | 11 +- 8 files changed, 299 insertions(+), 11 deletions(-) create mode 100644 framework/java/android/provider/DeviceConfigManager.java create mode 100644 framework/java/android/provider/DeviceConfigServiceDataStore.java create mode 100644 framework/java/android/provider/aidl/IDeviceConfigManager.aidl diff --git a/apex/Android.bp b/apex/Android.bp index 3c91cce..799f316 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -32,6 +32,16 @@ bootclasspath_fragment { split_packages: [ "android.provider", ], + + // The following packages and all their subpackages currently only + // contain classes from this bootclasspath_fragment. Listing a package + // here won't prevent other bootclasspath modules from adding classes in + // any of those packages but it will prevent them from adding those + // classes into an API surface, e.g. public, system, etc.. Doing so will + // result in a build failure due to inconsistent flags. + package_prefixes: [ + "android.provider.aidl", + ], }, // The bootclasspath_fragments that provide APIs on which this depends. fragments: [ diff --git a/framework/Android.bp b/framework/Android.bp index 747fac1..3c54db9 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -18,9 +18,15 @@ package { java_sdk_library { name: "framework-configinfrastructure", - srcs: [ "java/**/*.java" ], + srcs: [ + "java/**/*.java", + "java/**/*.aidl", + ], defaults: ["framework-module-defaults"], - permitted_packages: [ "android.provider" ], + permitted_packages: [ + "android.provider", + "android.provider.aidl", + ], apex_available: [ "com.android.configinfrastructure", ], diff --git a/framework/java/android/provider/DeviceConfig.java b/framework/java/android/provider/DeviceConfig.java index 6b89e05..98cf94e 100644 --- a/framework/java/android/provider/DeviceConfig.java +++ b/framework/java/android/provider/DeviceConfig.java @@ -44,6 +44,13 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; +import android.util.Log; + +import android.provider.aidl.IDeviceConfigManager; +import android.provider.DeviceConfigServiceManager; +import android.provider.DeviceConfigInitializer; +import android.os.IBinder; + /** * Device level configuration parameters which can be tuned by a separate configuration service. * Namespaces that end in "_native" such as {@link #NAMESPACE_NETD_NATIVE} are intended to be used @@ -53,6 +60,14 @@ import java.util.concurrent.Executor; */ @SystemApi public final class DeviceConfig { + + /** + * The name of the service that provides the logic to these APIs + * + * @hide + */ + public static final String SERVICE_NAME = "device_config_updatable"; + /** * Namespace for all accessibility related features. * @@ -992,7 +1007,7 @@ public final class DeviceConfig { @SystemApi @NonNull @RequiresPermission(READ_DEVICE_CONFIG) - public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) { + public static Properties getProperties(@NonNull String namespace, @NonNull String... names) { return sDataStore.getProperties(namespace, names); } diff --git a/framework/java/android/provider/DeviceConfigManager.java b/framework/java/android/provider/DeviceConfigManager.java new file mode 100644 index 0000000..5ae25aa --- /dev/null +++ b/framework/java/android/provider/DeviceConfigManager.java @@ -0,0 +1,68 @@ +/* + * 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.provider; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import android.annotation.SystemService; +import android.os.RemoteException; +import android.provider.aidl.IDeviceConfigManager; + +import java.util.HashMap; +import java.util.Map; + +/** + * @hide + */ +@SystemService(DeviceConfig.SERVICE_NAME) +public class DeviceConfigManager { + + private IDeviceConfigManager mService; + + public DeviceConfigManager(@NonNull IDeviceConfigManager service) { + mService = service; + } + + @NonNull + public DeviceConfig.Properties getProperties(@NonNull String namespace, + @NonNull String... names) { + try { + Map map = mService.getProperties(namespace, names); + return new DeviceConfig.Properties(namespace, map); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public boolean setProperties(@NonNull String namespace, @NonNull Map values) { + try { + return mService.setProperties(namespace, values); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public boolean setProperty(@NonNull String namespace, @NonNull String name, + @Nullable String value, boolean makeDefault) { + try { + return mService.setProperty(namespace, name, value, makeDefault); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/framework/java/android/provider/DeviceConfigServiceDataStore.java b/framework/java/android/provider/DeviceConfigServiceDataStore.java new file mode 100644 index 0000000..ed78f68 --- /dev/null +++ b/framework/java/android/provider/DeviceConfigServiceDataStore.java @@ -0,0 +1,159 @@ +/* + * 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.provider; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.os.IBinder; +import android.provider.aidl.IDeviceConfigManager; +import android.util.Slog; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.concurrent.Executor; + +/** + * @hide + */ +public class DeviceConfigServiceDataStore /*implements DeviceConfigDataStore*/ { + + private static final boolean DEBUG = false; + private static final String TAG = "DeviceConfigServiceDataStore"; + private static final Object mLock = new Object(); + + // TODO(b/265948914): finish implementation of this data store and make it actually implement + // the interface + private DeviceConfigManager mManager; + + public DeviceConfigServiceDataStore() { + + } + + public DeviceConfigServiceDataStore(DeviceConfigManager deviceConfigManager) { + mManager = deviceConfigManager; + } + + private @Nullable DeviceConfigManager createManagerIfNeeded() { + if (mManager != null) { + return mManager; + } + synchronized (mLock) { + if (mManager != null) { + return mManager; + } + + IBinder binder = DeviceConfigInitializer.getDeviceConfigServiceManager() + .getDeviceConfigUpdatableServiceRegisterer() + .get(); + + if (binder != null) { + IDeviceConfigManager manager = IDeviceConfigManager.Stub.asInterface( + binder); + mManager = new DeviceConfigManager(manager); + } + return mManager; + } + } + +// @Override + @NonNull + public DeviceConfig.Properties getProperties(@NonNull String namespace, + @NonNull String... names) { + createManagerIfNeeded(); + + if (mManager == null) { + if(DEBUG) { + Slog.d(TAG, "ServiceDS - getProperties before ready " + namespace + " " + + Arrays.toString(names)); + } + return new DeviceConfig.Properties(namespace, new HashMap<>()); + } + return mManager.getProperties(namespace, names); + } + +// @Override + public boolean setProperties(@NonNull DeviceConfig.Properties properties) + throws DeviceConfig.BadConfigException { + createManagerIfNeeded(); + if (mManager == null) { + if(DEBUG) { + Slog.d(TAG, "ServiceDS - setProperties before ready " + properties.getNamespace() + " " + properties); + } + return false; + } + return mManager.setProperties(properties.getNamespace(), properties.getPropertyValues()); + } +// +// @Override + public boolean setProperty(@NonNull String namespace, @NonNull String name, + @Nullable String value, boolean makeDefault) { + createManagerIfNeeded(); + if (mManager == null) { + if (DEBUG) { + Slog.d(TAG, "ServiceDS - setProperty before ready " + namespace + " " + name); + } + return false; + } + return mManager.setProperty(namespace, name, value, makeDefault); + } +// +// @Override + public boolean deleteProperty(@NonNull String namespace, @NonNull String name) { + // return mManager.deleteProperty(namespace, name); + return false; + } +// +// @Override + public void resetToDefaults(int resetMode, @Nullable String namespace) { +// mManager.resetToDefaults(resetMode, namespace); + } +// +// @Override + public void setSyncDisabledMode(int syncDisabledMode) { +// mManager.setSyncDisabledMode(syncDisabledMode); + } +// +// @Override + public int getSyncDisabledMode() { + // return mManager.getSyncDisabledMode(); + return 0; + } +// +// @Override + public void setMonitorCallback(@NonNull ContentResolver resolver, @NonNull Executor executor, + @NonNull DeviceConfig.MonitorCallback callback) { +// mManager.setMonitorCallback(resolver, executor, callback); + } +// +// @Override + public void clearMonitorCallback(@NonNull ContentResolver resolver) { +// mManager.clearMonitorCallback(resolver); + } +// +// @Override + public void registerContentObserver(@NonNull String namespace, boolean notifyForDescendants, + ContentObserver contentObserver) { +// mManager.registerContentObserver(namespace, notifyForescendants, contentObserver); + } +// +// @Override + public void unregisterContentObserver(@NonNull ContentObserver contentObserver) { +// mManager.unregisterContentObserver(contentObserver); + } +} diff --git a/framework/java/android/provider/aidl/IDeviceConfigManager.aidl b/framework/java/android/provider/aidl/IDeviceConfigManager.aidl new file mode 100644 index 0000000..b6c74af --- /dev/null +++ b/framework/java/android/provider/aidl/IDeviceConfigManager.aidl @@ -0,0 +1,16 @@ +package android.provider.aidl; + +/** + * {@hide} + */ +interface IDeviceConfigManager { +// TODO(b/265948914): maybe rename this IDeviceConfigService ? ManagerService? + + Map getProperties(String namespace, in String[] names); + + boolean setProperties(String namespace, in Map values); + + boolean setProperty(String namespace, String key, String value, boolean makeDefault); + + // TODO(b/265948914): add remaining methods +} diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigInit.java b/service/java/com/android/server/deviceconfig/DeviceConfigInit.java index 0921d81..de8c7f5 100644 --- a/service/java/com/android/server/deviceconfig/DeviceConfigInit.java +++ b/service/java/com/android/server/deviceconfig/DeviceConfigInit.java @@ -6,8 +6,12 @@ import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.Context; import android.os.Binder; +import android.provider.DeviceConfig; +import android.provider.DeviceConfigManager; import android.provider.UpdatableDeviceConfigServiceReadiness; +import android.provider.aidl.IDeviceConfigManager; + import com.android.server.SystemService; /** @hide */ @@ -21,25 +25,26 @@ public class DeviceConfigInit { /** @hide */ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public static class Lifecycle extends SystemService { - private DeviceConfigShellService mShellService; + private DeviceConfigServiceImpl mService; /** @hide */ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public Lifecycle(@NonNull Context context) { super(context); - // this service is always instantiated but should only launch subsequent services + // this service is always instantiated but should only launch subsequent service(s) // if the module is ready if (UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) { - mShellService = new DeviceConfigShellService(); + mService = new DeviceConfigServiceImpl(getContext()); + publishBinderService(DeviceConfig.SERVICE_NAME, mService); } } - /** @hide */ + /** + * @hide + */ @Override public void onStart() { - if (UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) { - publishBinderService("device_config_updatable", mShellService); - } + // no op } } } diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java b/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java index 7b51b6a..0fec796 100644 --- a/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java +++ b/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java @@ -18,6 +18,8 @@ package com.android.server.deviceconfig; import android.content.Context; import android.os.RemoteException; +import android.provider.aidl.IDeviceConfigManager; +import android.provider.DeviceConfigInitializer; import com.android.server.deviceconfig.db.DeviceConfigDbAdapter; import com.android.server.deviceconfig.db.DeviceConfigDbHelper; @@ -30,22 +32,29 @@ import java.util.Map; * * @hide */ -public class DeviceConfigServiceImpl { +public class DeviceConfigServiceImpl extends IDeviceConfigManager.Stub { private final DeviceConfigDbAdapter mDbAdapter; public DeviceConfigServiceImpl(Context context) { DeviceConfigDbHelper dbHelper = new DeviceConfigDbHelper(context); mDbAdapter = new DeviceConfigDbAdapter(dbHelper.getWritableDatabase()); + + DeviceConfigInitializer.getDeviceConfigServiceManager() + .getDeviceConfigUpdatableServiceRegisterer() + .register(this); } + @Override public Map getProperties(String namespace, String[] names) throws RemoteException { return mDbAdapter.getValuesForNamespace(namespace, names); } + @Override public boolean setProperties(String namespace, Map values) { return mDbAdapter.setValues(namespace, values); } + @Override public boolean setProperty(String namespace, String key, String value, boolean makeDefault) { return mDbAdapter.setValue(namespace, key, value, makeDefault); } -- cgit v1.2.3 From d09294795348cb5194e00347aeb85bff85d8d6e0 Mon Sep 17 00:00:00 2001 From: Pedro Loureiro Date: Mon, 6 Feb 2023 21:18:52 +0000 Subject: Implement shell command handler Bug: 265948913 Test: manual Change-Id: Ic6d69a90ea23bb9d8f119667b21341b674c9cff7 --- .../deviceconfig/DeviceConfigServiceImpl.java | 32 +++++++++++++++++ .../deviceconfig/DeviceConfigShellService.java | 40 ---------------------- 2 files changed, 32 insertions(+), 40 deletions(-) delete mode 100644 service/java/com/android/server/deviceconfig/DeviceConfigShellService.java diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java b/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java index 0fec796..243fd01 100644 --- a/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java +++ b/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java @@ -16,7 +16,9 @@ package com.android.server.deviceconfig; +import android.annotation.NonNull; import android.content.Context; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.provider.aidl.IDeviceConfigManager; import android.provider.DeviceConfigInitializer; @@ -24,8 +26,11 @@ import android.provider.DeviceConfigInitializer; import com.android.server.deviceconfig.db.DeviceConfigDbAdapter; import com.android.server.deviceconfig.db.DeviceConfigDbHelper; +import java.io.PrintWriter; import java.util.Map; +import com.android.modules.utils.BasicShellCommandHandler; + /** * DeviceConfig Service implementation (updatable via Mainline) that uses a SQLite database as a storage mechanism * for the configuration values. @@ -58,4 +63,31 @@ public class DeviceConfigServiceImpl extends IDeviceConfigManager.Stub { public boolean setProperty(String namespace, String key, String value, boolean makeDefault) { return mDbAdapter.setValue(namespace, key, value, makeDefault); } + + @Override + public int handleShellCommand(@NonNull ParcelFileDescriptor in, + @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, + @NonNull String[] args) { + return (new MyShellCommand()).exec( + this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), + args); + } + + static final class MyShellCommand extends BasicShellCommandHandler { + + @Override + public int onCommand(String cmd) { + if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) { + onHelp(); + return -1; + } + return -1; + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("Device Config implemented in mainline"); + } + } } diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigShellService.java b/service/java/com/android/server/deviceconfig/DeviceConfigShellService.java deleted file mode 100644 index 14ced76..0000000 --- a/service/java/com/android/server/deviceconfig/DeviceConfigShellService.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.android.server.deviceconfig; - -import java.io.PrintWriter; - -import android.annotation.NonNull; -import android.os.Binder; -import android.os.ParcelFileDescriptor; - -import com.android.modules.utils.BasicShellCommandHandler; - -/** @hide */ -public class DeviceConfigShellService extends Binder { - - @Override - public int handleShellCommand(@NonNull ParcelFileDescriptor in, - @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, - @NonNull String[] args) { - return (new MyShellCommand()).exec( - this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), - args); - } - - static final class MyShellCommand extends BasicShellCommandHandler { - - @Override - public int onCommand(String cmd) { - if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) { - onHelp(); - return -1; - } - return -1; - } - - @Override - public void onHelp() { - PrintWriter pw = getOutPrintWriter(); - pw.println("Device Config implemented in mainline"); - } - } -} -- cgit v1.2.3 From 9d256db446fefefc4a984c8808a3f8f41fe09be5 Mon Sep 17 00:00:00 2001 From: Pedro Loureiro Date: Mon, 6 Mar 2023 16:23:16 +0000 Subject: Implement deleteProperty Bug: 265948914 Test: manual Change-Id: I14d867bd9b861573b9f9ed22adcd792ced25c507 --- framework/java/android/provider/DeviceConfigManager.java | 8 ++++++++ .../java/android/provider/DeviceConfigServiceDataStore.java | 10 ++++++++-- framework/java/android/provider/aidl/IDeviceConfigManager.aidl | 2 ++ .../android/server/deviceconfig/DeviceConfigServiceImpl.java | 6 ++++++ .../android/server/deviceconfig/db/DeviceConfigDbAdapter.java | 9 +++++++-- 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/framework/java/android/provider/DeviceConfigManager.java b/framework/java/android/provider/DeviceConfigManager.java index 5ae25aa..3e50b8a 100644 --- a/framework/java/android/provider/DeviceConfigManager.java +++ b/framework/java/android/provider/DeviceConfigManager.java @@ -65,4 +65,12 @@ public class DeviceConfigManager { throw e.rethrowFromSystemServer(); } } + + public boolean deleteProperty(@NonNull String namespace, @NonNull String name) { + try { + return mService.deleteProperty(namespace, name); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/framework/java/android/provider/DeviceConfigServiceDataStore.java b/framework/java/android/provider/DeviceConfigServiceDataStore.java index ed78f68..cdcd5a8 100644 --- a/framework/java/android/provider/DeviceConfigServiceDataStore.java +++ b/framework/java/android/provider/DeviceConfigServiceDataStore.java @@ -115,8 +115,14 @@ public class DeviceConfigServiceDataStore /*implements DeviceConfigDataStore*/ { // // @Override public boolean deleteProperty(@NonNull String namespace, @NonNull String name) { - // return mManager.deleteProperty(namespace, name); - return false; + createManagerIfNeeded(); + if (mManager == null) { + if (DEBUG) { + Slog.d(TAG, "ServiceDS - setProperty before ready " + namespace + " " + name); + } + return false; + } + return mManager.deleteProperty(namespace, name); } // // @Override diff --git a/framework/java/android/provider/aidl/IDeviceConfigManager.aidl b/framework/java/android/provider/aidl/IDeviceConfigManager.aidl index b6c74af..eef6e77 100644 --- a/framework/java/android/provider/aidl/IDeviceConfigManager.aidl +++ b/framework/java/android/provider/aidl/IDeviceConfigManager.aidl @@ -12,5 +12,7 @@ interface IDeviceConfigManager { boolean setProperty(String namespace, String key, String value, boolean makeDefault); + boolean deleteProperty(String namespace, String key); + // TODO(b/265948914): add remaining methods } diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java b/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java index 243fd01..ddc69db 100644 --- a/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java +++ b/service/java/com/android/server/deviceconfig/DeviceConfigServiceImpl.java @@ -64,6 +64,11 @@ public class DeviceConfigServiceImpl extends IDeviceConfigManager.Stub { return mDbAdapter.setValue(namespace, key, value, makeDefault); } + @Override + public boolean deleteProperty(String namespace, String key) { + return mDbAdapter.deleteValue(namespace, key); + } + @Override public int handleShellCommand(@NonNull ParcelFileDescriptor in, @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, @@ -74,6 +79,7 @@ public class DeviceConfigServiceImpl extends IDeviceConfigManager.Stub { } static final class MyShellCommand extends BasicShellCommandHandler { + // TODO(b/265948938) implement this @Override public int onCommand(String cmd) { diff --git a/service/java/com/android/server/deviceconfig/db/DeviceConfigDbAdapter.java b/service/java/com/android/server/deviceconfig/db/DeviceConfigDbAdapter.java index 2688a93..52ee5bd 100644 --- a/service/java/com/android/server/deviceconfig/db/DeviceConfigDbAdapter.java +++ b/service/java/com/android/server/deviceconfig/db/DeviceConfigDbAdapter.java @@ -136,10 +136,15 @@ public class DeviceConfigDbAdapter { // TODO(b/265948914) implement make default! } - public void deleteValue(String namespace, String key) { + /** + * + * @return true if any value was deleted + */ + public boolean deleteValue(String namespace, String key) { String where = DeviceConfigEntry.COLUMN_NAME_NAMESPACE + " = ? " + "and " + DeviceConfigEntry.COLUMN_NAME_KEY + " = ? "; String[] whereArgs = { namespace, key }; - mDb.delete(DeviceConfigEntry.TABLE_NAME, where, whereArgs); + int count = mDb.delete(DeviceConfigEntry.TABLE_NAME, where, whereArgs); + return count > 0; } } -- cgit v1.2.3 From d78010d41b879a134436fc12d6e6809e1319ae1b Mon Sep 17 00:00:00 2001 From: Kai Shi Date: Mon, 15 May 2023 12:06:54 -0700 Subject: Add the P/H experiment support for NFC Bug: 282814176 Test: manual compilation Change-Id: Idca6d77aaeab4e6b18e5ae0715a30b238ab7c87a --- framework/api/system-current.txt | 1 + framework/java/android/provider/DeviceConfig.java | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index cfe7cf7..871dbf8 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -63,6 +63,7 @@ package android.provider { field public static final String NAMESPACE_MEDIA_NATIVE = "media_native"; field public static final String NAMESPACE_NEARBY = "nearby"; field public static final String NAMESPACE_NETD_NATIVE = "netd_native"; + field public static final String NAMESPACE_NFC = "nfc"; field public static final String NAMESPACE_NNAPI_NATIVE = "nnapi_native"; field public static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization"; field public static final String NAMESPACE_OTA = "ota"; diff --git a/framework/java/android/provider/DeviceConfig.java b/framework/java/android/provider/DeviceConfig.java index b7fcad7..cff02d7 100644 --- a/framework/java/android/provider/DeviceConfig.java +++ b/framework/java/android/provider/DeviceConfig.java @@ -954,6 +954,14 @@ public final class DeviceConfig { @SystemApi public static final String NAMESPACE_REMOTE_AUTH = "remote_auth"; + /** + * Namespace for all near field communication (nfc) related features. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_NFC = "nfc"; + /** * The modes that can be used when disabling syncs to the 'config' settings. * @hide -- cgit v1.2.3 From 464f490e52a341e8c412a81a80c322c99c8a0b15 Mon Sep 17 00:00:00 2001 From: Motomu Utsumi Date: Fri, 12 May 2023 16:59:53 +0900 Subject: Add tethering native experiment namespace String to DeviceConfig Bug: 281944942 Bug: 282593625 Test: m Change-Id: I19f35705c6535412a0317f2cc305b5a93f5d1f88 --- framework/api/module-lib-current.txt | 1 + framework/java/android/provider/DeviceConfig.java | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 739f954..63b7a12 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -25,6 +25,7 @@ package android.provider { field public static final String NAMESPACE_SETTINGS_STATS = "settings_stats"; field public static final String NAMESPACE_SETTINGS_UI = "settings_ui"; field public static final String NAMESPACE_TARE = "tare"; + field public static final String NAMESPACE_TETHERING_U_OR_LATER_NATIVE = "tethering_u_or_later_native"; field public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native"; field public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot"; field public static final String NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE = "virtualization_framework_native"; diff --git a/framework/java/android/provider/DeviceConfig.java b/framework/java/android/provider/DeviceConfig.java index cff02d7..1532516 100644 --- a/framework/java/android/provider/DeviceConfig.java +++ b/framework/java/android/provider/DeviceConfig.java @@ -954,6 +954,19 @@ public final class DeviceConfig { @SystemApi public static final String NAMESPACE_REMOTE_AUTH = "remote_auth"; + + /** + * Namespace for tethering module native features. + * Flags defined in this namespace are only usable on + * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and newer. + * On older Android releases, they will not be propagated to native code. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final String NAMESPACE_TETHERING_U_OR_LATER_NATIVE = + "tethering_u_or_later_native"; + /** * Namespace for all near field communication (nfc) related features. * -- cgit v1.2.3 From 10beee206508f06a69871dc02be7de86cdca4cca Mon Sep 17 00:00:00 2001 From: Ted Bauer Date: Thu, 15 Jun 2023 18:07:36 +0000 Subject: Apply staged flags on reboot. Test: tested manually on personal device; tests forthcoming Bug: 286057899 Change-Id: Ia0a149aaf6bbfcccc215b60758128189b1f7197c --- framework/java/android/provider/DeviceConfig.java | 19 +++++++ .../deviceconfig/DeviceConfigApplyStagedFlags.java | 58 ++++++++++++++++++++++ .../server/deviceconfig/DeviceConfigInit.java | 14 ++++++ 3 files changed, 91 insertions(+) create mode 100644 service/java/com/android/server/deviceconfig/DeviceConfigApplyStagedFlags.java diff --git a/framework/java/android/provider/DeviceConfig.java b/framework/java/android/provider/DeviceConfig.java index 1532516..6c9da59 100644 --- a/framework/java/android/provider/DeviceConfig.java +++ b/framework/java/android/provider/DeviceConfig.java @@ -69,6 +69,25 @@ import android.os.IBinder; @SystemApi public final class DeviceConfig { + /** + * Namespace where flags are staged to be applied on reboot. + * + * @hide + */ + public static final String NAMESPACE_REBOOT_STAGING = "staged"; + + /** + * Delimiter for flags in the {@code staged} namespace. + * + * Flags in this namespace will be applied on reboot. Each flag + * in this namespace follows the format {@code namespace*flag}, + * where {@code namespace} is the namespace the flag will be written + * to on reboot, and {@code flag} is the name of the flag. + * + * @hide + */ + public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*"; + /** * The name of the service that provides the logic to these APIs * diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigApplyStagedFlags.java b/service/java/com/android/server/deviceconfig/DeviceConfigApplyStagedFlags.java new file mode 100644 index 0000000..14be86d --- /dev/null +++ b/service/java/com/android/server/deviceconfig/DeviceConfigApplyStagedFlags.java @@ -0,0 +1,58 @@ +package com.android.server.deviceconfig; + +import android.annotation.SystemApi; +import android.provider.DeviceConfig; +import android.util.Slog; + +import java.util.Map; + +/** + * Applies all reboot-staged flags. + * + * Currently, flags are staged by being stored in a special DeviceConfig namespace. + */ +class DeviceConfigApplyStagedFlags { + private static final String TAG = "DeviceConfigApplyStagedFlags"; + private static final String ESCAPE_SLASHES = "\\"; + + private DeviceConfigApplyStagedFlags() {} + + /** + * Applies all flags that were staged for reboot. + * + * Flags are staged by being stored in the DeviceConfig namespace "staged". + * They're stored there with the name {@code namespace*flagName}. + * + */ + public static void applyStagedFlags() { + DeviceConfig.Properties stagedProperties = + DeviceConfig.getProperties(DeviceConfig.NAMESPACE_REBOOT_STAGING); + + for (Map.Entry entry : stagedProperties.getPropertyValues().entrySet()) { + String name = entry.getKey(); + String value = entry.getValue(); + + String delimiter = ESCAPE_SLASHES + DeviceConfig.NAMESPACE_REBOOT_STAGING_DELIMITER; + String[] parts = name.split(delimiter); + if (parts.length != 2) { + Slog.w(TAG, "staged flag name '" + + name + + "' didn't follow 'namespace*flag' format, not applying"); + continue; + } + String namespace = parts[0]; + String flagName = parts[1]; + + if (DeviceConfig.setProperty(namespace, flagName, value, true)) { + if (!DeviceConfig.deleteProperty( + DeviceConfig.NAMESPACE_REBOOT_STAGING, name)) { + Slog.w(TAG, "failed to delete staged flag '" + + namespace + "/" + flagName + ":" + value + "'"); + } + } else { + Slog.w(TAG, "failed to apply staged flag '" + + namespace + "/" + flagName + ":" + value + "'"); + } + } + } +} diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigInit.java b/service/java/com/android/server/deviceconfig/DeviceConfigInit.java index de8c7f5..a06e3f2 100644 --- a/service/java/com/android/server/deviceconfig/DeviceConfigInit.java +++ b/service/java/com/android/server/deviceconfig/DeviceConfigInit.java @@ -46,5 +46,19 @@ public class DeviceConfigInit { public void onStart() { // no op } + + /** + * Apply staged flags on boot. + * + * @param phase one of {@code SystemService.BootPhase} + * @hide + */ + @Override + public void onBootPhase(/* @BootPhase */ int phase) { + // TODO(b/286057899): move this earlier in the boot process + if (phase == SystemService.PHASE_BOOT_COMPLETED) { + DeviceConfigApplyStagedFlags.applyStagedFlags(); + } + } } } -- cgit v1.2.3 From 1876b2fe41a64cb9358d0630642b823806e1537d Mon Sep 17 00:00:00 2001 From: Pedro Loureiro Date: Wed, 31 May 2023 17:57:13 +0000 Subject: Add DeviceConfig bootstrap values parsing logic This feature will allow to override feature flags at boot time, to be used in testing. The key advantage is that we can quickly switch between different sets of features enabled without recompiling the whole OS. Test: m ConfigInfrastructureServiceUnitTests Bug: 285261745 Change-Id: Idcdd6aacfa5b8e3023f2514b93e9888cc783233e --- service/Android.bp | 4 + .../deviceconfig/DeviceConfigBootstrapValues.java | 126 +++++++++++++++++++++ .../server/deviceconfig/DeviceConfigInit.java | 21 ++++ service/javatests/Android.bp | 53 +++++++++ service/javatests/AndroidManifest.xml | 34 ++++++ service/javatests/AndroidTest.xml | 46 ++++++++ service/javatests/data/bootstrap1.txt | 3 + .../DeviceConfigBootstrapValuesTest.java | 63 +++++++++++ 8 files changed, 350 insertions(+) create mode 100644 service/java/com/android/server/deviceconfig/DeviceConfigBootstrapValues.java create mode 100644 service/javatests/Android.bp create mode 100644 service/javatests/AndroidManifest.xml create mode 100644 service/javatests/AndroidTest.xml create mode 100644 service/javatests/data/bootstrap1.txt create mode 100644 service/javatests/src/com/android/server/deviceconfig/DeviceConfigBootstrapValuesTest.java diff --git a/service/Android.bp b/service/Android.bp index 240fe5f..88bc4eb 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -31,6 +31,7 @@ java_sdk_library { "com.android.configinfrastructure", ], static_libs: [ + "modules-utils-build", "modules-utils-shell-command-handler", ], libs: [ @@ -39,4 +40,7 @@ java_sdk_library { min_sdk_version: "UpsideDownCake", sdk_version: "system_server_current", jarjar_rules: "jarjar-rules.txt", + impl_library_visibility: [ + "//packages/modules/ConfigInfrastructure/service/javatests", + ], } diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigBootstrapValues.java b/service/java/com/android/server/deviceconfig/DeviceConfigBootstrapValues.java new file mode 100644 index 0000000..b480bc2 --- /dev/null +++ b/service/java/com/android/server/deviceconfig/DeviceConfigBootstrapValues.java @@ -0,0 +1,126 @@ +/* + * 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.deviceconfig; + +import android.annotation.SuppressLint; +import android.provider.DeviceConfig; +import android.util.Slog; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +/** + * @hide + */ +public class DeviceConfigBootstrapValues { + private static final String TAG = "DeviceConfig"; + private static final String SYSTEM_OVERRIDES_PATH = "file:///system/etc/device-config-defaults"; + private static final String META_NAMESPACE = "DeviceConfigBootstrapValues"; + private static final String META_KEY = "processed_values"; + + private final String defaultValuesPath; + + public DeviceConfigBootstrapValues() { + this(SYSTEM_OVERRIDES_PATH); + } + + public DeviceConfigBootstrapValues(String defaultValuesPath) { + this.defaultValuesPath = defaultValuesPath; + } + + /** + * Performs the logic to apply bootstrap values when needed. + * + * If a file with the bootstrap values exists and they haven't been parsed before, + * it will parse the file and apply the values. + * + * @throws IOException if there's a problem reading the bootstrap file + * @throws RuntimeException if setting the values in DeviceConfig throws an exception + */ + public void applyValuesIfNeeded() throws IOException { + if (getPath().toFile().exists()) { + if (checkIfHasAlreadyParsedBootstrapValues()) { + Slog.i(TAG, "Bootstrap values already parsed, not processing again"); + } else { + parseAndApplyBootstrapValues(); + Slog.i(TAG, "Parsed bootstrap values"); + } + } else { + Slog.i(TAG, "Bootstrap values not found"); + } + } + + @SuppressLint("MissingPermission") + private boolean checkIfHasAlreadyParsedBootstrapValues() { + DeviceConfig.Properties properties = DeviceConfig.getProperties(META_NAMESPACE); + return properties.getKeyset().size() > 0; + } + + @SuppressLint("MissingPermission") + private void parseAndApplyBootstrapValues() throws IOException { + Path path = getPath(); + try (Stream lines = Files.lines(path)) { + lines.forEach(line -> processLine(line)); + } + // store a property in DeviceConfig so that we know we have successufully + // processed this + writeToDeviceConfig(META_NAMESPACE, META_KEY, "true"); + } + + private void processLine(String line) { + // contents for each line: + // :.=[enabled|disabled] + // we actually use . combined in calls into DeviceConfig + int namespaceDelimiter = line.indexOf(':'); + String namespace = line.substring(0, namespaceDelimiter); + if (namespaceDelimiter < 1) { + throw new IllegalArgumentException("Unexpectedly found : at index " + + namespaceDelimiter); + } + int valueDelimiter = line.indexOf('='); + if (valueDelimiter < 5) { + throw new IllegalArgumentException("Unexpectedly found = at index " + valueDelimiter); + } + String key = line.substring(namespaceDelimiter + 1, valueDelimiter); + String value = line.substring(valueDelimiter + 1); + String val; + if ("enabled".equals(value)) { + val = "true"; + } else if ("disabled".equals(value)) { + val = "false"; + } else { + throw new IllegalArgumentException("Received unexpected value: " + value); + } + writeToDeviceConfig(namespace, key, val); + } + + @SuppressLint("MissingPermission") + private void writeToDeviceConfig(String namespace, String key, String value) { + boolean result = DeviceConfig.setProperty(namespace, key, value, /* makeDefault= */ true); + if (!result) { + throw new RuntimeException("Failed to set DeviceConfig property [" + namespace + "] " + + key + "=" + value); + } + } + + private Path getPath() { + return Path.of(URI.create(defaultValuesPath)); + } +} diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigInit.java b/service/java/com/android/server/deviceconfig/DeviceConfigInit.java index a06e3f2..ddb3c5c 100644 --- a/service/java/com/android/server/deviceconfig/DeviceConfigInit.java +++ b/service/java/com/android/server/deviceconfig/DeviceConfigInit.java @@ -1,6 +1,7 @@ package com.android.server.deviceconfig; import java.io.FileDescriptor; +import java.io.IOException; import android.annotation.NonNull; import android.annotation.SystemApi; @@ -11,12 +12,16 @@ import android.provider.DeviceConfigManager; import android.provider.UpdatableDeviceConfigServiceReadiness; import android.provider.aidl.IDeviceConfigManager; +import android.util.Slog; + +import com.android.modules.utils.build.SdkLevel; import com.android.server.SystemService; /** @hide */ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public class DeviceConfigInit { + private static final String TAG = "DEVICE_CONFIG_INIT"; private DeviceConfigInit() { // do not instantiate @@ -37,6 +42,7 @@ public class DeviceConfigInit { mService = new DeviceConfigServiceImpl(getContext()); publishBinderService(DeviceConfig.SERVICE_NAME, mService); } + applyBootstrapValues(); } /** @@ -60,5 +66,20 @@ public class DeviceConfigInit { DeviceConfigApplyStagedFlags.applyStagedFlags(); } } + + private void applyBootstrapValues() { + if (SdkLevel.isAtLeastV()) { + try { + new DeviceConfigBootstrapValues().applyValuesIfNeeded(); + } catch (RuntimeException e) { + Slog.e(TAG, "Failed to load boot overrides", e); + throw e; + } catch (IOException e) { + Slog.e(TAG, "Failed to load boot overrides", e); + throw new RuntimeException(e); + } + } + } } + } diff --git a/service/javatests/Android.bp b/service/javatests/Android.bp new file mode 100644 index 0000000..3739fff --- /dev/null +++ b/service/javatests/Android.bp @@ -0,0 +1,53 @@ +// 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. + +filegroup { + name: "service-configinfrastructure-unitttests-bootstrap-files", + srcs: [ + "data/*", + ], +} + +android_test { + name: "ConfigInfrastructureServiceUnitTests", + min_sdk_version: "UpsideDownCake", + sdk_version: "module_current", + manifest: "AndroidManifest.xml", + test_config: "AndroidTest.xml", + data: [ + ":service-configinfrastructure-unitttests-bootstrap-files" + ], + srcs: [ + "src/**/*.java", + ], + test_suites: [ + "mts-configinfrastructure", + "general-tests" + ], + static_libs: [ + "androidx.test.runner", + "modules-utils-build", + "service-configinfrastructure.impl", + "truth-prebuilt", + ], + libs: [ + "android.test.base", + "android.test.mock", + "android.test.runner", + "framework-configinfrastructure", + ], + // Test coverage system runs on different devices. Need to + // compile for all architecture. + compile_multilib: "both", +} diff --git a/service/javatests/AndroidManifest.xml b/service/javatests/AndroidManifest.xml new file mode 100644 index 0000000..e0479a3 --- /dev/null +++ b/service/javatests/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + diff --git a/service/javatests/AndroidTest.xml b/service/javatests/AndroidTest.xml new file mode 100644 index 0000000..9c69017 --- /dev/null +++ b/service/javatests/AndroidTest.xml @@ -0,0 +1,46 @@ + + + + \ No newline at end of file diff --git a/service/javatests/data/bootstrap1.txt b/service/javatests/data/bootstrap1.txt new file mode 100644 index 0000000..3349679 --- /dev/null +++ b/service/javatests/data/bootstrap1.txt @@ -0,0 +1,3 @@ +a.a.a:b.b.b=enabled +a.a.a:b.b=disabled +b.b.b:c.c=enabled \ No newline at end of file diff --git a/service/javatests/src/com/android/server/deviceconfig/DeviceConfigBootstrapValuesTest.java b/service/javatests/src/com/android/server/deviceconfig/DeviceConfigBootstrapValuesTest.java new file mode 100644 index 0000000..9d77e8c --- /dev/null +++ b/service/javatests/src/com/android/server/deviceconfig/DeviceConfigBootstrapValuesTest.java @@ -0,0 +1,63 @@ +/* + * 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.deviceconfig; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; + +import android.provider.DeviceConfig; + +import com.android.modules.utils.build.SdkLevel; + +import com.android.server.deviceconfig.DeviceConfigBootstrapValues; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import java.io.IOException; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class DeviceConfigBootstrapValuesTest { + private static final String WRITE_DEVICE_CONFIG_PERMISSION = + "android.permission.WRITE_DEVICE_CONFIG"; + + private static final String READ_DEVICE_CONFIG_PERMISSION = + "android.permission.READ_DEVICE_CONFIG"; + + private static final String PATH_1 = "file:///data/local/tmp/deviceconfig/bootstrap1.txt"; + + @Test + public void assertParsesFiles() throws IOException { + assumeTrue(SdkLevel.isAtLeastV()); + InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( + WRITE_DEVICE_CONFIG_PERMISSION, READ_DEVICE_CONFIG_PERMISSION); + + DeviceConfigBootstrapValues values = new DeviceConfigBootstrapValues(PATH_1); + values.applyValuesIfNeeded(); + + assertTrue(DeviceConfig.getBoolean("a.a.a", "b.b.b", false)); + assertFalse(DeviceConfig.getBoolean("a.a.a", "b.b", true)); + assertTrue(DeviceConfig.getBoolean("b.b.b", "c.c", false)); + assertEquals(2, DeviceConfig.getProperties("a.a.a").getKeyset().size()); + assertEquals(1, DeviceConfig.getProperties("b.b.b").getKeyset().size()); + } +} -- cgit v1.2.3 From 3718f99aec3485d819f608b040c7f875a9b2d279 Mon Sep 17 00:00:00 2001 From: Ted Bauer Date: Fri, 21 Jul 2023 15:13:29 +0000 Subject: Update minSdkVersion to release version. Test: m Bug: 287567070 Change-Id: Ibda7e11503cb2fbf0948bc65f4ac72edcd3b8c85 --- apex/Android.bp | 1 + service/javatests/AndroidManifest.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apex/Android.bp b/apex/Android.bp index bd974e6..325eeee 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -90,6 +90,7 @@ apex { prebuilts: [ "current_sdkinfo", ], + min_sdk_version: "34", key: "com.android.configinfrastructure.key", certificate: ":com.android.configinfrastructure.certificate", } diff --git a/service/javatests/AndroidManifest.xml b/service/javatests/AndroidManifest.xml index e0479a3..663e401 100644 --- a/service/javatests/AndroidManifest.xml +++ b/service/javatests/AndroidManifest.xml @@ -18,7 +18,7 @@ - + -- cgit v1.2.3 From e39d43cd3d7675c5340c4f3fb226c549313b434f Mon Sep 17 00:00:00 2001 From: Ted Bauer Date: Mon, 24 Jul 2023 16:10:32 +0000 Subject: Update min SDK version for unit test target. Test: presubmit Bug: 287567070 Change-Id: I74238c55104caccc695db8e15e21c01f65d13797 --- service/javatests/Android.bp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/javatests/Android.bp b/service/javatests/Android.bp index 3739fff..3ec4631 100644 --- a/service/javatests/Android.bp +++ b/service/javatests/Android.bp @@ -21,7 +21,7 @@ filegroup { android_test { name: "ConfigInfrastructureServiceUnitTests", - min_sdk_version: "UpsideDownCake", + min_sdk_version: "34", sdk_version: "module_current", manifest: "AndroidManifest.xml", test_config: "AndroidTest.xml", -- cgit v1.2.3 From 82ddc6066bea89127ce99686fb5876cf767471db Mon Sep 17 00:00:00 2001 From: Ted Bauer Date: Thu, 27 Jul 2023 17:08:26 +0000 Subject: Remove need for READ_DEVICE_CONFIG to read flags. Test: m Bug: 288298466 Change-Id: Ie0cb5cfa14dbaf84714157e50f949793f40585f6 --- framework/api/system-current.txt | 4 ++-- framework/java/android/provider/DeviceConfig.java | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 871dbf8..5dcc7fe 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -6,12 +6,12 @@ package android.provider { method @RequiresPermission(android.Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS) public static void clearMonitorCallback(@NonNull android.content.ContentResolver); method @RequiresPermission(anyOf={android.Manifest.permission.WRITE_DEVICE_CONFIG, android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG}) public static boolean deleteProperty(@NonNull String, @NonNull String); method @NonNull public static java.util.Set getAdbWritableFlags(); - method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static boolean getBoolean(@NonNull String, @NonNull String, boolean); + method public static boolean getBoolean(@NonNull String, @NonNull String, boolean); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static float getFloat(@NonNull String, @NonNull String, float); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static int getInt(@NonNull String, @NonNull String, int); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static long getLong(@NonNull String, @NonNull String, long); method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...); - method @Nullable @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getProperty(@NonNull String, @NonNull String); + method @Nullable public static String getProperty(@NonNull String, @NonNull String); method @NonNull public static java.util.List getPublicNamespaces(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String); method @RequiresPermission(anyOf={android.Manifest.permission.WRITE_DEVICE_CONFIG, android.Manifest.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG}) public static int getSyncDisabledMode(); diff --git a/framework/java/android/provider/DeviceConfig.java b/framework/java/android/provider/DeviceConfig.java index 6c9da59..7af650a 100644 --- a/framework/java/android/provider/DeviceConfig.java +++ b/framework/java/android/provider/DeviceConfig.java @@ -1085,7 +1085,6 @@ public final class DeviceConfig { * @hide */ @SystemApi - @RequiresPermission(READ_DEVICE_CONFIG) @Nullable public static String getProperty(@NonNull String namespace, @NonNull String name) { // Fetch all properties for the namespace at once and cache them in the local process, so we @@ -1150,7 +1149,6 @@ public final class DeviceConfig { * @hide */ @SystemApi - @RequiresPermission(READ_DEVICE_CONFIG) public static boolean getBoolean(@NonNull String namespace, @NonNull String name, boolean defaultValue) { String value = getProperty(namespace, name); -- cgit v1.2.3 From c84103157b8c7a9946d1bb1666860e1bb2e98c44 Mon Sep 17 00:00:00 2001 From: Charlie Wang Date: Tue, 1 Aug 2023 10:26:06 -0700 Subject: Add more flags to allowlist. Test: m Bug: 273848032. Change-Id: I97a6d90d99dd3ad3ce3595fe91592e21abc64d6b --- framework/java/android/provider/WritableFlags.java | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/java/android/provider/WritableFlags.java b/framework/java/android/provider/WritableFlags.java index b8c5b28..5dd7536 100644 --- a/framework/java/android/provider/WritableFlags.java +++ b/framework/java/android/provider/WritableFlags.java @@ -1739,6 +1739,7 @@ final class WritableFlags { "vpn/enable_pixel_ppn_feature", "wallpaper_content/enabled", "wearable_sensing/service_enabled_platforms", + "wearable_sensing/service_enabled", "wear/ambient_auto_resume_timeout_max_reset_count", "wear/bedtime_hard_mode_feature_enabled", "wear/enable_backup_service_in_wear_framework", -- cgit v1.2.3 From 797f18be841ef6337389a568f772ef929573ab4a Mon Sep 17 00:00:00 2001 From: Dennis Shen Date: Tue, 1 Aug 2023 20:03:31 +0000 Subject: Setup device config to sys prop mapping Bug: b/293278653 Change-Id: I13878d8e17661f04b2ed5918d9838bf52be220d3 --- framework/api/system-current.txt | 1 + framework/java/android/provider/DeviceConfig.java | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 871dbf8..6cc0153 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -46,6 +46,7 @@ package android.provider { field public static final String NAMESPACE_CONNECTIVITY = "connectivity"; field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis"; field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture"; + field public static final String NAMESPACE_CORE_EXPERIMENTS_TEAM_INTERNAL = "core_experiments_team_internal"; field public static final String NAMESPACE_CREDENTIAL = "credential_manager"; field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot"; field public static final String NAMESPACE_DISPLAY_MANAGER = "display_manager"; diff --git a/framework/java/android/provider/DeviceConfig.java b/framework/java/android/provider/DeviceConfig.java index 6c9da59..3cc3e91 100644 --- a/framework/java/android/provider/DeviceConfig.java +++ b/framework/java/android/provider/DeviceConfig.java @@ -233,6 +233,14 @@ public final class DeviceConfig { @SystemApi public static final String NAMESPACE_BLUETOOTH = "bluetooth"; + /** + * Namespace for features relating to android core experiments team internal usage. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_CORE_EXPERIMENTS_TEAM_INTERNAL = "core_experiments_team_internal"; + /** * Namespace for all camera-related features that are used at the native level. * -- cgit v1.2.3 From 21e6b2dfda132a9d4c4ab55ca6d7854e08c7e87e Mon Sep 17 00:00:00 2001 From: Richard MacGregor Date: Wed, 12 Jul 2023 20:38:15 +0000 Subject: Cleanup permissions hub flags Bug: 290990618 Test: atest ActivityManagerApi29Test DiscreteAppopsTest HistoricalAppopsTest PrivacyDashboardSubattributionsTest PermissionHistoryTest PrivacyDashboardMultiuserTest Change-Id: Iac13f9af326225c0e831845aea28147f1aac39a8 --- framework/java/android/provider/WritableFlags.java | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/java/android/provider/WritableFlags.java b/framework/java/android/provider/WritableFlags.java index 5dd7536..c413beb 100644 --- a/framework/java/android/provider/WritableFlags.java +++ b/framework/java/android/provider/WritableFlags.java @@ -1475,7 +1475,6 @@ final class WritableFlags { "privacy/param", "privacy/param", "privacy/permission_rationale_enabled", - "privacy/permissions_hub_enabled", "privacy/permissions_hub_subattribution_enabled", "privacy/placeholder_safety_label_updates_enabled", "privacy/privacy_attribution_tag_full_log_enabled", -- cgit v1.2.3 From 2d0ce0065af8602293b8b29b9a3cf4807bd1099a Mon Sep 17 00:00:00 2001 From: Richard MacGregor Date: Wed, 9 Aug 2023 14:06:52 -0700 Subject: Cleanup warning_banner_enabled flag Bug: 294872248 Test: manual Change-Id: I1dad02484175b275b144e3820c3e49b902dc272c --- framework/java/android/provider/WritableFlags.java | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/java/android/provider/WritableFlags.java b/framework/java/android/provider/WritableFlags.java index 5dd7536..37c1811 100644 --- a/framework/java/android/provider/WritableFlags.java +++ b/framework/java/android/provider/WritableFlags.java @@ -1505,7 +1505,6 @@ final class WritableFlags { "privacy/safety_label_changes_job_interval_millis", "privacy/sc_accessibility_listener_enabled", "privacy/sc_accessibility_source_enabled", - "privacy/warning_banner_enabled", "profcollect_native_boot/enable", "remote_key_provisioning_native/enable_rkpd", "rollback/containing", -- cgit v1.2.3 From 3826a42010787df742477687ff6e4f99b98ec879 Mon Sep 17 00:00:00 2001 From: Motomu Utsumi Date: Tue, 15 Aug 2023 12:50:18 +0900 Subject: Fix the name of String constant for tethering native namespace Followed suggestion from api council. Value of the constant ("tethering_u_or_later_native") is kept since the namespace name is used in the platform code and can not be updated. Bug: 286049425 Test: m Change-Id: I3f35ca20a48b2a5fa19b9b9e978e7bb9f9980c0c --- framework/api/module-lib-current.txt | 2 +- framework/java/android/provider/DeviceConfig.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 63b7a12..ad11041 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -25,7 +25,7 @@ package android.provider { field public static final String NAMESPACE_SETTINGS_STATS = "settings_stats"; field public static final String NAMESPACE_SETTINGS_UI = "settings_ui"; field public static final String NAMESPACE_TARE = "tare"; - field public static final String NAMESPACE_TETHERING_U_OR_LATER_NATIVE = "tethering_u_or_later_native"; + field public static final String NAMESPACE_TETHERING_NATIVE = "tethering_u_or_later_native"; field public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native"; field public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot"; field public static final String NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE = "virtualization_framework_native"; diff --git a/framework/java/android/provider/DeviceConfig.java b/framework/java/android/provider/DeviceConfig.java index 3cc3e91..57bfd55 100644 --- a/framework/java/android/provider/DeviceConfig.java +++ b/framework/java/android/provider/DeviceConfig.java @@ -991,7 +991,7 @@ public final class DeviceConfig { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public static final String NAMESPACE_TETHERING_U_OR_LATER_NATIVE = + public static final String NAMESPACE_TETHERING_NATIVE = "tethering_u_or_later_native"; /** -- cgit v1.2.3 From aa82369979da7e3a8b7ba79963b3b0e22d5e1e37 Mon Sep 17 00:00:00 2001 From: Ted Bauer Date: Wed, 16 Aug 2023 17:02:42 -0400 Subject: Add APIs to set sticky local overrides in DC. Also add a getAllProperties API, which will be useful for clients of the override APIs; more details in bug. Bug: 293307017 Test: new CTS tests Change-Id: I46a1d7c409a85b22488a2b8ab61cde9a0c430eb8 --- apex/Android.bp | 1 + framework/Android.bp | 4 + framework/api/system-current.txt | 5 + framework/jarjar-rules.txt | 1 + framework/java/android/provider/DeviceConfig.java | 154 +++++++++++++++++++++ .../android/provider/DeviceConfigDataStore.java | 2 + .../android/provider/SettingsConfigDataStore.java | 10 ++ 7 files changed, 177 insertions(+) create mode 100644 framework/jarjar-rules.txt diff --git a/apex/Android.bp b/apex/Android.bp index 325eeee..243af7c 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -41,6 +41,7 @@ bootclasspath_fragment { // result in a build failure due to inconsistent flags. package_prefixes: [ "android.provider.aidl", + "android.provider.internal.modules.utils.build", ], }, // The bootclasspath_fragments that provide APIs on which this depends. diff --git a/framework/Android.bp b/framework/Android.bp index f14baea..1fb9ffd 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -35,4 +35,8 @@ java_sdk_library { impl_library_visibility: [ "//packages/modules/ConfigInfrastructure:__subpackages__" ], + static_libs: [ + "modules-utils-build", + ], + jarjar_rules: "jarjar-rules.txt", } diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 254a8b3..a69e7fd 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -3,9 +3,12 @@ package android.provider { public final class DeviceConfig { method public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener); + method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void clearAllLocalOverrides(); + method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void clearLocalOverride(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS) public static void clearMonitorCallback(@NonNull android.content.ContentResolver); method @RequiresPermission(anyOf={android.Manifest.permission.WRITE_DEVICE_CONFIG, android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG}) public static boolean deleteProperty(@NonNull String, @NonNull String); method @NonNull public static java.util.Set getAdbWritableFlags(); + method @NonNull public static java.util.Set getAllProperties(); method public static boolean getBoolean(@NonNull String, @NonNull String, boolean); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static float getFloat(@NonNull String, @NonNull String, float); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static int getInt(@NonNull String, @NonNull String, int); @@ -15,8 +18,10 @@ package android.provider { method @NonNull public static java.util.List getPublicNamespaces(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String); method @RequiresPermission(anyOf={android.Manifest.permission.WRITE_DEVICE_CONFIG, android.Manifest.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG}) public static int getSyncDisabledMode(); + method @NonNull public static java.util.Map> getUnderlyingValuesForOverriddenFlags(); method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener); method @RequiresPermission(anyOf={android.Manifest.permission.WRITE_DEVICE_CONFIG, android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG}) public static void resetToDefaults(int, @Nullable String); + method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setLocalOverride(@NonNull String, @NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS) public static void setMonitorCallback(@NonNull android.content.ContentResolver, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.MonitorCallback); method @RequiresPermission(anyOf={android.Manifest.permission.WRITE_DEVICE_CONFIG, android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG}) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties) throws android.provider.DeviceConfig.BadConfigException; method @RequiresPermission(anyOf={android.Manifest.permission.WRITE_DEVICE_CONFIG, android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG}) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean); diff --git a/framework/jarjar-rules.txt b/framework/jarjar-rules.txt new file mode 100644 index 0000000..c0fbba5 --- /dev/null +++ b/framework/jarjar-rules.txt @@ -0,0 +1 @@ +rule com.android.modules.utils.** android.provider.internal.modules.utils.@1 diff --git a/framework/java/android/provider/DeviceConfig.java b/framework/java/android/provider/DeviceConfig.java index 203283a..997007e 100644 --- a/framework/java/android/provider/DeviceConfig.java +++ b/framework/java/android/provider/DeviceConfig.java @@ -32,6 +32,7 @@ import android.annotation.SystemApi; import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; +import com.android.modules.utils.build.SdkLevel; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; @@ -46,6 +47,7 @@ import java.lang.annotation.Target; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -1049,6 +1051,9 @@ public final class DeviceConfig { private static final DeviceConfigDataStore sDataStore = new SettingsConfigDataStore(); + private static final String DEVICE_CONFIG_OVERRIDES_NAMESPACE = + "device_config_overrides"; + /** * Interface for monitoring callback functions. * @@ -1109,6 +1114,8 @@ public final class DeviceConfig { * Each call to {@link #setProperties(Properties)} is also atomic and ensures that either none * or all of the change is picked up here, but never only part of it. * + * If there are any local overrides applied, they will take precedence over underlying values. + * * @param namespace The namespace containing the properties to look up. * @param names The names of properties to look up, or empty to fetch all properties for the * given namespace. @@ -1124,9 +1131,80 @@ public final class DeviceConfig { @NonNull @RequiresPermission(READ_DEVICE_CONFIG) public static Properties getProperties(@NonNull String namespace, @NonNull String... names) { + Properties propertiesWithoutOverrides = + getPropertiesWithoutOverrides(namespace, names); + if (SdkLevel.isAtLeastV()) { + return applyOverrides(propertiesWithoutOverrides); + } else { + return propertiesWithoutOverrides; + } + } + + @NonNull + private static Properties getPropertiesWithoutOverrides(@NonNull String namespace, + @NonNull String... names) { return sDataStore.getProperties(namespace, names); } + private static Properties applyOverrides(@NonNull Properties properties) { + Properties overrides = + getPropertiesWithoutOverrides(DEVICE_CONFIG_OVERRIDES_NAMESPACE); + Map newPropertiesMap = new HashMap<>(); + + HashSet flags = new HashSet(properties.getKeyset()); + for (String override : overrides.getKeyset()) { + String[] namespaceAndFlag = override.split(":"); + if (properties.getNamespace().equals(namespaceAndFlag[0])) { + flags.add(namespaceAndFlag[1]); + } + } + + for (String flag : flags) { + String override = + overrides.getString(properties.getNamespace() + ":" + flag, null); + if (override != null) { + newPropertiesMap.put(flag, override); + } else { + newPropertiesMap.put(flag, properties.getString(flag, null)); + } + } + return new Properties(properties.getNamespace(), newPropertiesMap); + } + + /** + * List all stored flags. + * + * The keys take the form {@code namespace/name}, and the values are the flag values. + * + * @hide + */ + @SystemApi + @NonNull + public static Set getAllProperties() { + Map properties = sDataStore.getAllProperties(); + Map> propertyMaps = new HashMap<>(); + for (String flag : properties.keySet()) { + String[] namespaceAndFlag = flag.split("/"); + String namespace = namespaceAndFlag[0]; + String flagName = namespaceAndFlag[1]; + String override = + getProperty(DEVICE_CONFIG_OVERRIDES_NAMESPACE, namespace + ":" + flagName); + + String value = override != null ? override : properties.get(flag); + + if (!propertyMaps.containsKey(namespace)) { + propertyMaps.put(namespace, new HashMap<>()); + } + propertyMaps.get(namespace).put(flagName, value); + } + + HashSet result = new HashSet<>(); + for (Map.Entry> entry : propertyMaps.entrySet()) { + result.add(new Properties(entry.getKey(), entry.getValue())); + } + return result; + } + /** * Look up the String value of a property for a particular namespace. * @@ -1239,6 +1317,82 @@ public final class DeviceConfig { } } + /** + * Set flag {@code namespace/name} to {@code value}, and ignores server-updates for this flag. + * + * Can still be called even if there is no underlying value set. + * + * Returns {@code true} if successful, or {@code false} if the storage implementation throws + * errors. + * + * @hide + */ + @SystemApi + @RequiresPermission(WRITE_DEVICE_CONFIG) + public static boolean setLocalOverride(@NonNull String namespace, @NonNull String name, + @NonNull String value) { + return setProperty(DEVICE_CONFIG_OVERRIDES_NAMESPACE, namespace + ":" + name, value, false); + } + + /** + * Clear all local sticky overrides. + * + * @hide + */ + @SystemApi + @RequiresPermission(WRITE_DEVICE_CONFIG) + public static void clearAllLocalOverrides() { + Properties overrides = getProperties(DEVICE_CONFIG_OVERRIDES_NAMESPACE); + for (String overrideName : overrides.getKeyset()) { + deleteProperty(DEVICE_CONFIG_OVERRIDES_NAMESPACE, overrideName); + } + } + + /** + * Clear local sticky override for flag {@code namespace/name}. + * + * @hide + */ + @SystemApi + @RequiresPermission(WRITE_DEVICE_CONFIG) + public static void clearLocalOverride(@NonNull String namespace, + @NonNull String name) { + deleteProperty(DEVICE_CONFIG_OVERRIDES_NAMESPACE, namespace + ":" + name); + } + + /** + * Return a map containing all flags that have been overridden. + * + * The keys of the outer map are namespaces. They keys of the inner maps are + * flag names. The values of the inner maps are the underlying flag values + * (not to be confused with their overridden values). + * + * @hide + */ + @NonNull + @SystemApi + public static Map> getUnderlyingValuesForOverriddenFlags() { + Properties overrides = getProperties(DEVICE_CONFIG_OVERRIDES_NAMESPACE); + HashMap> result = new HashMap<>(); + for (Map.Entry entry : overrides.getPropertyValues().entrySet()) { + String[] namespaceAndFlag = entry.getKey().split(":"); + String namespace = namespaceAndFlag[0]; + String flag = namespaceAndFlag[1]; + + String actualValue = + getPropertiesWithoutOverrides(namespace, flag) + .getString(flag, null); + if (result.get(namespace) != null) { + result.get(namespace).put(flag, actualValue); + } else { + HashMap innerMap = new HashMap<>(); + innerMap.put(flag, actualValue); + result.put(namespace, innerMap); + } + } + return result; + } + /** * Create a new property with the provided name and value in the provided namespace, or * update the value of such a property if it already exists. The same name can exist in multiple diff --git a/framework/java/android/provider/DeviceConfigDataStore.java b/framework/java/android/provider/DeviceConfigDataStore.java index d74d833..bd9647c 100644 --- a/framework/java/android/provider/DeviceConfigDataStore.java +++ b/framework/java/android/provider/DeviceConfigDataStore.java @@ -24,11 +24,13 @@ import android.database.ContentObserver; import android.provider.DeviceConfig; import java.util.concurrent.Executor; +import java.util.Map; /** * @hide */ public interface DeviceConfigDataStore { + @NonNull Map getAllProperties(); @NonNull DeviceConfig.Properties getProperties(@NonNull String namespace, @NonNull String ... names); diff --git a/framework/java/android/provider/SettingsConfigDataStore.java b/framework/java/android/provider/SettingsConfigDataStore.java index f48017e..33a880d 100644 --- a/framework/java/android/provider/SettingsConfigDataStore.java +++ b/framework/java/android/provider/SettingsConfigDataStore.java @@ -17,13 +17,18 @@ package android.provider; import android.content.ContentResolver; +import android.content.pm.PackageManager; import android.database.ContentObserver; import android.annotation.NonNull; import android.annotation.Nullable; import java.util.Arrays; +import java.util.ArrayList; import java.util.concurrent.Executor; +import java.util.HashMap; +import java.util.Map; + /** * TODO: want to change the package of this class @@ -31,6 +36,11 @@ import java.util.concurrent.Executor; * @hide */ public class SettingsConfigDataStore implements DeviceConfigDataStore { + @Override + public @NonNull Map getAllProperties() { + return Settings.Config.getAllStrings(); + } + @Override public @NonNull DeviceConfig.Properties getProperties(@NonNull String namespace, @NonNull String... names) { -- cgit v1.2.3 From 52a973c1ca9a88b6d13f42cd5dec5f9dcbf87a59 Mon Sep 17 00:00:00 2001 From: mrulhania Date: Tue, 12 Sep 2023 14:53:01 -0700 Subject: clean up location_accuracy flag The feature is now always enabled on S+ devices. Bug: 294870972 Test: atest LocationAccuracyTest Change-Id: I8efbd0317bb1aa1562b9fc122bfbc43e33c0850c --- framework/java/android/provider/WritableFlags.java | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/java/android/provider/WritableFlags.java b/framework/java/android/provider/WritableFlags.java index 3a910e4..1457d43 100644 --- a/framework/java/android/provider/WritableFlags.java +++ b/framework/java/android/provider/WritableFlags.java @@ -1463,7 +1463,6 @@ final class WritableFlags { "privacy/location_access_check_enabled", "privacy/location_access_check_delay_millis", "privacy/location_access_check_periodic_interval_millis", - "privacy/location_accuracy_enabled", "privacy/location_indicators_enabled", "privacy/location_indicators_show_system", "privacy/location_indicators_small_enabled", -- cgit v1.2.3 From d5a06be1b823a5310892ec49c8a206726408129f Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Wed, 20 Sep 2023 19:39:13 +0000 Subject: ConfigInfrastructure: enforce FlaggedApi Bug: 297362755 Test: m checkapi Change-Id: I6835bfc5914f2d986cdc710b08a48ec23818bd58 --- framework/api/module-lib-lint-baseline.txt | 3 +++ framework/api/system-lint-baseline.txt | 15 +++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 framework/api/module-lib-lint-baseline.txt create mode 100644 framework/api/system-lint-baseline.txt diff --git a/framework/api/module-lib-lint-baseline.txt b/framework/api/module-lib-lint-baseline.txt new file mode 100644 index 0000000..feb6f83 --- /dev/null +++ b/framework/api/module-lib-lint-baseline.txt @@ -0,0 +1,3 @@ +// Baseline format: 1.0 +UnflaggedApi: android.provider.DeviceConfig#NAMESPACE_TETHERING_NATIVE: + New API must be flagged with @FlaggedApi: field android.provider.DeviceConfig.NAMESPACE_TETHERING_NATIVE diff --git a/framework/api/system-lint-baseline.txt b/framework/api/system-lint-baseline.txt new file mode 100644 index 0000000..5cb0f45 --- /dev/null +++ b/framework/api/system-lint-baseline.txt @@ -0,0 +1,15 @@ +// Baseline format: 1.0 +UnflaggedApi: android.provider.DeviceConfig#NAMESPACE_CORE_EXPERIMENTS_TEAM_INTERNAL: + New API must be flagged with @FlaggedApi: field android.provider.DeviceConfig.NAMESPACE_CORE_EXPERIMENTS_TEAM_INTERNAL +UnflaggedApi: android.provider.DeviceConfig#NAMESPACE_NFC: + New API must be flagged with @FlaggedApi: field android.provider.DeviceConfig.NAMESPACE_NFC +UnflaggedApi: android.provider.DeviceConfig#clearAllLocalOverrides(): + New API must be flagged with @FlaggedApi: method android.provider.DeviceConfig.clearAllLocalOverrides() +UnflaggedApi: android.provider.DeviceConfig#clearLocalOverride(String, String): + New API must be flagged with @FlaggedApi: method android.provider.DeviceConfig.clearLocalOverride(String,String) +UnflaggedApi: android.provider.DeviceConfig#getAllProperties(): + New API must be flagged with @FlaggedApi: method android.provider.DeviceConfig.getAllProperties() +UnflaggedApi: android.provider.DeviceConfig#getUnderlyingValuesForOverriddenFlags(): + New API must be flagged with @FlaggedApi: method android.provider.DeviceConfig.getUnderlyingValuesForOverriddenFlags() +UnflaggedApi: android.provider.DeviceConfig#setLocalOverride(String, String, String): + New API must be flagged with @FlaggedApi: method android.provider.DeviceConfig.setLocalOverride(String,String,String) -- cgit v1.2.3 From 3836b88e013728b731e51ba1edaff3931315e651 Mon Sep 17 00:00:00 2001 From: Ted Bauer Date: Wed, 20 Sep 2023 17:19:50 -0400 Subject: Remove application of staged flags in DC service init. Bug: 301096965 Test: m Change-Id: Ibc1de0b1df25148aad81b7635a1be64d0f780863 --- .../deviceconfig/DeviceConfigApplyStagedFlags.java | 58 ---------------------- .../server/deviceconfig/DeviceConfigInit.java | 14 ------ 2 files changed, 72 deletions(-) delete mode 100644 service/java/com/android/server/deviceconfig/DeviceConfigApplyStagedFlags.java diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigApplyStagedFlags.java b/service/java/com/android/server/deviceconfig/DeviceConfigApplyStagedFlags.java deleted file mode 100644 index 14be86d..0000000 --- a/service/java/com/android/server/deviceconfig/DeviceConfigApplyStagedFlags.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.android.server.deviceconfig; - -import android.annotation.SystemApi; -import android.provider.DeviceConfig; -import android.util.Slog; - -import java.util.Map; - -/** - * Applies all reboot-staged flags. - * - * Currently, flags are staged by being stored in a special DeviceConfig namespace. - */ -class DeviceConfigApplyStagedFlags { - private static final String TAG = "DeviceConfigApplyStagedFlags"; - private static final String ESCAPE_SLASHES = "\\"; - - private DeviceConfigApplyStagedFlags() {} - - /** - * Applies all flags that were staged for reboot. - * - * Flags are staged by being stored in the DeviceConfig namespace "staged". - * They're stored there with the name {@code namespace*flagName}. - * - */ - public static void applyStagedFlags() { - DeviceConfig.Properties stagedProperties = - DeviceConfig.getProperties(DeviceConfig.NAMESPACE_REBOOT_STAGING); - - for (Map.Entry entry : stagedProperties.getPropertyValues().entrySet()) { - String name = entry.getKey(); - String value = entry.getValue(); - - String delimiter = ESCAPE_SLASHES + DeviceConfig.NAMESPACE_REBOOT_STAGING_DELIMITER; - String[] parts = name.split(delimiter); - if (parts.length != 2) { - Slog.w(TAG, "staged flag name '" - + name - + "' didn't follow 'namespace*flag' format, not applying"); - continue; - } - String namespace = parts[0]; - String flagName = parts[1]; - - if (DeviceConfig.setProperty(namespace, flagName, value, true)) { - if (!DeviceConfig.deleteProperty( - DeviceConfig.NAMESPACE_REBOOT_STAGING, name)) { - Slog.w(TAG, "failed to delete staged flag '" - + namespace + "/" + flagName + ":" + value + "'"); - } - } else { - Slog.w(TAG, "failed to apply staged flag '" - + namespace + "/" + flagName + ":" + value + "'"); - } - } - } -} diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigInit.java b/service/java/com/android/server/deviceconfig/DeviceConfigInit.java index ddb3c5c..f9c7792 100644 --- a/service/java/com/android/server/deviceconfig/DeviceConfigInit.java +++ b/service/java/com/android/server/deviceconfig/DeviceConfigInit.java @@ -53,20 +53,6 @@ public class DeviceConfigInit { // no op } - /** - * Apply staged flags on boot. - * - * @param phase one of {@code SystemService.BootPhase} - * @hide - */ - @Override - public void onBootPhase(/* @BootPhase */ int phase) { - // TODO(b/286057899): move this earlier in the boot process - if (phase == SystemService.PHASE_BOOT_COMPLETED) { - DeviceConfigApplyStagedFlags.applyStagedFlags(); - } - } - private void applyBootstrapValues() { if (SdkLevel.isAtLeastV()) { try { -- cgit v1.2.3 From aed07f70c84658e0d8fc08f8152a40f05124f972 Mon Sep 17 00:00:00 2001 From: Kiran Ramachandra Date: Wed, 4 Oct 2023 23:16:22 +0000 Subject: Cleaned up feature flag location_access_check_enabled Test: Ensured the tests relavent to this feature pass - CtsPermissionTestCases:LocationAccessCheckTest Change-Id: I2a781e002012108ceff305345eeffda9b4224a7d Bug: b/294872488 --- framework/java/android/provider/WritableFlags.java | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/java/android/provider/WritableFlags.java b/framework/java/android/provider/WritableFlags.java index 1457d43..0219346 100644 --- a/framework/java/android/provider/WritableFlags.java +++ b/framework/java/android/provider/WritableFlags.java @@ -1460,7 +1460,6 @@ final class WritableFlags { "privacy/camera_mic_icons_enabled", "privacy/camera_toggle_enabled", "privacy/data_sharing_update_period_millis", - "privacy/location_access_check_enabled", "privacy/location_access_check_delay_millis", "privacy/location_access_check_periodic_interval_millis", "privacy/location_indicators_enabled", -- cgit v1.2.3 From a6a28934e93fd607035ef378f0d25f1363be5d20 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Tue, 10 Oct 2023 17:11:25 +0100 Subject: Prepare for keeping concrete overrides of abstract methods Currently, metalava will discard concrete overrides of abstract methods from the API signature files which causes problems when attempting to generate stubs from those files and also discards information that may be important in API reviews. This change is the first step in the process of changing that behavior. This change hard codes the current behavior for discarding the concrete overrides (`add-additional-overrides=no`) into those signature files that will (or are likely) to be affected by the change in behavior. That allows the default behavior to be changed for all the other files without affecting these. Follow up changes will remove the hard coding from the signature files and will update the contents which will include the concrete overrides of the abstract methods. More information and the background for this change is available at go/add-overriding-one-pager. Test: m checkapi Bug: 299366704 (cherry picked from https://android-review.googlesource.com/q/commit:caab77d07da047fdb6dc75728b760996b4b48ae2) Merged-In: Ie7635e0b7ae04ca348ca3d1e3a339133def0ce3b Change-Id: Ie7635e0b7ae04ca348ca3d1e3a339133def0ce3b --- framework/api/current.txt | 2 ++ framework/api/module-lib-current.txt | 2 ++ framework/api/module-lib-removed.txt | 2 ++ framework/api/removed.txt | 2 ++ framework/api/system-current.txt | 2 ++ framework/api/system-removed.txt | 2 ++ service/api/system-server-current.txt | 2 ++ service/api/system-server-removed.txt | 2 ++ 8 files changed, 16 insertions(+) diff --git a/framework/api/current.txt b/framework/api/current.txt index d802177..14191eb 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 739f954..782934f 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.provider { public final class DeviceConfig { diff --git a/framework/api/module-lib-removed.txt b/framework/api/module-lib-removed.txt index d802177..14191eb 100644 --- a/framework/api/module-lib-removed.txt +++ b/framework/api/module-lib-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/framework/api/removed.txt b/framework/api/removed.txt index d802177..14191eb 100644 --- a/framework/api/removed.txt +++ b/framework/api/removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index cfe7cf7..db1f990 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.provider { public final class DeviceConfig { diff --git a/framework/api/system-removed.txt b/framework/api/system-removed.txt index d802177..14191eb 100644 --- a/framework/api/system-removed.txt +++ b/framework/api/system-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/service/api/system-server-current.txt b/service/api/system-server-current.txt index 722ace1..f464685 100644 --- a/service/api/system-server-current.txt +++ b/service/api/system-server-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package com.android.server.deviceconfig { public class DeviceConfigInit { diff --git a/service/api/system-server-removed.txt b/service/api/system-server-removed.txt index d802177..14191eb 100644 --- a/service/api/system-server-removed.txt +++ b/service/api/system-server-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 -- cgit v1.2.3 From f95adb9f18a5e395c3ab38101a905b1940dc602a Mon Sep 17 00:00:00 2001 From: Dennis Shen Date: Wed, 20 Sep 2023 18:15:02 +0000 Subject: Make string constants available to outside class for native boot stable flags Bug: b/300111812 Change-Id: Ic15c0903311bd5a48b86461384fa1846d260ca42 --- framework/java/android/provider/DeviceConfig.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/framework/java/android/provider/DeviceConfig.java b/framework/java/android/provider/DeviceConfig.java index 997007e..6f169ac 100644 --- a/framework/java/android/provider/DeviceConfig.java +++ b/framework/java/android/provider/DeviceConfig.java @@ -71,25 +71,6 @@ import android.os.IBinder; @SystemApi public final class DeviceConfig { - /** - * Namespace where flags are staged to be applied on reboot. - * - * @hide - */ - public static final String NAMESPACE_REBOOT_STAGING = "staged"; - - /** - * Delimiter for flags in the {@code staged} namespace. - * - * Flags in this namespace will be applied on reboot. Each flag - * in this namespace follows the format {@code namespace*flag}, - * where {@code namespace} is the namespace the flag will be written - * to on reboot, and {@code flag} is the name of the flag. - * - * @hide - */ - public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*"; - /** * The name of the service that provides the logic to these APIs * -- cgit v1.2.3 From c3e289a82622aefa0cfe2c78fb39fe9636983648 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Fri, 13 Oct 2023 00:38:46 +0100 Subject: Add additional overriding methods to signature files This change adds overriding methods that are essential to compile the stubs generated from the signature files. More information and the background for this change is available at go/add-overriding-one-pager. Test: m checkbuild --build-from-text-stub Bug: 299366704 (cherry picked from https://android-review.googlesource.com/q/commit:f1c80d341c5c70b3649d5ac6c7afb3ee3b47ef23) Merged-In: I72ec7af2b0c122c4e61e9f675f3f588ed0448d52 Change-Id: I72ec7af2b0c122c4e61e9f675f3f588ed0448d52 --- framework/api/current.txt | 2 -- framework/api/module-lib-current.txt | 2 -- framework/api/module-lib-removed.txt | 2 -- framework/api/removed.txt | 2 -- framework/api/system-current.txt | 2 -- framework/api/system-removed.txt | 2 -- service/api/system-server-current.txt | 3 +-- service/api/system-server-removed.txt | 2 -- 8 files changed, 1 insertion(+), 16 deletions(-) diff --git a/framework/api/current.txt b/framework/api/current.txt index 14191eb..d802177 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 782934f..739f954 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package android.provider { public final class DeviceConfig { diff --git a/framework/api/module-lib-removed.txt b/framework/api/module-lib-removed.txt index 14191eb..d802177 100644 --- a/framework/api/module-lib-removed.txt +++ b/framework/api/module-lib-removed.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/framework/api/removed.txt b/framework/api/removed.txt index 14191eb..d802177 100644 --- a/framework/api/removed.txt +++ b/framework/api/removed.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index db1f990..cfe7cf7 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package android.provider { public final class DeviceConfig { diff --git a/framework/api/system-removed.txt b/framework/api/system-removed.txt index 14191eb..d802177 100644 --- a/framework/api/system-removed.txt +++ b/framework/api/system-removed.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/service/api/system-server-current.txt b/service/api/system-server-current.txt index f464685..29cd1d5 100644 --- a/service/api/system-server-current.txt +++ b/service/api/system-server-current.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package com.android.server.deviceconfig { public class DeviceConfigInit { @@ -8,6 +6,7 @@ package com.android.server.deviceconfig { public static class DeviceConfigInit.Lifecycle extends com.android.server.SystemService { ctor public DeviceConfigInit.Lifecycle(@NonNull android.content.Context); + method public void onStart(); } } diff --git a/service/api/system-server-removed.txt b/service/api/system-server-removed.txt index 14191eb..d802177 100644 --- a/service/api/system-server-removed.txt +++ b/service/api/system-server-removed.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 -- cgit v1.2.3 From 61e3b2a4ecefc7278e0d7f6ed8ab9c6aa5477d31 Mon Sep 17 00:00:00 2001 From: Ted Bauer Date: Mon, 9 Oct 2023 17:54:44 -0400 Subject: Add boot notification to ConfigInfra. Bug: 298391955 Test: atest ConfigInfrastructureServiceUnitTests Change-Id: I9c1166b0107e8ea188a40ced71228fdab3ac6b0f --- apex/Android.bp | 3 + service/Android.bp | 19 +++ service/ServiceResources/Android.bp | 36 +++++ service/ServiceResources/AndroidManifest.xml | 27 ++++ service/ServiceResources/res/drawable/ic_flag.xml | 10 ++ service/ServiceResources/res/values/strings.xml | 7 + service/flags.aconfig | 8 + .../deviceconfig/BootNotificationCreator.java | 164 +++++++++++++++++++++ .../server/deviceconfig/DeviceConfigInit.java | 15 +- service/javatests/Android.bp | 1 + .../deviceconfig/BootNotificationCreatorTest.java | 80 ++++++++++ 11 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 service/ServiceResources/Android.bp create mode 100644 service/ServiceResources/AndroidManifest.xml create mode 100644 service/ServiceResources/res/drawable/ic_flag.xml create mode 100644 service/ServiceResources/res/values/strings.xml create mode 100644 service/flags.aconfig create mode 100644 service/java/com/android/server/deviceconfig/BootNotificationCreator.java create mode 100644 service/javatests/src/com/android/server/deviceconfig/BootNotificationCreatorTest.java diff --git a/apex/Android.bp b/apex/Android.bp index 243af7c..abed18f 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -94,6 +94,9 @@ apex { min_sdk_version: "34", key: "com.android.configinfrastructure.key", certificate: ":com.android.configinfrastructure.certificate", + apps: [ + "DeviceConfigServiceResources", + ], } sdk { diff --git a/service/Android.bp b/service/Android.bp index 88bc4eb..8d0fb79 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -33,9 +33,11 @@ java_sdk_library { static_libs: [ "modules-utils-build", "modules-utils-shell-command-handler", + "device_config_reboot_flags_java_lib", ], libs: [ "framework-configinfrastructure.impl", + "DeviceConfigServiceResources", ], min_sdk_version: "UpsideDownCake", sdk_version: "system_server_current", @@ -44,3 +46,20 @@ java_sdk_library { "//packages/modules/ConfigInfrastructure/service/javatests", ], } + +aconfig_declarations { + name: "device_config_reboot_flags", + package: "com.android.server.deviceconfig", + srcs: [ + "flags.aconfig", + ], +} + +java_aconfig_library { + name: "device_config_reboot_flags_java_lib", + min_sdk_version: "UpsideDownCake", + apex_available: [ + "com.android.configinfrastructure", + ], + aconfig_declarations: "device_config_reboot_flags", +} diff --git a/service/ServiceResources/Android.bp b/service/ServiceResources/Android.bp new file mode 100644 index 0000000..cd2dcc8 --- /dev/null +++ b/service/ServiceResources/Android.bp @@ -0,0 +1,36 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// APK to hold all the wifi overlayable resources. +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_app { + name: "DeviceConfigServiceResources", + package_name: "com.android.server.deviceconfig.resources", + sdk_version: "system_current", + resource_dirs: [ + "res", + ], + certificate: "platform", + min_sdk_version: "34", + // platform_apis: true, + export_package_resources: true, + apex_available: [ + "com.android.configinfrastructure", + ], +} diff --git a/service/ServiceResources/AndroidManifest.xml b/service/ServiceResources/AndroidManifest.xml new file mode 100644 index 0000000..4fc5e06 --- /dev/null +++ b/service/ServiceResources/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/service/ServiceResources/res/drawable/ic_flag.xml b/service/ServiceResources/res/drawable/ic_flag.xml new file mode 100644 index 0000000..db86d5d --- /dev/null +++ b/service/ServiceResources/res/drawable/ic_flag.xml @@ -0,0 +1,10 @@ + + + diff --git a/service/ServiceResources/res/values/strings.xml b/service/ServiceResources/res/values/strings.xml new file mode 100644 index 0000000..53407e0 --- /dev/null +++ b/service/ServiceResources/res/values/strings.xml @@ -0,0 +1,7 @@ + + + + New flags available + + Tap to reboot and apply new trunkfood flag values + diff --git a/service/flags.aconfig b/service/flags.aconfig new file mode 100644 index 0000000..6240af1 --- /dev/null +++ b/service/flags.aconfig @@ -0,0 +1,8 @@ +package: "com.android.server.deviceconfig" + +flag { + name: "enable_reboot_notification" + namespace: "core_experiments_team_internal" + description: "If enabled, a notification appears when flags are staged to be applied on reboot." + bug: "296462695" +} diff --git a/service/java/com/android/server/deviceconfig/BootNotificationCreator.java b/service/java/com/android/server/deviceconfig/BootNotificationCreator.java new file mode 100644 index 0000000..388fedc --- /dev/null +++ b/service/java/com/android/server/deviceconfig/BootNotificationCreator.java @@ -0,0 +1,164 @@ +package com.android.server.deviceconfig; + +import android.annotation.NonNull; +import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.IntentFilter; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.Context; +import android.graphics.drawable.Icon; +import android.os.PowerManager; +import android.provider.DeviceConfig.OnPropertiesChangedListener; +import android.provider.DeviceConfig.Properties; +import android.util.Slog; +import com.android.server.deviceconfig.resources.R; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import static android.app.NotificationManager.IMPORTANCE_HIGH; + +/** + * Creates notifications when flags are staged on the device. + * + * The notification alerts the user to reboot, to apply the staged flags. + * + * @hide + */ +class BootNotificationCreator implements OnPropertiesChangedListener { + private static final String TAG = "DeviceConfigBootNotificationCreator"; + + private static final String RESOURCES_PACKAGE = + "com.android.server.deviceconfig.resources"; + + private static final String REBOOT_REASON = "DeviceConfig"; + + private static final String ACTION_TRIGGER_HARD_REBOOT = + "com.android.server.deviceconfig.TRIGGER_HARD_REBOOT"; + private static final String ACTION_POST_NOTIFICATION = + "com.android.server.deviceconfig.POST_NOTIFICATION"; + + private static final String CHANNEL_ID = "trunk-stable-flags"; + private static final String CHANNEL_NAME = "Trunkfood flags"; + private static final int NOTIFICATION_ID = 111555; + + private NotificationManager notificationManager; + private PowerManager powerManager; + private AlarmManager alarmManager; + + private Context context; + + private static final int REBOOT_HOUR = 18; + private static final int REBOOT_MINUTE = 2; + + public BootNotificationCreator(@NonNull Context context) { + this.context = context; + + this.context.registerReceiver( + new HardRebootBroadcastReceiver(), + new IntentFilter(ACTION_TRIGGER_HARD_REBOOT), + Context.RECEIVER_EXPORTED); + this.context.registerReceiver( + new PostNotificationBroadcastReceiver(), + new IntentFilter(ACTION_POST_NOTIFICATION), + Context.RECEIVER_EXPORTED); + } + + @Override + public void onPropertiesChanged(Properties properties) { + if (!tryInitializeDependenciesIfNeeded()) { + Slog.i(TAG, "not posting notif; service dependencies not ready"); + return; + } + + PendingIntent pendingIntent = + PendingIntent.getBroadcast( + context, + /* requestCode= */ 1, + new Intent(ACTION_POST_NOTIFICATION), + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + + ZonedDateTime now = Instant + .ofEpochMilli(System.currentTimeMillis()) + .atZone(ZoneId.systemDefault()); + + LocalDateTime currentTime = now.toLocalDateTime(); + LocalDateTime postTime = now.toLocalDate().atTime(REBOOT_HOUR, REBOOT_MINUTE); + + LocalDateTime scheduledPostTime = + currentTime.isBefore(postTime) ? postTime : postTime.plusDays(1); + long scheduledPostTimeLong = scheduledPostTime + .atZone(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli(); + + alarmManager.setExact( + AlarmManager.RTC_WAKEUP, scheduledPostTimeLong, pendingIntent); + } + + private class PostNotificationBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + PendingIntent pendingIntent = + PendingIntent.getBroadcast( + context, + /* requestCode= */ 1, + new Intent(ACTION_TRIGGER_HARD_REBOOT), + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + + try { + Context resourcesContext = context.createPackageContext(RESOURCES_PACKAGE, 0); + Notification notification = new Notification.Builder(context, CHANNEL_ID) + .setContentText(resourcesContext.getString(R.string.boot_notification_content)) + .setContentTitle(resourcesContext.getString(R.string.boot_notification_title)) + .setSmallIcon(Icon.createWithResource(resourcesContext, R.drawable.ic_flag)) + .setContentIntent(pendingIntent) + .build(); + notificationManager.notify(NOTIFICATION_ID, notification); + } catch (NameNotFoundException e) { + Slog.e(TAG, "failed to post boot notification", e); + } + } + } + + private class HardRebootBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + powerManager.reboot(REBOOT_REASON); + } + } + + /** + * If deps are not initialized yet, try to initialize them. + * + * @return true if the dependencies are newly or already initialized, + * or false if they are not ready yet + */ + private boolean tryInitializeDependenciesIfNeeded() { + if (notificationManager == null) { + notificationManager = context.getSystemService(NotificationManager.class); + if (notificationManager != null) { + notificationManager.createNotificationChannel( + new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, IMPORTANCE_HIGH)); + } + } + + if (alarmManager == null) { + alarmManager = context.getSystemService(AlarmManager.class); + } + + if (powerManager == null) { + powerManager = context.getSystemService(PowerManager.class); + } + + return notificationManager != null + && alarmManager != null + && powerManager != null; + } +} diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigInit.java b/service/java/com/android/server/deviceconfig/DeviceConfigInit.java index f9c7792..42cee70 100644 --- a/service/java/com/android/server/deviceconfig/DeviceConfigInit.java +++ b/service/java/com/android/server/deviceconfig/DeviceConfigInit.java @@ -3,13 +3,18 @@ package com.android.server.deviceconfig; import java.io.FileDescriptor; import java.io.IOException; +import android.content.Intent; import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.Context; +import android.os.AsyncTask; import android.os.Binder; import android.provider.DeviceConfig; import android.provider.DeviceConfigManager; import android.provider.UpdatableDeviceConfigServiceReadiness; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.content.ComponentName; import android.provider.aidl.IDeviceConfigManager; import android.util.Slog; @@ -18,10 +23,13 @@ import com.android.modules.utils.build.SdkLevel; import com.android.server.SystemService; +import static com.android.server.deviceconfig.Flags.enableRebootNotification; + /** @hide */ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public class DeviceConfigInit { private static final String TAG = "DEVICE_CONFIG_INIT"; + private static final String STAGED_NAMESPACE = "staged"; private DeviceConfigInit() { // do not instantiate @@ -50,7 +58,12 @@ public class DeviceConfigInit { */ @Override public void onStart() { - // no op + if (enableRebootNotification()) { + DeviceConfig.addOnPropertiesChangedListener( + STAGED_NAMESPACE, + AsyncTask.THREAD_POOL_EXECUTOR, + new BootNotificationCreator(getContext().getApplicationContext())); + } } private void applyBootstrapValues() { diff --git a/service/javatests/Android.bp b/service/javatests/Android.bp index 3ec4631..726c86e 100644 --- a/service/javatests/Android.bp +++ b/service/javatests/Android.bp @@ -40,6 +40,7 @@ android_test { "modules-utils-build", "service-configinfrastructure.impl", "truth-prebuilt", + "mockito-target-minus-junit4", ], libs: [ "android.test.base", diff --git a/service/javatests/src/com/android/server/deviceconfig/BootNotificationCreatorTest.java b/service/javatests/src/com/android/server/deviceconfig/BootNotificationCreatorTest.java new file mode 100644 index 0000000..394864d --- /dev/null +++ b/service/javatests/src/com/android/server/deviceconfig/BootNotificationCreatorTest.java @@ -0,0 +1,80 @@ +/* + * 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.deviceconfig; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; +import android.content.Context; +import java.util.HashMap; + +import android.content.ContextWrapper; +import org.mockito.Mockito; +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.Properties; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; +import android.app.AlarmManager; + +import java.io.IOException; + +import org.junit.Test; +import org.junit.Before; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class BootNotificationCreatorTest { + Context mockContext; + AlarmManager mockAlarmManager; + + BootNotificationCreator bootNotificationCreator; + + @Before + public void setUp() { + mockAlarmManager = mock(AlarmManager.class); + mockContext = new ContextWrapper(getInstrumentation().getTargetContext()) { + @Override + public Object getSystemService(String name) { + if (name.equals(Context.ALARM_SERVICE)) { + return mockAlarmManager; + } else { + return super.getSystemService(name); + } + } + }; + bootNotificationCreator = new BootNotificationCreator(mockContext); + } + + @Test + public void testNotificationScheduledWhenFlagStaged() { + HashMap flags = new HashMap(); + flags.put("test", "flag"); + Properties properties = new Properties("staged", flags); + + bootNotificationCreator.onPropertiesChanged(properties); + + Mockito.verify(mockAlarmManager).setExact(anyInt(), anyLong(), any()); + } +} -- cgit v1.2.3 From ebdddd05048af120aef12b74b6ed96ac7755beb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Kosi=C5=84ski?= Date: Sat, 14 Oct 2023 00:51:10 +0000 Subject: Clean up obsolete aliases for Truth. Bug: 255714762 Test: presubmit Change-Id: Ib334889b1cddeebd1c1d7e1796fd35652fcbbce1 --- service/javatests/Android.bp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/service/javatests/Android.bp b/service/javatests/Android.bp index 726c86e..047d146 100644 --- a/service/javatests/Android.bp +++ b/service/javatests/Android.bp @@ -26,20 +26,20 @@ android_test { manifest: "AndroidManifest.xml", test_config: "AndroidTest.xml", data: [ - ":service-configinfrastructure-unitttests-bootstrap-files" + ":service-configinfrastructure-unitttests-bootstrap-files", ], srcs: [ "src/**/*.java", ], test_suites: [ "mts-configinfrastructure", - "general-tests" + "general-tests", ], static_libs: [ "androidx.test.runner", "modules-utils-build", "service-configinfrastructure.impl", - "truth-prebuilt", + "truth", "mockito-target-minus-junit4", ], libs: [ -- cgit v1.2.3 From 21753b42cb8154d5a4a8a6e4a3766427100da06c Mon Sep 17 00:00:00 2001 From: Ted Bauer Date: Tue, 17 Oct 2023 13:28:56 -0400 Subject: Add action to notification. Previously, the user would just tap the notification to trigger a reboot. This change adds a separate button. Test: m Bug: 296462695 Change-Id: Iaad341fb72e0b31d97053cec81496b64035e9839 --- .../ServiceResources/res/drawable/ic_restart.xml | 26 ++++++++++++++++++++++ service/ServiceResources/res/values/strings.xml | 4 +++- .../deviceconfig/BootNotificationCreator.java | 7 +++++- 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 service/ServiceResources/res/drawable/ic_restart.xml diff --git a/service/ServiceResources/res/drawable/ic_restart.xml b/service/ServiceResources/res/drawable/ic_restart.xml new file mode 100644 index 0000000..24d7c34 --- /dev/null +++ b/service/ServiceResources/res/drawable/ic_restart.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/service/ServiceResources/res/values/strings.xml b/service/ServiceResources/res/values/strings.xml index 53407e0..ed60624 100644 --- a/service/ServiceResources/res/values/strings.xml +++ b/service/ServiceResources/res/values/strings.xml @@ -3,5 +3,7 @@ New flags available - Tap to reboot and apply new trunkfood flag values + Your device has new trunkfood flags available. A restart is required to apply them. + + Tap to reboot diff --git a/service/java/com/android/server/deviceconfig/BootNotificationCreator.java b/service/java/com/android/server/deviceconfig/BootNotificationCreator.java index 388fedc..38a4e57 100644 --- a/service/java/com/android/server/deviceconfig/BootNotificationCreator.java +++ b/service/java/com/android/server/deviceconfig/BootNotificationCreator.java @@ -3,6 +3,7 @@ package com.android.server.deviceconfig; import android.annotation.NonNull; import android.app.AlarmManager; import android.app.Notification; +import android.app.Notification.Action; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; @@ -114,11 +115,15 @@ class BootNotificationCreator implements OnPropertiesChangedListener { try { Context resourcesContext = context.createPackageContext(RESOURCES_PACKAGE, 0); + Action action = new Action.Builder( + Icon.createWithResource(resourcesContext, R.drawable.ic_restart), + resourcesContext.getString(R.string.boot_notification_action_text), + pendingIntent).build(); Notification notification = new Notification.Builder(context, CHANNEL_ID) .setContentText(resourcesContext.getString(R.string.boot_notification_content)) .setContentTitle(resourcesContext.getString(R.string.boot_notification_title)) .setSmallIcon(Icon.createWithResource(resourcesContext, R.drawable.ic_flag)) - .setContentIntent(pendingIntent) + .addAction(action) .build(); notificationManager.notify(NOTIFICATION_ID, notification); } catch (NameNotFoundException e) { -- cgit v1.2.3 From f55b40f6ca866354123380198c8ad040749f7638 Mon Sep 17 00:00:00 2001 From: Veena Arvind Date: Mon, 25 Sep 2023 19:12:00 +0000 Subject: Add unattended reboot functionality to apply flags Bug: 297502146 Test: atest ConfigInfrastructureServiceUnitTests[com.google.android.configinfrastructure.apex] Change-Id: If537611b0d7c4e1583d2a11a0dd5f9ca353820c6 --- service/flags.aconfig | 6 + .../server/deviceconfig/DeviceConfigInit.java | 116 ++++++----- .../deviceconfig/UnattendedRebootManager.java | 223 +++++++++++++++++++++ .../UnattendedRebootManagerInjector.java | 43 ++++ service/javatests/Android.bp | 5 +- .../deviceconfig/UnattendedRebootManagerTest.java | 214 ++++++++++++++++++++ 6 files changed, 552 insertions(+), 55 deletions(-) create mode 100644 service/java/com/android/server/deviceconfig/UnattendedRebootManager.java create mode 100644 service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java create mode 100644 service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java diff --git a/service/flags.aconfig b/service/flags.aconfig index 6240af1..a9ca293 100644 --- a/service/flags.aconfig +++ b/service/flags.aconfig @@ -6,3 +6,9 @@ flag { description: "If enabled, a notification appears when flags are staged to be applied on reboot." bug: "296462695" } +flag { + name: "enable_unattended_reboot" + namespace: "core_experiments_team_internal" + description: "This flag controls enabling the unattended reboot feature for applying flags." + bug: "297502146" +} diff --git a/service/java/com/android/server/deviceconfig/DeviceConfigInit.java b/service/java/com/android/server/deviceconfig/DeviceConfigInit.java index 42cee70..6336619 100644 --- a/service/java/com/android/server/deviceconfig/DeviceConfigInit.java +++ b/service/java/com/android/server/deviceconfig/DeviceConfigInit.java @@ -1,84 +1,92 @@ package com.android.server.deviceconfig; -import java.io.FileDescriptor; -import java.io.IOException; +import static com.android.server.deviceconfig.Flags.enableRebootNotification; +import static com.android.server.deviceconfig.Flags.enableUnattendedReboot; +import java.io.IOException; import android.content.Intent; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.content.BroadcastReceiver; import android.content.Context; import android.os.AsyncTask; import android.os.Binder; +import android.content.IntentFilter; import android.provider.DeviceConfig; -import android.provider.DeviceConfigManager; import android.provider.UpdatableDeviceConfigServiceReadiness; import android.content.ServiceConnection; import android.os.IBinder; import android.content.ComponentName; - -import android.provider.aidl.IDeviceConfigManager; import android.util.Slog; - import com.android.modules.utils.build.SdkLevel; - import com.android.server.SystemService; -import static com.android.server.deviceconfig.Flags.enableRebootNotification; - /** @hide */ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public class DeviceConfigInit { - private static final String TAG = "DEVICE_CONFIG_INIT"; - private static final String STAGED_NAMESPACE = "staged"; + private static final String TAG = "DEVICE_CONFIG_INIT"; + private static final String STAGED_NAMESPACE = "staged"; - private DeviceConfigInit() { - // do not instantiate - } + private DeviceConfigInit() { + // do not instantiate + } + + /** @hide */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) + public static class Lifecycle extends SystemService { + private DeviceConfigServiceImpl mService; + private UnattendedRebootManager mUnattendedRebootManager; /** @hide */ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) - public static class Lifecycle extends SystemService { - private DeviceConfigServiceImpl mService; - - /** @hide */ - @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) - public Lifecycle(@NonNull Context context) { - super(context); - // this service is always instantiated but should only launch subsequent service(s) - // if the module is ready - if (UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) { - mService = new DeviceConfigServiceImpl(getContext()); - publishBinderService(DeviceConfig.SERVICE_NAME, mService); - } - applyBootstrapValues(); - } + public Lifecycle(@NonNull Context context) { + super(context); + // this service is always instantiated but should only launch subsequent service(s) + // if the module is ready + if (UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) { + mService = new DeviceConfigServiceImpl(getContext()); + publishBinderService(DeviceConfig.SERVICE_NAME, mService); + } + applyBootstrapValues(); + } - /** - * @hide - */ - @Override - public void onStart() { - if (enableRebootNotification()) { - DeviceConfig.addOnPropertiesChangedListener( - STAGED_NAMESPACE, - AsyncTask.THREAD_POOL_EXECUTOR, - new BootNotificationCreator(getContext().getApplicationContext())); - } - } + /** @hide */ + @Override + public void onStart() { + if (enableRebootNotification()) { + DeviceConfig.addOnPropertiesChangedListener( + STAGED_NAMESPACE, + AsyncTask.THREAD_POOL_EXECUTOR, + new BootNotificationCreator(getContext().getApplicationContext())); + } + if (enableUnattendedReboot()) { + mUnattendedRebootManager = + new UnattendedRebootManager(getContext().getApplicationContext()); + getContext() + .registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mUnattendedRebootManager.prepareUnattendedReboot(); + mUnattendedRebootManager.scheduleReboot(); + } + }, + new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); + } + } - private void applyBootstrapValues() { - if (SdkLevel.isAtLeastV()) { - try { - new DeviceConfigBootstrapValues().applyValuesIfNeeded(); - } catch (RuntimeException e) { - Slog.e(TAG, "Failed to load boot overrides", e); - throw e; - } catch (IOException e) { - Slog.e(TAG, "Failed to load boot overrides", e); - throw new RuntimeException(e); - } - } + private void applyBootstrapValues() { + if (SdkLevel.isAtLeastV()) { + try { + new DeviceConfigBootstrapValues().applyValuesIfNeeded(); + } catch (RuntimeException e) { + Slog.e(TAG, "Failed to load boot overrides", e); + throw e; + } catch (IOException e) { + Slog.e(TAG, "Failed to load boot overrides", e); + throw new RuntimeException(e); } + } } - + } } diff --git a/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java b/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java new file mode 100644 index 0000000..c360f4d --- /dev/null +++ b/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java @@ -0,0 +1,223 @@ +package com.android.server.deviceconfig; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AlarmManager; +import android.app.KeyguardManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.os.PowerManager; +import android.os.RecoverySystem; +import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +/** + * Reboot scheduler for applying aconfig flags. + * + *

If device is password protected, uses Resume on Reboot to reboot + * the device, otherwise proceeds with regular reboot. + * + * @hide + */ +final class UnattendedRebootManager { + private static final int DEFAULT_REBOOT_WINDOW_START_TIME_HOUR = 2; + + private static final int DEFAULT_REBOOT_FREQUENCY_DAYS = 2; + + private static final String TAG = "UnattendedRebootManager"; + + static final String REBOOT_REASON = "deviceconfig"; + + @VisibleForTesting + static final String ACTION_RESUME_ON_REBOOT_LSKF_CAPTURED = + "com.android.server.deviceconfig.RESUME_ON_REBOOOT_LSKF_CAPTURED"; + + @VisibleForTesting + static final String ACTION_TRIGGER_REBOOT = "com.android.server.deviceconfig.TRIGGER_REBOOT"; + + private final Context mContext; + + private boolean mLskfCaptured; + + private final UnattendedRebootManagerInjector mInjector; + + private static class InjectorImpl implements UnattendedRebootManagerInjector { + InjectorImpl() { + /*no op*/ + } + + public long now() { + return System.currentTimeMillis(); + } + + public ZoneId zoneId() { + return ZoneId.systemDefault(); + } + + public int getRebootStartTime() { + return DEFAULT_REBOOT_WINDOW_START_TIME_HOUR; + } + + public int getRebootFrequency() { + return DEFAULT_REBOOT_FREQUENCY_DAYS; + } + + public void setRebootAlarm(Context context, long rebootTimeMillis) { + AlarmManager alarmManager = context.getSystemService(AlarmManager.class); + PendingIntent pendingIntent = + PendingIntent.getBroadcast( + context, + /* requestCode= */ 0, + new Intent(ACTION_TRIGGER_REBOOT), + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); + + alarmManager.setExact(AlarmManager.RTC_WAKEUP, rebootTimeMillis, pendingIntent); + } + + public int rebootAndApply(@NonNull Context context, @NonNull String reason, boolean slotSwitch) + throws IOException { + return RecoverySystem.rebootAndApply(context, reason, slotSwitch); + } + + public void prepareForUnattendedUpdate( + @NonNull Context context, @NonNull String updateToken, @Nullable IntentSender intentSender) + throws IOException { + RecoverySystem.prepareForUnattendedUpdate(context, updateToken, intentSender); + } + + public boolean isPreparedForUnattendedUpdate(@NonNull Context context) throws IOException { + return RecoverySystem.isPreparedForUnattendedUpdate(context); + } + + public void regularReboot(Context context) { + PowerManager powerManager = context.getSystemService(PowerManager.class); + powerManager.reboot(REBOOT_REASON); + } + } + + @VisibleForTesting + UnattendedRebootManager(Context context, UnattendedRebootManagerInjector injector) { + mContext = context; + mInjector = injector; + + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mLskfCaptured = true; + } + }, + new IntentFilter(ACTION_RESUME_ON_REBOOT_LSKF_CAPTURED), + Context.RECEIVER_EXPORTED); + + // Do not export receiver so that tests don't trigger reboot. + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + tryRebootOrSchedule(); + } + }, + new IntentFilter(ACTION_TRIGGER_REBOOT), + Context.RECEIVER_NOT_EXPORTED); + } + + UnattendedRebootManager(Context context) { + this(context, new InjectorImpl()); + } + + public void prepareUnattendedReboot() { + Log.i(TAG, "Preparing for Unattended Reboot"); + // RoR only supported on devices with screen lock. + if (!isDeviceSecure(mContext)) { + return; + } + PendingIntent pendingIntent = + PendingIntent.getBroadcast( + mContext, + /* requestCode= */ 0, + new Intent(ACTION_RESUME_ON_REBOOT_LSKF_CAPTURED), + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); + + try { + mInjector.prepareForUnattendedUpdate( + mContext, /* updateToken= */ "", pendingIntent.getIntentSender()); + } catch (IOException e) { + Log.i(TAG, "prepareForUnattendedReboot failed with exception" + e.getLocalizedMessage()); + } + } + + public void scheduleReboot() { + // Reboot the next day at the reboot start time. + LocalDateTime timeToReboot = + Instant.ofEpochMilli(mInjector.now()) + .atZone(mInjector.zoneId()) + .toLocalDate() + .plusDays(mInjector.getRebootFrequency()) + .atTime(mInjector.getRebootStartTime(), /* minute= */ 0); + long rebootTimeMillis = timeToReboot.atZone(mInjector.zoneId()).toInstant().toEpochMilli(); + Log.v(TAG, "Scheduling unattended reboot at time " + timeToReboot); + + if (timeToReboot.isBefore( + LocalDateTime.ofInstant(Instant.ofEpochMilli(mInjector.now()), mInjector.zoneId()))) { + Log.w(TAG, "Reboot time has already passed."); + return; + } + + mInjector.setRebootAlarm(mContext, rebootTimeMillis); + } + + @VisibleForTesting + void tryRebootOrSchedule() { + // TODO(b/305259443): check network is connected + // Check if RoR is supported. + if (!isDeviceSecure(mContext)) { + Log.v(TAG, "Device is not secure. Proceed with regular reboot"); + mInjector.regularReboot(mContext); + } else if (isPreparedForUnattendedReboot()) { + try { + mInjector.rebootAndApply(mContext, REBOOT_REASON, /* slotSwitch= */ false); + } catch (IOException e) { + Log.e(TAG, e.getLocalizedMessage()); + } + // If reboot is successful, should not reach this. + } else { + // Lskf is not captured, try again the following day + prepareUnattendedReboot(); + scheduleReboot(); + } + } + + private boolean isPreparedForUnattendedReboot() { + try { + boolean isPrepared = mInjector.isPreparedForUnattendedUpdate(mContext); + if (isPrepared != mLskfCaptured) { + Log.w(TAG, "isPrepared != mLskfCaptured. Received " + isPrepared); + } + return isPrepared; + } catch (IOException e) { + Log.w(TAG, e.getLocalizedMessage()); + return mLskfCaptured; + } + } + + /** Returns true if the device has screen lock. */ + private static boolean isDeviceSecure(Context context) { + KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class); + if (keyguardManager == null) { + // Unknown if device is locked, proceed with RoR anyway. + Log.w(TAG, "Keyguard manager is null, proceeding with RoR anyway."); + return true; + } + return keyguardManager.isDeviceSecure(); + } +} diff --git a/service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java b/service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java new file mode 100644 index 0000000..4e883d3 --- /dev/null +++ b/service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java @@ -0,0 +1,43 @@ +package com.android.server.deviceconfig; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.IntentSender; +import android.os.RecoverySystem; + +import java.io.IOException; +import java.time.ZoneId; + +/** + * Dependency injectors for {@link com.android.server.deviceconfig.UnattendedRebootManager} to + * enable unit testing. + */ +interface UnattendedRebootManagerInjector { + + /** Time injectors. */ + long now(); + + ZoneId zoneId(); + + /** Reboot time injectors. */ + int getRebootStartTime(); + + int getRebootFrequency(); + + /** Reboot Alarm injector. */ + void setRebootAlarm(Context context, long rebootTimeMillis); + + /** {@link RecoverySystem} methods injectors. */ + int rebootAndApply(@NonNull Context context, @NonNull String reason, boolean slotSwitch) + throws IOException; + + void prepareForUnattendedUpdate( + @NonNull Context context, @NonNull String updateToken, @Nullable IntentSender intentSender) + throws IOException; + + boolean isPreparedForUnattendedUpdate(@NonNull Context context) throws IOException; + + /** Regular reboot injector. */ + void regularReboot(Context context); +} diff --git a/service/javatests/Android.bp b/service/javatests/Android.bp index 047d146..eb1561c 100644 --- a/service/javatests/Android.bp +++ b/service/javatests/Android.bp @@ -36,11 +36,14 @@ android_test { "general-tests", ], static_libs: [ + "androidx.test.rules", "androidx.test.runner", + "androidx.annotation_annotation", "modules-utils-build", "service-configinfrastructure.impl", - "truth", + "frameworks-base-testutils", "mockito-target-minus-junit4", + "truth", ], libs: [ "android.test.base", diff --git a/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java b/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java new file mode 100644 index 0000000..f87cf56 --- /dev/null +++ b/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java @@ -0,0 +1,214 @@ +package com.android.server.deviceconfig; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.server.deviceconfig.UnattendedRebootManager.ACTION_RESUME_ON_REBOOT_LSKF_CAPTURED; +import static com.android.server.deviceconfig.UnattendedRebootManager.ACTION_TRIGGER_REBOOT; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import android.app.KeyguardManager; +import android.content.BroadcastReceiver; +import android.content.ContextWrapper; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.util.Log; + +import androidx.test.filters.SmallTest; +import java.io.IOException; +import java.time.ZoneId; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; + +@SmallTest +public class UnattendedRebootManagerTest { + + private static final int REBOOT_FREQUENCY = 1; + private static final int REBOOT_HOUR = 2; + private static final long CURRENT_TIME = 1696452549304L; // 2023-10-04T13:49:09.304 + private static final long REBOOT_TIME = 1696496400000L; // 2023-10-05T02:00:00 + + private Context mContext; + + private KeyguardManager mKeyguardManager; + + FakeInjector mFakeInjector; + + private UnattendedRebootManager mRebootManager; + + @Before + public void setUp() throws Exception { + mKeyguardManager = mock(KeyguardManager.class); + + mContext = + new ContextWrapper(getInstrumentation().getTargetContext()) { + @Override + public Object getSystemService(String name) { + if (name.equals(Context.KEYGUARD_SERVICE)) { + return mKeyguardManager; + } + return super.getSystemService(name); + } + }; + + mFakeInjector = new FakeInjector(); + mRebootManager = new UnattendedRebootManager(mContext, mFakeInjector); + + // Need to register receiver in tests so that the test doesn't trigger reboot requested by + // deviceconfig. + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mRebootManager.tryRebootOrSchedule(); + } + }, + new IntentFilter(ACTION_TRIGGER_REBOOT), + Context.RECEIVER_EXPORTED); + } + + @Test + public void scheduleReboot() { + when(mKeyguardManager.isDeviceSecure()).thenReturn(true); + + mRebootManager.prepareUnattendedReboot(); + mRebootManager.scheduleReboot(); + + assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME); + assertTrue(mFakeInjector.isRebootAndApplied()); + assertFalse(mFakeInjector.isRegularRebooted()); + } + + @Test + public void scheduleReboot_noPinLock() { + when(mKeyguardManager.isDeviceSecure()).thenReturn(false); + + mRebootManager.prepareUnattendedReboot(); + mRebootManager.scheduleReboot(); + + assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME); + assertFalse(mFakeInjector.isRebootAndApplied()); + assertTrue(mFakeInjector.isRegularRebooted()); + } + + @Test + public void scheduleReboot_noPreparation() { + when(mKeyguardManager.isDeviceSecure()).thenReturn(true); + + mRebootManager.scheduleReboot(); + + assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME); + assertFalse(mFakeInjector.isRebootAndApplied()); + assertFalse(mFakeInjector.isRegularRebooted()); + } + + static class FakeInjector implements UnattendedRebootManagerInjector { + + private boolean isPreparedForUnattendedReboot; + private boolean rebootAndApplied; + private boolean regularRebooted; + private long actualRebootTime; + + FakeInjector() {} + + @Override + public void prepareForUnattendedUpdate( + @NonNull Context context, @NonNull String updateToken, @Nullable IntentSender intentSender) + throws IOException { + context.sendBroadcast(new Intent(ACTION_RESUME_ON_REBOOT_LSKF_CAPTURED)); + isPreparedForUnattendedReboot = true; + } + + @Override + public boolean isPreparedForUnattendedUpdate(@NonNull Context context) throws IOException { + return isPreparedForUnattendedReboot; + } + + @Override + public int rebootAndApply( + @NonNull Context context, @NonNull String reason, boolean slotSwitch) { + Log.i("UnattendedRebootManagerTest", "MockInjector.rebootAndApply"); + rebootAndApplied = true; + return 0; // No error. + } + + @Override + public int getRebootFrequency() { + return REBOOT_FREQUENCY; + } + + @Override + public void setRebootAlarm(Context context, long rebootTimeMillis) { + // reboot immediately + actualRebootTime = rebootTimeMillis; + context.sendBroadcast(new Intent(UnattendedRebootManager.ACTION_TRIGGER_REBOOT)); + + LatchingBroadcastReceiver rebootReceiver = new LatchingBroadcastReceiver(); + context.registerReceiver( + rebootReceiver, new IntentFilter(ACTION_TRIGGER_REBOOT), Context.RECEIVER_EXPORTED); + rebootReceiver.await(10, TimeUnit.SECONDS); + } + + @Override + public int getRebootStartTime() { + return REBOOT_HOUR; + } + + @Override + public long now() { + return CURRENT_TIME; + } + + @Override + public ZoneId zoneId() { + return ZoneId.of("America/Los_Angeles"); + } + + @Override + public void regularReboot(Context context) { + Log.i("UnattendedRebootManagerTest", "MockInjector.regularRebooted"); + regularRebooted = true; + } + + boolean isRebootAndApplied() { + return rebootAndApplied; + } + + boolean isRegularRebooted() { + return regularRebooted; + } + + public long getActualRebootTime() { + return actualRebootTime; + } + } + + /** + * A {@link BroadcastReceiver} with an internal latch that unblocks once any intent is received. + */ + private static class LatchingBroadcastReceiver extends BroadcastReceiver { + private CountDownLatch latch = new CountDownLatch(1); + + @Override + public void onReceive(Context context, Intent intent) { + latch.countDown(); + } + + public boolean await(long timeoutInMs, TimeUnit timeUnit) { + try { + return latch.await(timeoutInMs, timeUnit); + } catch (InterruptedException e) { + return false; + } + } + } +} -- cgit v1.2.3 From b6487df1597b46b204d25b3498825dde13c09427 Mon Sep 17 00:00:00 2001 From: Ted Bauer Date: Mon, 16 Oct 2023 15:16:37 -0400 Subject: Only schedule a notification on updates to aconfig flags. Test: m Bug: 298391955 Change-Id: I15604d265013413e5796d9cba46e0555604ef321 --- service/Android.bp | 2 + service/ServiceResources/AndroidManifest.xml | 4 +- service/jarjar-rules.txt | 2 + .../deviceconfig/BootNotificationCreator.java | 38 ++++- .../server/deviceconfig/DeviceConfigInit.java | 156 +++++++++++++-------- .../deviceconfig/BootNotificationCreatorTest.java | 56 +++++--- 6 files changed, 176 insertions(+), 82 deletions(-) diff --git a/service/Android.bp b/service/Android.bp index 8d0fb79..125d655 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -26,6 +26,7 @@ java_sdk_library { permitted_packages: [ "android.provider", "com.android.server.deviceconfig", + //"com.google.protobuf", ], apex_available: [ "com.android.configinfrastructure", @@ -34,6 +35,7 @@ java_sdk_library { "modules-utils-build", "modules-utils-shell-command-handler", "device_config_reboot_flags_java_lib", + "libaconfig_java_proto_lite" ], libs: [ "framework-configinfrastructure.impl", diff --git a/service/ServiceResources/AndroidManifest.xml b/service/ServiceResources/AndroidManifest.xml index 4fc5e06..d4e2796 100644 --- a/service/ServiceResources/AndroidManifest.xml +++ b/service/ServiceResources/AndroidManifest.xml @@ -1,7 +1,7 @@