diff options
| author | 2024-04-15 10:22:34 +0000 | |
|---|---|---|
| committer | 2024-05-21 07:37:19 +0000 | |
| commit | ef85555880bb79324d3d594e59d14ec4b510f905 (patch) | |
| tree | d6783617349f85d5e53f4892c652daff6b6b696a | |
| parent | 9db036da3a99a15c0f435aa92fbbab330f94e702 (diff) | |
Convert SettingsProxy and UserSettingsProxy to Kotlin.
Conversion to Kotlin is the first step towards migrating
register/unregister content observer APIs to background thread.
Test: atest / manual flash
Bug: 330299944
Flag: NONE Java to Kotlin migration, flag not needed.
Change-Id: I793d4bd9ad3d6ac9e39c3e97b7e4edc5b794ba84
9 files changed, 1258 insertions, 717 deletions
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 8b60ed035d07..c4929a101cce 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -766,6 +766,7 @@ android_robolectric_test { ], static_libs: [ "RoboTestLibraries", + "mockito-kotlin2", ], libs: [ "android.test.runner", diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java deleted file mode 100644 index aeed78ad4df4..000000000000 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util.settings; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ContentResolver; -import android.database.ContentObserver; -import android.net.Uri; -import android.provider.Settings; - -/** - * Used to interact with mainly with Settings.Global, but can also be used for Settings.System - * and Settings.Secure. To use the per-user System and Secure settings, {@link UserSettingsProxy} - * must be used instead. - * <p> - * This interface can be implemented to give instance method (instead of static method) versions - * of Settings.Global. It can be injected into class constructors and then faked or mocked as needed - * in tests. - * <p> - * You can ask for {@link GlobalSettings} to be injected as needed. - * <p> - * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods, - * normally found on {@link ContentResolver} instances, unifying setting related actions in one - * place. - */ -public interface SettingsProxy { - - /** - * Returns the {@link ContentResolver} this instance was constructed with. - */ - ContentResolver getContentResolver(); - - /** - * Construct the content URI for a particular name/value pair, - * useful for monitoring changes with a ContentObserver. - * @param name to look up in the table - * @return the corresponding content URI, or null if not present - */ - Uri getUriFor(String name); - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - * <p> - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserver(String name, ContentObserver settingsObserver) { - registerContentObserver(getUriFor(name), settingsObserver); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - */ - default void registerContentObserver(Uri uri, ContentObserver settingsObserver) { - registerContentObserver(uri, false, settingsObserver); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - * <p> - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserver(String name, boolean notifyForDescendants, - ContentObserver settingsObserver) { - registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - */ - default void registerContentObserver(Uri uri, boolean notifyForDescendants, - ContentObserver settingsObserver) { - getContentResolver().registerContentObserver( - uri, notifyForDescendants, settingsObserver); - } - - /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */ - default void unregisterContentObserver(ContentObserver settingsObserver) { - getContentResolver().unregisterContentObserver(settingsObserver); - } - - /** - * Look up a name in the database. - * @param name to look up in the table - * @return the corresponding value, or null if not present - */ - @Nullable - String getString(String name); - - /** - * Store a name/value pair into the database. - * @param name to store - * @param value to associate with the name - * @return true if the value was set, false on database errors - */ - boolean putString(String name, String value); - - /** - * Store a name/value pair into the database. - * <p> - * The method takes an optional tag to associate with the setting - * which can be used to clear only settings made by your package and - * associated with this tag by passing the tag to {@link - * #resetToDefaults(String)}. Anyone can override - * the current tag. Also if another package changes the setting - * then the tag will be set to the one specified in the set call - * which can be null. Also any of the settings setters that do not - * take a tag as an argument effectively clears the tag. - * </p><p> - * For example, if you set settings A and B with tags T1 and T2 and - * another app changes setting A (potentially to the same value), it - * can assign to it a tag T3 (note that now the package that changed - * the setting is not yours). Now if you reset your changes for T1 and - * T2 only setting B will be reset and A not (as it was changed by - * another package) but since A did not change you are in the desired - * initial state. Now if the other app changes the value of A (assuming - * you registered an observer in the beginning) you would detect that - * the setting was changed by another app and handle this appropriately - * (ignore, set back to some value, etc). - * </p><p> - * Also the method takes an argument whether to make the value the - * default for this setting. If the system already specified a default - * value, then the one passed in here will <strong>not</strong> - * be set as the default. - * </p> - * - * @param name to store. - * @param value to associate with the name. - * @param tag to associate with the setting. - * @param makeDefault whether to make the value the default one. - * @return true if the value was set, false on database errors. - * - * @see #resetToDefaults(String) - * - */ - boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag, - boolean makeDefault); - - /** - * Convenience function for retrieving a single secure settings value - * as an integer. Note that internally setting values are always - * stored as strings; this function converts the string to an integer - * for you. The default value will be returned if the setting is - * not defined or not an integer. - * - * @param name The name of the setting to retrieve. - * @param def Value to return if the setting is not defined. - * - * @return The setting's current value, or 'def' if it is not defined - * or not a valid integer. - */ - default int getInt(String name, int def) { - String v = getString(name); - try { - return v != null ? Integer.parseInt(v) : def; - } catch (NumberFormatException e) { - return def; - } - } - - /** - * Convenience function for retrieving a single secure settings value - * as an integer. Note that internally setting values are always - * stored as strings; this function converts the string to an integer - * for you. - * <p> - * This version does not take a default value. If the setting has not - * been set, or the string value is not a number, - * it throws {@link Settings.SettingNotFoundException}. - * - * @param name The name of the setting to retrieve. - * - * @throws Settings.SettingNotFoundException Thrown if a setting by the given - * name can't be found or the setting value is not an integer. - * - * @return The setting's current value. - */ - default int getInt(String name) - throws Settings.SettingNotFoundException { - String v = getString(name); - try { - return Integer.parseInt(v); - } catch (NumberFormatException e) { - throw new Settings.SettingNotFoundException(name); - } - } - - /** - * Convenience function for updating a single settings value as an - * integer. This will either create a new entry in the table if the - * given name does not exist, or modify the value of the existing row - * with that name. Note that internally setting values are always - * stored as strings, so this function converts the given value to a - * string before storing it. - * - * @param name The name of the setting to modify. - * @param value The new value for the setting. - * @return true if the value was set, false on database errors - */ - default boolean putInt(String name, int value) { - return putString(name, Integer.toString(value)); - } - - /** - * Convenience function for retrieving a single secure settings value - * as a boolean. Note that internally setting values are always - * stored as strings; this function converts the string to a boolean - * for you. The default value will be returned if the setting is - * not defined or not a boolean. - * - * @param name The name of the setting to retrieve. - * @param def Value to return if the setting is not defined. - * - * @return The setting's current value, or 'def' if it is not defined - * or not a valid boolean. - */ - default boolean getBool(String name, boolean def) { - return getInt(name, def ? 1 : 0) != 0; - } - - /** - * Convenience function for retrieving a single secure settings value - * as a boolean. Note that internally setting values are always - * stored as strings; this function converts the string to a boolean - * for you. - * <p> - * This version does not take a default value. If the setting has not - * been set, or the string value is not a number, - * it throws {@link Settings.SettingNotFoundException}. - * - * @param name The name of the setting to retrieve. - * - * @throws Settings.SettingNotFoundException Thrown if a setting by the given - * name can't be found or the setting value is not a boolean. - * - * @return The setting's current value. - */ - default boolean getBool(String name) - throws Settings.SettingNotFoundException { - return getInt(name) != 0; - } - - /** - * Convenience function for updating a single settings value as a - * boolean. This will either create a new entry in the table if the - * given name does not exist, or modify the value of the existing row - * with that name. Note that internally setting values are always - * stored as strings, so this function converts the given value to a - * string before storing it. - * - * @param name The name of the setting to modify. - * @param value The new value for the setting. - * @return true if the value was set, false on database errors - */ - default boolean putBool(String name, boolean value) { - return putInt(name, value ? 1 : 0); - } - - /** - * Convenience function for retrieving a single secure settings value - * as a {@code long}. Note that internally setting values are always - * stored as strings; this function converts the string to a {@code long} - * for you. The default value will be returned if the setting is - * not defined or not a {@code long}. - * - * @param name The name of the setting to retrieve. - * @param def Value to return if the setting is not defined. - * - * @return The setting's current value, or 'def' if it is not defined - * or not a valid {@code long}. - */ - default long getLong(String name, long def) { - String valString = getString(name); - return parseLongOrUseDefault(valString, def); - } - - /** Convert a string to a long, or uses a default if the string is malformed or null */ - static long parseLongOrUseDefault(String valString, long def) { - long value; - try { - value = valString != null ? Long.parseLong(valString) : def; - } catch (NumberFormatException e) { - value = def; - } - return value; - } - - /** - * Convenience function for retrieving a single secure settings value - * as a {@code long}. Note that internally setting values are always - * stored as strings; this function converts the string to a {@code long} - * for you. - * <p> - * This version does not take a default value. If the setting has not - * been set, or the string value is not a number, - * it throws {@link Settings.SettingNotFoundException}. - * - * @param name The name of the setting to retrieve. - * - * @return The setting's current value. - * @throws Settings.SettingNotFoundException Thrown if a setting by the given - * name can't be found or the setting value is not an integer. - */ - default long getLong(String name) - throws Settings.SettingNotFoundException { - String valString = getString(name); - return parseLongOrThrow(name, valString); - } - - /** Convert a string to a long, or throws an exception if the string is malformed or null */ - static long parseLongOrThrow(String name, String valString) - throws Settings.SettingNotFoundException { - try { - return Long.parseLong(valString); - } catch (NumberFormatException e) { - throw new Settings.SettingNotFoundException(name); - } - } - - /** - * Convenience function for updating a secure settings value as a long - * integer. This will either create a new entry in the table if the - * given name does not exist, or modify the value of the existing row - * with that name. Note that internally setting values are always - * stored as strings, so this function converts the given value to a - * string before storing it. - * - * @param name The name of the setting to modify. - * @param value The new value for the setting. - * @return true if the value was set, false on database errors - */ - default boolean putLong(String name, long value) { - return putString(name, Long.toString(value)); - } - - /** - * Convenience function for retrieving a single secure settings value - * as a floating point number. Note that internally setting values are - * always stored as strings; this function converts the string to an - * float for you. The default value will be returned if the setting - * is not defined or not a valid float. - * - * @param name The name of the setting to retrieve. - * @param def Value to return if the setting is not defined. - * - * @return The setting's current value, or 'def' if it is not defined - * or not a valid float. - */ - default float getFloat(String name, float def) { - String v = getString(name); - return parseFloat(v, def); - } - - /** Convert a string to a float, or uses a default if the string is malformed or null */ - static float parseFloat(String v, float def) { - try { - return v != null ? Float.parseFloat(v) : def; - } catch (NumberFormatException e) { - return def; - } - } - - /** - * Convenience function for retrieving a single secure settings value - * as a float. Note that internally setting values are always - * stored as strings; this function converts the string to a float - * for you. - * <p> - * This version does not take a default value. If the setting has not - * been set, or the string value is not a number, - * it throws {@link Settings.SettingNotFoundException}. - * - * @param name The name of the setting to retrieve. - * - * @throws Settings.SettingNotFoundException Thrown if a setting by the given - * name can't be found or the setting value is not a float. - * - * @return The setting's current value. - */ - default float getFloat(String name) - throws Settings.SettingNotFoundException { - String v = getString(name); - return parseFloatOrThrow(name, v); - } - - /** Convert a string to a float, or throws an exception if the string is malformed or null */ - static float parseFloatOrThrow(String name, String v) - throws Settings.SettingNotFoundException { - if (v == null) { - throw new Settings.SettingNotFoundException(name); - } - try { - return Float.parseFloat(v); - } catch (NumberFormatException e) { - throw new Settings.SettingNotFoundException(name); - } - } - - /** - * Convenience function for updating a single settings value as a - * floating point number. This will either create a new entry in the - * table if the given name does not exist, or modify the value of the - * existing row with that name. Note that internally setting values - * are always stored as strings, so this function converts the given - * value to a string before storing it. - * - * @param name The name of the setting to modify. - * @param value The new value for the setting. - * @return true if the value was set, false on database errors - */ - default boolean putFloat(String name, float value) { - return putString(name, Float.toString(value)); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt new file mode 100644 index 000000000000..ec89610ce014 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.util.settings + +import android.content.ContentResolver +import android.database.ContentObserver +import android.net.Uri +import android.provider.Settings.SettingNotFoundException + +/** + * Used to interact with mainly with Settings.Global, but can also be used for Settings.System and + * Settings.Secure. To use the per-user System and Secure settings, [UserSettingsProxy] must be used + * instead. + * + * This interface can be implemented to give instance method (instead of static method) versions of + * Settings.Global. It can be injected into class constructors and then faked or mocked as needed in + * tests. + * + * You can ask for [GlobalSettings] to be injected as needed. + * + * This class also provides [.registerContentObserver] methods, normally found on [ContentResolver] + * instances, unifying setting related actions in one place. + */ +interface SettingsProxy { + /** Returns the [ContentResolver] this instance was constructed with. */ + fun getContentResolver(): ContentResolver + + /** + * Construct the content URI for a particular name/value pair, useful for monitoring changes + * with a ContentObserver. + * + * @param name to look up in the table + * @return the corresponding content URI, or null if not present + */ + fun getUriFor(name: String): Uri + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * Implicitly calls [getUriFor] on the passed in name. + */ + fun registerContentObserver(name: String, settingsObserver: ContentObserver) { + registerContentObserver(getUriFor(name), settingsObserver) + } + + /** Convenience wrapper around [ContentResolver.registerContentObserver].' */ + fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) = + registerContentObserver(uri, false, settingsObserver) + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * Implicitly calls [getUriFor] on the passed in name. + */ + fun registerContentObserver( + name: String, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver + ) = registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver) + + /** Convenience wrapper around [ContentResolver.registerContentObserver].' */ + fun registerContentObserver( + uri: Uri, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver + ) = getContentResolver().registerContentObserver(uri, notifyForDescendants, settingsObserver) + + /** See [ContentResolver.unregisterContentObserver]. */ + fun unregisterContentObserver(settingsObserver: ContentObserver) = + getContentResolver().unregisterContentObserver(settingsObserver) + + /** + * Look up a name in the database. + * + * @param name to look up in the table + * @return the corresponding value, or null if not present + */ + fun getString(name: String): String + + /** + * Store a name/value pair into the database. + * + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + */ + fun putString(name: String, value: String): Boolean + + /** + * Store a name/value pair into the database. + * + * The method takes an optional tag to associate with the setting which can be used to clear + * only settings made by your package and associated with this tag by passing the tag to + * [ ][.resetToDefaults]. Anyone can override the current tag. Also if another package changes + * the setting then the tag will be set to the one specified in the set call which can be null. + * Also any of the settings setters that do not take a tag as an argument effectively clears the + * tag. + * + * For example, if you set settings A and B with tags T1 and T2 and another app changes setting + * A (potentially to the same value), it can assign to it a tag T3 (note that now the package + * that changed the setting is not yours). Now if you reset your changes for T1 and T2 only + * setting B will be reset and A not (as it was changed by another package) but since A did not + * change you are in the desired initial state. Now if the other app changes the value of A + * (assuming you registered an observer in the beginning) you would detect that the setting was + * changed by another app and handle this appropriately (ignore, set back to some value, etc). + * + * Also the method takes an argument whether to make the value the default for this setting. If + * the system already specified a default value, then the one passed in here will **not** be set + * as the default. + * + * @param name to store. + * @param value to associate with the name. + * @param tag to associate with the setting. + * @param makeDefault whether to make the value the default one. + * @return true if the value was set, false on database errors. + * @see .resetToDefaults + */ + fun putString(name: String, value: String, tag: String, makeDefault: Boolean): Boolean + + /** + * Convenience function for retrieving a single secure settings value as an integer. Note that + * internally setting values are always stored as strings; this function converts the string to + * an integer for you. The default value will be returned if the setting is not defined or not + * an integer. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * @return The setting's current value, or 'def' if it is not defined or not a valid integer. + */ + fun getInt(name: String, def: Int): Int { + val v = getString(name) + return try { + v.toInt() + } catch (e: NumberFormatException) { + def + } + } + + /** + * Convenience function for retrieving a single secure settings value as an integer. Note that + * internally setting values are always stored as strings; this function converts the string to + * an integer for you. + * + * This version does not take a default value. If the setting has not been set, or the string + * value is not a number, it throws [Settings.SettingNotFoundException]. + * + * @param name The name of the setting to retrieve. + * @return The setting's current value. + * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be + * found or the setting value is not an integer. + */ + @Throws(SettingNotFoundException::class) + fun getInt(name: String): Int { + val v = getString(name) + return try { + v.toInt() + } catch (e: NumberFormatException) { + throw SettingNotFoundException(name) + } + } + + /** + * Convenience function for updating a single settings value as an integer. This will either + * create a new entry in the table if the given name does not exist, or modify the value of the + * existing row with that name. Note that internally setting values are always stored as + * strings, so this function converts the given value to a string before storing it. + * + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + fun putInt(name: String, value: Int): Boolean { + return putString(name, value.toString()) + } + + /** + * Convenience function for retrieving a single secure settings value as a boolean. Note that + * internally setting values are always stored as strings; this function converts the string to + * a boolean for you. The default value will be returned if the setting is not defined or not a + * boolean. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * @return The setting's current value, or 'def' if it is not defined or not a valid boolean. + */ + fun getBool(name: String, def: Boolean): Boolean { + return getInt(name, if (def) 1 else 0) != 0 + } + + /** + * Convenience function for retrieving a single secure settings value as a boolean. Note that + * internally setting values are always stored as strings; this function converts the string to + * a boolean for you. + * + * This version does not take a default value. If the setting has not been set, or the string + * value is not a number, it throws [Settings.SettingNotFoundException]. + * + * @param name The name of the setting to retrieve. + * @return The setting's current value. + * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be + * found or the setting value is not a boolean. + */ + @Throws(SettingNotFoundException::class) + fun getBool(name: String): Boolean { + return getInt(name) != 0 + } + + /** + * Convenience function for updating a single settings value as a boolean. This will either + * create a new entry in the table if the given name does not exist, or modify the value of the + * existing row with that name. Note that internally setting values are always stored as + * strings, so this function converts the given value to a string before storing it. + * + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + fun putBool(name: String, value: Boolean): Boolean { + return putInt(name, if (value) 1 else 0) + } + + /** + * Convenience function for retrieving a single secure settings value as a `long`. Note that + * internally setting values are always stored as strings; this function converts the string to + * a `long` for you. The default value will be returned if the setting is not defined or not a + * `long`. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * @return The setting's current value, or 'def' if it is not defined or not a valid `long`. + */ + fun getLong(name: String, def: Long): Long { + val valString = getString(name) + return parseLongOrUseDefault(valString, def) + } + + /** + * Convenience function for retrieving a single secure settings value as a `long`. Note that + * internally setting values are always stored as strings; this function converts the string to + * a `long` for you. + * + * This version does not take a default value. If the setting has not been set, or the string + * value is not a number, it throws [Settings.SettingNotFoundException]. + * + * @param name The name of the setting to retrieve. + * @return The setting's current value. + * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be + * found or the setting value is not an integer. + */ + @Throws(SettingNotFoundException::class) + fun getLong(name: String): Long { + val valString = getString(name) + return parseLongOrThrow(name, valString) + } + + /** + * Convenience function for updating a secure settings value as a long integer. This will either + * create a new entry in the table if the given name does not exist, or modify the value of the + * existing row with that name. Note that internally setting values are always stored as + * strings, so this function converts the given value to a string before storing it. + * + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + fun putLong(name: String, value: Long): Boolean { + return putString(name, value.toString()) + } + + /** + * Convenience function for retrieving a single secure settings value as a floating point + * number. Note that internally setting values are always stored as strings; this function + * converts the string to an float for you. The default value will be returned if the setting is + * not defined or not a valid float. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * @return The setting's current value, or 'def' if it is not defined or not a valid float. + */ + fun getFloat(name: String, def: Float): Float { + val v = getString(name) + return parseFloat(v, def) + } + + /** + * Convenience function for retrieving a single secure settings value as a float. Note that + * internally setting values are always stored as strings; this function converts the string to + * a float for you. + * + * This version does not take a default value. If the setting has not been set, or the string + * value is not a number, it throws [Settings.SettingNotFoundException]. + * + * @param name The name of the setting to retrieve. + * @return The setting's current value. + * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be + * found or the setting value is not a float. + */ + @Throws(SettingNotFoundException::class) + fun getFloat(name: String): Float { + val v = getString(name) + return parseFloatOrThrow(name, v) + } + + /** + * Convenience function for updating a single settings value as a floating point number. This + * will either create a new entry in the table if the given name does not exist, or modify the + * value of the existing row with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a string before storing it. + * + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + fun putFloat(name: String, value: Float): Boolean { + return putString(name, value.toString()) + } + + companion object { + /** Convert a string to a long, or uses a default if the string is malformed or null */ + @JvmStatic + fun parseLongOrUseDefault(valString: String, def: Long): Long { + val value: Long + value = + try { + valString.toLong() + } catch (e: NumberFormatException) { + def + } + return value + } + + /** Convert a string to a long, or throws an exception if the string is malformed or null */ + @JvmStatic + @Throws(SettingNotFoundException::class) + fun parseLongOrThrow(name: String, valString: String?): Long { + if (valString == null) { + throw SettingNotFoundException(name) + } + return try { + valString.toLong() + } catch (e: NumberFormatException) { + throw SettingNotFoundException(name) + } + } + + /** Convert a string to a float, or uses a default if the string is malformed or null */ + @JvmStatic + fun parseFloat(v: String?, def: Float): Float { + return try { + v?.toFloat() ?: def + } catch (e: NumberFormatException) { + def + } + } + + /** + * Convert a string to a float, or throws an exception if the string is malformed or null + */ + @JvmStatic + @Throws(SettingNotFoundException::class) + fun parseFloatOrThrow(name: String, v: String?): Float { + if (v == null) { + throw SettingNotFoundException(name) + } + return try { + v.toFloat() + } catch (e: NumberFormatException) { + throw SettingNotFoundException(name) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java deleted file mode 100644 index 10cf08221fb3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util.settings; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.content.ContentResolver; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.UserHandle; -import android.provider.Settings; - -import com.android.app.tracing.TraceUtils; -import com.android.systemui.settings.UserTracker; - -import kotlin.Unit; - -/** - * Used to interact with per-user Settings.Secure and Settings.System settings (but not - * Settings.Global, since those do not vary per-user) - * <p> - * This interface can be implemented to give instance method (instead of static method) versions - * of Settings.Secure and Settings.System. It can be injected into class constructors and then - * faked or mocked as needed in tests. - * <p> - * You can ask for {@link SecureSettings} or {@link SystemSettings} to be injected as needed. - * <p> - * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods, - * normally found on {@link ContentResolver} instances, unifying setting related actions in one - * place. - */ -public interface UserSettingsProxy extends SettingsProxy { - - /** - * Returns that {@link UserTracker} this instance was constructed with. - */ - UserTracker getUserTracker(); - - /** - * Returns the user id for the associated {@link ContentResolver}. - */ - default int getUserId() { - return getContentResolver().getUserId(); - } - - /** - * Returns the actual current user handle when querying with the current user. Otherwise, - * returns the passed in user id. - */ - default int getRealUserHandle(int userHandle) { - if (userHandle != UserHandle.USER_CURRENT) { - return userHandle; - } - return getUserTracker().getUserId(); - } - - @Override - default void registerContentObserver(Uri uri, ContentObserver settingsObserver) { - registerContentObserverForUser(uri, settingsObserver, getUserId()); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - */ - @Override - default void registerContentObserver(Uri uri, boolean notifyForDescendants, - ContentObserver settingsObserver) { - registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId()); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - * - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserverForUser( - String name, ContentObserver settingsObserver, int userHandle) { - registerContentObserverForUser( - getUriFor(name), settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - */ - default void registerContentObserverForUser( - Uri uri, ContentObserver settingsObserver, int userHandle) { - registerContentObserverForUser( - uri, false, settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - * - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserverForUser( - String name, boolean notifyForDescendants, ContentObserver settingsObserver, - int userHandle) { - registerContentObserverForUser( - getUriFor(name), notifyForDescendants, settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - */ - default void registerContentObserverForUser( - Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver, - int userHandle) { - TraceUtils.trace( - () -> { - // The limit for trace tags length is 127 chars, which leaves us 90 for Uri. - return "USP#registerObserver#[" + uri.toString() + "]"; - }, () -> { - getContentResolver().registerContentObserver( - uri, notifyForDescendants, settingsObserver, - getRealUserHandle(userHandle)); - return Unit.INSTANCE; - }); - } - - /** - * Look up a name in the database. - * @param name to look up in the table - * @return the corresponding value, or null if not present - */ - @Override - default String getString(String name) { - return getStringForUser(name, getUserId()); - } - - /**See {@link #getString(String)}. */ - String getStringForUser(String name, int userHandle); - - /** - * Store a name/value pair into the database. Values written by this method will be - * overridden if a restore happens in the future. - * - * @param name to store - * @param value to associate with the name - * @return true if the value was set, false on database errors - */ - boolean putString(String name, String value, boolean overrideableByRestore); - - @Override - default boolean putString(String name, String value) { - return putStringForUser(name, value, getUserId()); - } - - /** See {@link #putString(String, String)}. */ - boolean putStringForUser(String name, String value, int userHandle); - - /** See {@link #putString(String, String)}. */ - boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag, - boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore); - - @Override - default int getInt(String name, int def) { - return getIntForUser(name, def, getUserId()); - } - - /** See {@link #getInt(String, int)}. */ - default int getIntForUser(String name, int def, int userHandle) { - String v = getStringForUser(name, userHandle); - try { - return v != null ? Integer.parseInt(v) : def; - } catch (NumberFormatException e) { - return def; - } - } - - @Override - default int getInt(String name) throws Settings.SettingNotFoundException { - return getIntForUser(name, getUserId()); - } - - /** See {@link #getInt(String)}. */ - default int getIntForUser(String name, int userHandle) - throws Settings.SettingNotFoundException { - String v = getStringForUser(name, userHandle); - try { - return Integer.parseInt(v); - } catch (NumberFormatException e) { - throw new Settings.SettingNotFoundException(name); - } - } - - @Override - default boolean putInt(String name, int value) { - return putIntForUser(name, value, getUserId()); - } - - /** See {@link #putInt(String, int)}. */ - default boolean putIntForUser(String name, int value, int userHandle) { - return putStringForUser(name, Integer.toString(value), userHandle); - } - - @Override - default boolean getBool(String name, boolean def) { - return getBoolForUser(name, def, getUserId()); - } - - /** See {@link #getBool(String, boolean)}. */ - default boolean getBoolForUser(String name, boolean def, int userHandle) { - return getIntForUser(name, def ? 1 : 0, userHandle) != 0; - } - - @Override - default boolean getBool(String name) throws Settings.SettingNotFoundException { - return getBoolForUser(name, getUserId()); - } - - /** See {@link #getBool(String)}. */ - default boolean getBoolForUser(String name, int userHandle) - throws Settings.SettingNotFoundException { - return getIntForUser(name, userHandle) != 0; - } - - @Override - default boolean putBool(String name, boolean value) { - return putBoolForUser(name, value, getUserId()); - } - - /** See {@link #putBool(String, boolean)}. */ - default boolean putBoolForUser(String name, boolean value, int userHandle) { - return putIntForUser(name, value ? 1 : 0, userHandle); - } - - /** See {@link #getLong(String, long)}. */ - default long getLongForUser(String name, long def, int userHandle) { - String valString = getStringForUser(name, userHandle); - return SettingsProxy.parseLongOrUseDefault(valString, def); - } - - /** See {@link #getLong(String)}. */ - default long getLongForUser(String name, int userHandle) - throws Settings.SettingNotFoundException { - String valString = getStringForUser(name, userHandle); - return SettingsProxy.parseLongOrThrow(name, valString); - } - - /** See {@link #putLong(String, long)}. */ - default boolean putLongForUser(String name, long value, int userHandle) { - return putStringForUser(name, Long.toString(value), userHandle); - } - - /** See {@link #getFloat(String)}. */ - default float getFloatForUser(String name, float def, int userHandle) { - String v = getStringForUser(name, userHandle); - return SettingsProxy.parseFloat(v, def); - } - - /** See {@link #getFloat(String, float)}. */ - default float getFloatForUser(String name, int userHandle) - throws Settings.SettingNotFoundException { - String v = getStringForUser(name, userHandle); - return SettingsProxy.parseFloatOrThrow(name, v); - } - - /** See {@link #putFloat(String, float)} */ - default boolean putFloatForUser(String name, float value, int userHandle) { - return putStringForUser(name, Float.toString(value), userHandle); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt new file mode 100644 index 000000000000..2285270b0bc7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt @@ -0,0 +1,269 @@ +/* + * 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.systemui.util.settings + +import android.annotation.UserIdInt +import android.database.ContentObserver +import android.net.Uri +import android.os.UserHandle +import android.provider.Settings.SettingNotFoundException +import com.android.app.tracing.TraceUtils.trace +import com.android.systemui.settings.UserTracker +import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat +import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloatOrThrow +import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrThrow +import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrUseDefault + +/** + * Used to interact with per-user Settings.Secure and Settings.System settings (but not + * Settings.Global, since those do not vary per-user) + * + * This interface can be implemented to give instance method (instead of static method) versions of + * Settings.Secure and Settings.System. It can be injected into class constructors and then faked or + * mocked as needed in tests. + * + * You can ask for [SecureSettings] or [SystemSettings] to be injected as needed. + * + * This class also provides [.registerContentObserver] methods, normally found on [ContentResolver] + * instances, unifying setting related actions in one place. + */ +interface UserSettingsProxy : SettingsProxy { + + /** Returns that [UserTracker] this instance was constructed with. */ + val userTracker: UserTracker + + /** Returns the user id for the associated [ContentResolver]. */ + var userId: Int + get() = getContentResolver().userId + set(_) { + throw UnsupportedOperationException( + "userId cannot be set in interface, use setter from an implementation instead." + ) + } + + /** + * Returns the actual current user handle when querying with the current user. Otherwise, + * returns the passed in user id. + */ + fun getRealUserHandle(userHandle: Int): Int { + return if (userHandle != UserHandle.USER_CURRENT) { + userHandle + } else userTracker.userId + } + + override fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) { + registerContentObserverForUser(uri, settingsObserver, userId) + } + + /** Convenience wrapper around [ContentResolver.registerContentObserver].' */ + override fun registerContentObserver( + uri: Uri, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver + ) { + registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, userId) + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver] + * + * Implicitly calls [getUriFor] on the passed in name. + */ + fun registerContentObserverForUser( + name: String, + settingsObserver: ContentObserver, + userHandle: Int + ) { + registerContentObserverForUser(getUriFor(name), settingsObserver, userHandle) + } + + /** Convenience wrapper around [ContentResolver.registerContentObserver] */ + fun registerContentObserverForUser( + uri: Uri, + settingsObserver: ContentObserver, + userHandle: Int + ) { + registerContentObserverForUser(uri, false, settingsObserver, userHandle) + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver] + * + * Implicitly calls [getUriFor] on the passed in name. + */ + fun registerContentObserverForUser( + name: String, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver, + userHandle: Int + ) { + registerContentObserverForUser( + getUriFor(name), + notifyForDescendants, + settingsObserver, + userHandle + ) + } + + /** Convenience wrapper around [ContentResolver.registerContentObserver] */ + fun registerContentObserverForUser( + uri: Uri, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver, + userHandle: Int + ) { + trace({ "USP#registerObserver#[$uri]" }) { + getContentResolver() + .registerContentObserver( + uri, + notifyForDescendants, + settingsObserver, + getRealUserHandle(userHandle) + ) + Unit + } + } + + /** + * Look up a name in the database. + * + * @param name to look up in the table + * @return the corresponding value, or null if not present + */ + override fun getString(name: String): String { + return getStringForUser(name, userId) + } + + /** See [getString]. */ + fun getStringForUser(name: String, userHandle: Int): String + + /** + * Store a name/value pair into the database. Values written by this method will be overridden + * if a restore happens in the future. + * + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + */ + fun putString(name: String, value: String, overrideableByRestore: Boolean): Boolean + override fun putString(name: String, value: String): Boolean { + return putStringForUser(name, value, userId) + } + + /** Similar implementation to [putString] for the specified [userHandle]. */ + fun putStringForUser(name: String, value: String, userHandle: Int): Boolean + + /** Similar implementation to [putString] for the specified [userHandle]. */ + fun putStringForUser( + name: String, + value: String, + tag: String?, + makeDefault: Boolean, + @UserIdInt userHandle: Int, + overrideableByRestore: Boolean + ): Boolean + + override fun getInt(name: String, def: Int): Int { + return getIntForUser(name, def, userId) + } + + /** Similar implementation to [getInt] for the specified [userHandle]. */ + fun getIntForUser(name: String, def: Int, userHandle: Int): Int { + val v = getStringForUser(name, userHandle) + return try { + v.toInt() + } catch (e: NumberFormatException) { + def + } + } + + @Throws(SettingNotFoundException::class) + override fun getInt(name: String) = getIntForUser(name, userId) + + /** Similar implementation to [getInt] for the specified [userHandle]. */ + @Throws(SettingNotFoundException::class) + fun getIntForUser(name: String, userHandle: Int): Int { + val v = getStringForUser(name, userHandle) + return try { + v.toInt() + } catch (e: NumberFormatException) { + throw SettingNotFoundException(name) + } + } + + override fun putInt(name: String, value: Int) = putIntForUser(name, value, userId) + + /** Similar implementation to [getInt] for the specified [userHandle]. */ + fun putIntForUser(name: String, value: Int, userHandle: Int) = + putStringForUser(name, value.toString(), userHandle) + + override fun getBool(name: String, def: Boolean) = getBoolForUser(name, def, userId) + + /** Similar implementation to [getBool] for the specified [userHandle]. */ + fun getBoolForUser(name: String, def: Boolean, userHandle: Int) = + getIntForUser(name, if (def) 1 else 0, userHandle) != 0 + + @Throws(SettingNotFoundException::class) + override fun getBool(name: String) = getBoolForUser(name, userId) + + /** Similar implementation to [getBool] for the specified [userHandle]. */ + @Throws(SettingNotFoundException::class) + fun getBoolForUser(name: String, userHandle: Int): Boolean { + return getIntForUser(name, userHandle) != 0 + } + + override fun putBool(name: String, value: Boolean): Boolean { + return putBoolForUser(name, value, userId) + } + + /** Similar implementation to [putBool] for the specified [userHandle]. */ + fun putBoolForUser(name: String, value: Boolean, userHandle: Int) = + putIntForUser(name, if (value) 1 else 0, userHandle) + + /** Similar implementation to [getLong] for the specified [userHandle]. */ + fun getLongForUser(name: String, def: Long, userHandle: Int): Long { + val valString = getStringForUser(name, userHandle) + return parseLongOrUseDefault(valString, def) + } + + /** Similar implementation to [getLong] for the specified [userHandle]. */ + @Throws(SettingNotFoundException::class) + fun getLongForUser(name: String, userHandle: Int): Long { + val valString = getStringForUser(name, userHandle) + return parseLongOrThrow(name, valString) + } + + /** Similar implementation to [putLong] for the specified [userHandle]. */ + fun putLongForUser(name: String, value: Long, userHandle: Int) = + putStringForUser(name, value.toString(), userHandle) + + /** Similar implementation to [getFloat] for the specified [userHandle]. */ + fun getFloatForUser(name: String, def: Float, userHandle: Int): Float { + val v = getStringForUser(name, userHandle) + return parseFloat(v, def) + } + + /** Similar implementation to [getFloat] for the specified [userHandle]. */ + @Throws(SettingNotFoundException::class) + fun getFloatForUser(name: String, userHandle: Int): Float { + val v = getStringForUser(name, userHandle) + return parseFloatOrThrow(name, v) + } + + /** Similar implementation to [putFloat] for the specified [userHandle]. */ + fun putFloatForUser(name: String, value: Float, userHandle: Int) = + putStringForUser(name, value.toString(), userHandle) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt index 0df4fbf86d98..9ba56d27fdf3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt @@ -36,12 +36,12 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any private const val USER_ID = 8 diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt index 7856f9bce5cc..a89139b18bed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt @@ -196,7 +196,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(globalSettings) .registerContentObserver( eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)), - settingsObserverCaptor.capture() + capture(settingsObserverCaptor) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt new file mode 100644 index 000000000000..ab95707046d9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.settings + +import android.content.ContentResolver +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.Looper +import android.provider.Settings.SettingNotFoundException +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.kotlin.eq + +/** Tests for [SettingsProxy]. */ +@RunWith(AndroidTestingRunner::class) +@SmallTest +@TestableLooper.RunWithLooper +class SettingsProxyTest : SysuiTestCase() { + + private lateinit var mSettings: SettingsProxy + private lateinit var mContentObserver: ContentObserver + + @Before + fun setUp() { + mSettings = FakeSettingsProxy() + mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {} + } + + @Test + fun registerContentObserver_inputString_success() { + mSettings.registerContentObserver(TEST_SETTING, mContentObserver) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver)) + } + + @Test + fun registerContentObserver_inputString_notifyForDescendants_true() { + mSettings.registerContentObserver( + TEST_SETTING, + notifyForDescendants = true, + mContentObserver + ) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) + } + + @Test + fun registerContentObserver_inputUri_success() { + mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver)) + } + + @Test + fun registerContentObserver_inputUri_notifyForDescendants_true() { + mSettings.registerContentObserver( + TEST_SETTING_URI, + notifyForDescendants = true, + mContentObserver + ) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) + } + + @Test + fun unregisterContentObserver() { + mSettings.unregisterContentObserver(mContentObserver) + verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver)) + } + + @Test + fun getString_keyPresent_returnValidValue() { + mSettings.putString(TEST_SETTING, "test") + assertThat(mSettings.getString(TEST_SETTING)).isEqualTo("test") + } + + @Test + fun getString_keyAbsent_returnEmptyValue() { + assertThat(mSettings.getString(TEST_SETTING)).isEmpty() + } + + @Test + fun getInt_keyPresent_returnValidValue() { + mSettings.putInt(TEST_SETTING, 2) + assertThat(mSettings.getInt(TEST_SETTING)).isEqualTo(2) + } + + @Test + fun getInt_keyPresent_nonIntegerValue_throwException() { + assertThrows(SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getInt(TEST_SETTING) + } + } + + @Test + fun getInt_keyAbsent_throwException() { + assertThrows(SettingNotFoundException::class.java) { mSettings.getInt(TEST_SETTING) } + } + + @Test + fun getInt_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getInt(TEST_SETTING, 5)).isEqualTo(5) + } + + @Test + fun getBool_keyPresent_returnValidValue() { + mSettings.putBool(TEST_SETTING, true) + assertThat(mSettings.getBool(TEST_SETTING)).isTrue() + } + + @Test + fun getBool_keyPresent_nonBooleanValue_throwException() { + assertThrows(SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getBool(TEST_SETTING) + } + } + + @Test + fun getBool_keyAbsent_throwException() { + assertThrows(SettingNotFoundException::class.java) { mSettings.getBool(TEST_SETTING) } + } + + @Test + fun getBool_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getBool(TEST_SETTING, false)).isEqualTo(false) + } + + @Test + fun getLong_keyPresent_returnValidValue() { + mSettings.putLong(TEST_SETTING, 1L) + assertThat(mSettings.getLong(TEST_SETTING)).isEqualTo(1L) + } + + @Test + fun getLong_keyPresent_nonLongValue_throwException() { + assertThrows(SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getLong(TEST_SETTING) + } + } + + @Test + fun getLong_keyAbsent_throwException() { + assertThrows(SettingNotFoundException::class.java) { mSettings.getLong(TEST_SETTING) } + } + + @Test + fun getLong_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getLong(TEST_SETTING, 2L)).isEqualTo(2L) + } + + @Test + fun getFloat_keyPresent_returnValidValue() { + mSettings.putFloat(TEST_SETTING, 2.5F) + assertThat(mSettings.getFloat(TEST_SETTING)).isEqualTo(2.5F) + } + + @Test + fun getFloat_keyPresent_nonFloatValue_throwException() { + assertThrows(SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getFloat(TEST_SETTING) + } + } + + @Test + fun getFloat_keyAbsent_throwException() { + assertThrows(SettingNotFoundException::class.java) { mSettings.getFloat(TEST_SETTING) } + } + + @Test + fun getFloat_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F) + } + + private class FakeSettingsProxy : SettingsProxy { + + private val mContentResolver = mock(ContentResolver::class.java) + private val settingToValueMap: MutableMap<String, String> = mutableMapOf() + + override fun getContentResolver() = mContentResolver + + override fun getUriFor(name: String) = + Uri.parse(StringBuilder().append("content://settings/").append(name).toString()) + + override fun getString(name: String): String { + return settingToValueMap[name] ?: "" + } + + override fun putString(name: String, value: String): Boolean { + settingToValueMap[name] = value + return true + } + + override fun putString( + name: String, + value: String, + tag: String, + makeDefault: Boolean + ): Boolean { + settingToValueMap[name] = value + return true + } + } + + companion object { + private const val TEST_SETTING = "test_setting" + private val TEST_SETTING_URI = Uri.parse("content://settings/test_setting") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt new file mode 100644 index 000000000000..56328b933602 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.settings + +import android.content.ContentResolver +import android.content.pm.UserInfo +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.Looper +import android.provider.Settings +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.settings.UserTracker +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.kotlin.eq + +/** Tests for [UserSettingsProxy]. */ +@RunWith(AndroidTestingRunner::class) +@SmallTest +@TestableLooper.RunWithLooper +class UserSettingsProxyTest : SysuiTestCase() { + + private var mUserTracker = FakeUserTracker() + private var mSettings: UserSettingsProxy = FakeUserSettingsProxy(mUserTracker) + private var mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {} + + @Before + fun setUp() { + mUserTracker.set( + listOf(UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_MAIN)), + selectedUserIndex = 0 + ) + } + + @Test + fun registerContentObserverForUser_inputString_success() { + mSettings.registerContentObserverForUser( + TEST_SETTING, + mContentObserver, + mUserTracker.userId + ) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(false), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + + @Test + fun registerContentObserverForUser_inputString_notifyForDescendants_true() { + mSettings.registerContentObserverForUser( + TEST_SETTING, + notifyForDescendants = true, + mContentObserver, + mUserTracker.userId + ) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(true), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + + @Test + fun registerContentObserverForUser_inputUri_success() { + mSettings.registerContentObserverForUser( + TEST_SETTING_URI, + mContentObserver, + mUserTracker.userId + ) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(false), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + + @Test + fun registerContentObserverForUser_inputUri_notifyForDescendants_true() { + mSettings.registerContentObserverForUser( + TEST_SETTING_URI, + notifyForDescendants = true, + mContentObserver, + mUserTracker.userId + ) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(true), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + + @Test + fun registerContentObserver_inputUri_success() { + mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), eq(0)) + } + + @Test + fun registerContentObserver_inputUri_notifyForDescendants_true() { + mSettings.registerContentObserver( + TEST_SETTING_URI, + notifyForDescendants = true, + mContentObserver + ) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), eq(0)) + } + + @Test + fun getString_keyPresent_returnValidValue() { + mSettings.putString(TEST_SETTING, "test") + assertThat(mSettings.getString(TEST_SETTING)).isEqualTo("test") + } + + @Test + fun getString_keyAbsent_returnEmptyValue() { + assertThat(mSettings.getString(TEST_SETTING)).isEmpty() + } + + @Test + fun getStringForUser_multipleUsers_validResult() { + mSettings.putStringForUser(TEST_SETTING, "test", MAIN_USER_ID) + mSettings.putStringForUser(TEST_SETTING, "test1", SECONDARY_USER_ID) + assertThat(mSettings.getStringForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo("test") + assertThat(mSettings.getStringForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo("test1") + } + + @Test + fun getInt_keyPresent_returnValidValue() { + mSettings.putInt(TEST_SETTING, 2) + assertThat(mSettings.getInt(TEST_SETTING)).isEqualTo(2) + } + + @Test + fun getInt_keyPresent_nonIntegerValue_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getInt(TEST_SETTING) + } + } + + @Test + fun getInt_keyAbsent_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.getInt(TEST_SETTING) + } + } + + @Test + fun getInt_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getInt(TEST_SETTING, 5)).isEqualTo(5) + } + + @Test + fun getIntForUser_multipleUsers__validResult() { + mSettings.putIntForUser(TEST_SETTING, 1, MAIN_USER_ID) + mSettings.putIntForUser(TEST_SETTING, 2, SECONDARY_USER_ID) + assertThat(mSettings.getIntForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(1) + assertThat(mSettings.getIntForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(2) + } + + @Test + fun getBool_keyPresent_returnValidValue() { + mSettings.putBool(TEST_SETTING, true) + assertThat(mSettings.getBool(TEST_SETTING)).isTrue() + } + + @Test + fun getBool_keyPresent_nonBooleanValue_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getBool(TEST_SETTING) + } + } + + @Test + fun getBool_keyAbsent_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.getBool(TEST_SETTING) + } + } + + @Test + fun getBool_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getBool(TEST_SETTING, false)).isEqualTo(false) + } + + @Test + fun getBoolForUser_multipleUsers__validResult() { + mSettings.putBoolForUser(TEST_SETTING, true, MAIN_USER_ID) + mSettings.putBoolForUser(TEST_SETTING, false, SECONDARY_USER_ID) + assertThat(mSettings.getBoolForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(true) + assertThat(mSettings.getBoolForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(false) + } + + @Test + fun getLong_keyPresent_returnValidValue() { + mSettings.putLong(TEST_SETTING, 1L) + assertThat(mSettings.getLong(TEST_SETTING)).isEqualTo(1L) + } + + @Test + fun getLong_keyPresent_nonLongValue_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getLong(TEST_SETTING) + } + } + + @Test + fun getLong_keyAbsent_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.getLong(TEST_SETTING) + } + } + + @Test + fun getLong_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getLong(TEST_SETTING, 2L)).isEqualTo(2L) + } + + @Test + fun getLongForUser_multipleUsers__validResult() { + mSettings.putLongForUser(TEST_SETTING, 1L, MAIN_USER_ID) + mSettings.putLongForUser(TEST_SETTING, 2L, SECONDARY_USER_ID) + assertThat(mSettings.getLongForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(1L) + assertThat(mSettings.getLongForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(2L) + } + + @Test + fun getFloat_keyPresent_returnValidValue() { + mSettings.putFloat(TEST_SETTING, 2.5F) + assertThat(mSettings.getFloat(TEST_SETTING)).isEqualTo(2.5F) + } + + @Test + fun getFloat_keyPresent_nonFloatValue_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getFloat(TEST_SETTING) + } + } + + @Test + fun getFloat_keyAbsent_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.getFloat(TEST_SETTING) + } + } + + @Test + fun getFloat_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F) + } + + @Test + fun getFloatForUser_multipleUsers__validResult() { + mSettings.putFloatForUser(TEST_SETTING, 1F, MAIN_USER_ID) + mSettings.putFloatForUser(TEST_SETTING, 2F, SECONDARY_USER_ID) + assertThat(mSettings.getFloatForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(1F) + assertThat(mSettings.getFloatForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(2F) + } + + /** + * Fake implementation of [UserSettingsProxy]. + * + * This class uses a mock of [ContentResolver] to test the [ContentObserver] registration APIs. + */ + private class FakeUserSettingsProxy(override val userTracker: UserTracker) : UserSettingsProxy { + + private val mContentResolver = mock(ContentResolver::class.java) + private val userIdToSettingsValueMap: MutableMap<Int, MutableMap<String, String>> = + mutableMapOf() + + override fun getContentResolver() = mContentResolver + + override fun getUriFor(name: String) = + Uri.parse(StringBuilder().append(URI_PREFIX).append(name).toString()) + + override fun getStringForUser(name: String, userHandle: Int) = + userIdToSettingsValueMap[userHandle]?.get(name) ?: "" + + override fun putString( + name: String, + value: String, + overrideableByRestore: Boolean + ): Boolean { + userIdToSettingsValueMap[DEFAULT_USER_ID]?.put(name, value) + return true + } + + override fun putString( + name: String, + value: String, + tag: String, + makeDefault: Boolean + ): Boolean { + putStringForUser(name, value, DEFAULT_USER_ID) + return true + } + + override fun putStringForUser(name: String, value: String, userHandle: Int): Boolean { + userIdToSettingsValueMap[userHandle] = mutableMapOf(Pair(name, value)) + return true + } + + override fun putStringForUser( + name: String, + value: String, + tag: String?, + makeDefault: Boolean, + userHandle: Int, + overrideableByRestore: Boolean + ): Boolean { + userIdToSettingsValueMap[userHandle]?.set(name, value) + return true + } + + private companion object { + const val DEFAULT_USER_ID = 0 + const val URI_PREFIX = "content://settings/" + } + } + + private companion object { + const val MAIN_USER_ID = 10 + const val SECONDARY_USER_ID = 20 + const val TEST_SETTING = "test_setting" + val TEST_SETTING_URI = Uri.parse("content://settings/test_setting") + } +} |