summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/provider/Settings.java594
-rw-r--r--core/java/android/util/AtomicFile.java2
-rwxr-xr-xcore/res/res/values/symbols.xml3
-rw-r--r--core/res/res/xml/bookmarks.xml59
-rw-r--r--packages/SettingsProvider/Android.mk3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java115
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags5
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java19
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java2578
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java574
-rw-r--r--packages/SettingsProvider/test/Android.mk13
-rw-r--r--packages/SettingsProvider/test/AndroidManifest.xml35
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java185
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderPerformanceTest.java121
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java407
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java4
-rw-r--r--services/core/java/com/android/server/policy/ShortcutManager.java182
17 files changed, 3723 insertions, 1176 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 250e80f..7b3eceb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -51,14 +51,21 @@ import android.os.Build.VERSION_CODES;
import android.speech.tts.TextToSpeech;
import android.text.TextUtils;
import android.util.AndroidException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.ILockSettings;
import java.net.URISyntaxException;
+import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
/**
* The Settings provider contains global system-level device preferences.
@@ -1192,6 +1199,11 @@ public final class Settings {
public static final class System extends NameValueTable {
public static final String SYS_PROP_SETTING_VERSION = "sys.settings_system_version";
+ /** @hide */
+ public static interface Validator {
+ public boolean validate(String value);
+ }
+
/**
* The content:// style URL for this table
*/
@@ -1294,13 +1306,56 @@ public final class Settings {
MOVED_TO_GLOBAL.add(Settings.Global.CERT_PIN_UPDATE_METADATA_URL);
}
+ private static final Validator sBooleanValidator =
+ new DiscreteValueValidator(new String[] {"0", "1"});
+
+ private static final Validator sNonNegativeIntegerValidator = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ return Integer.parseInt(value) >= 0;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ };
+
+ private static final Validator sVolumeValidator =
+ new InclusiveFloatRangeValidator(0, 1);
+
+ private static final Validator sUriValidator = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ Uri.decode(value);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+ };
+
+ private static final Validator sLenientIpAddressValidator = new Validator() {
+ private static final int MAX_IPV6_LENGTH = 45;
+
+ @Override
+ public boolean validate(String value) {
+ return value.length() <= MAX_IPV6_LENGTH;
+ }
+ };
+
/** @hide */
- public static void getMovedKeys(HashSet<String> outKeySet) {
+ public static void getMovedToGlobalSettings(Set<String> outKeySet) {
outKeySet.addAll(MOVED_TO_GLOBAL);
outKeySet.addAll(MOVED_TO_SECURE_THEN_GLOBAL);
}
/** @hide */
+ public static void getMovedToSecureSettings(Set<String> outKeySet) {
+ outKeySet.addAll(MOVED_TO_SECURE);
+ }
+
+ /** @hide */
public static void getNonLegacyMovedKeys(HashSet<String> outKeySet) {
outKeySet.addAll(MOVED_TO_GLOBAL);
}
@@ -1723,6 +1778,56 @@ public final class Settings {
putIntForUser(cr, SHOW_GTALK_SERVICE_STATUS, flag ? 1 : 0, userHandle);
}
+ private static final class DiscreteValueValidator implements Validator {
+ private final String[] mValues;
+
+ public DiscreteValueValidator(String[] values) {
+ mValues = values;
+ }
+
+ public boolean validate(String value) {
+ return ArrayUtils.contains(mValues, value);
+ }
+ }
+
+ private static final class InclusiveIntegerRangeValidator implements Validator {
+ private final int mMin;
+ private final int mMax;
+
+ public InclusiveIntegerRangeValidator(int min, int max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ public boolean validate(String value) {
+ try {
+ final int intValue = Integer.parseInt(value);
+ return intValue >= mMin && intValue <= mMax;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+
+ private static final class InclusiveFloatRangeValidator implements Validator {
+ private final float mMin;
+ private final float mMax;
+
+ public InclusiveFloatRangeValidator(float min, float max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ public boolean validate(String value) {
+ try {
+ final float floatValue = Float.parseFloat(value);
+ return floatValue >= mMin && floatValue <= mMax;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+
/**
* @deprecated Use {@link android.provider.Settings.Global#STAY_ON_WHILE_PLUGGED_IN} instead
*/
@@ -1741,6 +1846,9 @@ public final class Settings {
*/
public static final String END_BUTTON_BEHAVIOR = "end_button_behavior";
+ private static final Validator END_BUTTON_BEHAVIOR_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 3);
+
/**
* END_BUTTON_BEHAVIOR value for "go home".
* @hide
@@ -1765,6 +1873,8 @@ public final class Settings {
*/
public static final String ADVANCED_SETTINGS = "advanced_settings";
+ private static final Validator ADVANCED_SETTINGS_VALIDATOR = sBooleanValidator;
+
/**
* ADVANCED_SETTINGS default value.
* @hide
@@ -1864,6 +1974,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_USE_STATIC_IP = "wifi_use_static_ip";
+ private static final Validator WIFI_USE_STATIC_IP_VALIDATOR = sBooleanValidator;
+
/**
* The static IP address.
* <p>
@@ -1874,6 +1986,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_IP = "wifi_static_ip";
+ private static final Validator WIFI_STATIC_IP_VALIDATOR = sLenientIpAddressValidator;
+
/**
* If using static IP, the gateway's IP address.
* <p>
@@ -1884,6 +1998,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_GATEWAY = "wifi_static_gateway";
+ private static final Validator WIFI_STATIC_GATEWAY_VALIDATOR = sLenientIpAddressValidator;
+
/**
* If using static IP, the net mask.
* <p>
@@ -1894,6 +2010,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_NETMASK = "wifi_static_netmask";
+ private static final Validator WIFI_STATIC_NETMASK_VALIDATOR = sLenientIpAddressValidator;
+
/**
* If using static IP, the primary DNS's IP address.
* <p>
@@ -1904,6 +2022,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_DNS1 = "wifi_static_dns1";
+ private static final Validator WIFI_STATIC_DNS1_VALIDATOR = sLenientIpAddressValidator;
+
/**
* If using static IP, the secondary DNS's IP address.
* <p>
@@ -1914,6 +2034,7 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_DNS2 = "wifi_static_dns2";
+ private static final Validator WIFI_STATIC_DNS2_VALIDATOR = sLenientIpAddressValidator;
/**
* Determines whether remote devices may discover and/or connect to
@@ -1926,6 +2047,9 @@ public final class Settings {
public static final String BLUETOOTH_DISCOVERABILITY =
"bluetooth_discoverability";
+ private static final Validator BLUETOOTH_DISCOVERABILITY_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 2);
+
/**
* Bluetooth discoverability timeout. If this value is nonzero, then
* Bluetooth becomes discoverable for a certain number of seconds,
@@ -1934,6 +2058,9 @@ public final class Settings {
public static final String BLUETOOTH_DISCOVERABILITY_TIMEOUT =
"bluetooth_discoverability_timeout";
+ private static final Validator BLUETOOTH_DISCOVERABILITY_TIMEOUT_VALIDATOR =
+ sNonNegativeIntegerValidator;
+
/**
* @deprecated Use {@link android.provider.Settings.Secure#LOCK_PATTERN_ENABLED}
* instead
@@ -1957,7 +2084,6 @@ public final class Settings {
public static final String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED =
"lock_pattern_tactile_feedback_enabled";
-
/**
* A formatted string of the next alarm that is set, or the empty string
* if there is no alarm set.
@@ -1967,11 +2093,31 @@ public final class Settings {
@Deprecated
public static final String NEXT_ALARM_FORMATTED = "next_alarm_formatted";
+ private static final Validator NEXT_ALARM_FORMATTED_VALIDATOR = new Validator() {
+ private static final int MAX_LENGTH = 1000;
+ @Override
+ public boolean validate(String value) {
+ // TODO: No idea what the correct format is.
+ return value == null || value.length() > MAX_LENGTH;
+ }
+ };
+
/**
* Scaling factor for fonts, float.
*/
public static final String FONT_SCALE = "font_scale";
+ private static final Validator FONT_SCALE_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ return Float.parseFloat(value) >= 0;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ };
+
/**
* Name of an application package to be debugged.
*
@@ -1996,6 +2142,8 @@ public final class Settings {
@Deprecated
public static final String DIM_SCREEN = "dim_screen";
+ private static final Validator DIM_SCREEN_VALIDATOR = sBooleanValidator;
+
/**
* The amount of time in milliseconds before the device goes to sleep or begins
* to dream after a period of inactivity. This value is also known as the
@@ -2004,16 +2152,23 @@ public final class Settings {
*/
public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
+ private static final Validator SCREEN_OFF_TIMEOUT_VALIDATOR = sNonNegativeIntegerValidator;
+
/**
* The screen backlight brightness between 0 and 255.
*/
public static final String SCREEN_BRIGHTNESS = "screen_brightness";
+ private static final Validator SCREEN_BRIGHTNESS_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 255);
+
/**
* Control whether to enable automatic brightness mode.
*/
public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode";
+ private static final Validator SCREEN_BRIGHTNESS_MODE_VALIDATOR = sBooleanValidator;
+
/**
* Adjustment to auto-brightness to make it generally more (>0.0 <1.0)
* or less (<0.0 >-1.0) bright.
@@ -2021,6 +2176,9 @@ public final class Settings {
*/
public static final String SCREEN_AUTO_BRIGHTNESS_ADJ = "screen_auto_brightness_adj";
+ private static final Validator SCREEN_AUTO_BRIGHTNESS_ADJ_VALIDATOR =
+ new InclusiveFloatRangeValidator(-1, 1);
+
/**
* SCREEN_BRIGHTNESS_MODE value for manual mode.
*/
@@ -2056,12 +2214,18 @@ public final class Settings {
*/
public static final String MODE_RINGER_STREAMS_AFFECTED = "mode_ringer_streams_affected";
- /**
+ private static final Validator MODE_RINGER_STREAMS_AFFECTED_VALIDATOR =
+ sNonNegativeIntegerValidator;
+
+ /**
* Determines which streams are affected by mute. The
* stream type's bit should be set to 1 if it should be muted when a mute request
* is received.
*/
- public static final String MUTE_STREAMS_AFFECTED = "mute_streams_affected";
+ public static final String MUTE_STREAMS_AFFECTED = "mute_streams_affected";
+
+ private static final Validator MUTE_STREAMS_AFFECTED_VALIDATOR =
+ sNonNegativeIntegerValidator;
/**
* Whether vibrate is on for different events. This is used internally,
@@ -2069,6 +2233,8 @@ public final class Settings {
*/
public static final String VIBRATE_ON = "vibrate_on";
+ private static final Validator VIBRATE_ON_VALIDATOR = sBooleanValidator;
+
/**
* If 1, redirects the system vibrator to all currently attached input devices
* that support vibration. If there are no such input devices, then the system
@@ -2083,54 +2249,72 @@ public final class Settings {
*/
public static final String VIBRATE_INPUT_DEVICES = "vibrate_input_devices";
+ private static final Validator VIBRATE_INPUT_DEVICES_VALIDATOR = sBooleanValidator;
+
/**
* Ringer volume. This is used internally, changing this value will not
* change the volume. See AudioManager.
*/
public static final String VOLUME_RING = "volume_ring";
+ private static final Validator VOLUME_RING_VALIDATOR = sVolumeValidator;
+
/**
* System/notifications volume. This is used internally, changing this
* value will not change the volume. See AudioManager.
*/
public static final String VOLUME_SYSTEM = "volume_system";
+ private static final Validator VOLUME_SYSTEM_VALIDATOR = sVolumeValidator;
+
/**
* Voice call volume. This is used internally, changing this value will
* not change the volume. See AudioManager.
*/
public static final String VOLUME_VOICE = "volume_voice";
+ private static final Validator VOLUME_VOICE_VALIDATOR = sVolumeValidator;
+
/**
* Music/media/gaming volume. This is used internally, changing this
* value will not change the volume. See AudioManager.
*/
public static final String VOLUME_MUSIC = "volume_music";
+ private static final Validator VOLUME_MUSIC_VALIDATOR = sVolumeValidator;
+
/**
* Alarm volume. This is used internally, changing this
* value will not change the volume. See AudioManager.
*/
public static final String VOLUME_ALARM = "volume_alarm";
+ private static final Validator VOLUME_ALARM_VALIDATOR = sVolumeValidator;
+
/**
* Notification volume. This is used internally, changing this
* value will not change the volume. See AudioManager.
*/
public static final String VOLUME_NOTIFICATION = "volume_notification";
+ private static final Validator VOLUME_NOTIFICATION_VALIDATOR = sVolumeValidator;
+
/**
* Bluetooth Headset volume. This is used internally, changing this value will
* not change the volume. See AudioManager.
*/
public static final String VOLUME_BLUETOOTH_SCO = "volume_bluetooth_sco";
+ private static final Validator VOLUME_BLUETOOTH_SCO_VALIDATOR = sVolumeValidator;
+
/**
* Master volume (float in the range 0.0f to 1.0f).
* @hide
*/
public static final String VOLUME_MASTER = "volume_master";
+ private static final Validator VOLUME_MASTER_VALIDATOR = sVolumeValidator;
+
/**
* Master volume mute (int 1 = mute, 0 = not muted).
*
@@ -2138,6 +2322,8 @@ public final class Settings {
*/
public static final String VOLUME_MASTER_MUTE = "volume_master_mute";
+ private static final Validator VOLUME_MASTER_MUTE_VALIDATOR = sBooleanValidator;
+
/**
* Microphone mute (int 1 = mute, 0 = not muted).
*
@@ -2145,6 +2331,8 @@ public final class Settings {
*/
public static final String MICROPHONE_MUTE = "microphone_mute";
+ private static final Validator MICROPHONE_MUTE_VALIDATOR = sBooleanValidator;
+
/**
* Whether the notifications should use the ring volume (value of 1) or
* a separate notification volume (value of 0). In most cases, users
@@ -2163,6 +2351,8 @@ public final class Settings {
public static final String NOTIFICATIONS_USE_RING_VOLUME =
"notifications_use_ring_volume";
+ private static final Validator NOTIFICATIONS_USE_RING_VOLUME_VALIDATOR = sBooleanValidator;
+
/**
* Whether silent mode should allow vibration feedback. This is used
* internally in AudioService and the Sound settings activity to
@@ -2177,6 +2367,8 @@ public final class Settings {
*/
public static final String VIBRATE_IN_SILENT = "vibrate_in_silent";
+ private static final Validator VIBRATE_IN_SILENT_VALIDATOR = sBooleanValidator;
+
/**
* The mapping of stream type (integer) to its setting.
*/
@@ -2203,6 +2395,8 @@ public final class Settings {
*/
public static final String RINGTONE = "ringtone";
+ private static final Validator RINGTONE_VALIDATOR = sUriValidator;
+
/**
* A {@link Uri} that will point to the current default ringtone at any
* given time.
@@ -2221,6 +2415,8 @@ public final class Settings {
*/
public static final String NOTIFICATION_SOUND = "notification_sound";
+ private static final Validator NOTIFICATION_SOUND_VALIDATOR = sUriValidator;
+
/**
* A {@link Uri} that will point to the current default notification
* sound at any given time.
@@ -2237,6 +2433,8 @@ public final class Settings {
*/
public static final String ALARM_ALERT = "alarm_alert";
+ private static final Validator ALARM_ALERT_VALIDATOR = sUriValidator;
+
/**
* A {@link Uri} that will point to the current default alarm alert at
* any given time.
@@ -2252,30 +2450,52 @@ public final class Settings {
*/
public static final String MEDIA_BUTTON_RECEIVER = "media_button_receiver";
+ private static final Validator MEDIA_BUTTON_RECEIVER_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ ComponentName.unflattenFromString(value);
+ return true;
+ } catch (NullPointerException e) {
+ return false;
+ }
+ }
+ };
+
/**
* Setting to enable Auto Replace (AutoText) in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_AUTO_REPLACE = "auto_replace";
+ private static final Validator TEXT_AUTO_REPLACE_VALIDATOR = sBooleanValidator;
+
/**
* Setting to enable Auto Caps in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_AUTO_CAPS = "auto_caps";
+ private static final Validator TEXT_AUTO_CAPS_VALIDATOR = sBooleanValidator;
+
/**
* Setting to enable Auto Punctuate in text editors. 1 = On, 0 = Off. This
* feature converts two spaces to a "." and space.
*/
public static final String TEXT_AUTO_PUNCTUATE = "auto_punctuate";
+ private static final Validator TEXT_AUTO_PUNCTUATE_VALIDATOR = sBooleanValidator;
+
/**
* Setting to showing password characters in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_SHOW_PASSWORD = "show_password";
+ private static final Validator TEXT_SHOW_PASSWORD_VALIDATOR = sBooleanValidator;
+
public static final String SHOW_GTALK_SERVICE_STATUS =
"SHOW_GTALK_SERVICE_STATUS";
+ private static final Validator SHOW_GTALK_SERVICE_STATUS_VALIDATOR = sBooleanValidator;
+
/**
* Name of activity to use for wallpaper on the home screen.
*
@@ -2284,6 +2504,18 @@ public final class Settings {
@Deprecated
public static final String WALLPAPER_ACTIVITY = "wallpaper_activity";
+ private static final Validator WALLPAPER_ACTIVITY_VALIDATOR = new Validator() {
+ private static final int MAX_LENGTH = 1000;
+
+ @Override
+ public boolean validate(String value) {
+ if (value != null && value.length() > MAX_LENGTH) {
+ return false;
+ }
+ return ComponentName.unflattenFromString(value) != null;
+ }
+ };
+
/**
* @deprecated Use {@link android.provider.Settings.Global#AUTO_TIME}
* instead
@@ -2305,6 +2537,10 @@ public final class Settings {
*/
public static final String TIME_12_24 = "time_12_24";
+ /** @hide */
+ public static final Validator TIME_12_24_VALIDATOR =
+ new DiscreteValueValidator(new String[] {"12", "24"});
+
/**
* Date format string
* mm/dd/yyyy
@@ -2313,6 +2549,19 @@ public final class Settings {
*/
public static final String DATE_FORMAT = "date_format";
+ /** @hide */
+ public static final Validator DATE_FORMAT_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ new SimpleDateFormat(value);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+ };
+
/**
* Whether the setup wizard has been run before (on first boot), or if
* it still needs to be run.
@@ -2322,6 +2571,9 @@ public final class Settings {
*/
public static final String SETUP_WIZARD_HAS_RUN = "setup_wizard_has_run";
+ /** @hide */
+ public static final Validator SETUP_WIZARD_HAS_RUN_VALIDATOR = sBooleanValidator;
+
/**
* Scaling factor for normal window animations. Setting to 0 will disable window
* animations.
@@ -2358,6 +2610,9 @@ public final class Settings {
*/
public static final String ACCELEROMETER_ROTATION = "accelerometer_rotation";
+ /** @hide */
+ public static final Validator ACCELEROMETER_ROTATION_VALIDATOR = sBooleanValidator;
+
/**
* Default screen rotation when no other policy applies.
* When {@link #ACCELEROMETER_ROTATION} is zero and no on-screen Activity expresses a
@@ -2368,6 +2623,10 @@ public final class Settings {
*/
public static final String USER_ROTATION = "user_rotation";
+ /** @hide */
+ public static final Validator USER_ROTATION_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 3);
+
/**
* Control whether the rotation lock toggle in the System UI should be hidden.
* Typically this is done for accessibility purposes to make it harder for
@@ -2382,6 +2641,10 @@ public final class Settings {
public static final String HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY =
"hide_rotation_lock_toggle_for_accessibility";
+ /** @hide */
+ public static final Validator HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY_VALIDATOR =
+ sBooleanValidator;
+
/**
* Whether the phone vibrates when it is ringing due to an incoming call. This will
* be used by Phone and Setting apps; it shouldn't affect other apps.
@@ -2396,12 +2659,18 @@ public final class Settings {
*/
public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing";
+ /** @hide */
+ public static final Validator VIBRATE_WHEN_RINGING_VALIDATOR = sBooleanValidator;
+
/**
* Whether the audible DTMF tones are played by the dialer when dialing. The value is
* boolean (1 or 0).
*/
public static final String DTMF_TONE_WHEN_DIALING = "dtmf_tone";
+ /** @hide */
+ public static final Validator DTMF_TONE_WHEN_DIALING_VALIDATOR = sBooleanValidator;
+
/**
* CDMA only settings
* DTMF tone type played by the dialer when dialing.
@@ -2411,6 +2680,9 @@ public final class Settings {
*/
public static final String DTMF_TONE_TYPE_WHEN_DIALING = "dtmf_tone_type";
+ /** @hide */
+ public static final Validator DTMF_TONE_TYPE_WHEN_DIALING_VALIDATOR = sBooleanValidator;
+
/**
* Whether the hearing aid is enabled. The value is
* boolean (1 or 0).
@@ -2418,6 +2690,9 @@ public final class Settings {
*/
public static final String HEARING_AID = "hearing_aid";
+ /** @hide */
+ public static final Validator HEARING_AID_VALIDATOR = sBooleanValidator;
+
/**
* CDMA only settings
* TTY Mode
@@ -2429,18 +2704,27 @@ public final class Settings {
*/
public static final String TTY_MODE = "tty_mode";
+ /** @hide */
+ public static final Validator TTY_MODE_VALIDATOR = new InclusiveIntegerRangeValidator(0, 3);
+
/**
* Whether the sounds effects (key clicks, lid open ...) are enabled. The value is
* boolean (1 or 0).
*/
public static final String SOUND_EFFECTS_ENABLED = "sound_effects_enabled";
+ /** @hide */
+ public static final Validator SOUND_EFFECTS_ENABLED_VALIDATOR = sBooleanValidator;
+
/**
* Whether the haptic feedback (long presses, ...) are enabled. The value is
* boolean (1 or 0).
*/
public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled";
+ /** @hide */
+ public static final Validator HAPTIC_FEEDBACK_ENABLED_VALIDATOR = sBooleanValidator;
+
/**
* @deprecated Each application that shows web suggestions should have its own
* setting for this.
@@ -2448,6 +2732,9 @@ public final class Settings {
@Deprecated
public static final String SHOW_WEB_SUGGESTIONS = "show_web_suggestions";
+ /** @hide */
+ public static final Validator SHOW_WEB_SUGGESTIONS_VALIDATOR = sBooleanValidator;
+
/**
* Whether the notification LED should repeatedly flash when a notification is
* pending. The value is boolean (1 or 0).
@@ -2455,6 +2742,9 @@ public final class Settings {
*/
public static final String NOTIFICATION_LIGHT_PULSE = "notification_light_pulse";
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_VALIDATOR = sBooleanValidator;
+
/**
* Show pointer location on screen?
* 0 = no
@@ -2463,6 +2753,9 @@ public final class Settings {
*/
public static final String POINTER_LOCATION = "pointer_location";
+ /** @hide */
+ public static final Validator POINTER_LOCATION_VALIDATOR = sBooleanValidator;
+
/**
* Show touch positions on screen?
* 0 = no
@@ -2471,6 +2764,9 @@ public final class Settings {
*/
public static final String SHOW_TOUCHES = "show_touches";
+ /** @hide */
+ public static final Validator SHOW_TOUCHES_VALIDATOR = sBooleanValidator;
+
/**
* Log raw orientation data from
* {@link com.android.server.policy.WindowOrientationListener} for use with the
@@ -2482,6 +2778,9 @@ public final class Settings {
public static final String WINDOW_ORIENTATION_LISTENER_LOG =
"window_orientation_listener_log";
+ /** @hide */
+ public static final Validator WINDOW_ORIENTATION_LISTENER_LOG_VALIDATOR = sBooleanValidator;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#POWER_SOUNDS_ENABLED}
* instead
@@ -2504,12 +2803,18 @@ public final class Settings {
*/
public static final String LOCKSCREEN_SOUNDS_ENABLED = "lockscreen_sounds_enabled";
+ /** @hide */
+ public static final Validator LOCKSCREEN_SOUNDS_ENABLED_VALIDATOR = sBooleanValidator;
+
/**
* Whether the lockscreen should be completely disabled.
* @hide
*/
public static final String LOCKSCREEN_DISABLED = "lockscreen.disabled";
+ /** @hide */
+ public static final Validator LOCKSCREEN_DISABLED_VALIDATOR = sBooleanValidator;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#LOW_BATTERY_SOUND}
* instead
@@ -2574,6 +2879,9 @@ public final class Settings {
*/
public static final String SIP_RECEIVE_CALLS = "sip_receive_calls";
+ /** @hide */
+ public static final Validator SIP_RECEIVE_CALLS_VALIDATOR = sBooleanValidator;
+
/**
* Call Preference String.
* "SIP_ALWAYS" : Always use SIP with network access
@@ -2582,18 +2890,28 @@ public final class Settings {
*/
public static final String SIP_CALL_OPTIONS = "sip_call_options";
+ /** @hide */
+ public static final Validator SIP_CALL_OPTIONS_VALIDATOR = new DiscreteValueValidator(
+ new String[] {"SIP_ALWAYS", "SIP_ADDRESS_ONLY"});
+
/**
* One of the sip call options: Always use SIP with network access.
* @hide
*/
public static final String SIP_ALWAYS = "SIP_ALWAYS";
+ /** @hide */
+ public static final Validator SIP_ALWAYS_VALIDATOR = sBooleanValidator;
+
/**
* One of the sip call options: Only if destination is a SIP address.
* @hide
*/
public static final String SIP_ADDRESS_ONLY = "SIP_ADDRESS_ONLY";
+ /** @hide */
+ public static final Validator SIP_ADDRESS_ONLY_VALIDATOR = sBooleanValidator;
+
/**
* @deprecated Use SIP_ALWAYS or SIP_ADDRESS_ONLY instead. Formerly used to indicate that
* the user should be prompted each time a call is made whether it should be placed using
@@ -2604,6 +2922,9 @@ public final class Settings {
@Deprecated
public static final String SIP_ASK_ME_EACH_TIME = "SIP_ASK_ME_EACH_TIME";
+ /** @hide */
+ public static final Validator SIP_ASK_ME_EACH_TIME_VALIDATOR = sBooleanValidator;
+
/**
* Pointer speed setting.
* This is an integer value in a range between -7 and +7, so there are 15 possible values.
@@ -2614,12 +2935,19 @@ public final class Settings {
*/
public static final String POINTER_SPEED = "pointer_speed";
+ /** @hide */
+ public static final Validator POINTER_SPEED_VALIDATOR =
+ new InclusiveFloatRangeValidator(-7, 7);
+
/**
* Whether lock-to-app will be triggered by long-press on recents.
* @hide
*/
public static final String LOCK_TO_APP_ENABLED = "lock_to_app_enabled";
+ /** @hide */
+ public static final Validator LOCK_TO_APP_ENABLED_VALIDATOR = sBooleanValidator;
+
/**
* I am the lolrus.
* <p>
@@ -2629,6 +2957,16 @@ public final class Settings {
*/
public static final String EGG_MODE = "egg_mode";
+ /** @hide */
+ public static final Validator EGG_MODE_VALIDATOR = sBooleanValidator;
+
+ /**
+ * IMPORTANT: If you add a new public settings you also have to add it to
+ * PUBLIC_SETTINGS below. If the new setting is hidden you have to add
+ * it to PRIVATE_SETTINGS below. Also add a validator that can validate
+ * the setting value. See an example above.
+ */
+
/**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
@@ -2699,17 +3037,207 @@ public final class Settings {
};
/**
+ * These are all pulbic system settings
+ *
+ * @hide
+ */
+ public static final Set<String> PUBLIC_SETTINGS = new ArraySet<>();
+ static {
+ PUBLIC_SETTINGS.add(END_BUTTON_BEHAVIOR);
+ PUBLIC_SETTINGS.add(WIFI_USE_STATIC_IP);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_IP);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_GATEWAY);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_NETMASK);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_DNS1);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_DNS2);
+ PUBLIC_SETTINGS.add(BLUETOOTH_DISCOVERABILITY);
+ PUBLIC_SETTINGS.add(BLUETOOTH_DISCOVERABILITY_TIMEOUT);
+ PUBLIC_SETTINGS.add(NEXT_ALARM_FORMATTED);
+ PUBLIC_SETTINGS.add(FONT_SCALE);
+ PUBLIC_SETTINGS.add(DIM_SCREEN);
+ PUBLIC_SETTINGS.add(SCREEN_OFF_TIMEOUT);
+ PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS);
+ PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_MODE);
+ PUBLIC_SETTINGS.add(MODE_RINGER_STREAMS_AFFECTED);
+ PUBLIC_SETTINGS.add(MUTE_STREAMS_AFFECTED);
+ PUBLIC_SETTINGS.add(VIBRATE_ON);
+ PUBLIC_SETTINGS.add(VOLUME_RING);
+ PUBLIC_SETTINGS.add(VOLUME_SYSTEM);
+ PUBLIC_SETTINGS.add(VOLUME_VOICE);
+ PUBLIC_SETTINGS.add(VOLUME_MUSIC);
+ PUBLIC_SETTINGS.add(VOLUME_ALARM);
+ PUBLIC_SETTINGS.add(VOLUME_NOTIFICATION);
+ PUBLIC_SETTINGS.add(VOLUME_BLUETOOTH_SCO);
+ PUBLIC_SETTINGS.add(RINGTONE);
+ PUBLIC_SETTINGS.add(NOTIFICATION_SOUND);
+ PUBLIC_SETTINGS.add(ALARM_ALERT);
+ PUBLIC_SETTINGS.add(TEXT_AUTO_REPLACE);
+ PUBLIC_SETTINGS.add(TEXT_AUTO_CAPS);
+ PUBLIC_SETTINGS.add(TEXT_AUTO_PUNCTUATE);
+ PUBLIC_SETTINGS.add(TEXT_SHOW_PASSWORD);
+ PUBLIC_SETTINGS.add(SHOW_GTALK_SERVICE_STATUS);
+ PUBLIC_SETTINGS.add(WALLPAPER_ACTIVITY);
+ PUBLIC_SETTINGS.add(TIME_12_24);
+ PUBLIC_SETTINGS.add(DATE_FORMAT);
+ PUBLIC_SETTINGS.add(SETUP_WIZARD_HAS_RUN);
+ PUBLIC_SETTINGS.add(ACCELEROMETER_ROTATION);
+ PUBLIC_SETTINGS.add(USER_ROTATION);
+ PUBLIC_SETTINGS.add(DTMF_TONE_WHEN_DIALING);
+ PUBLIC_SETTINGS.add(SOUND_EFFECTS_ENABLED);
+ PUBLIC_SETTINGS.add(HAPTIC_FEEDBACK_ENABLED);
+ PUBLIC_SETTINGS.add(SHOW_WEB_SUGGESTIONS);
+ }
+
+ /**
+ * These are all hidden system settings.
+ *
+ * @hide
+ */
+ public static final Set<String> PRIVATE_SETTINGS = new ArraySet<>();
+ static {
+ PRIVATE_SETTINGS.add(WIFI_USE_STATIC_IP);
+ PRIVATE_SETTINGS.add(END_BUTTON_BEHAVIOR);
+ PRIVATE_SETTINGS.add(ADVANCED_SETTINGS);
+ PRIVATE_SETTINGS.add(SCREEN_AUTO_BRIGHTNESS_ADJ);
+ PRIVATE_SETTINGS.add(VIBRATE_INPUT_DEVICES);
+ PRIVATE_SETTINGS.add(VOLUME_MASTER);
+ PRIVATE_SETTINGS.add(VOLUME_MASTER_MUTE);
+ PRIVATE_SETTINGS.add(MICROPHONE_MUTE);
+ PRIVATE_SETTINGS.add(NOTIFICATIONS_USE_RING_VOLUME);
+ PRIVATE_SETTINGS.add(VIBRATE_IN_SILENT);
+ PRIVATE_SETTINGS.add(MEDIA_BUTTON_RECEIVER);
+ PRIVATE_SETTINGS.add(HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY);
+ PRIVATE_SETTINGS.add(VIBRATE_WHEN_RINGING);
+ PRIVATE_SETTINGS.add(DTMF_TONE_TYPE_WHEN_DIALING);
+ PRIVATE_SETTINGS.add(HEARING_AID);
+ PRIVATE_SETTINGS.add(TTY_MODE);
+ PRIVATE_SETTINGS.add(NOTIFICATION_LIGHT_PULSE);
+ PRIVATE_SETTINGS.add(POINTER_LOCATION);
+ PRIVATE_SETTINGS.add(SHOW_TOUCHES);
+ PRIVATE_SETTINGS.add(WINDOW_ORIENTATION_LISTENER_LOG);
+ PRIVATE_SETTINGS.add(POWER_SOUNDS_ENABLED);
+ PRIVATE_SETTINGS.add(DOCK_SOUNDS_ENABLED);
+ PRIVATE_SETTINGS.add(LOCKSCREEN_SOUNDS_ENABLED);
+ PRIVATE_SETTINGS.add(LOCKSCREEN_DISABLED);
+ PRIVATE_SETTINGS.add(LOW_BATTERY_SOUND);
+ PRIVATE_SETTINGS.add(DESK_DOCK_SOUND);
+ PRIVATE_SETTINGS.add(DESK_UNDOCK_SOUND);
+ PRIVATE_SETTINGS.add(CAR_DOCK_SOUND);
+ PRIVATE_SETTINGS.add(CAR_UNDOCK_SOUND);
+ PRIVATE_SETTINGS.add(LOCK_SOUND);
+ PRIVATE_SETTINGS.add(UNLOCK_SOUND);
+ PRIVATE_SETTINGS.add(SIP_RECEIVE_CALLS);
+ PRIVATE_SETTINGS.add(SIP_CALL_OPTIONS);
+ PRIVATE_SETTINGS.add(SIP_ALWAYS);
+ PRIVATE_SETTINGS.add(SIP_ADDRESS_ONLY);
+ PRIVATE_SETTINGS.add(SIP_ASK_ME_EACH_TIME);
+ PRIVATE_SETTINGS.add(POINTER_SPEED);
+ PRIVATE_SETTINGS.add(LOCK_TO_APP_ENABLED);
+ PRIVATE_SETTINGS.add(EGG_MODE);
+ }
+
+ /**
+ * These are all pulbic system settings
+ *
+ * @hide
+ */
+ public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
+ static {
+ VALIDATORS.put(END_BUTTON_BEHAVIOR,END_BUTTON_BEHAVIOR_VALIDATOR);
+ VALIDATORS.put(WIFI_USE_STATIC_IP, WIFI_USE_STATIC_IP_VALIDATOR);
+ VALIDATORS.put(BLUETOOTH_DISCOVERABILITY, BLUETOOTH_DISCOVERABILITY_VALIDATOR);
+ VALIDATORS.put(BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+ BLUETOOTH_DISCOVERABILITY_TIMEOUT_VALIDATOR);
+ VALIDATORS.put(NEXT_ALARM_FORMATTED, NEXT_ALARM_FORMATTED_VALIDATOR);
+ VALIDATORS.put(FONT_SCALE, FONT_SCALE_VALIDATOR);
+ VALIDATORS.put(DIM_SCREEN, DIM_SCREEN_VALIDATOR);
+ VALIDATORS.put(SCREEN_OFF_TIMEOUT, SCREEN_OFF_TIMEOUT_VALIDATOR);
+ VALIDATORS.put(SCREEN_BRIGHTNESS, SCREEN_BRIGHTNESS_VALIDATOR);
+ VALIDATORS.put(SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_VALIDATOR);
+ VALIDATORS.put(MODE_RINGER_STREAMS_AFFECTED, MODE_RINGER_STREAMS_AFFECTED_VALIDATOR);
+ VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR);
+ VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR);
+ VALIDATORS.put(VOLUME_RING, VOLUME_RING_VALIDATOR);
+ VALIDATORS.put(VOLUME_SYSTEM, VOLUME_SYSTEM_VALIDATOR);
+ VALIDATORS.put(VOLUME_VOICE, VOLUME_VOICE_VALIDATOR);
+ VALIDATORS.put(VOLUME_MUSIC, VOLUME_MUSIC_VALIDATOR);
+ VALIDATORS.put(VOLUME_ALARM, VOLUME_ALARM_VALIDATOR);
+ VALIDATORS.put(VOLUME_NOTIFICATION, VOLUME_NOTIFICATION_VALIDATOR);
+ VALIDATORS.put(VOLUME_BLUETOOTH_SCO, VOLUME_BLUETOOTH_SCO_VALIDATOR);
+ VALIDATORS.put(RINGTONE, RINGTONE_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_SOUND, NOTIFICATION_SOUND_VALIDATOR);
+ VALIDATORS.put(ALARM_ALERT, ALARM_ALERT_VALIDATOR);
+ VALIDATORS.put(TEXT_AUTO_REPLACE, TEXT_AUTO_REPLACE_VALIDATOR);
+ VALIDATORS.put(TEXT_AUTO_CAPS, TEXT_AUTO_CAPS_VALIDATOR);
+ VALIDATORS.put(TEXT_AUTO_PUNCTUATE, TEXT_AUTO_PUNCTUATE_VALIDATOR);
+ VALIDATORS.put(TEXT_SHOW_PASSWORD, TEXT_SHOW_PASSWORD_VALIDATOR);
+ VALIDATORS.put(SHOW_GTALK_SERVICE_STATUS, SHOW_GTALK_SERVICE_STATUS_VALIDATOR);
+ VALIDATORS.put(WALLPAPER_ACTIVITY, WALLPAPER_ACTIVITY_VALIDATOR);
+ VALIDATORS.put(TIME_12_24, TIME_12_24_VALIDATOR);
+ VALIDATORS.put(DATE_FORMAT, DATE_FORMAT_VALIDATOR);
+ VALIDATORS.put(SETUP_WIZARD_HAS_RUN, SETUP_WIZARD_HAS_RUN_VALIDATOR);
+ VALIDATORS.put(ACCELEROMETER_ROTATION, ACCELEROMETER_ROTATION_VALIDATOR);
+ VALIDATORS.put(USER_ROTATION, USER_ROTATION_VALIDATOR);
+ VALIDATORS.put(DTMF_TONE_WHEN_DIALING, DTMF_TONE_WHEN_DIALING_VALIDATOR);
+ VALIDATORS.put(SOUND_EFFECTS_ENABLED, SOUND_EFFECTS_ENABLED_VALIDATOR);
+ VALIDATORS.put(HAPTIC_FEEDBACK_ENABLED, HAPTIC_FEEDBACK_ENABLED_VALIDATOR);
+ VALIDATORS.put(SHOW_WEB_SUGGESTIONS, SHOW_WEB_SUGGESTIONS_VALIDATOR);
+ VALIDATORS.put(WIFI_USE_STATIC_IP, WIFI_USE_STATIC_IP_VALIDATOR);
+ VALIDATORS.put(END_BUTTON_BEHAVIOR, END_BUTTON_BEHAVIOR_VALIDATOR);
+ VALIDATORS.put(ADVANCED_SETTINGS, ADVANCED_SETTINGS_VALIDATOR);
+ VALIDATORS.put(SCREEN_AUTO_BRIGHTNESS_ADJ, SCREEN_AUTO_BRIGHTNESS_ADJ_VALIDATOR);
+ VALIDATORS.put(VIBRATE_INPUT_DEVICES, VIBRATE_INPUT_DEVICES_VALIDATOR);
+ VALIDATORS.put(VOLUME_MASTER, VOLUME_MASTER_VALIDATOR);
+ VALIDATORS.put(VOLUME_MASTER_MUTE, VOLUME_MASTER_MUTE_VALIDATOR);
+ VALIDATORS.put(MICROPHONE_MUTE, MICROPHONE_MUTE_VALIDATOR);
+ VALIDATORS.put(NOTIFICATIONS_USE_RING_VOLUME, NOTIFICATIONS_USE_RING_VOLUME_VALIDATOR);
+ VALIDATORS.put(VIBRATE_IN_SILENT, VIBRATE_IN_SILENT_VALIDATOR);
+ VALIDATORS.put(MEDIA_BUTTON_RECEIVER, MEDIA_BUTTON_RECEIVER_VALIDATOR);
+ VALIDATORS.put(HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY,
+ HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY_VALIDATOR);
+ VALIDATORS.put(VIBRATE_WHEN_RINGING, VIBRATE_WHEN_RINGING_VALIDATOR);
+ VALIDATORS.put(DTMF_TONE_TYPE_WHEN_DIALING, DTMF_TONE_TYPE_WHEN_DIALING_VALIDATOR);
+ VALIDATORS.put(HEARING_AID, HEARING_AID_VALIDATOR);
+ VALIDATORS.put(TTY_MODE, TTY_MODE_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE, NOTIFICATION_LIGHT_PULSE_VALIDATOR);
+ VALIDATORS.put(POINTER_LOCATION, POINTER_LOCATION_VALIDATOR);
+ VALIDATORS.put(SHOW_TOUCHES, SHOW_TOUCHES_VALIDATOR);
+ VALIDATORS.put(WINDOW_ORIENTATION_LISTENER_LOG,
+ WINDOW_ORIENTATION_LISTENER_LOG_VALIDATOR);
+ VALIDATORS.put(LOCKSCREEN_SOUNDS_ENABLED, LOCKSCREEN_SOUNDS_ENABLED_VALIDATOR);
+ VALIDATORS.put(LOCKSCREEN_DISABLED, LOCKSCREEN_DISABLED_VALIDATOR);
+ VALIDATORS.put(SIP_RECEIVE_CALLS, SIP_RECEIVE_CALLS_VALIDATOR);
+ VALIDATORS.put(SIP_CALL_OPTIONS, SIP_CALL_OPTIONS_VALIDATOR);
+ VALIDATORS.put(SIP_ALWAYS, SIP_ALWAYS_VALIDATOR);
+ VALIDATORS.put(SIP_ADDRESS_ONLY, SIP_ADDRESS_ONLY_VALIDATOR);
+ VALIDATORS.put(SIP_ASK_ME_EACH_TIME, SIP_ASK_ME_EACH_TIME_VALIDATOR);
+ VALIDATORS.put(POINTER_SPEED, POINTER_SPEED_VALIDATOR);
+ VALIDATORS.put(LOCK_TO_APP_ENABLED, LOCK_TO_APP_ENABLED_VALIDATOR);
+ VALIDATORS.put(EGG_MODE, EGG_MODE_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_IP, WIFI_STATIC_IP_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_GATEWAY, WIFI_STATIC_GATEWAY_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_NETMASK, WIFI_STATIC_NETMASK_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_DNS1, WIFI_STATIC_DNS1_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_DNS2, WIFI_STATIC_DNS2_VALIDATOR);
+ }
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
- * @hide
*/
- public static final String[] CLONE_TO_MANAGED_PROFILE = {
- DATE_FORMAT,
- HAPTIC_FEEDBACK_ENABLED,
- SOUND_EFFECTS_ENABLED,
- TEXT_SHOW_PASSWORD,
- TIME_12_24
- };
+ private static final Set<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
+ static {
+ CLONE_TO_MANAGED_PROFILE.add(DATE_FORMAT);
+ CLONE_TO_MANAGED_PROFILE.add(HAPTIC_FEEDBACK_ENABLED);
+ CLONE_TO_MANAGED_PROFILE.add(SOUND_EFFECTS_ENABLED);
+ CLONE_TO_MANAGED_PROFILE.add(TEXT_SHOW_PASSWORD);
+ CLONE_TO_MANAGED_PROFILE.add(TIME_12_24);
+ }
+
+ /** @hide */
+ public static void getCloneToManagedProfileSettings(Set<String> outKeySet) {
+ outKeySet.addAll(CLONE_TO_MANAGED_PROFILE);
+ }
/**
* When to use Wi-Fi calling
@@ -3099,7 +3627,7 @@ public final class Settings {
}
/** @hide */
- public static void getMovedKeys(HashSet<String> outKeySet) {
+ public static void getMovedToGlobalSettings(Set<String> outKeySet) {
outKeySet.addAll(MOVED_TO_GLOBAL);
}
@@ -4896,22 +5424,27 @@ public final class Settings {
/**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
- * @hide
*/
- public static final String[] CLONE_TO_MANAGED_PROFILE = {
- ACCESSIBILITY_ENABLED,
- ALLOW_MOCK_LOCATION,
- ALLOWED_GEOLOCATION_ORIGINS,
- DEFAULT_INPUT_METHOD,
- ENABLED_ACCESSIBILITY_SERVICES,
- ENABLED_INPUT_METHODS,
- LOCATION_MODE,
- LOCATION_PROVIDERS_ALLOWED,
- LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
- SELECTED_INPUT_METHOD_SUBTYPE,
- SELECTED_SPELL_CHECKER,
- SELECTED_SPELL_CHECKER_SUBTYPE
- };
+ private static final Set<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
+ static {
+ CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_ENABLED);
+ CLONE_TO_MANAGED_PROFILE.add(ALLOW_MOCK_LOCATION);
+ CLONE_TO_MANAGED_PROFILE.add(ALLOWED_GEOLOCATION_ORIGINS);
+ CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
+ CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES);
+ CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
+ CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
+ CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
+ CLONE_TO_MANAGED_PROFILE.add(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE);
+ CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER);
+ CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER_SUBTYPE);
+ }
+
+ /** @hide */
+ public static void getCloneToManagedProfileSettings(Set<String> outKeySet) {
+ outKeySet.addAll(CLONE_TO_MANAGED_PROFILE);
+ }
/**
* Helper method for determining if a location provider is enabled.
@@ -6693,6 +7226,11 @@ public final class Settings {
MOVED_TO_SECURE.add(Settings.Global.INSTALL_NON_MARKET_APPS);
}
+ /** @hide */
+ public static void getMovedToSecureSettings(Set<String> outKeySet) {
+ outKeySet.addAll(MOVED_TO_SECURE);
+ }
+
/**
* Look up a name in the database.
* @param resolver to access the database with
diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java
index a6466fc..3aa3447 100644
--- a/core/java/android/util/AtomicFile.java
+++ b/core/java/android/util/AtomicFile.java
@@ -102,7 +102,7 @@ public class AtomicFile {
str = new FileOutputStream(mBaseName);
} catch (FileNotFoundException e) {
File parent = mBaseName.getParentFile();
- if (!parent.mkdir()) {
+ if (!parent.mkdirs()) {
throw new IOException("Couldn't create directory " + mBaseName);
}
FileUtils.setPermissions(
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1e784af..f149cf9 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2164,4 +2164,7 @@
<java-symbol type="bool" name="allow_stacked_button_bar" />
<java-symbol type="id" name="spacer" />
+
+ <java-symbol type="xml" name="bookmarks" />
+
</resources>
diff --git a/core/res/res/xml/bookmarks.xml b/core/res/res/xml/bookmarks.xml
new file mode 100644
index 0000000..454f456
--- /dev/null
+++ b/core/res/res/xml/bookmarks.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<!--
+ Default system bookmarks for AOSP.
+ Bookmarks for vendor apps should be added to a bookmarks resource overlay; not here.
+
+ Typical shortcuts (not necessarily defined here):
+ 'a': Calculator
+ 'b': Browser
+ 'c': Contacts
+ 'e': Email
+ 'g': GMail
+ 'l': Calendar
+ 'm': Maps
+ 'p': Music
+ 's': SMS
+ 't': Talk
+ 'y': YouTube
+-->
+<bookmarks>
+ <bookmark
+ category="android.intent.category.APP_CALCULATOR"
+ shortcut="a" />
+ <bookmark
+ category="android.intent.category.APP_BROWSER"
+ shortcut="b" />
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c" />
+ <bookmark
+ category="android.intent.category.APP_EMAIL"
+ shortcut="e" />
+ <bookmark
+ category="android.intent.category.APP_CALENDAR"
+ shortcut="l" />
+ <bookmark
+ category="android.intent.category.APP_MAPS"
+ shortcut="m" />
+ <bookmark
+ category="android.intent.category.APP_MUSIC"
+ shortcut="p" />
+ <bookmark
+ category="android.intent.category.APP_MESSAGING"
+ shortcut="s" />
+</bookmarks>
diff --git a/packages/SettingsProvider/Android.mk b/packages/SettingsProvider/Android.mk
index c16f7b6..2b833b2 100644
--- a/packages/SettingsProvider/Android.mk
+++ b/packages/SettingsProvider/Android.mk
@@ -3,7 +3,8 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES := $(call all-subdir-java-files) \
+ src/com/android/providers/settings/EventLogTags.logtags
LOCAL_JAVA_LIBRARIES := telephony-common ims-common
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 06e26bd..729efcb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -58,6 +58,7 @@ import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Database helper class for {@link SettingsProvider}.
@@ -78,6 +79,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
private static final HashSet<String> mValidTables = new HashSet<String>();
+ private static final String DATABASE_JOURNAL_SUFFIX = "-journal";
+ private static final String DATABASE_BACKUP_SUFFIX = "-backup";
+
private static final String TABLE_SYSTEM = "system";
private static final String TABLE_SECURE = "secure";
private static final String TABLE_GLOBAL = "global";
@@ -86,13 +90,13 @@ public class DatabaseHelper extends SQLiteOpenHelper {
mValidTables.add(TABLE_SYSTEM);
mValidTables.add(TABLE_SECURE);
mValidTables.add(TABLE_GLOBAL);
- mValidTables.add("bluetooth_devices");
- mValidTables.add("bookmarks");
// These are old.
+ mValidTables.add("bluetooth_devices");
+ mValidTables.add("bookmarks");
mValidTables.add("favorites");
- mValidTables.add("gservices");
mValidTables.add("old_favorites");
+ mValidTables.add("android_metadata");
}
static String dbNameForUser(final int userHandle) {
@@ -118,6 +122,33 @@ public class DatabaseHelper extends SQLiteOpenHelper {
return mValidTables.contains(name);
}
+ public void dropDatabase() {
+ close();
+ File databaseFile = mContext.getDatabasePath(getDatabaseName());
+ if (databaseFile.exists()) {
+ databaseFile.delete();
+ }
+ File databaseJournalFile = mContext.getDatabasePath(getDatabaseName()
+ + DATABASE_JOURNAL_SUFFIX);
+ if (databaseJournalFile.exists()) {
+ databaseJournalFile.delete();
+ }
+ }
+
+ public void backupDatabase() {
+ close();
+ File databaseFile = mContext.getDatabasePath(getDatabaseName());
+ if (!databaseFile.exists()) {
+ return;
+ }
+ File backupFile = mContext.getDatabasePath(getDatabaseName()
+ + DATABASE_BACKUP_SUFFIX);
+ if (backupFile.exists()) {
+ return;
+ }
+ databaseFile.renameTo(backupFile);
+ }
+
private void createSecureTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE secure (" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT," +
@@ -1221,9 +1252,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
// Migrate now-global settings. Note that this happens before
// new users can be created.
createGlobalTable(db);
- String[] settingsToMove = hashsetToStringArray(SettingsProvider.sSystemGlobalKeys);
+ String[] settingsToMove = setToStringArray(
+ SettingsProvider.sSystemMovedToGlobalSettings);
moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_GLOBAL, settingsToMove, false);
- settingsToMove = hashsetToStringArray(SettingsProvider.sSecureGlobalKeys);
+ settingsToMove = setToStringArray(
+ SettingsProvider.sSecureMovedToGlobalSettings);
moveSettingsToNewTable(db, TABLE_SECURE, TABLE_GLOBAL, settingsToMove, false);
db.setTransactionSuccessful();
@@ -1489,9 +1522,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
db.beginTransaction();
try {
// Migrate now-global settings
- String[] settingsToMove = hashsetToStringArray(SettingsProvider.sSystemGlobalKeys);
+ String[] settingsToMove = setToStringArray(
+ SettingsProvider.sSystemMovedToGlobalSettings);
moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_GLOBAL, settingsToMove, true);
- settingsToMove = hashsetToStringArray(SettingsProvider.sSecureGlobalKeys);
+ settingsToMove = setToStringArray(
+ SettingsProvider.sSecureMovedToGlobalSettings);
moveSettingsToNewTable(db, TABLE_SECURE, TABLE_GLOBAL, settingsToMove, true);
db.setTransactionSuccessful();
@@ -1855,7 +1890,8 @@ public class DatabaseHelper extends SQLiteOpenHelper {
try {
stmt = db.compileStatement("INSERT OR IGNORE INTO global(name,value)"
+ " VALUES(?,?);");
- loadSetting(stmt, Settings.Global.ENHANCED_4G_MODE_ENABLED, ImsConfig.FeatureValueConstants.ON);
+ loadSetting(stmt, Settings.Global.ENHANCED_4G_MODE_ENABLED,
+ ImsConfig.FeatureValueConstants.ON);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
@@ -1895,34 +1931,50 @@ public class DatabaseHelper extends SQLiteOpenHelper {
}
upgradeVersion = 118;
}
+
+ /**
+ * IMPORTANT: Do not add any more upgrade steps here as the global,
+ * secure, and system settings are no longer stored in a database
+ * but are kept in memory and persisted to XML. The correct places
+ * for adding upgrade steps are:
+ *
+ * Global: SettingsProvider.UpgradeController#onUpgradeGlobalSettings
+ * Secure: SettingsProvider.UpgradeController#onUpgradeSecureSettings
+ * System: SettingsProvider.UpgradeController#onUpgradeSystemSettings
+ */
+
// *** Remember to update DATABASE_VERSION above!
if (upgradeVersion != currentVersion) {
- Log.w(TAG, "Got stuck trying to upgrade from version " + upgradeVersion
- + ", must wipe the settings provider");
- db.execSQL("DROP TABLE IF EXISTS global");
- db.execSQL("DROP TABLE IF EXISTS globalIndex1");
- db.execSQL("DROP TABLE IF EXISTS system");
- db.execSQL("DROP INDEX IF EXISTS systemIndex1");
- db.execSQL("DROP TABLE IF EXISTS secure");
- db.execSQL("DROP INDEX IF EXISTS secureIndex1");
- db.execSQL("DROP TABLE IF EXISTS gservices");
- db.execSQL("DROP INDEX IF EXISTS gservicesIndex1");
- db.execSQL("DROP TABLE IF EXISTS bluetooth_devices");
- db.execSQL("DROP TABLE IF EXISTS bookmarks");
- db.execSQL("DROP INDEX IF EXISTS bookmarksIndex1");
- db.execSQL("DROP INDEX IF EXISTS bookmarksIndex2");
- db.execSQL("DROP TABLE IF EXISTS favorites");
- onCreate(db);
-
- // Added for diagnosing settings.db wipes after the fact
- String wipeReason = oldVersion + "/" + upgradeVersion + "/" + currentVersion;
- db.execSQL("INSERT INTO secure(name,value) values('" +
- "wiped_db_reason" + "','" + wipeReason + "');");
+ recreateDatabase(db, oldVersion, upgradeVersion, currentVersion);
}
}
- private String[] hashsetToStringArray(HashSet<String> set) {
+ public void recreateDatabase(SQLiteDatabase db, int oldVersion,
+ int upgradeVersion, int currentVersion) {
+ db.execSQL("DROP TABLE IF EXISTS global");
+ db.execSQL("DROP TABLE IF EXISTS globalIndex1");
+ db.execSQL("DROP TABLE IF EXISTS system");
+ db.execSQL("DROP INDEX IF EXISTS systemIndex1");
+ db.execSQL("DROP TABLE IF EXISTS secure");
+ db.execSQL("DROP INDEX IF EXISTS secureIndex1");
+ db.execSQL("DROP TABLE IF EXISTS gservices");
+ db.execSQL("DROP INDEX IF EXISTS gservicesIndex1");
+ db.execSQL("DROP TABLE IF EXISTS bluetooth_devices");
+ db.execSQL("DROP TABLE IF EXISTS bookmarks");
+ db.execSQL("DROP INDEX IF EXISTS bookmarksIndex1");
+ db.execSQL("DROP INDEX IF EXISTS bookmarksIndex2");
+ db.execSQL("DROP TABLE IF EXISTS favorites");
+
+ onCreate(db);
+
+ // Added for diagnosing settings.db wipes after the fact
+ String wipeReason = oldVersion + "/" + upgradeVersion + "/" + currentVersion;
+ db.execSQL("INSERT INTO secure(name,value) values('" +
+ "wiped_db_reason" + "','" + wipeReason + "');");
+ }
+
+ private String[] setToStringArray(Set<String> set) {
String[] array = new String[set.size()];
return set.toArray(array);
}
@@ -2639,7 +2691,8 @@ public class DatabaseHelper extends SQLiteOpenHelper {
loadBooleanSetting(stmt, Settings.Global.GUEST_USER_ENABLED,
R.bool.def_guest_user_enabled);
- loadSetting(stmt, Settings.Global.ENHANCED_4G_MODE_ENABLED, ImsConfig.FeatureValueConstants.ON);
+ loadSetting(stmt, Settings.Global.ENHANCED_4G_MODE_ENABLED,
+ ImsConfig.FeatureValueConstants.ON);
// --- New global settings start here
} finally {
if (stmt != null) stmt.close();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags b/packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags
new file mode 100644
index 0000000..298d776
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags
@@ -0,0 +1,5 @@
+# See system/core/logcat/e for a description of the format of this file.
+
+option java_package com.android.providers.settings;
+
+52100 unsupported_settings_query (uri|3),(selection|3),(whereArgs|3)
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 264dcae..8371117 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -110,11 +110,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
private static final String TAG = "SettingsBackupAgent";
- private static final int COLUMN_NAME = 1;
- private static final int COLUMN_VALUE = 2;
-
private static final String[] PROJECTION = {
- Settings.NameValueTable._ID,
Settings.NameValueTable.NAME,
Settings.NameValueTable.VALUE
};
@@ -473,8 +469,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
ParcelFileDescriptor newState) throws IOException {
HashSet<String> movedToGlobal = new HashSet<String>();
- Settings.System.getMovedKeys(movedToGlobal);
- Settings.Secure.getMovedKeys(movedToGlobal);
+ Settings.System.getMovedToGlobalSettings(movedToGlobal);
+ Settings.Secure.getMovedToGlobalSettings(movedToGlobal);
while (data.readNextHeader()) {
final String key = data.getKey();
@@ -577,8 +573,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
if (version <= FULL_BACKUP_VERSION) {
// Generate the moved-to-global lookup table
HashSet<String> movedToGlobal = new HashSet<String>();
- Settings.System.getMovedKeys(movedToGlobal);
- Settings.Secure.getMovedKeys(movedToGlobal);
+ Settings.System.getMovedToGlobalSettings(movedToGlobal);
+ Settings.Secure.getMovedToGlobalSettings(movedToGlobal);
// system settings data first
int nBytes = in.readInt();
@@ -824,11 +820,14 @@ public class SettingsBackupAgent extends BackupAgentHelper {
String key = settings[i];
String value = cachedEntries.remove(key);
+ final int nameColumnIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME);
+ final int valueColumnIndex = cursor.getColumnIndex(Settings.NameValueTable.VALUE);
+
// If the value not cached, let us look it up.
if (value == null) {
while (!cursor.isAfterLast()) {
- String cursorKey = cursor.getString(COLUMN_NAME);
- String cursorValue = cursor.getString(COLUMN_VALUE);
+ String cursorKey = cursor.getString(nameColumnIndex);
+ String cursorValue = cursor.getString(valueColumnIndex);
cursor.moveToNext();
if (key.equals(cursorKey)) {
value = cursorValue;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 6828301..ff2c004 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -16,1315 +16,1823 @@
package com.android.providers.settings;
-import java.io.FileNotFoundException;
-import java.security.SecureRandom;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
+import android.Manifest;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.backup.BackupManager;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
-import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
-import android.database.AbstractCursor;
import android.database.Cursor;
+import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteQueryBuilder;
+import android.hardware.camera2.utils.ArrayUtils;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.DropBoxManager;
-import android.os.FileObserver;
+import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.provider.Settings.Secure;
import android.text.TextUtils;
-import android.util.Log;
-import android.util.LruCache;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
-
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.BackgroundThread;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import com.android.providers.settings.SettingsState.Setting;
+
+/**
+ * <p>
+ * This class is a content provider that publishes the system settings.
+ * It can be accessed via the content provider APIs or via custom call
+ * commands. The latter is a bit faster and is the preferred way to access
+ * the platform settings.
+ * </p>
+ * <p>
+ * There are three settings types, global (with signature level protection
+ * and shared across users), secure (with signature permission level
+ * protection and per user), and system (with dangerous permission level
+ * protection and per user). Global settings are stored under the device owner.
+ * Each of these settings is represented by a {@link
+ * com.android.providers.settings.SettingsState} object mapped to an integer
+ * key derived from the setting type in the most significant bits and user
+ * id in the least significant bits. Settings are synchronously loaded on
+ * instantiation of a SettingsState and asynchronously persisted on mutation.
+ * Settings are stored in the user specific system directory.
+ * </p>
+ * <p>
+ * Apps targeting APIs Lollipop MR1 and lower can add custom settings entries
+ * and get a warning. Targeting higher API version prohibits this as the
+ * system settings are not a place for apps to save their state. When a package
+ * is removed the settings it added are deleted. Apps cannot delete system
+ * settings added by the platform. System settings values are validated to
+ * ensure the clients do not put bad values. Global and secure settings are
+ * changed only by trusted parties, therefore no validation is performed. Also
+ * there is a limit on the amount of app specific settings that can be added
+ * to prevent unlimited growth of the system process memory footprint.
+ * </p>
+ */
+@SuppressWarnings("deprecation")
public class SettingsProvider extends ContentProvider {
- private static final String TAG = "SettingsProvider";
- private static final boolean LOCAL_LOGV = false;
+ private static final boolean DEBUG = false;
+
+ private static final boolean DROP_DATABASE_ON_MIGRATION = !Build.IS_DEBUGGABLE;
- private static final boolean USER_CHECK_THROWS = true;
+ private static final String LOG_TAG = "SettingsProvider";
private static final String TABLE_SYSTEM = "system";
private static final String TABLE_SECURE = "secure";
private static final String TABLE_GLOBAL = "global";
+
+ // Old tables no longer exist.
private static final String TABLE_FAVORITES = "favorites";
private static final String TABLE_OLD_FAVORITES = "old_favorites";
+ private static final String TABLE_BLUETOOTH_DEVICES = "bluetooth_devices";
+ private static final String TABLE_BOOKMARKS = "bookmarks";
+ private static final String TABLE_ANDROID_METADATA = "android_metadata";
- private static final String[] COLUMN_VALUE = new String[] { "value" };
+ // The set of removed legacy tables.
+ private static final Set<String> REMOVED_LEGACY_TABLES = new ArraySet<>();
+ static {
+ REMOVED_LEGACY_TABLES.add(TABLE_FAVORITES);
+ REMOVED_LEGACY_TABLES.add(TABLE_OLD_FAVORITES);
+ REMOVED_LEGACY_TABLES.add(TABLE_BLUETOOTH_DEVICES);
+ REMOVED_LEGACY_TABLES.add(TABLE_BOOKMARKS);
+ REMOVED_LEGACY_TABLES.add(TABLE_ANDROID_METADATA);
+ }
- // Caches for each user's settings, access-ordered for acting as LRU.
- // Guarded by themselves.
- private static final int MAX_CACHE_ENTRIES = 200;
- private static final SparseArray<SettingsCache> sSystemCaches
- = new SparseArray<SettingsCache>();
- private static final SparseArray<SettingsCache> sSecureCaches
- = new SparseArray<SettingsCache>();
- private static final SettingsCache sGlobalCache = new SettingsCache(TABLE_GLOBAL);
+ private static final int MUTATION_OPERATION_INSERT = 1;
+ private static final int MUTATION_OPERATION_DELETE = 2;
+ private static final int MUTATION_OPERATION_UPDATE = 3;
- // The count of how many known (handled by SettingsProvider)
- // database mutations are currently being handled for this user.
- // Used by file observers to not reload the database when it's ourselves
- // modifying it.
- private static final SparseArray<AtomicInteger> sKnownMutationsInFlight
- = new SparseArray<AtomicInteger>();
+ private static final String[] ALL_COLUMNS = new String[] {
+ Settings.NameValueTable._ID,
+ Settings.NameValueTable.NAME,
+ Settings.NameValueTable.VALUE
+ };
- // Each defined user has their own settings
- protected final SparseArray<DatabaseHelper> mOpenHelpers = new SparseArray<DatabaseHelper>();
+ private static final Bundle NULL_SETTING = Bundle.forPair(Settings.NameValueTable.VALUE, null);
- // Keep the list of managed profiles synced here
- private List<UserInfo> mManagedProfiles = null;
+ // Per user settings that cannot be modified if associated user restrictions are enabled.
+ private static final Map<String, String> sSettingToUserRestrictionMap = new ArrayMap<>();
+ static {
+ sSettingToUserRestrictionMap.put(Settings.Secure.LOCATION_MODE,
+ UserManager.DISALLOW_SHARE_LOCATION);
+ sSettingToUserRestrictionMap.put(Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ UserManager.DISALLOW_SHARE_LOCATION);
+ sSettingToUserRestrictionMap.put(Settings.Secure.INSTALL_NON_MARKET_APPS,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
+ sSettingToUserRestrictionMap.put(Settings.Global.ADB_ENABLED,
+ UserManager.DISALLOW_DEBUGGING_FEATURES);
+ sSettingToUserRestrictionMap.put(Settings.Global.PACKAGE_VERIFIER_ENABLE,
+ UserManager.ENSURE_VERIFY_APPS);
+ sSettingToUserRestrictionMap.put(Settings.Global.PREFERRED_NETWORK_MODE,
+ UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
+ }
- // Over this size we don't reject loading or saving settings but
- // we do consider them broken/malicious and don't keep them in
- // memory at least:
- private static final int MAX_CACHE_ENTRY_SIZE = 500;
+ // Per user secure settings that moved to the for all users global settings.
+ static final Set<String> sSecureMovedToGlobalSettings = new ArraySet<>();
+ static {
+ Settings.Secure.getMovedToGlobalSettings(sSecureMovedToGlobalSettings);
+ }
- private static final Bundle NULL_SETTING = Bundle.forPair("value", null);
+ // Per user system settings that moved to the for all users global settings.
+ static final Set<String> sSystemMovedToGlobalSettings = new ArraySet<>();
+ static {
+ Settings.System.getMovedToGlobalSettings(sSystemMovedToGlobalSettings);
+ }
- // Used as a sentinel value in an instance equality test when we
- // want to cache the existence of a key, but not store its value.
- private static final Bundle TOO_LARGE_TO_CACHE_MARKER = Bundle.forPair("_dummy", null);
+ // Per user system settings that moved to the per user secure settings.
+ static final Set<String> sSystemMovedToSecureSettings = new ArraySet<>();
+ static {
+ Settings.System.getMovedToSecureSettings(sSystemMovedToSecureSettings);
+ }
- private UserManager mUserManager;
- private BackupManager mBackupManager;
+ // Per all users global settings that moved to the per user secure settings.
+ static final Set<String> sGlobalMovedToSecureSettings = new ArraySet<>();
+ static {
+ Settings.Global.getMovedToSecureSettings(sGlobalMovedToSecureSettings);
+ }
- /**
- * Settings which need to be treated as global/shared in multi-user environments.
- */
- static final HashSet<String> sSecureGlobalKeys;
- static final HashSet<String> sSystemGlobalKeys;
+ // Per user secure settings that are cloned for the managed profiles of the user.
+ private static final Set<String> sSecureCloneToManagedSettings = new ArraySet<>();
+ static {
+ Settings.Secure.getCloneToManagedProfileSettings(sSecureCloneToManagedSettings);
+ }
- // Settings that cannot be modified if associated user restrictions are enabled.
- static final Map<String, String> sRestrictedKeys;
+ // Per user system settings that are cloned for the managed profiles of the user.
+ private static final Set<String> sSystemCloneToManagedSettings = new ArraySet<>();
+ static {
+ Settings.System.getCloneToManagedProfileSettings(sSystemCloneToManagedSettings);
+ }
- private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid";
+ private final Object mLock = new Object();
- static final HashSet<String> sSecureCloneToManagedKeys;
- static final HashSet<String> sSystemCloneToManagedKeys;
+ @GuardedBy("mLock")
+ private SettingsRegistry mSettingsRegistry;
- static {
- // Keys (name column) from the 'secure' table that are now in the owner user's 'global'
- // table, shared across all users
- // These must match Settings.Secure.MOVED_TO_GLOBAL
- sSecureGlobalKeys = new HashSet<String>();
- Settings.Secure.getMovedKeys(sSecureGlobalKeys);
-
- // Keys from the 'system' table now moved to 'global'
- // These must match Settings.System.MOVED_TO_GLOBAL
- sSystemGlobalKeys = new HashSet<String>();
- Settings.System.getNonLegacyMovedKeys(sSystemGlobalKeys);
-
- sRestrictedKeys = new HashMap<String, String>();
- sRestrictedKeys.put(Settings.Secure.LOCATION_MODE, UserManager.DISALLOW_SHARE_LOCATION);
- sRestrictedKeys.put(Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- UserManager.DISALLOW_SHARE_LOCATION);
- sRestrictedKeys.put(Settings.Secure.INSTALL_NON_MARKET_APPS,
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
- sRestrictedKeys.put(Settings.Global.ADB_ENABLED, UserManager.DISALLOW_DEBUGGING_FEATURES);
- sRestrictedKeys.put(Settings.Global.PACKAGE_VERIFIER_ENABLE,
- UserManager.ENSURE_VERIFY_APPS);
- sRestrictedKeys.put(Settings.Global.PREFERRED_NETWORK_MODE,
- UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
+ @GuardedBy("mLock")
+ private UserManager mUserManager;
+
+ @GuardedBy("mLock")
+ private AppOpsManager mAppOpsManager;
- sSecureCloneToManagedKeys = new HashSet<String>();
- for (int i = 0; i < Settings.Secure.CLONE_TO_MANAGED_PROFILE.length; i++) {
- sSecureCloneToManagedKeys.add(Settings.Secure.CLONE_TO_MANAGED_PROFILE[i]);
+ @GuardedBy("mLock")
+ private PackageManager mPackageManager;
+
+ @Override
+ public boolean onCreate() {
+ synchronized (mLock) {
+ mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+ mAppOpsManager = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+ mPackageManager = getContext().getPackageManager();
+ mSettingsRegistry = new SettingsRegistry();
}
- sSystemCloneToManagedKeys = new HashSet<String>();
- for (int i = 0; i < Settings.System.CLONE_TO_MANAGED_PROFILE.length; i++) {
- sSystemCloneToManagedKeys.add(Settings.System.CLONE_TO_MANAGED_PROFILE[i]);
+ registerBroadcastReceivers();
+ return true;
+ }
+
+ @Override
+ public Bundle call(String method, String name, Bundle args) {
+ synchronized (mLock) {
+ final int requestingUserId = getRequestingUserId(args);
+ switch (method) {
+ case Settings.CALL_METHOD_GET_GLOBAL: {
+ Setting setting = getGlobalSettingLocked(name);
+ return packageValueForCallResult(setting);
+ }
+
+ case Settings.CALL_METHOD_GET_SECURE: {
+ Setting setting = getSecureSettingLocked(name, requestingUserId);
+ return packageValueForCallResult(setting);
+ }
+
+ case Settings.CALL_METHOD_GET_SYSTEM: {
+ Setting setting = getSystemSettingLocked(name, requestingUserId);
+ return packageValueForCallResult(setting);
+ }
+
+ case Settings.CALL_METHOD_PUT_GLOBAL: {
+ String value = getSettingValue(args);
+ insertGlobalSettingLocked(name, value, requestingUserId);
+ } break;
+
+ case Settings.CALL_METHOD_PUT_SECURE: {
+ String value = getSettingValue(args);
+ insertSecureSettingLocked(name, value, requestingUserId);
+ } break;
+
+ case Settings.CALL_METHOD_PUT_SYSTEM: {
+ String value = getSettingValue(args);
+ insertSystemSettingLocked(name, value, requestingUserId);
+ } break;
+
+ default: {
+ Slog.w(LOG_TAG, "call() with invalid method: " + method);
+ } break;
+ }
}
+ return null;
}
- private boolean settingMovedToGlobal(final String name) {
- return sSecureGlobalKeys.contains(name) || sSystemGlobalKeys.contains(name);
+ @Override
+ public String getType(Uri uri) {
+ Arguments args = new Arguments(uri, null, null, true);
+ if (TextUtils.isEmpty(args.name)) {
+ return "vnd.android.cursor.dir/" + args.table;
+ } else {
+ return "vnd.android.cursor.item/" + args.table;
+ }
}
- /**
- * Decode a content URL into the table, projection, and arguments
- * used to access the corresponding database rows.
- */
- private static class SqlArguments {
- public String table;
- public final String where;
- public final String[] args;
-
- /** Operate on existing rows. */
- SqlArguments(Uri url, String where, String[] args) {
- if (url.getPathSegments().size() == 1) {
- // of the form content://settings/secure, arbitrary where clause
- this.table = url.getPathSegments().get(0);
- if (!DatabaseHelper.isValidTable(this.table)) {
- throw new IllegalArgumentException("Bad root path: " + this.table);
+ @Override
+ public Cursor query(Uri uri, String[] projection, String where, String[] whereArgs,
+ String order) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "query() for user: " + UserHandle.getCallingUserId());
+ }
+
+ Arguments args = new Arguments(uri, where, whereArgs, true);
+ String[] normalizedProjection = normalizeProjection(projection);
+
+ // If a legacy table that is gone, done.
+ if (REMOVED_LEGACY_TABLES.contains(args.table)) {
+ return new MatrixCursor(normalizedProjection, 0);
+ }
+
+ synchronized (mLock) {
+ switch (args.table) {
+ case TABLE_GLOBAL: {
+ if (args.name != null) {
+ Setting setting = getGlobalSettingLocked(args.name);
+ return packageSettingForQuery(setting, normalizedProjection);
+ } else {
+ return getAllGlobalSettingsLocked(projection);
+ }
}
- this.where = where;
- this.args = args;
- } else if (url.getPathSegments().size() != 2) {
- throw new IllegalArgumentException("Invalid URI: " + url);
- } else if (!TextUtils.isEmpty(where)) {
- throw new UnsupportedOperationException("WHERE clause not supported: " + url);
- } else {
- // of the form content://settings/secure/element_name, no where clause
- this.table = url.getPathSegments().get(0);
- if (!DatabaseHelper.isValidTable(this.table)) {
- throw new IllegalArgumentException("Bad root path: " + this.table);
+
+ case TABLE_SECURE: {
+ final int userId = UserHandle.getCallingUserId();
+ if (args.name != null) {
+ Setting setting = getSecureSettingLocked(args.name, userId);
+ return packageSettingForQuery(setting, normalizedProjection);
+ } else {
+ return getAllSecureSettingsLocked(userId, projection);
+ }
}
- if (TABLE_SYSTEM.equals(this.table) || TABLE_SECURE.equals(this.table) ||
- TABLE_GLOBAL.equals(this.table)) {
- this.where = Settings.NameValueTable.NAME + "=?";
- final String name = url.getPathSegments().get(1);
- this.args = new String[] { name };
- // Rewrite the table for known-migrated names
- if (TABLE_SYSTEM.equals(this.table) || TABLE_SECURE.equals(this.table)) {
- if (sSecureGlobalKeys.contains(name) || sSystemGlobalKeys.contains(name)) {
- this.table = TABLE_GLOBAL;
- }
+
+ case TABLE_SYSTEM: {
+ final int userId = UserHandle.getCallingUserId();
+ if (args.name != null) {
+ Setting setting = getSystemSettingLocked(args.name, userId);
+ return packageSettingForQuery(setting, normalizedProjection);
+ } else {
+ return getAllSystemSettingsLocked(userId, projection);
}
- } else {
- // of the form content://bookmarks/19
- this.where = "_id=" + ContentUris.parseId(url);
- this.args = null;
+ }
+
+ default: {
+ throw new IllegalArgumentException("Invalid Uri path:" + uri);
}
}
}
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "insert() for user: " + UserHandle.getCallingUserId());
+ }
+
+ String table = getValidTableOrThrow(uri);
+
+ // If a legacy table that is gone, done.
+ if (REMOVED_LEGACY_TABLES.contains(table)) {
+ return null;
+ }
+
+ String name = values.getAsString(Settings.Secure.NAME);
+ if (TextUtils.isEmpty(name)) {
+ return null;
+ }
- /** Insert new rows (no where clause allowed). */
- SqlArguments(Uri url) {
- if (url.getPathSegments().size() == 1) {
- this.table = url.getPathSegments().get(0);
- if (!DatabaseHelper.isValidTable(this.table)) {
- throw new IllegalArgumentException("Bad root path: " + this.table);
+ String value = values.getAsString(Settings.Secure.VALUE);
+
+ synchronized (mLock) {
+ switch (table) {
+ case TABLE_GLOBAL: {
+ if (insertGlobalSettingLocked(name, value, UserHandle.getCallingUserId())) {
+ return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name);
+ }
+ } break;
+
+ case TABLE_SECURE: {
+ if (insertSecureSettingLocked(name, value, UserHandle.getCallingUserId())) {
+ return Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
+ }
+ } break;
+
+ case TABLE_SYSTEM: {
+ if (insertSystemSettingLocked(name, value, UserHandle.getCallingUserId())) {
+ return Uri.withAppendedPath(Settings.System.CONTENT_URI, name);
+ }
+ } break;
+
+ default: {
+ throw new IllegalArgumentException("Bad Uri path:" + uri);
}
- this.where = null;
- this.args = null;
- } else {
- throw new IllegalArgumentException("Invalid URI: " + url);
}
}
+
+ return null;
}
- /**
- * Get the content URI of a row added to a table.
- * @param tableUri of the entire table
- * @param values found in the row
- * @param rowId of the row
- * @return the content URI for this particular row
- */
- private Uri getUriFor(Uri tableUri, ContentValues values, long rowId) {
- if (tableUri.getPathSegments().size() != 1) {
- throw new IllegalArgumentException("Invalid URI: " + tableUri);
- }
- String table = tableUri.getPathSegments().get(0);
- if (TABLE_SYSTEM.equals(table) ||
- TABLE_SECURE.equals(table) ||
- TABLE_GLOBAL.equals(table)) {
- String name = values.getAsString(Settings.NameValueTable.NAME);
- return Uri.withAppendedPath(tableUri, name);
- } else {
- return ContentUris.withAppendedId(tableUri, rowId);
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] allValues) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "bulkInsert() for user: " + UserHandle.getCallingUserId());
}
- }
- /**
- * Send a notification when a particular content URI changes.
- * Modify the system property used to communicate the version of
- * this table, for tables which have such a property. (The Settings
- * contract class uses these to provide client-side caches.)
- * @param uri to send notifications for
- */
- private void sendNotify(Uri uri, int userHandle) {
- // Update the system property *first*, so if someone is listening for
- // a notification and then using the contract class to get their data,
- // the system property will be updated and they'll get the new data.
-
- boolean backedUpDataChanged = false;
- String property = null, table = uri.getPathSegments().get(0);
- final boolean isGlobal = table.equals(TABLE_GLOBAL);
- if (table.equals(TABLE_SYSTEM)) {
- property = Settings.System.SYS_PROP_SETTING_VERSION;
- backedUpDataChanged = true;
- } else if (table.equals(TABLE_SECURE)) {
- property = Settings.Secure.SYS_PROP_SETTING_VERSION;
- backedUpDataChanged = true;
- } else if (isGlobal) {
- property = Settings.Global.SYS_PROP_SETTING_VERSION; // this one is global
- backedUpDataChanged = true;
- }
-
- if (property != null) {
- long version = SystemProperties.getLong(property, 0) + 1;
- if (LOCAL_LOGV) Log.v(TAG, "property: " + property + "=" + version);
- SystemProperties.set(property, Long.toString(version));
- }
-
- // Inform the backup manager about a data change
- if (backedUpDataChanged) {
- mBackupManager.dataChanged();
- }
- // Now send the notification through the content framework.
-
- String notify = uri.getQueryParameter("notify");
- if (notify == null || "true".equals(notify)) {
- final int notifyTarget = isGlobal ? UserHandle.USER_ALL : userHandle;
- final long oldId = Binder.clearCallingIdentity();
- try {
- getContext().getContentResolver().notifyChange(uri, null, true, notifyTarget);
- } finally {
- Binder.restoreCallingIdentity(oldId);
+ int insertionCount = 0;
+ final int valuesCount = allValues.length;
+ for (int i = 0; i < valuesCount; i++) {
+ ContentValues values = allValues[i];
+ if (insert(uri, values) != null) {
+ insertionCount++;
}
- if (LOCAL_LOGV) Log.v(TAG, "notifying for " + notifyTarget + ": " + uri);
- } else {
- if (LOCAL_LOGV) Log.v(TAG, "notification suppressed: " + uri);
}
+
+ return insertionCount;
}
- /**
- * Make sure the caller has permission to write this data.
- * @param args supplied by the caller
- * @throws SecurityException if the caller is forbidden to write.
- */
- private void checkWritePermissions(SqlArguments args) {
- if ((TABLE_SECURE.equals(args.table) || TABLE_GLOBAL.equals(args.table)) &&
- getContext().checkCallingOrSelfPermission(
- android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
- PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(
- String.format("Permission denial: writing to secure settings requires %1$s",
- android.Manifest.permission.WRITE_SECURE_SETTINGS));
+ @Override
+ public int delete(Uri uri, String where, String[] whereArgs) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "delete() for user: " + UserHandle.getCallingUserId());
}
- }
- private void checkUserRestrictions(String setting, int userId) {
- String userRestriction = sRestrictedKeys.get(setting);
- if (!TextUtils.isEmpty(userRestriction)
- && mUserManager.hasUserRestriction(userRestriction, new UserHandle(userId))) {
- throw new SecurityException(
- "Permission denial: user is restricted from changing this setting.");
+ Arguments args = new Arguments(uri, where, whereArgs, false);
+
+ // If a legacy table that is gone, done.
+ if (REMOVED_LEGACY_TABLES.contains(args.table)) {
+ return 0;
}
- }
- // FileObserver for external modifications to the database file.
- // Note that this is for platform developers only with
- // userdebug/eng builds who should be able to tinker with the
- // sqlite database out from under the SettingsProvider, which is
- // normally the exclusive owner of the database. But we keep this
- // enabled all the time to minimize development-vs-user
- // differences in testing.
- private static SparseArray<SettingsFileObserver> sObserverInstances
- = new SparseArray<SettingsFileObserver>();
- private class SettingsFileObserver extends FileObserver {
- private final AtomicBoolean mIsDirty = new AtomicBoolean(false);
- private final int mUserHandle;
- private final String mPath;
-
- public SettingsFileObserver(int userHandle, String path) {
- super(path, FileObserver.CLOSE_WRITE |
- FileObserver.CREATE | FileObserver.DELETE |
- FileObserver.MOVED_TO | FileObserver.MODIFY);
- mUserHandle = userHandle;
- mPath = path;
- }
-
- public void onEvent(int event, String path) {
- final AtomicInteger mutationCount;
- synchronized (SettingsProvider.this) {
- mutationCount = sKnownMutationsInFlight.get(mUserHandle);
- }
- if (mutationCount != null && mutationCount.get() > 0) {
- // our own modification.
- return;
- }
- Log.d(TAG, "User " + mUserHandle + " external modification to " + mPath
- + "; event=" + event);
- if (!mIsDirty.compareAndSet(false, true)) {
- // already handled. (we get a few update events
- // during an sqlite write)
- return;
+ if (TextUtils.isEmpty(args.name)) {
+ return 0;
+ }
+
+ synchronized (mLock) {
+ switch (args.table) {
+ case TABLE_GLOBAL: {
+ final int userId = UserHandle.getCallingUserId();
+ return deleteGlobalSettingLocked(args.name, userId) ? 1 : 0;
+ }
+
+ case TABLE_SECURE: {
+ final int userId = UserHandle.getCallingUserId();
+ return deleteSecureSettingLocked(args.name, userId) ? 1 : 0;
+ }
+
+ case TABLE_SYSTEM: {
+ final int userId = UserHandle.getCallingUserId();
+ return deleteSystemSettingLocked(args.name, userId) ? 1 : 0;
+ }
+
+ default: {
+ throw new IllegalArgumentException("Bad Uri path:" + uri);
+ }
}
- Log.d(TAG, "User " + mUserHandle + " updating our caches for " + mPath);
- fullyPopulateCaches(mUserHandle);
- mIsDirty.set(false);
}
}
@Override
- public boolean onCreate() {
- mBackupManager = new BackupManager(getContext());
- mUserManager = UserManager.get(getContext());
+ public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "update() for user: " + UserHandle.getCallingUserId());
+ }
+
+ Arguments args = new Arguments(uri, where, whereArgs, false);
+
+ // If a legacy table that is gone, done.
+ if (REMOVED_LEGACY_TABLES.contains(args.table)) {
+ return 0;
+ }
+
+ String value = values.getAsString(Settings.Secure.VALUE);
+ if (TextUtils.isEmpty(value)) {
+ return 0;
+ }
+
+ synchronized (mLock) {
+ switch (args.table) {
+ case TABLE_GLOBAL: {
+ final int userId = UserHandle.getCallingUserId();
+ return updateGlobalSettingLocked(args.name, value, userId) ? 1 : 0;
+ }
+
+ case TABLE_SECURE: {
+ final int userId = UserHandle.getCallingUserId();
+ return updateSecureSettingLocked(args.name, value, userId) ? 1 : 0;
+ }
+
+ case TABLE_SYSTEM: {
+ final int userId = UserHandle.getCallingUserId();
+ return updateSystemSettingLocked(args.name, value, userId) ? 1 : 0;
+ }
+
+ default: {
+ throw new IllegalArgumentException("Invalid Uri path:" + uri);
+ }
+ }
+ }
+ }
- setAppOps(AppOpsManager.OP_NONE, AppOpsManager.OP_WRITE_SETTINGS);
- establishDbTracking(UserHandle.USER_OWNER);
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ throw new FileNotFoundException("Direct file access no longer supported; "
+ + "ringtone playback is available through android.media.Ringtone");
+ }
+ private void registerBroadcastReceivers() {
IntentFilter userFilter = new IntentFilter();
userFilter.addAction(Intent.ACTION_USER_REMOVED);
- userFilter.addAction(Intent.ACTION_USER_ADDED);
+ userFilter.addAction(Intent.ACTION_USER_STOPPED);
+
getContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
UserHandle.USER_OWNER);
- if (intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
- onUserRemoved(userHandle);
- } else if (intent.getAction().equals(Intent.ACTION_USER_ADDED)) {
- onProfilesChanged();
+
+ switch (intent.getAction()) {
+ case Intent.ACTION_USER_REMOVED: {
+ mSettingsRegistry.removeUserStateLocked(userId, true);
+ } break;
+
+ case Intent.ACTION_USER_STOPPED: {
+ mSettingsRegistry.removeUserStateLocked(userId, false);
+ } break;
}
}
}, userFilter);
- onProfilesChanged();
+ PackageMonitor monitor = new PackageMonitor() {
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ synchronized (mLock) {
+ mSettingsRegistry.onPackageRemovedLocked(packageName,
+ UserHandle.getUserId(uid));
+ }
+ }
+ };
- return true;
+ // package changes
+ monitor.register(getContext(), BackgroundThread.getHandler().getLooper(),
+ UserHandle.ALL, true);
}
- void onUserRemoved(int userHandle) {
- synchronized (this) {
- // the db file itself will be deleted automatically, but we need to tear down
- // our caches and other internal bookkeeping.
- FileObserver observer = sObserverInstances.get(userHandle);
- if (observer != null) {
- observer.stopWatching();
- sObserverInstances.delete(userHandle);
- }
+ private Cursor getAllGlobalSettingsLocked(String[] projection) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "getAllGlobalSettingsLocked()");
+ }
+
+ // Get the settings.
+ SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
+ SettingsRegistry.SETTINGS_TYPE_GLOBAL, UserHandle.USER_OWNER);
+
+ List<String> names = settingsState.getSettingNamesLocked();
- mOpenHelpers.delete(userHandle);
- sSystemCaches.delete(userHandle);
- sSecureCaches.delete(userHandle);
- sKnownMutationsInFlight.delete(userHandle);
- onProfilesChanged();
+ final int nameCount = names.size();
+
+ String[] normalizedProjection = normalizeProjection(projection);
+ MatrixCursor result = new MatrixCursor(normalizedProjection, nameCount);
+
+ // Anyone can get the global settings, so no security checks.
+ for (int i = 0; i < nameCount; i++) {
+ String name = names.get(i);
+ Setting setting = settingsState.getSettingLocked(name);
+ appendSettingToCursor(result, setting);
}
+
+ return result;
}
- /**
- * Updates the list of managed profiles. It assumes that only the primary user
- * can have managed profiles. Modify this code if that changes in the future.
- */
- void onProfilesChanged() {
- synchronized (this) {
- mManagedProfiles = mUserManager.getProfiles(UserHandle.USER_OWNER);
- if (mManagedProfiles != null) {
- // Remove the primary user from the list
- for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
- if (mManagedProfiles.get(i).id == UserHandle.USER_OWNER) {
- mManagedProfiles.remove(i);
- }
- }
- // If there are no managed profiles, reset the variable
- if (mManagedProfiles.size() == 0) {
- mManagedProfiles = null;
- }
+ private Setting getGlobalSettingLocked(String name) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "getGlobalSetting(" + name + ")");
+ }
+
+ // Get the value.
+ return mSettingsRegistry.getSettingLocked(SettingsRegistry.SETTINGS_TYPE_GLOBAL,
+ UserHandle.USER_OWNER, name);
+ }
+
+ private boolean updateGlobalSettingLocked(String name, String value, int requestingUserId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "updateGlobalSettingLocked(" + name + ", " + value + ")");
+ }
+ return mutateGlobalSettingLocked(name, value, requestingUserId, MUTATION_OPERATION_UPDATE);
+ }
+
+ private boolean insertGlobalSettingLocked(String name, String value, int requestingUserId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "insertGlobalSettingLocked(" + name + ", " + value + ")");
+ }
+ return mutateGlobalSettingLocked(name, value, requestingUserId, MUTATION_OPERATION_INSERT);
+ }
+
+ private boolean deleteGlobalSettingLocked(String name, int requestingUserId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "deleteGlobalSettingLocked(" + name + ")");
+ }
+ return mutateGlobalSettingLocked(name, null, requestingUserId, MUTATION_OPERATION_DELETE);
+ }
+
+ private boolean mutateGlobalSettingLocked(String name, String value, int requestingUserId,
+ int operation) {
+ // Make sure the caller can change the settings - treated as secure.
+ enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
+
+ // Verify whether this operation is allowed for the calling package.
+ if (!isAppOpWriteSettingsAllowedForCallingPackage()) {
+ return false;
+ }
+
+ // Resolve the userId on whose behalf the call is made.
+ final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+
+ // If this is a setting that is currently restricted for this user, done.
+ if (isGlobalOrSecureSettingRestrictedForUser(name, callingUserId)) {
+ return false;
+ }
+
+ // Perform the mutation.
+ switch (operation) {
+ case MUTATION_OPERATION_INSERT: {
+ return mSettingsRegistry.insertSettingLocked(SettingsRegistry.SETTINGS_TYPE_GLOBAL,
+ UserHandle.USER_OWNER, name, value, getCallingPackage());
}
- if (LOCAL_LOGV) {
- Slog.d(TAG, "Managed Profiles = " + mManagedProfiles);
+
+ case MUTATION_OPERATION_DELETE: {
+ return mSettingsRegistry.deleteSettingLocked(
+ SettingsRegistry.SETTINGS_TYPE_GLOBAL,
+ UserHandle.USER_OWNER, name);
+ }
+
+ case MUTATION_OPERATION_UPDATE: {
+ return mSettingsRegistry.updateSettingLocked(SettingsRegistry.SETTINGS_TYPE_GLOBAL,
+ UserHandle.USER_OWNER, name, value, getCallingPackage());
}
}
+
+ return false;
}
- private void establishDbTracking(int userHandle) {
- if (LOCAL_LOGV) {
- Slog.i(TAG, "Installing settings db helper and caches for user " + userHandle);
+ private Cursor getAllSecureSettingsLocked(int userId, String[] projection) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "getAllSecureSettings(" + userId + ")");
}
- DatabaseHelper dbhelper;
+ // Resolve the userId on whose behalf the call is made.
+ final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
+
+ List<String> names = mSettingsRegistry.getSettingsNamesLocked(
+ SettingsRegistry.SETTINGS_TYPE_SECURE, callingUserId);
+
+ final int nameCount = names.size();
- synchronized (this) {
- dbhelper = mOpenHelpers.get(userHandle);
- if (dbhelper == null) {
- dbhelper = new DatabaseHelper(getContext(), userHandle);
- mOpenHelpers.append(userHandle, dbhelper);
+ String[] normalizedProjection = normalizeProjection(projection);
+ MatrixCursor result = new MatrixCursor(normalizedProjection, nameCount);
- sSystemCaches.append(userHandle, new SettingsCache(TABLE_SYSTEM));
- sSecureCaches.append(userHandle, new SettingsCache(TABLE_SECURE));
- sKnownMutationsInFlight.append(userHandle, new AtomicInteger(0));
+ for (int i = 0; i < nameCount; i++) {
+ String name = names.get(i);
+
+ // Determine the owning user as some profile settings are cloned from the parent.
+ final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);
+
+ // Special case for location (sigh).
+ if (isLocationProvidersAllowedRestricted(name, callingUserId, owningUserId)) {
+ return null;
}
+
+ Setting setting = mSettingsRegistry.getSettingLocked(
+ SettingsRegistry.SETTINGS_TYPE_SECURE, owningUserId, name);
+ appendSettingToCursor(result, setting);
}
- // Initialization of the db *outside* the locks. It's possible that racing
- // threads might wind up here, the second having read the cache entries
- // written by the first, but that's benign: the SQLite helper implementation
- // manages concurrency itself, and it's important that we not run the db
- // initialization with any of our own locks held, so we're fine.
- SQLiteDatabase db = dbhelper.getWritableDatabase();
-
- // Watch for external modifications to the database files,
- // keeping our caches in sync. We synchronize the observer set
- // separately, and of course it has to run after the db file
- // itself was set up by the DatabaseHelper.
- synchronized (sObserverInstances) {
- if (sObserverInstances.get(userHandle) == null) {
- SettingsFileObserver observer = new SettingsFileObserver(userHandle, db.getPath());
- sObserverInstances.append(userHandle, observer);
- observer.startWatching();
- }
+ return result;
+ }
+
+ private Setting getSecureSettingLocked(String name, int requestingUserId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "getSecureSetting(" + name + ", " + requestingUserId + ")");
}
- ensureAndroidIdIsSet(userHandle);
+ // Resolve the userId on whose behalf the call is made.
+ final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
- startAsyncCachePopulation(userHandle);
- }
+ // Determine the owning user as some profile settings are cloned from the parent.
+ final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);
+
+ // Special case for location (sigh).
+ if (isLocationProvidersAllowedRestricted(name, callingUserId, owningUserId)) {
+ return null;
+ }
- class CachePrefetchThread extends Thread {
- private int mUserHandle;
+ // Get the value.
+ return mSettingsRegistry.getSettingLocked(SettingsRegistry.SETTINGS_TYPE_SECURE,
+ owningUserId, name);
+ }
- CachePrefetchThread(int userHandle) {
- super("populate-settings-caches");
- mUserHandle = userHandle;
+ private boolean insertSecureSettingLocked(String name, String value, int requestingUserId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "insertSecureSettingLocked(" + name + ", " + value + ", "
+ + requestingUserId + ")");
}
- @Override
- public void run() {
- fullyPopulateCaches(mUserHandle);
+ return mutateSecureSettingLocked(name, value, requestingUserId, MUTATION_OPERATION_INSERT);
+ }
+
+ private boolean deleteSecureSettingLocked(String name, int requestingUserId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "deleteSecureSettingLocked(" + name + ", " + requestingUserId + ")");
}
+
+ return mutateSecureSettingLocked(name, null, requestingUserId, MUTATION_OPERATION_DELETE);
}
- private void startAsyncCachePopulation(int userHandle) {
- new CachePrefetchThread(userHandle).start();
+ private boolean updateSecureSettingLocked(String name, String value, int requestingUserId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "updateSecureSettingLocked(" + name + ", " + value + ", "
+ + requestingUserId + ")");
+ }
+
+ return mutateSecureSettingLocked(name, value, requestingUserId, MUTATION_OPERATION_UPDATE);
}
- private void fullyPopulateCaches(final int userHandle) {
- DatabaseHelper dbHelper;
- synchronized (this) {
- dbHelper = mOpenHelpers.get(userHandle);
+ private boolean mutateSecureSettingLocked(String name, String value, int requestingUserId,
+ int operation) {
+ // Make sure the caller can change the settings.
+ enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
+
+ // Verify whether this operation is allowed for the calling package.
+ if (!isAppOpWriteSettingsAllowedForCallingPackage()) {
+ return false;
}
- if (dbHelper == null) {
- // User is gone.
- return;
+
+ // Resolve the userId on whose behalf the call is made.
+ final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+
+ // If this is a setting that is currently restricted for this user, done.
+ if (isGlobalOrSecureSettingRestrictedForUser(name, callingUserId)) {
+ return false;
}
- // Only populate the globals cache once, for the owning user
- if (userHandle == UserHandle.USER_OWNER) {
- fullyPopulateCache(dbHelper, TABLE_GLOBAL, sGlobalCache);
+
+ // Determine the owning user as some profile settings are cloned from the parent.
+ final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);
+
+ // Only the owning user can change the setting.
+ if (owningUserId != callingUserId) {
+ return false;
}
- fullyPopulateCache(dbHelper, TABLE_SECURE, sSecureCaches.get(userHandle));
- fullyPopulateCache(dbHelper, TABLE_SYSTEM, sSystemCaches.get(userHandle));
- }
- // Slurp all values (if sane in number & size) into cache.
- private void fullyPopulateCache(DatabaseHelper dbHelper, String table, SettingsCache cache) {
- SQLiteDatabase db = dbHelper.getReadableDatabase();
- Cursor c = db.query(
- table,
- new String[] { Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE },
- null, null, null, null, null,
- "" + (MAX_CACHE_ENTRIES + 1) /* limit */);
- try {
- synchronized (cache) {
- cache.evictAll();
- cache.setFullyMatchesDisk(true); // optimistic
- int rows = 0;
- while (c.moveToNext()) {
- rows++;
- String name = c.getString(0);
- String value = c.getString(1);
- cache.populate(name, value);
- }
- if (rows > MAX_CACHE_ENTRIES) {
- // Somewhat redundant, as removeEldestEntry() will
- // have already done this, but to be explicit:
- cache.setFullyMatchesDisk(false);
- Log.d(TAG, "row count exceeds max cache entries for table " + table);
- }
- if (LOCAL_LOGV) Log.d(TAG, "cache for settings table '" + table
- + "' rows=" + rows + "; fullycached=" + cache.fullyMatchesDisk());
+ // Special cases for location providers (sigh).
+ if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
+ return updateLocationProvidersAllowed(value, owningUserId);
+ }
+
+ // Mutate the value.
+ switch(operation) {
+ case MUTATION_OPERATION_INSERT: {
+ return mSettingsRegistry.insertSettingLocked(SettingsRegistry.SETTINGS_TYPE_SECURE,
+ owningUserId, name, value, getCallingPackage());
+ }
+
+ case MUTATION_OPERATION_DELETE: {
+ return mSettingsRegistry.deleteSettingLocked(
+ SettingsRegistry.SETTINGS_TYPE_SECURE,
+ owningUserId, name);
+ }
+
+ case MUTATION_OPERATION_UPDATE: {
+ return mSettingsRegistry.updateSettingLocked(SettingsRegistry.SETTINGS_TYPE_SECURE,
+ owningUserId, name, value, getCallingPackage());
}
- } finally {
- c.close();
}
+
+ return false;
}
- private boolean ensureAndroidIdIsSet(int userHandle) {
- final Cursor c = queryForUser(Settings.Secure.CONTENT_URI,
- new String[] { Settings.NameValueTable.VALUE },
- Settings.NameValueTable.NAME + "=?",
- new String[] { Settings.Secure.ANDROID_ID }, null,
- userHandle);
- try {
- final String value = c.moveToNext() ? c.getString(0) : null;
- if (value == null) {
- // sanity-check the user before touching the db
- final UserInfo user = mUserManager.getUserInfo(userHandle);
- if (user == null) {
- // can happen due to races when deleting users; treat as benign
- return false;
- }
+ private Cursor getAllSystemSettingsLocked(int userId, String[] projection) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "getAllSecureSystemLocked(" + userId + ")");
+ }
- final SecureRandom random = new SecureRandom();
- final String newAndroidIdValue = Long.toHexString(random.nextLong());
- final ContentValues values = new ContentValues();
- values.put(Settings.NameValueTable.NAME, Settings.Secure.ANDROID_ID);
- values.put(Settings.NameValueTable.VALUE, newAndroidIdValue);
- final Uri uri = insertForUser(Settings.Secure.CONTENT_URI, values, userHandle);
- if (uri == null) {
- Slog.e(TAG, "Unable to generate new ANDROID_ID for user " + userHandle);
- return false;
- }
- Slog.d(TAG, "Generated and saved new ANDROID_ID [" + newAndroidIdValue
- + "] for user " + userHandle);
- // Write a dropbox entry if it's a restricted profile
- if (user.isRestricted()) {
- DropBoxManager dbm = (DropBoxManager)
- getContext().getSystemService(Context.DROPBOX_SERVICE);
- if (dbm != null && dbm.isTagEnabled(DROPBOX_TAG_USERLOG)) {
- dbm.addText(DROPBOX_TAG_USERLOG, System.currentTimeMillis()
- + ",restricted_profile_ssaid,"
- + newAndroidIdValue + "\n");
- }
- }
- }
- return true;
- } finally {
- c.close();
+ // Resolve the userId on whose behalf the call is made.
+ final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
+
+ List<String> names = mSettingsRegistry.getSettingsNamesLocked(
+ SettingsRegistry.SETTINGS_TYPE_SYSTEM, callingUserId);
+
+ final int nameCount = names.size();
+
+ String[] normalizedProjection = normalizeProjection(projection);
+ MatrixCursor result = new MatrixCursor(normalizedProjection, nameCount);
+
+ for (int i = 0; i < nameCount; i++) {
+ String name = names.get(i);
+
+ // Determine the owning user as some profile settings are cloned from the parent.
+ final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);
+
+ Setting setting = mSettingsRegistry.getSettingLocked(
+ SettingsRegistry.SETTINGS_TYPE_SYSTEM, owningUserId, name);
+ appendSettingToCursor(result, setting);
}
+
+ return result;
}
- // Lazy-initialize the settings caches for non-primary users
- private SettingsCache getOrConstructCache(int callingUser, SparseArray<SettingsCache> which) {
- getOrEstablishDatabase(callingUser); // ignore return value; we don't need it
- return which.get(callingUser);
+ private Setting getSystemSettingLocked(String name, int requestingUserId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "getSystemSetting(" + name + ", " + requestingUserId + ")");
+ }
+
+ // Resolve the userId on whose behalf the call is made.
+ final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+
+ // Determine the owning user as some profile settings are cloned from the parent.
+ final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);
+
+ // Get the value.
+ return mSettingsRegistry.getSettingLocked(SettingsRegistry.SETTINGS_TYPE_SYSTEM,
+ owningUserId, name);
}
- // Lazy initialize the database helper and caches for this user, if necessary
- private DatabaseHelper getOrEstablishDatabase(int callingUser) {
- if (callingUser >= Process.SYSTEM_UID) {
- if (USER_CHECK_THROWS) {
- throw new IllegalArgumentException("Uid rather than user handle: " + callingUser);
- } else {
- Slog.wtf(TAG, "establish db for uid rather than user: " + callingUser);
- }
+ private boolean insertSystemSettingLocked(String name, String value, int requestingUserId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "insertSystemSettingLocked(" + name + ", " + value + ", "
+ + requestingUserId + ")");
}
- long oldId = Binder.clearCallingIdentity();
- try {
- DatabaseHelper dbHelper;
- synchronized (this) {
- dbHelper = mOpenHelpers.get(callingUser);
+ return mutateSystemSettingLocked(name, value, requestingUserId, MUTATION_OPERATION_INSERT);
+ }
+
+ private boolean deleteSystemSettingLocked(String name, int requestingUserId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "deleteSystemSettingLocked(" + name + ", " + requestingUserId + ")");
+ }
+
+ return mutateSystemSettingLocked(name, null, requestingUserId, MUTATION_OPERATION_DELETE);
+ }
+
+ private boolean updateSystemSettingLocked(String name, String value, int requestingUserId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "updateSystemSettingLocked(" + name + ", " + value + ", "
+ + requestingUserId + ")");
+ }
+
+ return mutateSystemSettingLocked(name, value, requestingUserId, MUTATION_OPERATION_UPDATE);
+ }
+
+ private boolean mutateSystemSettingLocked(String name, String value, int runAsUserId,
+ int operation) {
+ // Make sure the caller can change the settings.
+ enforceWritePermission(Manifest.permission.WRITE_SETTINGS);
+
+ // Verify whether this operation is allowed for the calling package.
+ if (!isAppOpWriteSettingsAllowedForCallingPackage()) {
+ return false;
+ }
+
+ // Enforce what the calling package can mutate in the system settings.
+ enforceRestrictedSystemSettingsMutationForCallingPackageLocked(operation, name);
+
+ // Resolve the userId on whose behalf the call is made.
+ final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(runAsUserId);
+
+ // Determine the owning user as some profile settings are cloned from the parent.
+ final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);
+
+ // Only the owning user id can change the setting.
+ if (owningUserId != callingUserId) {
+ return false;
+ }
+
+ // Mutate the value.
+ switch (operation) {
+ case MUTATION_OPERATION_INSERT: {
+ validateSystemSettingValue(name, value);
+ return mSettingsRegistry.insertSettingLocked(SettingsRegistry.SETTINGS_TYPE_SYSTEM,
+ owningUserId, name, value, getCallingPackage());
}
- if (null == dbHelper) {
- establishDbTracking(callingUser);
- synchronized (this) {
- dbHelper = mOpenHelpers.get(callingUser);
- }
+
+ case MUTATION_OPERATION_DELETE: {
+ return mSettingsRegistry.deleteSettingLocked(
+ SettingsRegistry.SETTINGS_TYPE_SYSTEM,
+ owningUserId, name);
+ }
+
+ case MUTATION_OPERATION_UPDATE: {
+ validateSystemSettingValue(name, value);
+ return mSettingsRegistry.updateSettingLocked(SettingsRegistry.SETTINGS_TYPE_SYSTEM,
+ owningUserId, name, value, getCallingPackage());
}
- return dbHelper;
- } finally {
- Binder.restoreCallingIdentity(oldId);
}
+
+ return false;
}
- public SettingsCache cacheForTable(final int callingUser, String tableName) {
- if (TABLE_SYSTEM.equals(tableName)) {
- return getOrConstructCache(callingUser, sSystemCaches);
+ private void validateSystemSettingValue(String name, String value) {
+ Settings.System.Validator validator = Settings.System.VALIDATORS.get(name);
+ if (validator != null && !validator.validate(value)) {
+ throw new IllegalArgumentException("Invalid value: " + value
+ + " for setting: " + name);
}
- if (TABLE_SECURE.equals(tableName)) {
- return getOrConstructCache(callingUser, sSecureCaches);
+ }
+
+ private boolean isLocationProvidersAllowedRestricted(String name, int callingUserId,
+ int owningUserId) {
+ // Optimization - location providers are restricted only for managed profiles.
+ if (callingUserId == owningUserId) {
+ return false;
}
- if (TABLE_GLOBAL.equals(tableName)) {
- return sGlobalCache;
+ if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)
+ && mUserManager.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION,
+ new UserHandle(callingUserId))) {
+ return true;
}
- return null;
+ return false;
}
- /**
- * Used for wiping a whole cache on deletes when we're not
- * sure what exactly was deleted or changed.
- */
- public void invalidateCache(final int callingUser, String tableName) {
- SettingsCache cache = cacheForTable(callingUser, tableName);
- if (cache == null) {
- return;
+ private boolean isGlobalOrSecureSettingRestrictedForUser(String setting, int userId) {
+ String restriction = sSettingToUserRestrictionMap.get(setting);
+ if (restriction == null) {
+ return false;
}
- synchronized (cache) {
- cache.evictAll();
- cache.mCacheFullyMatchesDisk = false;
+ return mUserManager.hasUserRestriction(restriction, new UserHandle(userId));
+ }
+
+ private int resolveOwningUserIdForSecureSettingLocked(int userId, String setting) {
+ return resolveOwningUserIdLocked(userId, sSecureCloneToManagedSettings, setting);
+ }
+
+ private int resolveOwningUserIdForSystemSettingLocked(int userId, String setting) {
+ return resolveOwningUserIdLocked(userId, sSystemCloneToManagedSettings, setting);
+ }
+
+ private int resolveOwningUserIdLocked(int userId, Set<String> keys, String name) {
+ final int parentId = getGroupParentLocked(userId);
+ if (parentId != userId && keys.contains(name)) {
+ return parentId;
}
+ return userId;
}
- /**
- * Checks if the calling user is a managed profile of the primary user.
- * Currently only the primary user (USER_OWNER) can have managed profiles.
- * @param callingUser the user trying to read/write settings
- * @return true if it is a managed profile of the primary user
- */
- private boolean isManagedProfile(int callingUser) {
- synchronized (this) {
- if (mManagedProfiles == null) return false;
- for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
- if (mManagedProfiles.get(i).id == callingUser) {
- return true;
+ private void enforceRestrictedSystemSettingsMutationForCallingPackageLocked(int operation,
+ String name) {
+ // System/root/shell can mutate whatever secure settings they want.
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == android.os.Process.SYSTEM_UID
+ || callingUid == Process.SHELL_UID
+ || callingUid == Process.ROOT_UID) {
+ return;
+ }
+
+ switch (operation) {
+ case MUTATION_OPERATION_INSERT:
+ // Insert updates.
+ case MUTATION_OPERATION_UPDATE: {
+ if (Settings.System.PUBLIC_SETTINGS.contains(name)) {
+ return;
}
- }
- return false;
+
+ // The calling package is already verified.
+ PackageInfo packageInfo = getCallingPackageInfoOrThrow();
+
+ // Privileged apps can do whatever they want.
+ if ((packageInfo.applicationInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+ return;
+ }
+
+ warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
+ packageInfo.applicationInfo.targetSdkVersion, name);
+ } break;
+
+ case MUTATION_OPERATION_DELETE: {
+ if (Settings.System.PUBLIC_SETTINGS.contains(name)
+ || Settings.System.PRIVATE_SETTINGS.contains(name)) {
+ throw new IllegalArgumentException("You cannot delete system defined"
+ + " secure settings.");
+ }
+
+ // The calling package is already verified.
+ PackageInfo packageInfo = getCallingPackageInfoOrThrow();
+
+ // Privileged apps can do whatever they want.
+ if ((packageInfo.applicationInfo.privateFlags &
+ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+ return;
+ }
+
+ warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
+ packageInfo.applicationInfo.targetSdkVersion, name);
+ } break;
}
}
- /**
- * Fast path that avoids the use of chatty remoted Cursors.
- */
- @Override
- public Bundle call(String method, String request, Bundle args) {
- int callingUser = UserHandle.getCallingUserId();
- if (args != null) {
- int reqUser = args.getInt(Settings.CALL_METHOD_USER_KEY, callingUser);
- if (reqUser != callingUser) {
- callingUser = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), reqUser, false, true,
- "get/set setting for user", null);
- if (LOCAL_LOGV) Slog.v(TAG, " access setting for user " + callingUser);
- }
+ private PackageInfo getCallingPackageInfoOrThrow() {
+ try {
+ return mPackageManager.getPackageInfo(getCallingPackage(), 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalStateException("Calling package doesn't exist");
}
+ }
- // Note: we assume that get/put operations for moved-to-global names have already
- // been directed to the new location on the caller side (otherwise we'd fix them
- // up here).
- DatabaseHelper dbHelper;
- SettingsCache cache;
+ private int getGroupParentLocked(int userId) {
+ // Most frequent use case.
+ if (userId == UserHandle.USER_OWNER) {
+ return userId;
+ }
+ // We are in the same process with the user manager and the returned
+ // user info is a cached instance, so just look up instead of cache.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ UserInfo userInfo = mUserManager.getProfileParent(userId);
+ return (userInfo != null) ? userInfo.id : userId;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
- // Get methods
- if (Settings.CALL_METHOD_GET_SYSTEM.equals(method)) {
- if (LOCAL_LOGV) Slog.v(TAG, "call(system:" + request + ") for " + callingUser);
- // Check if this request should be (re)directed to the primary user's db
- if (callingUser != UserHandle.USER_OWNER
- && shouldShadowParentProfile(callingUser, sSystemCloneToManagedKeys, request)) {
- callingUser = UserHandle.USER_OWNER;
- }
- dbHelper = getOrEstablishDatabase(callingUser);
- cache = sSystemCaches.get(callingUser);
- return lookupValue(dbHelper, TABLE_SYSTEM, cache, request);
- }
- if (Settings.CALL_METHOD_GET_SECURE.equals(method)) {
- if (LOCAL_LOGV) Slog.v(TAG, "call(secure:" + request + ") for " + callingUser);
- // Check if this is a setting to be copied from the primary user
- if (shouldShadowParentProfile(callingUser, sSecureCloneToManagedKeys, request)) {
- // If the request if for location providers and there's a restriction, return none
- if (Secure.LOCATION_PROVIDERS_ALLOWED.equals(request)
- && mUserManager.hasUserRestriction(
- UserManager.DISALLOW_SHARE_LOCATION, new UserHandle(callingUser))) {
- return sSecureCaches.get(callingUser).putIfAbsent(request, "");
- }
- callingUser = UserHandle.USER_OWNER;
- }
- dbHelper = getOrEstablishDatabase(callingUser);
- cache = sSecureCaches.get(callingUser);
- return lookupValue(dbHelper, TABLE_SECURE, cache, request);
- }
- if (Settings.CALL_METHOD_GET_GLOBAL.equals(method)) {
- if (LOCAL_LOGV) Slog.v(TAG, "call(global:" + request + ") for " + callingUser);
- // fast path: owner db & cache are immutable after onCreate() so we need not
- // guard on the attempt to look them up
- return lookupValue(getOrEstablishDatabase(UserHandle.USER_OWNER), TABLE_GLOBAL,
- sGlobalCache, request);
- }
-
- // Put methods - new value is in the args bundle under the key named by
- // the Settings.NameValueTable.VALUE static.
- final String newValue = (args == null)
- ? null : args.getString(Settings.NameValueTable.VALUE);
-
- // Framework can't do automatic permission checking for calls, so we need
- // to do it here.
- if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
+ private boolean isAppOpWriteSettingsAllowedForCallingPackage() {
+ final int callingUid = Binder.getCallingUid();
+
+ mAppOpsManager.checkPackage(Binder.getCallingUid(), getCallingPackage());
+
+ return mAppOpsManager.noteOp(AppOpsManager.OP_WRITE_SETTINGS, callingUid,
+ getCallingPackage()) == AppOpsManager.MODE_ALLOWED;
+ }
+
+ private void enforceWritePermission(String permission) {
+ if (getContext().checkCallingOrSelfPermission(permission)
!= PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(
- String.format("Permission denial: writing to settings requires %1$s",
- android.Manifest.permission.WRITE_SETTINGS));
+ throw new SecurityException("Permission denial: writing to settings requires:"
+ + permission);
+ }
+ }
+
+ /*
+ * Used to parse changes to the value of Settings.Secure.LOCATION_PROVIDERS_ALLOWED.
+ * This setting contains a list of the currently enabled location providers.
+ * But helper functions in android.providers.Settings can enable or disable
+ * a single provider by using a "+" or "-" prefix before the provider name.
+ *
+ * @returns whether the enabled location providers changed.
+ */
+ private boolean updateLocationProvidersAllowed(String value, int owningUserId) {
+ if (TextUtils.isEmpty(value)) {
+ return false;
}
- // Also need to take care of app op.
- if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SETTINGS, Binder.getCallingUid(),
- getCallingPackage()) != AppOpsManager.MODE_ALLOWED) {
- return null;
+ final char prefix = value.charAt(0);
+ if (prefix != '+' && prefix != '-') {
+ return false;
}
- final ContentValues values = new ContentValues();
- values.put(Settings.NameValueTable.NAME, request);
- values.put(Settings.NameValueTable.VALUE, newValue);
- if (Settings.CALL_METHOD_PUT_SYSTEM.equals(method)) {
- if (LOCAL_LOGV) {
- Slog.v(TAG, "call_put(system:" + request + "=" + newValue + ") for "
- + callingUser);
- }
- // Extra check for USER_OWNER to optimize for the 99%
- if (callingUser != UserHandle.USER_OWNER && shouldShadowParentProfile(callingUser,
- sSystemCloneToManagedKeys, request)) {
- // Don't write these settings, as they are cloned from the parent profile
- return null;
- }
- insertForUser(Settings.System.CONTENT_URI, values, callingUser);
- // Clone the settings to the managed profiles so that notifications can be sent out
- if (callingUser == UserHandle.USER_OWNER && mManagedProfiles != null
- && sSystemCloneToManagedKeys.contains(request)) {
- final long token = Binder.clearCallingIdentity();
- try {
- for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
- if (LOCAL_LOGV) {
- Slog.v(TAG, "putting to additional user "
- + mManagedProfiles.get(i).id);
- }
- insertForUser(Settings.System.CONTENT_URI, values,
- mManagedProfiles.get(i).id);
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- } else if (Settings.CALL_METHOD_PUT_SECURE.equals(method)) {
- if (LOCAL_LOGV) {
- Slog.v(TAG, "call_put(secure:" + request + "=" + newValue + ") for "
- + callingUser);
+ // skip prefix
+ value = value.substring(1);
+
+ Setting settingValue = getSecureSettingLocked(
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED, owningUserId);
+
+ String oldProviders = (settingValue != null) ? settingValue.getValue() : "";
+
+ int index = oldProviders.indexOf(value);
+ int end = index + value.length();
+
+ // check for commas to avoid matching on partial string
+ if (index > 0 && oldProviders.charAt(index - 1) != ',') {
+ index = -1;
+ }
+
+ // check for commas to avoid matching on partial string
+ if (end < oldProviders.length() && oldProviders.charAt(end) != ',') {
+ index = -1;
+ }
+
+ String newProviders;
+
+ if (prefix == '+' && index < 0) {
+ // append the provider to the list if not present
+ if (oldProviders.length() == 0) {
+ newProviders = value;
+ } else {
+ newProviders = oldProviders + ',' + value;
}
- // Extra check for USER_OWNER to optimize for the 99%
- if (callingUser != UserHandle.USER_OWNER && shouldShadowParentProfile(callingUser,
- sSecureCloneToManagedKeys, request)) {
- // Don't write these settings, as they are cloned from the parent profile
- return null;
+ } else if (prefix == '-' && index >= 0) {
+ // remove the provider from the list if present
+ // remove leading or trailing comma
+ if (index > 0) {
+ index--;
+ } else if (end < oldProviders.length()) {
+ end++;
}
- insertForUser(Settings.Secure.CONTENT_URI, values, callingUser);
- // Clone the settings to the managed profiles so that notifications can be sent out
- if (callingUser == UserHandle.USER_OWNER && mManagedProfiles != null
- && sSecureCloneToManagedKeys.contains(request)) {
- final long token = Binder.clearCallingIdentity();
- try {
- for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
- if (LOCAL_LOGV) {
- Slog.v(TAG, "putting to additional user "
- + mManagedProfiles.get(i).id);
- }
- try {
- insertForUser(Settings.Secure.CONTENT_URI, values,
- mManagedProfiles.get(i).id);
- } catch (SecurityException e) {
- // Temporary fix, see b/17450158
- Slog.w(TAG, "Cannot clone request '" + request + "' with value '"
- + newValue + "' to managed profile (id "
- + mManagedProfiles.get(i).id + ")", e);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- } else if (Settings.CALL_METHOD_PUT_GLOBAL.equals(method)) {
- if (LOCAL_LOGV) {
- Slog.v(TAG, "call_put(global:" + request + "=" + newValue + ") for "
- + callingUser);
+
+ newProviders = oldProviders.substring(0, index);
+ if (end < oldProviders.length()) {
+ newProviders += oldProviders.substring(end);
}
- insertForUser(Settings.Global.CONTENT_URI, values, callingUser);
} else {
- Slog.w(TAG, "call() with invalid method: " + method);
+ // nothing changed, so no need to update the database
+ return false;
}
- return null;
- }
+ updateSecureSettingLocked(Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ newProviders, owningUserId);
- /**
- * Check if the user is a managed profile and name is one of the settings to be cloned
- * from the parent profile.
- */
- private boolean shouldShadowParentProfile(int userId, HashSet<String> keys, String name) {
- return isManagedProfile(userId) && keys.contains(name);
+ return true;
}
- // Looks up value 'key' in 'table' and returns either a single-pair Bundle,
- // possibly with a null value, or null on failure.
- private Bundle lookupValue(DatabaseHelper dbHelper, String table,
- final SettingsCache cache, String key) {
- if (cache == null) {
- Slog.e(TAG, "cache is null for user " + UserHandle.getCallingUserId() + " : key=" + key);
- return null;
- }
- synchronized (cache) {
- Bundle value = cache.get(key);
- if (value != null) {
- if (value != TOO_LARGE_TO_CACHE_MARKER) {
- return value;
- }
- // else we fall through and read the value from disk
- } else if (cache.fullyMatchesDisk()) {
- // Fast path (very common). Don't even try touch disk
- // if we know we've slurped it all in. Trying to
- // touch the disk would mean waiting for yaffs2 to
- // give us access, which could takes hundreds of
- // milliseconds. And we're very likely being called
- // from somebody's UI thread...
- return NULL_SETTING;
+ private void sendNotify(Uri uri, int userId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ getContext().getContentResolver().notifyChange(uri, null, true, userId);
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Notifying for " + userId + ": " + uri);
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
+ }
- SQLiteDatabase db = dbHelper.getReadableDatabase();
- Cursor cursor = null;
- try {
- cursor = db.query(table, COLUMN_VALUE, "name=?", new String[]{key},
- null, null, null, null);
- if (cursor != null && cursor.getCount() == 1) {
- cursor.moveToFirst();
- return cache.putIfAbsent(key, cursor.getString(0));
+ private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
+ int targetSdkVersion, String name) {
+ // If the app targets Lollipop MR1 or older SDK we warn, otherwise crash.
+ if (targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
+ if (Settings.System.PRIVATE_SETTINGS.contains(name)) {
+ Slog.w(LOG_TAG, "You shouldn't not change private system settings."
+ + " This will soon become an error.");
+ } else {
+ Slog.w(LOG_TAG, "You shouldn't keep your settings in the secure settings."
+ + " This will soon become an error.");
+ }
+ } else {
+ if (Settings.System.PRIVATE_SETTINGS.contains(name)) {
+ throw new IllegalArgumentException("You cannot change private secure settings.");
+ } else {
+ throw new IllegalArgumentException("You cannot keep your settings in"
+ + " the secure settings.");
}
- } catch (SQLiteException e) {
- Log.w(TAG, "settings lookup error", e);
- return null;
- } finally {
- if (cursor != null) cursor.close();
}
- cache.putIfAbsent(key, null);
- return NULL_SETTING;
}
- @Override
- public Cursor query(Uri url, String[] select, String where, String[] whereArgs, String sort) {
- return queryForUser(url, select, where, whereArgs, sort, UserHandle.getCallingUserId());
+ private static int resolveCallingUserIdEnforcingPermissionsLocked(int requestingUserId) {
+ if (requestingUserId == UserHandle.getCallingUserId()) {
+ return requestingUserId;
+ }
+ return ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), requestingUserId, false, true,
+ "get/set setting for user", null);
}
- private Cursor queryForUser(Uri url, String[] select, String where, String[] whereArgs,
- String sort, int forUser) {
- if (LOCAL_LOGV) Slog.v(TAG, "query(" + url + ") for user " + forUser);
- SqlArguments args = new SqlArguments(url, where, whereArgs);
- DatabaseHelper dbH;
- dbH = getOrEstablishDatabase(
- TABLE_GLOBAL.equals(args.table) ? UserHandle.USER_OWNER : forUser);
- SQLiteDatabase db = dbH.getReadableDatabase();
-
- // The favorites table was moved from this provider to a provider inside Home
- // Home still need to query this table to upgrade from pre-cupcake builds
- // However, a cupcake+ build with no data does not contain this table which will
- // cause an exception in the SQL stack. The following line is a special case to
- // let the caller of the query have a chance to recover and avoid the exception
- if (TABLE_FAVORITES.equals(args.table)) {
- return null;
- } else if (TABLE_OLD_FAVORITES.equals(args.table)) {
- args.table = TABLE_FAVORITES;
- Cursor cursor = db.rawQuery("PRAGMA table_info(favorites);", null);
- if (cursor != null) {
- boolean exists = cursor.getCount() > 0;
- cursor.close();
- if (!exists) return null;
- } else {
- return null;
- }
+ private static Bundle packageValueForCallResult(Setting setting) {
+ if (setting == null) {
+ return NULL_SETTING;
}
+ return Bundle.forPair(Settings.NameValueTable.VALUE, setting.getValue());
+ }
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables(args.table);
+ private static int getRequestingUserId(Bundle args) {
+ final int callingUserId = UserHandle.getCallingUserId();
+ return (args != null) ? args.getInt(Settings.CALL_METHOD_USER_KEY, callingUserId)
+ : callingUserId;
+ }
- Cursor ret = qb.query(db, select, args.where, args.args, null, null, sort);
- // the default Cursor interface does not support per-user observation
- try {
- AbstractCursor c = (AbstractCursor) ret;
- c.setNotificationUri(getContext().getContentResolver(), url, forUser);
- } catch (ClassCastException e) {
- // details of the concrete Cursor implementation have changed and this code has
- // not been updated to match -- complain and fail hard.
- Log.wtf(TAG, "Incompatible cursor derivation!");
- throw e;
- }
- return ret;
+ private static String getSettingValue(Bundle args) {
+ return (args != null) ? args.getString(Settings.NameValueTable.VALUE) : null;
}
- @Override
- public String getType(Uri url) {
- // If SqlArguments supplies a where clause, then it must be an item
- // (because we aren't supplying our own where clause).
- SqlArguments args = new SqlArguments(url, null, null);
- if (TextUtils.isEmpty(args.where)) {
- return "vnd.android.cursor.dir/" + args.table;
- } else {
- return "vnd.android.cursor.item/" + args.table;
+ private static String getValidTableOrThrow(Uri uri) {
+ if (uri.getPathSegments().size() > 0) {
+ String table = uri.getPathSegments().get(0);
+ if (DatabaseHelper.isValidTable(table)) {
+ return table;
+ }
+ throw new IllegalArgumentException("Bad root path: " + table);
}
+ throw new IllegalArgumentException("Invalid URI:" + uri);
}
- @Override
- public int bulkInsert(Uri uri, ContentValues[] values) {
- final int callingUser = UserHandle.getCallingUserId();
- if (LOCAL_LOGV) Slog.v(TAG, "bulkInsert() for user " + callingUser);
- SqlArguments args = new SqlArguments(uri);
- if (TABLE_FAVORITES.equals(args.table)) {
- return 0;
+ private static MatrixCursor packageSettingForQuery(Setting setting, String[] projection) {
+ if (setting == null) {
+ return new MatrixCursor(projection, 0);
}
- checkWritePermissions(args);
- SettingsCache cache = cacheForTable(callingUser, args.table);
+ MatrixCursor cursor = new MatrixCursor(projection, 1);
+ appendSettingToCursor(cursor, setting);
+ return cursor;
+ }
- final AtomicInteger mutationCount;
- synchronized (this) {
- mutationCount = sKnownMutationsInFlight.get(callingUser);
- }
- if (mutationCount != null) {
- mutationCount.incrementAndGet();
+ private static String[] normalizeProjection(String[] projection) {
+ if (projection == null) {
+ return ALL_COLUMNS;
}
- DatabaseHelper dbH = getOrEstablishDatabase(
- TABLE_GLOBAL.equals(args.table) ? UserHandle.USER_OWNER : callingUser);
- SQLiteDatabase db = dbH.getWritableDatabase();
- db.beginTransaction();
- try {
- int numValues = values.length;
- for (int i = 0; i < numValues; i++) {
- checkUserRestrictions(values[i].getAsString(Settings.Secure.NAME), callingUser);
- if (db.insert(args.table, null, values[i]) < 0) return 0;
- SettingsCache.populate(cache, values[i]);
- if (LOCAL_LOGV) Log.v(TAG, args.table + " <- " + values[i]);
+
+ final int columnCount = projection.length;
+ for (int i = 0; i < columnCount; i++) {
+ String column = projection[i];
+ if (!ArrayUtils.contains(ALL_COLUMNS, column)) {
+ throw new IllegalArgumentException("Invalid column: " + column);
}
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- if (mutationCount != null) {
- mutationCount.decrementAndGet();
+ }
+
+ return projection;
+ }
+
+ private static void appendSettingToCursor(MatrixCursor cursor, Setting setting) {
+ final int columnCount = cursor.getColumnCount();
+
+ String[] values = new String[columnCount];
+
+ for (int i = 0; i < columnCount; i++) {
+ String column = cursor.getColumnName(i);
+
+ switch (column) {
+ case Settings.NameValueTable._ID: {
+ values[i] = setting.getId();
+ } break;
+
+ case Settings.NameValueTable.NAME: {
+ values[i] = setting.getName();
+ } break;
+
+ case Settings.NameValueTable.VALUE: {
+ values[i] = setting.getValue();
+ } break;
}
}
- sendNotify(uri, callingUser);
- return values.length;
+ cursor.addRow(values);
}
- /*
- * Used to parse changes to the value of Settings.Secure.LOCATION_PROVIDERS_ALLOWED.
- * This setting contains a list of the currently enabled location providers.
- * But helper functions in android.providers.Settings can enable or disable
- * a single provider by using a "+" or "-" prefix before the provider name.
- *
- * @returns whether the database needs to be updated or not, also modifying
- * 'initialValues' if needed.
- */
- private boolean parseProviderList(Uri url, ContentValues initialValues, int desiredUser) {
- String value = initialValues.getAsString(Settings.Secure.VALUE);
- String newProviders = null;
- if (value != null && value.length() > 1) {
- char prefix = value.charAt(0);
- if (prefix == '+' || prefix == '-') {
- // skip prefix
- value = value.substring(1);
-
- // read list of enabled providers into "providers"
- String providers = "";
- String[] columns = {Settings.Secure.VALUE};
- String where = Settings.Secure.NAME + "=\'" + Settings.Secure.LOCATION_PROVIDERS_ALLOWED + "\'";
- Cursor cursor = queryForUser(url, columns, where, null, null, desiredUser);
- if (cursor != null && cursor.getCount() == 1) {
- try {
- cursor.moveToFirst();
- providers = cursor.getString(0);
- } finally {
- cursor.close();
+ private static final class Arguments {
+ private static final Pattern WHERE_PATTERN_WITH_PARAM_NO_BRACKETS =
+ Pattern.compile("[\\s]*name[\\s]*=[\\s]*\\?[\\s]*");
+
+ private static final Pattern WHERE_PATTERN_WITH_PARAM_IN_BRACKETS =
+ Pattern.compile("[\\s]*\\([\\s]*name[\\s]*=[\\s]*\\?[\\s]*\\)[\\s]*");
+
+ private static final Pattern WHERE_PATTERN_NO_PARAM_IN_BRACKETS =
+ Pattern.compile("[\\s]*\\([\\s]*name[\\s]*=[\\s]*['\"].*['\"][\\s]*\\)[\\s]*");
+
+ private static final Pattern WHERE_PATTERN_NO_PARAM_NO_BRACKETS =
+ Pattern.compile("[\\s]*name[\\s]*=[\\s]*['\"].*['\"][\\s]*");
+
+ public final String table;
+ public final String name;
+
+ public Arguments(Uri uri, String where, String[] whereArgs, boolean supportAll) {
+ final int segmentSize = uri.getPathSegments().size();
+ switch (segmentSize) {
+ case 1: {
+ if (where != null
+ && (WHERE_PATTERN_WITH_PARAM_NO_BRACKETS.matcher(where).matches()
+ || WHERE_PATTERN_WITH_PARAM_IN_BRACKETS.matcher(where).matches())
+ && whereArgs.length == 1) {
+ name = whereArgs[0];
+ table = computeTableForSetting(uri, name);
+ } else if (where != null
+ && (WHERE_PATTERN_NO_PARAM_NO_BRACKETS.matcher(where).matches()
+ || WHERE_PATTERN_NO_PARAM_IN_BRACKETS.matcher(where).matches())) {
+ final int startIndex = Math.max(where.indexOf("'"),
+ where.indexOf("\"")) + 1;
+ final int endIndex = Math.max(where.lastIndexOf("'"),
+ where.lastIndexOf("\""));
+ name = where.substring(startIndex, endIndex);
+ table = computeTableForSetting(uri, name);
+ } else if (supportAll && where == null && whereArgs == null) {
+ name = null;
+ table = computeTableForSetting(uri, null);
+ } else if (uri.getPathSegments().size() == 2
+ && where == null && whereArgs == null) {
+ name = uri.getPathSegments().get(1);
+ table = computeTableForSetting(uri, name);
+ } else {
+ EventLogTags.writeUnsupportedSettingsQuery(
+ uri.toSafeString(), where, Arrays.toString(whereArgs));
+ throw new IllegalArgumentException("Only null where and args"
+ + " or name=? where and a single arg or name='SOME_SETTING' "
+ + "are supported uri: " + uri + " where: " + where + " args: "
+ + Arrays.toString(whereArgs));
}
+ } break;
+
+ default: {
+ throw new IllegalArgumentException("Invalid URI: " + uri);
}
+ }
+ }
- int index = providers.indexOf(value);
- int end = index + value.length();
- // check for commas to avoid matching on partial string
- if (index > 0 && providers.charAt(index - 1) != ',') index = -1;
- if (end < providers.length() && providers.charAt(end) != ',') index = -1;
+ public static String computeTableForSetting(Uri uri, String name) {
+ String table = getValidTableOrThrow(uri);
- if (prefix == '+' && index < 0) {
- // append the provider to the list if not present
- if (providers.length() == 0) {
- newProviders = value;
- } else {
- newProviders = providers + ',' + value;
- }
- } else if (prefix == '-' && index >= 0) {
- // remove the provider from the list if present
- // remove leading or trailing comma
- if (index > 0) {
- index--;
- } else if (end < providers.length()) {
- end++;
- }
+ if (name != null) {
+ if (sSystemMovedToSecureSettings.contains(name)) {
+ table = TABLE_SECURE;
+ }
- newProviders = providers.substring(0, index);
- if (end < providers.length()) {
- newProviders += providers.substring(end);
- }
- } else {
- // nothing changed, so no need to update the database
- return false;
+ if (sSystemMovedToGlobalSettings.contains(name)) {
+ table = TABLE_GLOBAL;
+ }
+
+ if (sSecureMovedToGlobalSettings.contains(name)) {
+ table = TABLE_GLOBAL;
}
- if (newProviders != null) {
- initialValues.put(Settings.Secure.VALUE, newProviders);
+ if (sGlobalMovedToSecureSettings.contains(name)) {
+ table = TABLE_SECURE;
}
}
- }
- return true;
+ return table;
+ }
}
- @Override
- public Uri insert(Uri url, ContentValues initialValues) {
- return insertForUser(url, initialValues, UserHandle.getCallingUserId());
- }
+ final class SettingsRegistry {
+ private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid";
+
+ private static final int SETTINGS_TYPE_GLOBAL = 0;
+ private static final int SETTINGS_TYPE_SYSTEM = 1;
+ private static final int SETTINGS_TYPE_SECURE = 2;
- // Settings.put*ForUser() always winds up here, so this is where we apply
- // policy around permission to write settings for other users.
- private Uri insertForUser(Uri url, ContentValues initialValues, int desiredUserHandle) {
- final int callingUser = UserHandle.getCallingUserId();
- if (callingUser != desiredUserHandle) {
- getContext().enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "Not permitted to access settings for other users");
+ private static final int SETTINGS_TYPE_MASK = 0xF0000000;
+ private static final int SETTINGS_TYPE_SHIFT = 28;
+
+ private static final String SETTINGS_FILE_GLOBAL = "settings_global.xml";
+ private static final String SETTINGS_FILE_SYSTEM = "settings_system.xml";
+ private static final String SETTINGS_FILE_SECURE = "settings_secure.xml";
+
+ private final SparseArray<SettingsState> mSettingsStates = new SparseArray<>();
+
+ private final BackupManager mBackupManager;
+
+ public SettingsRegistry() {
+ mBackupManager = new BackupManager(getContext());
+ migrateAllLegacySettingsIfNeeded();
}
- if (LOCAL_LOGV) Slog.v(TAG, "insert(" + url + ") for user " + desiredUserHandle
- + " by " + callingUser);
+ public List<String> getSettingsNamesLocked(int type, int userId) {
+ final int key = makeKey(type, userId);
+ SettingsState settingsState = peekSettingsStateLocked(key);
+ return settingsState.getSettingNamesLocked();
+ }
- SqlArguments args = new SqlArguments(url);
- if (TABLE_FAVORITES.equals(args.table)) {
- return null;
+ public SettingsState getSettingsLocked(int type, int userId) {
+ final int key = makeKey(type, userId);
+ return peekSettingsStateLocked(key);
}
- // Special case LOCATION_PROVIDERS_ALLOWED.
- // Support enabling/disabling a single provider (using "+" or "-" prefix)
- String name = initialValues.getAsString(Settings.Secure.NAME);
- if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
- if (!parseProviderList(url, initialValues, desiredUserHandle)) return null;
+ public void ensureSettingsForUserLocked(int userId) {
+ // Migrate the setting for this user if needed.
+ migrateLegacySettingsForUserIfNeededLocked(userId);
+
+ // Ensure global settings loaded if owner.
+ if (userId == UserHandle.USER_OWNER) {
+ final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_OWNER);
+ ensureSettingsStateLocked(globalKey);
+ }
+
+ // Ensure secure settings loaded.
+ final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
+ ensureSettingsStateLocked(secureKey);
+
+ // Make sure the secure settings have an Android id set.
+ SettingsState secureSettings = getSettingsLocked(SETTINGS_TYPE_SECURE, userId);
+ ensureSecureSettingAndroidIdSetLocked(secureSettings);
+
+ // Ensure system settings loaded.
+ final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
+ ensureSettingsStateLocked(systemKey);
+
+ // Upgrade the settings to the latest version.
+ UpgradeController upgrader = new UpgradeController(userId);
+ upgrader.upgradeIfNeededLocked();
}
- // If this is an insert() of a key that has been migrated to the global store,
- // redirect the operation to that store
- if (name != null) {
- if (sSecureGlobalKeys.contains(name) || sSystemGlobalKeys.contains(name)) {
- if (!TABLE_GLOBAL.equals(args.table)) {
- if (LOCAL_LOGV) Slog.i(TAG, "Rewrite of insert() of now-global key " + name);
- }
- args.table = TABLE_GLOBAL; // next condition will rewrite the user handle
+ private void ensureSettingsStateLocked(int key) {
+ if (mSettingsStates.get(key) == null) {
+ final int maxBytesPerPackage = getMaxBytesPerPackageForType(getTypeFromKey(key));
+ SettingsState settingsState = new SettingsState(mLock, getSettingsFile(key), key,
+ maxBytesPerPackage);
+ mSettingsStates.put(key, settingsState);
}
}
- // Check write permissions only after determining which table the insert will touch
- checkWritePermissions(args);
+ public void removeUserStateLocked(int userId, boolean permanently) {
+ // We always keep the global settings in memory.
- checkUserRestrictions(name, desiredUserHandle);
+ // Nuke system settings.
+ final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
+ final SettingsState systemSettingsState = mSettingsStates.get(systemKey);
+ if (systemSettingsState != null) {
+ if (permanently) {
+ mSettingsStates.remove(systemKey);
+ systemSettingsState.destroyLocked(null);
+ } else {
+ systemSettingsState.destroyLocked(new Runnable() {
+ @Override
+ public void run() {
+ mSettingsStates.remove(systemKey);
+ }
+ });
+ }
+ }
- // The global table is stored under the owner, always
- if (TABLE_GLOBAL.equals(args.table)) {
- desiredUserHandle = UserHandle.USER_OWNER;
+ // Nuke secure settings.
+ final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
+ final SettingsState secureSettingsState = mSettingsStates.get(secureKey);
+ if (secureSettingsState != null) {
+ if (permanently) {
+ mSettingsStates.remove(secureKey);
+ secureSettingsState.destroyLocked(null);
+ } else {
+ secureSettingsState.destroyLocked(new Runnable() {
+ @Override
+ public void run() {
+ mSettingsStates.remove(secureKey);
+ }
+ });
+ }
+ }
}
- SettingsCache cache = cacheForTable(desiredUserHandle, args.table);
- String value = initialValues.getAsString(Settings.NameValueTable.VALUE);
- if (SettingsCache.isRedundantSetValue(cache, name, value)) {
- return Uri.withAppendedPath(url, name);
+ public boolean insertSettingLocked(int type, int userId, String name, String value,
+ String packageName) {
+ final int key = makeKey(type, userId);
+
+ SettingsState settingsState = peekSettingsStateLocked(key);
+ final boolean success = settingsState.insertSettingLocked(name, value, packageName);
+
+ if (success) {
+ notifyForSettingsChange(key, name);
+ }
+ return success;
}
- final AtomicInteger mutationCount;
- synchronized (this) {
- mutationCount = sKnownMutationsInFlight.get(callingUser);
+ public boolean deleteSettingLocked(int type, int userId, String name) {
+ final int key = makeKey(type, userId);
+
+ SettingsState settingsState = peekSettingsStateLocked(key);
+ final boolean success = settingsState.deleteSettingLocked(name);
+
+ if (success) {
+ notifyForSettingsChange(key, name);
+ }
+ return success;
}
- if (mutationCount != null) {
- mutationCount.incrementAndGet();
+
+ public Setting getSettingLocked(int type, int userId, String name) {
+ final int key = makeKey(type, userId);
+
+ SettingsState settingsState = peekSettingsStateLocked(key);
+ return settingsState.getSettingLocked(name);
}
- DatabaseHelper dbH = getOrEstablishDatabase(desiredUserHandle);
- SQLiteDatabase db = dbH.getWritableDatabase();
- final long rowId = db.insert(args.table, null, initialValues);
- if (mutationCount != null) {
- mutationCount.decrementAndGet();
+
+ public boolean updateSettingLocked(int type, int userId, String name, String value,
+ String packageName) {
+ final int key = makeKey(type, userId);
+
+ SettingsState settingsState = peekSettingsStateLocked(key);
+ final boolean success = settingsState.updateSettingLocked(name, value, packageName);
+
+ if (success) {
+ notifyForSettingsChange(key, name);
+ }
+
+ return success;
}
- if (rowId <= 0) return null;
- SettingsCache.populate(cache, initialValues); // before we notify
+ public void onPackageRemovedLocked(String packageName, int userId) {
+ final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_OWNER);
+ SettingsState globalSettings = mSettingsStates.get(globalKey);
+ globalSettings.onPackageRemovedLocked(packageName);
- if (LOCAL_LOGV) Log.v(TAG, args.table + " <- " + initialValues
- + " for user " + desiredUserHandle);
- // Note that we use the original url here, not the potentially-rewritten table name
- url = getUriFor(url, initialValues, rowId);
- sendNotify(url, desiredUserHandle);
- return url;
- }
+ final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
+ SettingsState secureSettings = mSettingsStates.get(secureKey);
+ secureSettings.onPackageRemovedLocked(packageName);
- @Override
- public int delete(Uri url, String where, String[] whereArgs) {
- int callingUser = UserHandle.getCallingUserId();
- if (LOCAL_LOGV) Slog.v(TAG, "delete() for user " + callingUser);
- SqlArguments args = new SqlArguments(url, where, whereArgs);
- if (TABLE_FAVORITES.equals(args.table)) {
- return 0;
- } else if (TABLE_OLD_FAVORITES.equals(args.table)) {
- args.table = TABLE_FAVORITES;
- } else if (TABLE_GLOBAL.equals(args.table)) {
- callingUser = UserHandle.USER_OWNER;
+ final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
+ SettingsState systemSettings = mSettingsStates.get(systemKey);
+ systemSettings.onPackageRemovedLocked(packageName);
}
- checkWritePermissions(args);
- final AtomicInteger mutationCount;
- synchronized (this) {
- mutationCount = sKnownMutationsInFlight.get(callingUser);
+ private SettingsState peekSettingsStateLocked(int key) {
+ SettingsState settingsState = mSettingsStates.get(key);
+ if (settingsState != null) {
+ return settingsState;
+ }
+
+ ensureSettingsForUserLocked(getUserIdFromKey(key));
+ return mSettingsStates.get(key);
}
- if (mutationCount != null) {
- mutationCount.incrementAndGet();
+
+ private void migrateAllLegacySettingsIfNeeded() {
+ synchronized (mLock) {
+ final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_OWNER);
+ File globalFile = getSettingsFile(key);
+ if (globalFile.exists()) {
+ return;
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ List<UserInfo> users = mUserManager.getUsers(true);
+
+ final int userCount = users.size();
+ for (int i = 0; i < userCount; i++) {
+ final int userId = users.get(i).id;
+
+ DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId);
+ SQLiteDatabase database = dbHelper.getWritableDatabase();
+ migrateLegacySettingsForUserLocked(dbHelper, database, userId);
+
+ // Upgrade to the latest version.
+ UpgradeController upgrader = new UpgradeController(userId);
+ upgrader.upgradeIfNeededLocked();
+
+ // Drop from memory if not a running user.
+ if (!mUserManager.isUserRunning(new UserHandle(userId))) {
+ removeUserStateLocked(userId, false);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
- DatabaseHelper dbH = getOrEstablishDatabase(callingUser);
- SQLiteDatabase db = dbH.getWritableDatabase();
- int count = db.delete(args.table, args.where, args.args);
- if (mutationCount != null) {
- mutationCount.decrementAndGet();
+
+ private void migrateLegacySettingsForUserIfNeededLocked(int userId) {
+ // Every user has secure settings and if no file we need to migrate.
+ final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
+ File secureFile = getSettingsFile(secureKey);
+ if (secureFile.exists()) {
+ return;
+ }
+
+ DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId);
+ SQLiteDatabase database = dbHelper.getWritableDatabase();
+
+ migrateLegacySettingsForUserLocked(dbHelper, database, userId);
}
- if (count > 0) {
- invalidateCache(callingUser, args.table); // before we notify
- sendNotify(url, callingUser);
+
+ private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper,
+ SQLiteDatabase database, int userId) {
+ // Move over the global settings if owner.
+ if (userId == UserHandle.USER_OWNER) {
+ final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, userId);
+ ensureSettingsStateLocked(globalKey);
+ SettingsState globalSettings = mSettingsStates.get(globalKey);
+ migrateLegacySettingsLocked(globalSettings, database, TABLE_GLOBAL);
+ globalSettings.persistSyncLocked();
+ }
+
+ // Move over the secure settings.
+ final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
+ ensureSettingsStateLocked(secureKey);
+ SettingsState secureSettings = mSettingsStates.get(secureKey);
+ migrateLegacySettingsLocked(secureSettings, database, TABLE_SECURE);
+ ensureSecureSettingAndroidIdSetLocked(secureSettings);
+ secureSettings.persistSyncLocked();
+
+ // Move over the system settings.
+ final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
+ ensureSettingsStateLocked(systemKey);
+ SettingsState systemSettings = mSettingsStates.get(systemKey);
+ migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);
+ systemSettings.persistSyncLocked();
+
+ // Drop the database as now all is moved and persisted.
+ if (DROP_DATABASE_ON_MIGRATION) {
+ dbHelper.dropDatabase();
+ } else {
+ dbHelper.backupDatabase();
+ }
}
- startAsyncCachePopulation(callingUser);
- if (LOCAL_LOGV) Log.v(TAG, args.table + ": " + count + " row(s) deleted");
- return count;
- }
- @Override
- public int update(Uri url, ContentValues initialValues, String where, String[] whereArgs) {
- // NOTE: update() is never called by the front-end Settings API, and updates that
- // wind up affecting rows in Secure that are globally shared will not have the
- // intended effect (the update will be invisible to the rest of the system).
- // This should have no practical effect, since writes to the Secure db can only
- // be done by system code, and that code should be using the correct API up front.
- int callingUser = UserHandle.getCallingUserId();
- if (LOCAL_LOGV) Slog.v(TAG, "update() for user " + callingUser);
- SqlArguments args = new SqlArguments(url, where, whereArgs);
- if (TABLE_FAVORITES.equals(args.table)) {
- return 0;
- } else if (TABLE_GLOBAL.equals(args.table)) {
- callingUser = UserHandle.USER_OWNER;
+ private void migrateLegacySettingsLocked(SettingsState settingsState,
+ SQLiteDatabase database, String table) {
+ SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
+ queryBuilder.setTables(table);
+
+ Cursor cursor = queryBuilder.query(database, ALL_COLUMNS,
+ null, null, null, null, null);
+
+ if (cursor == null) {
+ return;
+ }
+
+ try {
+ if (!cursor.moveToFirst()) {
+ return;
+ }
+
+ final int nameColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.NAME);
+ final int valueColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.VALUE);
+
+ settingsState.setVersionLocked(database.getVersion());
+
+ while (!cursor.isAfterLast()) {
+ String name = cursor.getString(nameColumnIdx);
+ String value = cursor.getString(valueColumnIdx);
+ settingsState.insertSettingLocked(name, value,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ cursor.moveToNext();
+ }
+ } finally {
+ cursor.close();
+ }
}
- checkWritePermissions(args);
- checkUserRestrictions(initialValues.getAsString(Settings.Secure.NAME), callingUser);
- final AtomicInteger mutationCount;
- synchronized (this) {
- mutationCount = sKnownMutationsInFlight.get(callingUser);
+ private void ensureSecureSettingAndroidIdSetLocked(SettingsState secureSettings) {
+ Setting value = secureSettings.getSettingLocked(Settings.Secure.ANDROID_ID);
+
+ if (value != null) {
+ return;
+ }
+
+ final int userId = getUserIdFromKey(secureSettings.mKey);
+
+ final UserInfo user;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ user = mUserManager.getUserInfo(userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ if (user == null) {
+ // Can happen due to races when deleting users - treat as benign.
+ return;
+ }
+
+ String androidId = Long.toHexString(new SecureRandom().nextLong());
+ secureSettings.insertSettingLocked(Settings.Secure.ANDROID_ID, androidId,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+
+ Slog.d(LOG_TAG, "Generated and saved new ANDROID_ID [" + androidId
+ + "] for user " + userId);
+
+ // Write a drop box entry if it's a restricted profile
+ if (user.isRestricted()) {
+ DropBoxManager dbm = (DropBoxManager) getContext().getSystemService(
+ Context.DROPBOX_SERVICE);
+ if (dbm != null && dbm.isTagEnabled(DROPBOX_TAG_USERLOG)) {
+ dbm.addText(DROPBOX_TAG_USERLOG, System.currentTimeMillis()
+ + "," + DROPBOX_TAG_USERLOG + "," + androidId + "\n");
+ }
+ }
}
- if (mutationCount != null) {
- mutationCount.incrementAndGet();
+
+ private void notifyForSettingsChange(int key, String name) {
+ // Update the system property *first*, so if someone is listening for
+ // a notification and then using the contract class to get their data,
+ // the system property will be updated and they'll get the new data.
+
+ boolean backedUpDataChanged = false;
+ String property = null;
+ if (isGlobalSettingsKey(key)) {
+ property = Settings.Global.SYS_PROP_SETTING_VERSION;
+ backedUpDataChanged = true;
+ } else if (isSecureSettingsKey(key)) {
+ property = Settings.Secure.SYS_PROP_SETTING_VERSION;
+ backedUpDataChanged = true;
+ } else if (isSystemSettingsKey(key)) {
+ property = Settings.System.SYS_PROP_SETTING_VERSION;
+ backedUpDataChanged = true;
+ }
+
+ if (property != null) {
+ final long version = SystemProperties.getLong(property, 0) + 1;
+ SystemProperties.set(property, Long.toString(version));
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "System property " + property + "=" + version);
+ }
+ }
+
+ // Inform the backup manager about a data change
+ if (backedUpDataChanged) {
+ mBackupManager.dataChanged();
+ }
+
+ // Now send the notification through the content framework.
+
+ final int userId = getUserIdFromKey(key);
+ Uri uri = getNotificationUriFor(key, name);
+
+ sendNotify(uri, userId);
}
- DatabaseHelper dbH = getOrEstablishDatabase(callingUser);
- SQLiteDatabase db = dbH.getWritableDatabase();
- int count = db.update(args.table, initialValues, args.where, args.args);
- if (mutationCount != null) {
- mutationCount.decrementAndGet();
+
+ private int makeKey(int type, int userId) {
+ return (type << SETTINGS_TYPE_SHIFT) | userId;
}
- if (count > 0) {
- invalidateCache(callingUser, args.table); // before we notify
- sendNotify(url, callingUser);
+
+ private int getTypeFromKey(int key) {
+ return key >> SETTINGS_TYPE_SHIFT;
}
- startAsyncCachePopulation(callingUser);
- if (LOCAL_LOGV) Log.v(TAG, args.table + ": " + count + " row(s) <- " + initialValues);
- return count;
- }
- @Override
- public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- throw new FileNotFoundException("Direct file access no longer supported; "
- + "ringtone playback is available through android.media.Ringtone");
- }
+ private int getUserIdFromKey(int key) {
+ return key & ~SETTINGS_TYPE_MASK;
+ }
- /**
- * In-memory LRU Cache of system and secure settings, along with
- * associated helper functions to keep cache coherent with the
- * database.
- */
- private static final class SettingsCache extends LruCache<String, Bundle> {
+ private boolean isGlobalSettingsKey(int key) {
+ return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
+ }
- private final String mCacheName;
- private boolean mCacheFullyMatchesDisk = false; // has the whole database slurped.
+ private boolean isSystemSettingsKey(int key) {
+ return getTypeFromKey(key) == SETTINGS_TYPE_SYSTEM;
+ }
- public SettingsCache(String name) {
- super(MAX_CACHE_ENTRIES);
- mCacheName = name;
+ private boolean isSecureSettingsKey(int key) {
+ return getTypeFromKey(key) == SETTINGS_TYPE_SECURE;
}
- /**
- * Is the whole database table slurped into this cache?
- */
- public boolean fullyMatchesDisk() {
- synchronized (this) {
- return mCacheFullyMatchesDisk;
+ private File getSettingsFile(int key) {
+ if (isGlobalSettingsKey(key)) {
+ final int userId = getUserIdFromKey(key);
+ return new File(Environment.getUserSystemDirectory(userId),
+ SETTINGS_FILE_GLOBAL);
+ } else if (isSystemSettingsKey(key)) {
+ final int userId = getUserIdFromKey(key);
+ return new File(Environment.getUserSystemDirectory(userId),
+ SETTINGS_FILE_SYSTEM);
+ } else if (isSecureSettingsKey(key)) {
+ final int userId = getUserIdFromKey(key);
+ return new File(Environment.getUserSystemDirectory(userId),
+ SETTINGS_FILE_SECURE);
+ } else {
+ throw new IllegalArgumentException("Invalid settings key:" + key);
}
}
- public void setFullyMatchesDisk(boolean value) {
- synchronized (this) {
- mCacheFullyMatchesDisk = value;
+ private Uri getNotificationUriFor(int key, String name) {
+ if (isGlobalSettingsKey(key)) {
+ return (name != null) ? Uri.withAppendedPath(Settings.Global.CONTENT_URI, name)
+ : Settings.Global.CONTENT_URI;
+ } else if (isSecureSettingsKey(key)) {
+ return (name != null) ? Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name)
+ : Settings.Secure.CONTENT_URI;
+ } else if (isSystemSettingsKey(key)) {
+ return (name != null) ? Uri.withAppendedPath(Settings.System.CONTENT_URI, name)
+ : Settings.System.CONTENT_URI;
+ } else {
+ throw new IllegalArgumentException("Invalid settings key:" + key);
}
}
- @Override
- protected void entryRemoved(boolean evicted, String key, Bundle oldValue, Bundle newValue) {
- if (evicted) {
- mCacheFullyMatchesDisk = false;
+ private int getMaxBytesPerPackageForType(int type) {
+ switch (type) {
+ case SETTINGS_TYPE_GLOBAL:
+ case SETTINGS_TYPE_SECURE: {
+ return SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED;
+ }
+
+ default: {
+ return SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED;
+ }
}
}
- /**
- * Atomic cache population, conditional on size of value and if
- * we lost a race.
- *
- * @returns a Bundle to send back to the client from call(), even
- * if we lost the race.
- */
- public Bundle putIfAbsent(String key, String value) {
- Bundle bundle = (value == null) ? NULL_SETTING : Bundle.forPair("value", value);
- if (value == null || value.length() <= MAX_CACHE_ENTRY_SIZE) {
- synchronized (this) {
- if (get(key) == null) {
- put(key, bundle);
- }
+ private final class UpgradeController {
+ private static final int SETTINGS_VERSION = 118;
+
+ private final int mUserId;
+
+ public UpgradeController(int userId) {
+ mUserId = userId;
+ }
+
+ public void upgradeIfNeededLocked() {
+ // The version of all settings for a user is the same (all users have secure).
+ SettingsState secureSettings = getSettingsLocked(
+ SettingsRegistry.SETTINGS_TYPE_SECURE, mUserId);
+
+ // Try an update from the current state.
+ final int oldVersion = secureSettings.getVersionLocked();
+ final int newVersion = SETTINGS_VERSION;
+
+ // If up do data - done.
+ if (oldVersion == newVersion) {
+ return;
}
+
+ // Try to upgrade.
+ final int curVersion = onUpgradeLocked(mUserId, oldVersion, newVersion);
+
+ // If upgrade failed start from scratch and upgrade.
+ if (curVersion != newVersion) {
+ // Drop state we have for this user.
+ removeUserStateLocked(mUserId, true);
+
+ // Recreate the database.
+ DatabaseHelper dbHelper = new DatabaseHelper(getContext(), mUserId);
+ SQLiteDatabase database = dbHelper.getWritableDatabase();
+ dbHelper.recreateDatabase(database, newVersion, curVersion, oldVersion);
+
+ // Migrate the settings for this user.
+ migrateLegacySettingsForUserLocked(dbHelper, database, mUserId);
+
+ // Now upgrade should work fine.
+ onUpgradeLocked(mUserId, oldVersion, newVersion);
+ }
+
+ // Set the global settings version if owner.
+ if (mUserId == UserHandle.USER_OWNER) {
+ SettingsState globalSettings = getSettingsLocked(
+ SettingsRegistry.SETTINGS_TYPE_GLOBAL, mUserId);
+ globalSettings.setVersionLocked(newVersion);
+ }
+
+ // Set the secure settings version.
+ secureSettings.setVersionLocked(newVersion);
+
+ // Set the system settings version.
+ SettingsState systemSettings = getSettingsLocked(
+ SettingsRegistry.SETTINGS_TYPE_SYSTEM, mUserId);
+ systemSettings.setVersionLocked(newVersion);
}
- return bundle;
- }
- /**
- * Populates a key in a given (possibly-null) cache.
- */
- public static void populate(SettingsCache cache, ContentValues contentValues) {
- if (cache == null) {
- return;
+ private SettingsState getGlobalSettingsLocked() {
+ return getSettingsLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_OWNER);
}
- String name = contentValues.getAsString(Settings.NameValueTable.NAME);
- if (name == null) {
- Log.w(TAG, "null name populating settings cache.");
- return;
+
+ private SettingsState getSecureSettingsLocked(int userId) {
+ return getSettingsLocked(SETTINGS_TYPE_SECURE, userId);
}
- String value = contentValues.getAsString(Settings.NameValueTable.VALUE);
- cache.populate(name, value);
- }
- public void populate(String name, String value) {
- synchronized (this) {
- if (value == null || value.length() <= MAX_CACHE_ENTRY_SIZE) {
- put(name, Bundle.forPair(Settings.NameValueTable.VALUE, value));
- } else {
- put(name, TOO_LARGE_TO_CACHE_MARKER);
- }
+ private SettingsState getSystemSettingsLocked(int userId) {
+ return getSettingsLocked(SETTINGS_TYPE_SYSTEM, userId);
}
- }
- /**
- * For suppressing duplicate/redundant settings inserts early,
- * checking our cache first (but without faulting it in),
- * before going to sqlite with the mutation.
- */
- public static boolean isRedundantSetValue(SettingsCache cache, String name, String value) {
- if (cache == null) return false;
- synchronized (cache) {
- Bundle bundle = cache.get(name);
- if (bundle == null) return false;
- String oldValue = bundle.getPairValue();
- if (oldValue == null && value == null) return true;
- if ((oldValue == null) != (value == null)) return false;
- return oldValue.equals(value);
+ private int onUpgradeLocked(int userId, int oldVersion, int newVersion) {
+ if (DEBUG) {
+ Slog.w(LOG_TAG, "Upgrading settings for user: " + userId + " from version: "
+ + oldVersion + " to version: " + newVersion);
+ }
+
+ // You must perform all necessary mutations to bring the settings
+ // for this user from the old to the new version. When you add a new
+ // upgrade step you *must* update SETTINGS_VERSION.
+
+ /**
+ * This is an example of moving a setting from secure to global.
+ *
+ * int currentVersion = oldVersion;
+ * if (currentVersion == 118) {
+ * // Remove from the secure settings.
+ * SettingsState secureSettings = getSecureSettingsLocked(userId);
+ * String name = "example_setting_to_move";
+ * String value = secureSettings.getSetting(name);
+ * secureSettings.deleteSetting(name);
+ *
+ * // Add to the global settings.
+ * SettingsState globalSettings = getGlobalSettingsLocked();
+ * globalSettings.insertSetting(name, value, SettingsState.SYSTEM_PACKAGE_NAME);
+ *
+ * // Update the current version.
+ * currentVersion = 119;
+ * }
+ *
+ * // Return the current version.
+ * return currentVersion;
+ */
+
+ return SettingsState.VERSION_UNDEFINED;
}
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
new file mode 100644
index 0000000..e63d220
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright (C) 2015 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.providers.settings;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import libcore.io.IoUtils;
+import libcore.util.Objects;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class contains the state for one type of settings. It is responsible
+ * for saving the state asynchronously to an XML file after a mutation and
+ * loading the from an XML file on construction.
+ * <p>
+ * This class uses the same lock as the settings provider to ensure that
+ * multiple changes made by the settings provider, e,g, upgrade, bulk insert,
+ * etc, are atomically persisted since the asynchronous persistence is using
+ * the same lock to grab the current state to write to disk.
+ * </p>
+ */
+final class SettingsState {
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_PERSISTENCE = false;
+
+ private static final String LOG_TAG = "SettingsState";
+
+ private static final long WRITE_SETTINGS_DELAY_MILLIS = 200;
+ private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000;
+
+ public static final int MAX_BYTES_PER_APP_PACKAGE_UNLIMITED = -1;
+ public static final int MAX_BYTES_PER_APP_PACKAGE_LIMITED = 20000;
+
+ public static final String SYSTEM_PACKAGE_NAME = "android";
+
+ public static final int VERSION_UNDEFINED = -1;
+
+ private static final String TAG_SETTINGS = "settings";
+ private static final String TAG_SETTING = "setting";
+ private static final String ATTR_PACKAGE = "package";
+
+ private static final String ATTR_VERSION = "version";
+ private static final String ATTR_ID = "id";
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_VALUE = "value";
+
+ private static final String NULL_VALUE = "null";
+
+ private final Object mLock;
+
+ private final Handler mHandler = new MyHandler();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<String, Setting> mSettings = new ArrayMap<>();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<String, Integer> mPackageToMemoryUsage;
+
+ @GuardedBy("mLock")
+ private final int mMaxBytesPerAppPackage;
+
+ @GuardedBy("mLock")
+ private final File mStatePersistFile;
+
+ public final int mKey;
+
+ @GuardedBy("mLock")
+ private int mVersion = VERSION_UNDEFINED;
+
+ @GuardedBy("mLock")
+ private long mLastNotWrittenMutationTimeMillis;
+
+ @GuardedBy("mLock")
+ private boolean mDirty;
+
+ @GuardedBy("mLock")
+ private boolean mWriteScheduled;
+
+ public SettingsState(Object lock, File file, int key, int maxBytesPerAppPackage) {
+ // It is important that we use the same lock as the settings provider
+ // to ensure multiple mutations on this state are atomicaly persisted
+ // as the async persistence should be blocked while we make changes.
+ mLock = lock;
+ mStatePersistFile = file;
+ mKey = key;
+ if (maxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_LIMITED) {
+ mMaxBytesPerAppPackage = maxBytesPerAppPackage;
+ mPackageToMemoryUsage = new ArrayMap<>();
+ } else {
+ mMaxBytesPerAppPackage = maxBytesPerAppPackage;
+ mPackageToMemoryUsage = null;
+ }
+ synchronized (mLock) {
+ readStateSyncLocked();
+ }
+ }
+
+ // The settings provider must hold its lock when calling here.
+ public int getVersionLocked() {
+ return mVersion;
+ }
+
+ // The settings provider must hold its lock when calling here.
+ public void setVersionLocked(int version) {
+ if (version == mVersion) {
+ return;
+ }
+ mVersion = version;
+
+ scheduleWriteIfNeededLocked();
+ }
+
+ // The settings provider must hold its lock when calling here.
+ public void onPackageRemovedLocked(String packageName) {
+ boolean removedSomething = false;
+
+ final int settingCount = mSettings.size();
+ for (int i = settingCount - 1; i >= 0; i--) {
+ String name = mSettings.keyAt(i);
+ // Settings defined by use are never dropped.
+ if (Settings.System.PUBLIC_SETTINGS.contains(name)
+ || Settings.System.PRIVATE_SETTINGS.contains(name)) {
+ continue;
+ }
+ Setting setting = mSettings.valueAt(i);
+ if (packageName.equals(setting.packageName)) {
+ mSettings.removeAt(i);
+ removedSomething = true;
+ }
+ }
+
+ if (removedSomething) {
+ scheduleWriteIfNeededLocked();
+ }
+ }
+
+ // The settings provider must hold its lock when calling here.
+ public List<String> getSettingNamesLocked() {
+ ArrayList<String> names = new ArrayList<>();
+ final int settingsCount = mSettings.size();
+ for (int i = 0; i < settingsCount; i++) {
+ String name = mSettings.keyAt(i);
+ names.add(name);
+ }
+ return names;
+ }
+
+ // The settings provider must hold its lock when calling here.
+ public Setting getSettingLocked(String name) {
+ if (TextUtils.isEmpty(name)) {
+ return null;
+ }
+ return mSettings.get(name);
+ }
+
+ // The settings provider must hold its lock when calling here.
+ public boolean updateSettingLocked(String name, String value, String packageName) {
+ if (!hasSettingLocked(name)) {
+ return false;
+ }
+
+ return insertSettingLocked(name, value, packageName);
+ }
+
+ // The settings provider must hold its lock when calling here.
+ public boolean insertSettingLocked(String name, String value, String packageName) {
+ if (TextUtils.isEmpty(name)) {
+ return false;
+ }
+
+ Setting oldState = mSettings.get(name);
+ String oldValue = (oldState != null) ? oldState.value : null;
+
+ if (oldState != null) {
+ if (!oldState.update(value, packageName)) {
+ return false;
+ }
+ } else {
+ Setting state = new Setting(name, value, packageName);
+ mSettings.put(name, state);
+ }
+
+ updateMemoryUsagePerPackageLocked(packageName, oldValue, value);
+
+ scheduleWriteIfNeededLocked();
+
+ return true;
+ }
+
+ // The settings provider must hold its lock when calling here.
+ public void persistSyncLocked() {
+ mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
+ doWriteState();
+ }
+
+ // The settings provider must hold its lock when calling here.
+ public boolean deleteSettingLocked(String name) {
+ if (TextUtils.isEmpty(name) || !hasSettingLocked(name)) {
+ return false;
+ }
+
+ Setting oldState = mSettings.remove(name);
+
+ updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, null);
+
+ scheduleWriteIfNeededLocked();
+
+ return true;
+ }
+
+ // The settings provider must hold its lock when calling here.
+ public void destroyLocked(Runnable callback) {
+ mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
+ if (callback != null) {
+ if (mDirty) {
+ // Do it without a delay.
+ mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS,
+ callback).sendToTarget();
+ return;
+ }
+ callback.run();
+ }
+ }
+
+ private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue,
+ String newValue) {
+ if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) {
+ return;
+ }
+
+ if (SYSTEM_PACKAGE_NAME.equals(packageName)) {
+ return;
+ }
+
+ final int oldValueSize = (oldValue != null) ? oldValue.length() : 0;
+ final int newValueSize = (newValue != null) ? newValue.length() : 0;
+ final int deltaSize = newValueSize - oldValueSize;
+
+ Integer currentSize = mPackageToMemoryUsage.get(packageName);
+ final int newSize = Math.max((currentSize != null)
+ ? currentSize + deltaSize : deltaSize, 0);
+
+ if (newSize > mMaxBytesPerAppPackage) {
+ throw new IllegalStateException("You are adding too many system settings. "
+ + "You should stop using system settings for app specific data.");
+ }
+
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Settings for package: " + packageName
+ + " size: " + newSize + " bytes.");
+ }
+
+ mPackageToMemoryUsage.put(packageName, newSize);
+ }
+
+ private boolean hasSettingLocked(String name) {
+ return mSettings.indexOfKey(name) >= 0;
+ }
+
+ private void scheduleWriteIfNeededLocked() {
+ // If dirty then we have a write already scheduled.
+ if (!mDirty) {
+ mDirty = true;
+ writeStateAsyncLocked();
+ }
+ }
+
+ private void writeStateAsyncLocked() {
+ final long currentTimeMillis = SystemClock.uptimeMillis();
+
+ if (mWriteScheduled) {
+ mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
+
+ // If enough time passed, write without holding off anymore.
+ final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis
+ - mLastNotWrittenMutationTimeMillis;
+ if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_SETTINGS_DELAY_MILLIS) {
+ mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS).sendToTarget();
+ return;
+ }
+
+ // Hold off a bit more as settings are frequently changing.
+ final long maxDelayMillis = Math.max(mLastNotWrittenMutationTimeMillis
+ + MAX_WRITE_SETTINGS_DELAY_MILLIS - currentTimeMillis, 0);
+ final long writeDelayMillis = Math.min(WRITE_SETTINGS_DELAY_MILLIS, maxDelayMillis);
+
+ Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS);
+ mHandler.sendMessageDelayed(message, writeDelayMillis);
+ } else {
+ mLastNotWrittenMutationTimeMillis = currentTimeMillis;
+ Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS);
+ mHandler.sendMessageDelayed(message, WRITE_SETTINGS_DELAY_MILLIS);
+ mWriteScheduled = true;
+ }
+ }
+
+ private void doWriteState() {
+ if (DEBUG_PERSISTENCE) {
+ Slog.i(LOG_TAG, "[PERSIST START]");
+ }
+
+ AtomicFile destination = new AtomicFile(mStatePersistFile);
+
+ final int version;
+ final ArrayMap<String, Setting> settings;
+
+ synchronized (mLock) {
+ version = mVersion;
+ settings = new ArrayMap<>(mSettings);
+ mDirty = false;
+ mWriteScheduled = false;
+ }
+
+ FileOutputStream out = null;
+ try {
+ out = destination.startWrite();
+
+ XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(out, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_SETTINGS);
+ serializer.attribute(null, ATTR_VERSION, String.valueOf(version));
+
+ final int settingCount = settings.size();
+ for (int i = 0; i < settingCount; i++) {
+ Setting setting = settings.valueAt(i);
+
+ serializer.startTag(null, TAG_SETTING);
+ serializer.attribute(null, ATTR_ID, setting.getId());
+ serializer.attribute(null, ATTR_NAME, setting.getName());
+ serializer.attribute(null, ATTR_VALUE, packValue(setting.getValue()));
+ serializer.attribute(null, ATTR_PACKAGE, packValue(setting.getPackageName()));
+ serializer.endTag(null, TAG_SETTING);
+
+ if (DEBUG_PERSISTENCE) {
+ Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" + setting.getValue());
+ }
+ }
+
+ serializer.endTag(null, TAG_SETTINGS);
+ serializer.endDocument();
+ destination.finishWrite(out);
+
+ if (DEBUG_PERSISTENCE) {
+ Slog.i(LOG_TAG, "[PERSIST END]");
+ }
+
+ } catch (IOException e) {
+ Slog.w(LOG_TAG, "Failed to write settings, restoring backup", e);
+ destination.failWrite(out);
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private void readStateSyncLocked() {
+ FileInputStream in;
+ if (!mStatePersistFile.exists()) {
+ return;
+ }
+ try {
+ in = new FileInputStream(mStatePersistFile);
+ } catch (FileNotFoundException fnfe) {
+ Slog.i(LOG_TAG, "No settings state");
+ return;
+ }
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parseStateLocked(parser);
+ } catch (XmlPullParserException | IOException ise) {
+ throw new IllegalStateException("Failed parsing settings file: "
+ + mStatePersistFile , ise);
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ private void parseStateLocked(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_SETTINGS);
+
+ mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
+
+ parser.next();
+
+ while (parseSettingLocked(parser)) {
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_SETTINGS);
+ }
+
+ private boolean parseSettingLocked(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ skipEmptyTextTags(parser);
+ if (!accept(parser, XmlPullParser.START_TAG, TAG_SETTING)) {
+ return false;
+ }
+
+ String id = parser.getAttributeValue(null, ATTR_ID);
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+ String value = parser.getAttributeValue(null, ATTR_VALUE);
+ String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
+ mSettings.put(name, new Setting(name, unpackValue(value),
+ unpackValue(packageName), id));
+
+ if (DEBUG_PERSISTENCE) {
+ Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value);
+ }
+
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_SETTING);
+
+ return true;
+ }
+
+ private void expect(XmlPullParser parser, int type, String tag)
+ throws IOException, XmlPullParserException {
+ if (!accept(parser, type, tag)) {
+ throw new XmlPullParserException("Expected event: " + type
+ + " and tag: " + tag + " but got event: " + parser.getEventType()
+ + " and tag:" + parser.getName());
+ }
+ }
+
+ private void skipEmptyTextTags(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ while (accept(parser, XmlPullParser.TEXT, null)
+ && "\n".equals(parser.getText())) {
+ parser.next();
+ }
+ }
+
+ private boolean accept(XmlPullParser parser, int type, String tag)
+ throws IOException, XmlPullParserException {
+ if (parser.getEventType() != type) {
+ return false;
+ }
+ if (tag != null) {
+ if (!tag.equals(parser.getName())) {
+ return false;
+ }
+ } else if (parser.getName() != null) {
+ return false;
+ }
+ return true;
+ }
+
+ private final class MyHandler extends Handler {
+ public static final int MSG_PERSIST_SETTINGS = 1;
+
+ public MyHandler() {
+ super(BackgroundThread.getHandler().getLooper());
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_PERSIST_SETTINGS: {
+ Runnable callback = (Runnable) message.obj;
+ doWriteState();
+ if (callback != null) {
+ callback.run();
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ private static String packValue(String value) {
+ if (value == null) {
+ return NULL_VALUE;
+ }
+ return value;
+ }
+
+ private static String unpackValue(String value) {
+ if (NULL_VALUE.equals(value)) {
+ return null;
+ }
+ return value;
+ }
+
+ public static final class Setting {
+ private static long sNextId;
+
+ private String name;
+ private String value;
+ private String packageName;
+ private String id;
+
+ public Setting(String name, String value, String packageName) {
+ init(name, value, packageName, String.valueOf(sNextId++));
+ }
+
+ public Setting(String name, String value, String packageName, String id) {
+ sNextId = Math.max(sNextId, Long.valueOf(id));
+ init(name, value, packageName, String.valueOf(sNextId));
+ }
+
+ private void init(String name, String value, String packageName, String id) {
+ this.name = name;
+ this.value = value;
+ this.packageName = packageName;
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public boolean update(String value, String packageName) {
+ if (Objects.equal(value, this.value)) {
+ return false;
+ }
+ this.value = value;
+ this.packageName = packageName;
+ this.id = String.valueOf(sNextId++);
+ return true;
+ }
+ }
+}
diff --git a/packages/SettingsProvider/test/Android.mk b/packages/SettingsProvider/test/Android.mk
new file mode 100644
index 0000000..01c6ccf
--- /dev/null
+++ b/packages/SettingsProvider/test/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := SettingsProviderTest
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE) \ No newline at end of file
diff --git a/packages/SettingsProvider/test/AndroidManifest.xml b/packages/SettingsProvider/test/AndroidManifest.xml
new file mode 100644
index 0000000..7a86b5f
--- /dev/null
+++ b/packages/SettingsProvider/test/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.providers.setting.test">
+
+ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" />
+
+ <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+ <uses-permission android:name="android.permission.MANAGE_USERS"/>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.providers.setting.test"
+ android:label="Settings Provider Tests" />
+</manifest>
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
new file mode 100644
index 0000000..f713c33
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2015 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.providers.settings;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+
+import java.util.List;
+
+/**
+ * Base class for the SettingContentProvider tests.
+ */
+abstract class BaseSettingsProviderTest extends AndroidTestCase {
+ protected static final int SETTING_TYPE_GLOBAL = 1;
+ protected static final int SETTING_TYPE_SECURE = 2;
+ protected static final int SETTING_TYPE_SYSTEM = 3;
+
+ protected static final String FAKE_SETTING_NAME = "fake_setting_name";
+ protected static final String FAKE_SETTING_NAME_1 = "fake_setting_name1";
+ protected static final String FAKE_SETTING_VALUE = "fake_setting_value";
+ protected static final String FAKE_SETTING_VALUE_1 = "fake_setting_value_1";
+
+ private static final String[] NAME_VALUE_COLUMNS = new String[] {
+ Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE
+ };
+
+ protected int mSecondaryUserId = UserHandle.USER_OWNER;
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+
+ UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ List<UserInfo> users = userManager.getUsers();
+ final int userCount = users.size();
+ for (int i = 0; i < userCount; i++) {
+ UserInfo user = users.get(i);
+ if (!user.isPrimary() && !user.isManagedProfile()) {
+ mSecondaryUserId = user.id;
+ break;
+ }
+ }
+ }
+
+ protected void setStringViaFrontEndApiSetting(int type, String name, String value, int userId) {
+ ContentResolver contentResolver = getContext().getContentResolver();
+
+ switch (type) {
+ case SETTING_TYPE_GLOBAL: {
+ Settings.Global.putStringForUser(contentResolver, name, value, userId);
+ } break;
+
+ case SETTING_TYPE_SECURE: {
+ Settings.Secure.putStringForUser(contentResolver, name, value, userId);
+ } break;
+
+ case SETTING_TYPE_SYSTEM: {
+ Settings.System.putStringForUser(contentResolver, name, value, userId);
+ } break;
+
+ default: {
+ throw new IllegalArgumentException("Invalid type: " + type);
+ }
+ }
+ }
+
+ protected String getStringViaFrontEndApiSetting(int type, String name, int userId) {
+ ContentResolver contentResolver = getContext().getContentResolver();
+
+ switch (type) {
+ case SETTING_TYPE_GLOBAL: {
+ return Settings.Global.getStringForUser(contentResolver, name, userId);
+ }
+
+ case SETTING_TYPE_SECURE: {
+ return Settings.Secure.getStringForUser(contentResolver, name, userId);
+ }
+
+ case SETTING_TYPE_SYSTEM: {
+ return Settings.System.getStringForUser(contentResolver, name, userId);
+ }
+
+ default: {
+ throw new IllegalArgumentException("Invalid type: " + type);
+ }
+ }
+ }
+
+ protected Uri insertStringViaProviderApi(int type, String name, String value,
+ boolean withTableRowUri) {
+ Uri uri = getBaseUriForType(type);
+ if (withTableRowUri) {
+ uri = Uri.withAppendedPath(uri, name);
+ }
+ ContentValues values = new ContentValues();
+ values.put(Settings.NameValueTable.NAME, name);
+ values.put(Settings.NameValueTable.VALUE, value);
+
+ return getContext().getContentResolver().insert(uri, values);
+ }
+
+ protected int deleteStringViaProviderApi(int type, String name) {
+ Uri uri = getBaseUriForType(type);
+ return getContext().getContentResolver().delete(uri, "name=?", new String[]{name});
+ }
+
+ protected int updateStringViaProviderApiSetting(int type, String name, String value) {
+ Uri uri = getBaseUriForType(type);
+ ContentValues values = new ContentValues();
+ values.put(Settings.NameValueTable.NAME, name);
+ values.put(Settings.NameValueTable.VALUE, value);
+ return getContext().getContentResolver().update(uri, values, "name=?",
+ new String[]{name});
+ }
+
+ protected String queryStringViaProviderApi(int type, String name) {
+ return queryStringViaProviderApi(type, name, false);
+ }
+
+ protected String queryStringViaProviderApi(int type, String name, boolean queryStringInQuotes) {
+ Uri uri = getBaseUriForType(type);
+
+ String queryString = queryStringInQuotes ? "(name=?)" : "name=?";
+
+ Cursor cursor = getContext().getContentResolver().query(uri, NAME_VALUE_COLUMNS,
+ queryString, new String[]{name}, null);
+
+ if (cursor == null) {
+ return null;
+ }
+
+ try {
+ if (cursor.moveToFirst()) {
+ final int valueColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.VALUE);
+ return cursor.getString(valueColumnIdx);
+ }
+ } finally {
+ cursor.close();
+ }
+
+ return null;
+ }
+
+ protected static Uri getBaseUriForType(int type) {
+ switch (type) {
+ case SETTING_TYPE_GLOBAL: {
+ return Settings.Global.CONTENT_URI;
+ }
+
+ case SETTING_TYPE_SECURE: {
+ return Settings.Secure.CONTENT_URI;
+ }
+
+ case SETTING_TYPE_SYSTEM: {
+ return Settings.System.CONTENT_URI;
+ }
+
+ default: {
+ throw new IllegalArgumentException("Invalid type: " + type);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderPerformanceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderPerformanceTest.java
new file mode 100644
index 0000000..d581f3b
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderPerformanceTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 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.providers.settings;
+
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+
+/**
+* Performance tests for the SettingContentProvider.
+*/
+public class SettingsProviderPerformanceTest extends BaseSettingsProviderTest {
+ private static final String LOG_TAG = "SettingsProviderPerformanceTest";
+
+ private static final int ITERATION_COUNT = 100;
+
+ private static final int MICRO_SECONDS_IN_MILLISECOND = 1000;
+
+ private static final long MAX_AVERAGE_SET_AND_GET_SETTING_DURATION_MILLIS = 20;
+
+ public void testSetAndGetPerformanceForGlobalViaFrontEndApi() throws Exception {
+ // Start with a clean slate.
+ insertStringViaProviderApi(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME, FAKE_SETTING_VALUE, false);
+
+ final long startTimeMicro = SystemClock.currentTimeMicro();
+
+ try {
+ for (int i = 0; i < ITERATION_COUNT; i++) {
+ // Set the setting to its first value.
+ updateStringViaProviderApiSetting(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE);
+
+ // Make sure the setting changed.
+ String firstValue = getStringViaFrontEndApiSetting(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME, UserHandle.USER_OWNER);
+ assertEquals("Setting value didn't change", FAKE_SETTING_VALUE, firstValue);
+
+ // Set the setting to its second value.
+ updateStringViaProviderApiSetting(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE_1);
+
+ // Make sure the setting changed.
+ String secondValue = getStringViaFrontEndApiSetting(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME, UserHandle.USER_OWNER);
+ assertEquals("Setting value didn't change", FAKE_SETTING_VALUE_1, secondValue);
+ }
+ } finally {
+ // Clean up.
+ deleteStringViaProviderApi(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME);
+ }
+
+ final long elapsedTimeMicro = SystemClock.currentTimeMicro() - startTimeMicro;
+
+ final long averageTimePerIterationMillis = (long) ((((float) elapsedTimeMicro)
+ / ITERATION_COUNT) / MICRO_SECONDS_IN_MILLISECOND);
+
+ Log.i(LOG_TAG, "Average time to set and get setting via provider APIs: "
+ + averageTimePerIterationMillis + " ms");
+
+ assertTrue("Setting and getting a settings takes too long.", averageTimePerIterationMillis
+ < MAX_AVERAGE_SET_AND_GET_SETTING_DURATION_MILLIS);
+ }
+
+ public void testSetAndGetPerformanceForGlobalViaProviderApi() throws Exception {
+ // Start with a clean slate.
+ deleteStringViaProviderApi(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME);
+
+ final long startTimeMicro = SystemClock.currentTimeMicro();
+
+ try {
+ for (int i = 0; i < ITERATION_COUNT; i++) {
+ // Set the setting to its first value.
+ setStringViaFrontEndApiSetting(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE, UserHandle.USER_OWNER);
+
+ // Make sure the setting changed.
+ String firstValue = getStringViaFrontEndApiSetting(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME, UserHandle.USER_OWNER);
+ assertEquals("Setting value didn't change", FAKE_SETTING_VALUE, firstValue);
+
+ // Set the setting to its second value.
+ setStringViaFrontEndApiSetting(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE_1, UserHandle.USER_OWNER);
+
+ // Make sure the setting changed.
+ String secondValue = getStringViaFrontEndApiSetting(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME, UserHandle.USER_OWNER);
+ assertEquals("Setting value didn't change", FAKE_SETTING_VALUE_1, secondValue);
+ }
+ } finally {
+ // Clean up.
+ deleteStringViaProviderApi(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME);
+ }
+
+ final long elapsedTimeMicro = SystemClock.currentTimeMicro() - startTimeMicro;
+
+ final long averageTimePerIterationMillis = (long) ((((float) elapsedTimeMicro)
+ / ITERATION_COUNT) / MICRO_SECONDS_IN_MILLISECOND);
+
+ Log.i(LOG_TAG, "Average time to set and get setting via front-eng APIs: "
+ + averageTimePerIterationMillis + " ms");
+
+ assertTrue("Setting and getting a settings takes too long.", averageTimePerIterationMillis
+ < MAX_AVERAGE_SET_AND_GET_SETTING_DURATION_MILLIS);
+ }
+}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
new file mode 100644
index 0000000..cbfcbf5
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2015 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.providers.settings;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Tests for the SettingContentProvider.
+ *
+ * Before you run this test you must add a secondary user.
+ */
+public class SettingsProviderTest extends BaseSettingsProviderTest {
+ private static final String LOG_TAG = "SettingsProviderTest";
+
+ private static final long WAIT_FOR_SETTING_URI_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec
+
+ private static final String[] NAME_VALUE_COLUMNS = new String[]{
+ Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE
+ };
+
+ private final Object mLock = new Object();
+
+ public void testSetAndGetGlobalViaFrontEndApiForOwnerUser() throws Exception {
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_GLOBAL, UserHandle.USER_OWNER);
+ }
+
+ public void testSetAndGetGlobalViaFrontEndApiForNonOwnerUser() throws Exception {
+ if (mSecondaryUserId == UserHandle.USER_OWNER) {
+ Log.w(LOG_TAG, "No secondary user. Skipping "
+ + "testSetAndGetGlobalViaFrontEndApiForNonOwnerUser");
+ return;
+ }
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_GLOBAL, mSecondaryUserId);
+ }
+
+ public void testSetAndGetSecureViaFrontEndApiForOwnerUser() throws Exception {
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SECURE, UserHandle.USER_OWNER);
+ }
+
+ public void testSetAndGetSecureViaFrontEndApiForNonOwnerUser() throws Exception {
+ if (mSecondaryUserId == UserHandle.USER_OWNER) {
+ Log.w(LOG_TAG, "No secondary user. Skipping "
+ + "testSetAndGetSecureViaFrontEndApiForNonOwnerUser");
+ return;
+ }
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SECURE, mSecondaryUserId);
+ }
+
+ public void testSetAndGetSystemViaFrontEndApiForOwnerUser() throws Exception {
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SYSTEM, UserHandle.USER_OWNER);
+ }
+
+ public void testSetAndGetSystemViaFrontEndApiForNonOwnerUser() throws Exception {
+ if (mSecondaryUserId == UserHandle.USER_OWNER) {
+ Log.w(LOG_TAG, "No secondary user. Skipping "
+ + "testSetAndGetSystemViaFrontEndApiForNonOwnerUser");
+ return;
+ }
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SYSTEM, mSecondaryUserId);
+ }
+
+ public void testSetAndGetGlobalViaProviderApi() throws Exception {
+ performSetAndGetSettingTestViaProviderApi(SETTING_TYPE_GLOBAL);
+ }
+
+ public void testSetAndGetSecureViaProviderApi() throws Exception {
+ performSetAndGetSettingTestViaProviderApi(SETTING_TYPE_SECURE);
+ }
+
+ public void testSetAndGetSystemViaProviderApi() throws Exception {
+ performSetAndGetSettingTestViaProviderApi(SETTING_TYPE_SYSTEM);
+ }
+
+ public void testSelectAllGlobalViaProviderApi() throws Exception {
+ setSettingViaProviderApiAndAssertSuccessfulChange(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME, FAKE_SETTING_VALUE, false);
+ try {
+ queryAllSettingsViaProviderApiSettingAndAssertSettingPresent(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME);
+ } finally {
+ deleteStringViaProviderApi(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME);
+ }
+ }
+
+ public void testSelectAllSecureViaProviderApi() throws Exception {
+ setSettingViaProviderApiAndAssertSuccessfulChange(SETTING_TYPE_SECURE,
+ FAKE_SETTING_NAME, FAKE_SETTING_VALUE, false);
+ try {
+ queryAllSettingsViaProviderApiSettingAndAssertSettingPresent(SETTING_TYPE_SECURE,
+ FAKE_SETTING_NAME);
+ } finally {
+ deleteStringViaProviderApi(SETTING_TYPE_SECURE, FAKE_SETTING_NAME);
+ }
+ }
+
+ public void testSelectAllSystemViaProviderApi() throws Exception {
+ setSettingViaProviderApiAndAssertSuccessfulChange(SETTING_TYPE_SYSTEM,
+ FAKE_SETTING_NAME, FAKE_SETTING_VALUE, true);
+ try {
+ queryAllSettingsViaProviderApiSettingAndAssertSettingPresent(SETTING_TYPE_SYSTEM,
+ FAKE_SETTING_NAME);
+ } finally {
+ deleteStringViaProviderApi(SETTING_TYPE_SYSTEM, FAKE_SETTING_NAME);
+ }
+ }
+
+ public void testQueryUpdateDeleteGlobalViaProviderApi() throws Exception {
+ doTestQueryUpdateDeleteGlobalViaProviderApiForType(SETTING_TYPE_GLOBAL);
+ }
+
+ public void testQueryUpdateDeleteSecureViaProviderApi() throws Exception {
+ doTestQueryUpdateDeleteGlobalViaProviderApiForType(SETTING_TYPE_SECURE);
+ }
+
+ public void testQueryUpdateDeleteSystemViaProviderApi() throws Exception {
+ doTestQueryUpdateDeleteGlobalViaProviderApiForType(SETTING_TYPE_SYSTEM);
+ }
+
+ public void testBulkInsertGlobalViaProviderApi() throws Exception {
+ toTestBulkInsertViaProviderApiForType(SETTING_TYPE_GLOBAL);
+ }
+
+ public void testBulkInsertSystemViaProviderApi() throws Exception {
+ toTestBulkInsertViaProviderApiForType(SETTING_TYPE_SYSTEM);
+ }
+
+ public void testBulkInsertSecureViaProviderApi() throws Exception {
+ toTestBulkInsertViaProviderApiForType(SETTING_TYPE_SECURE);
+ }
+
+ public void testAppCannotRunsSystemOutOfMemoryWritingSystemSettings() throws Exception {
+ int insertedCount = 0;
+ try {
+ for (; insertedCount < 1200; insertedCount++) {
+ Log.w(LOG_TAG, "Adding app specific setting: " + insertedCount);
+ insertStringViaProviderApi(SETTING_TYPE_SYSTEM,
+ String.valueOf(insertedCount), FAKE_SETTING_VALUE, false);
+ }
+ fail("Adding app specific settings must be bound.");
+ } catch (Exception e) {
+ for (; insertedCount >= 0; insertedCount--) {
+ Log.w(LOG_TAG, "Removing app specific setting: " + insertedCount);
+ deleteStringViaProviderApi(SETTING_TYPE_SYSTEM,
+ String.valueOf(insertedCount));
+ }
+ }
+ }
+
+ public void testQueryStringInBracketsGlobalViaProviderApiForType() throws Exception {
+ doTestQueryStringInBracketsViaProviderApiForType(SETTING_TYPE_GLOBAL);
+ }
+
+ public void testQueryStringInBracketsSecureViaProviderApiForType() throws Exception {
+ doTestQueryStringInBracketsViaProviderApiForType(SETTING_TYPE_SECURE);
+ }
+
+ public void testQueryStringInBracketsSystemViaProviderApiForType() throws Exception {
+ doTestQueryStringInBracketsViaProviderApiForType(SETTING_TYPE_SYSTEM);
+ }
+
+ private void doTestQueryStringInBracketsViaProviderApiForType(int type) {
+ // Make sure we have a clean slate.
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
+
+ try {
+ // Insert the setting.
+ final Uri uri = insertStringViaProviderApi(type, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE, false);
+ Uri expectUri = Uri.withAppendedPath(getBaseUriForType(type), FAKE_SETTING_NAME);
+ assertEquals("Did not get expected Uri.", expectUri, uri);
+
+ // Make sure the first setting is there.
+ String firstValue = queryStringViaProviderApi(type, FAKE_SETTING_NAME, true);
+ assertEquals("Setting must be present", FAKE_SETTING_VALUE, firstValue);
+ } finally {
+ // Clean up.
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
+ }
+ }
+
+ private void toTestBulkInsertViaProviderApiForType(int type) {
+ // Make sure we have a clean slate.
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME_1);
+
+ try {
+ Uri uri = getBaseUriForType(type);
+ ContentValues[] allValues = new ContentValues[2];
+
+ // Insert the first setting.
+ ContentValues firstValues = new ContentValues();
+ firstValues.put(Settings.NameValueTable.NAME, FAKE_SETTING_NAME);
+ firstValues.put(Settings.NameValueTable.VALUE, FAKE_SETTING_VALUE);
+ allValues[0] = firstValues;
+
+ // Insert the first setting.
+ ContentValues secondValues = new ContentValues();
+ secondValues.put(Settings.NameValueTable.NAME, FAKE_SETTING_NAME_1);
+ secondValues.put(Settings.NameValueTable.VALUE, FAKE_SETTING_VALUE_1);
+ allValues[1] = secondValues;
+
+ // Verify insertion count.
+ final int insertCount = getContext().getContentResolver().bulkInsert(uri, allValues);
+ assertSame("Couldn't insert both values", 2, insertCount);
+
+ // Make sure the first setting is there.
+ String firstValue = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
+ assertEquals("First setting must be present", FAKE_SETTING_VALUE, firstValue);
+
+ // Make sure the second setting is there.
+ String secondValue = queryStringViaProviderApi(type, FAKE_SETTING_NAME_1);
+ assertEquals("Second setting must be present", FAKE_SETTING_VALUE_1, secondValue);
+ } finally {
+ // Clean up.
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME_1);
+ }
+ }
+
+ private void doTestQueryUpdateDeleteGlobalViaProviderApiForType(int type) throws Exception {
+ // Make sure it is not there.
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
+
+ // Now selection should return nothing.
+ String value = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
+ assertNull("Setting should not be present.", value);
+
+ // Insert the setting.
+ Uri uri = insertStringViaProviderApi(type,
+ FAKE_SETTING_NAME, FAKE_SETTING_VALUE, false);
+ Uri expectUri = Uri.withAppendedPath(getBaseUriForType(type), FAKE_SETTING_NAME);
+ assertEquals("Did not get expected Uri.", expectUri, uri);
+
+ // Now selection should return the setting.
+ value = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
+ assertEquals("Setting should be present.", FAKE_SETTING_VALUE, value);
+
+ // Update the setting.
+ final int changeCount = updateStringViaProviderApiSetting(type,
+ FAKE_SETTING_NAME, FAKE_SETTING_VALUE_1);
+ assertEquals("Did not get expected change count.", 1, changeCount);
+
+ // Now selection should return the new setting.
+ value = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
+ assertEquals("Setting should be present.", FAKE_SETTING_VALUE_1, value);
+
+ // Delete the setting.
+ final int deletedCount = deleteStringViaProviderApi(type,
+ FAKE_SETTING_NAME);
+ assertEquals("Did not get expected deleted count", 1, deletedCount);
+
+ // Now selection should return nothing.
+ value = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
+ assertNull("Setting should not be present.", value);
+ }
+
+ private void performSetAndGetSettingTestViaFrontEndApi(int type, int userId)
+ throws Exception {
+ try {
+ // Change the setting and assert a successful change.
+ setSettingViaFrontEndApiAndAssertSuccessfulChange(type, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE, userId);
+ } finally {
+ // Remove the setting.
+ setStringViaFrontEndApiSetting(type, FAKE_SETTING_NAME, null, userId);
+ }
+ }
+
+ private void performSetAndGetSettingTestViaProviderApi(int type)
+ throws Exception {
+ try {
+ // Change the setting and assert a successful change.
+ setSettingViaProviderApiAndAssertSuccessfulChange(type, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE, true);
+ } finally {
+ // Remove the setting.
+ setSettingViaProviderApiAndAssertSuccessfulChange(type, FAKE_SETTING_NAME, null,
+ true);
+ }
+ }
+
+ private void setSettingViaFrontEndApiAndAssertSuccessfulChange(final int type,
+ final String name, final String value, final int userId) throws Exception {
+ setSettingAndAssertSuccessfulChange(new Runnable() {
+ @Override
+ public void run() {
+ setStringViaFrontEndApiSetting(type, name, value, userId);
+ }
+ }, type, name, value, userId);
+ }
+
+ private void setSettingViaProviderApiAndAssertSuccessfulChange(final int type,
+ final String name, final String value, final boolean withTableRowUri)
+ throws Exception {
+ setSettingAndAssertSuccessfulChange(new Runnable() {
+ @Override
+ public void run() {
+ insertStringViaProviderApi(type, name, value, withTableRowUri);
+ }
+ }, type, name, value, UserHandle.USER_OWNER);
+ }
+
+ private void setSettingAndAssertSuccessfulChange(Runnable setCommand, final int type,
+ final String name, final String value, final int userId) throws Exception {
+ ContentResolver contentResolver = getContext().getContentResolver();
+
+ final Uri settingUri = getBaseUriForType(type);
+
+ final AtomicBoolean success = new AtomicBoolean();
+
+ ContentObserver contentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ public void onChange(boolean selfChange, Uri changeUri, int changeId) {
+ Log.i(LOG_TAG, "onChange(" + selfChange + ", " + changeUri + ", " + changeId + ")");
+ assertEquals("Wrong change Uri", changeUri, settingUri);
+ assertEquals("Wrong user id", userId, changeId);
+ String changeValue = getStringViaFrontEndApiSetting(type, name, userId);
+ assertEquals("Wrong setting value", value, changeValue);
+
+ success.set(true);
+
+ synchronized (mLock) {
+ mLock.notifyAll();
+ }
+ }
+ };
+
+ contentResolver.registerContentObserver(settingUri, false, contentObserver, userId);
+
+ try {
+ setCommand.run();
+
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ synchronized (mLock) {
+ if (success.get()) {
+ return;
+ }
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ if (elapsedTimeMillis > WAIT_FOR_SETTING_URI_CHANGE_TIMEOUT_MILLIS) {
+ fail("Could not change setting for "
+ + WAIT_FOR_SETTING_URI_CHANGE_TIMEOUT_MILLIS + " ms");
+ }
+ final long remainingTimeMillis = WAIT_FOR_SETTING_URI_CHANGE_TIMEOUT_MILLIS
+ - elapsedTimeMillis;
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ } finally {
+ contentResolver.unregisterContentObserver(contentObserver);
+ }
+ }
+
+ private void queryAllSettingsViaProviderApiSettingAndAssertSettingPresent(int type,
+ String name) {
+ Uri uri = getBaseUriForType(type);
+
+ Cursor cursor = getContext().getContentResolver().query(uri, NAME_VALUE_COLUMNS,
+ null, null, null);
+
+ if (cursor == null || !cursor.moveToFirst()) {
+ fail("Nothing selected");
+ }
+
+ try {
+ final int nameColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.NAME);
+
+ while (cursor.moveToNext()) {
+ String currentName = cursor.getString(nameColumnIdx);
+ if (name.equals(currentName)) {
+ return;
+ }
+ }
+
+ fail("Not found setting: " + name);
+ } finally {
+ cursor.close();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ec8a775..5bb193a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1195,8 +1195,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
} catch (RemoteException ex) { }
mSettingsObserver = new SettingsObserver(mHandler);
mSettingsObserver.observe();
- mShortcutManager = new ShortcutManager(context, mHandler);
- mShortcutManager.observe();
+ mShortcutManager = new ShortcutManager(context);
mUiMode = context.getResources().getInteger(
com.android.internal.R.integer.config_defaultUiModeType);
mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -4751,7 +4750,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
msg.setAsynchronous(true);
msg.sendToTarget();
}
- break;
}
}
diff --git a/services/core/java/com/android/server/policy/ShortcutManager.java b/services/core/java/com/android/server/policy/ShortcutManager.java
index 6a0136a..76f56bc 100644
--- a/services/core/java/com/android/server/policy/ShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ShortcutManager.java
@@ -16,81 +16,47 @@
package com.android.server.policy;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.os.Handler;
-import android.provider.Settings;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
+import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.KeyCharacterMap;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
-import java.net.URISyntaxException;
+import java.io.IOException;
/**
* Manages quick launch shortcuts by:
* <li> Keeping the local copy in sync with the database (this is an observer)
* <li> Returning a shortcut-matching intent to clients
*/
-class ShortcutManager extends ContentObserver {
-
+class ShortcutManager {
private static final String TAG = "ShortcutManager";
-
- private static final int COLUMN_SHORTCUT = 0;
- private static final int COLUMN_INTENT = 1;
- private static final String[] sProjection = new String[] {
- Settings.Bookmarks.SHORTCUT, Settings.Bookmarks.INTENT
- };
-
- private Context mContext;
- private Cursor mCursor;
- /** Map of a shortcut to its intent. */
- private SparseArray<Intent> mShortcutIntents;
-
- public ShortcutManager(Context context, Handler handler) {
- super(handler);
-
- mContext = context;
- mShortcutIntents = new SparseArray<Intent>();
- }
- /** Observes the provider of shortcut+intents */
- public void observe() {
- mCursor = mContext.getContentResolver().query(
- Settings.Bookmarks.CONTENT_URI, sProjection, null, null, null);
- mCursor.registerContentObserver(this);
- updateShortcuts();
- }
+ private static final String TAG_BOOKMARKS = "bookmarks";
+ private static final String TAG_BOOKMARK = "bookmark";
- @Override
- public void onChange(boolean selfChange) {
- updateShortcuts();
- }
-
- private void updateShortcuts() {
- Cursor c = mCursor;
- if (!c.requery()) {
- Log.e(TAG, "ShortcutObserver could not re-query shortcuts.");
- return;
- }
+ private static final String ATTRIBUTE_PACKAGE = "package";
+ private static final String ATTRIBUTE_CLASS = "class";
+ private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+ private static final String ATTRIBUTE_CATEGORY = "category";
- mShortcutIntents.clear();
- while (c.moveToNext()) {
- int shortcut = c.getInt(COLUMN_SHORTCUT);
- if (shortcut == 0) continue;
- String intentURI = c.getString(COLUMN_INTENT);
- Intent intent = null;
- try {
- intent = Intent.getIntent(intentURI);
- } catch (URISyntaxException e) {
- Log.w(TAG, "Intent URI for shortcut invalid.", e);
- }
- if (intent == null) continue;
- mShortcutIntents.put(shortcut, intent);
- }
- }
+ private final SparseArray<ShortcutInfo> mShortcuts = new SparseArray<>();
+
+ private final Context mContext;
+ public ShortcutManager(Context context) {
+ mContext = context;
+ loadShortcuts();
+ }
+
/**
* Gets the shortcut intent for a given keycode+modifier. Make sure you
* strip whatever modifier is used for invoking shortcuts (for example,
@@ -107,23 +73,105 @@ class ShortcutManager extends ContentObserver {
* @return The intent that matches the shortcut, or null if not found.
*/
public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) {
- Intent intent = null;
+ ShortcutInfo shortcut = null;
// First try the exact keycode (with modifiers).
- int shortcut = kcm.get(keyCode, metaState);
- if (shortcut != 0) {
- intent = mShortcutIntents.get(shortcut);
+ int shortcutChar = kcm.get(keyCode, metaState);
+ if (shortcutChar != 0) {
+ shortcut = mShortcuts.get(shortcutChar);
}
// Next try the primary character on that key.
- if (intent == null) {
- shortcut = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
- if (shortcut != 0) {
- intent = mShortcutIntents.get(shortcut);
+ if (shortcut == null) {
+ shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
+ if (shortcutChar != 0) {
+ shortcut = mShortcuts.get(shortcutChar);
}
}
- return intent;
+ return (shortcut != null) ? shortcut.intent : null;
}
+ private void loadShortcuts() {
+ PackageManager packageManager = mContext.getPackageManager();
+ try {
+ XmlResourceParser parser = mContext.getResources().getXml(
+ com.android.internal.R.xml.bookmarks);
+ XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+
+ if (!TAG_BOOKMARK.equals(parser.getName())) {
+ break;
+ }
+
+ String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
+ String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
+ String shortcutName = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
+ String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
+
+ if (TextUtils.isEmpty(shortcutName)) {
+ Log.w(TAG, "Unable to get shortcut for: " + packageName + "/" + className);
+ continue;
+ }
+
+ final int shortcutChar = shortcutName.charAt(0);
+
+ final Intent intent;
+ final String title;
+ if (packageName != null && className != null) {
+ ActivityInfo info = null;
+ ComponentName componentName = new ComponentName(packageName, className);
+ try {
+ info = packageManager.getActivityInfo(componentName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ String[] packages = packageManager.canonicalToCurrentPackageNames(
+ new String[] { packageName });
+ componentName = new ComponentName(packages[0], className);
+ try {
+ info = packageManager.getActivityInfo(componentName, 0);
+ } catch (PackageManager.NameNotFoundException e1) {
+ Log.w(TAG, "Unable to add bookmark: " + packageName
+ + "/" + className, e);
+ continue;
+ }
+ }
+
+ intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(componentName);
+ title = info.loadLabel(packageManager).toString();
+ } else if (categoryName != null) {
+ intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
+ title = "";
+ } else {
+ Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
+ + ": missing package/class or category attributes");
+ continue;
+ }
+
+ ShortcutInfo shortcut = new ShortcutInfo(title, intent);
+ mShortcuts.put(shortcutChar, shortcut);
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Got exception parsing bookmarks.", e);
+ } catch (IOException e) {
+ Log.w(TAG, "Got exception parsing bookmarks.", e);
+ }
+ }
+
+ private static final class ShortcutInfo {
+ public final String title;
+ public final Intent intent;
+
+ public ShortcutInfo(String title, Intent intent) {
+ this.title = title;
+ this.intent = intent;
+ }
+ }
}