diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2ed8c44ddc2f5..f68547599719c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2790,6 +2790,31 @@ public class Intent implements Parcelable, Cloneable {
/** {@hide} */
public static final String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR";
+ /**
+ * Broadcast action: report that a settings element is being restored from backup. The intent
+ * contains three extras: EXTRA_SETTING_NAME is a string naming the restored setting,
+ * EXTRA_SETTING_NEW_VALUE is the value being restored, and EXTRA_SETTING_PREVIOUS_VALUE
+ * is the value of that settings entry prior to the restore operation. All of these values are
+ * represented as strings.
+ *
+ *
This broadcast is sent only for settings provider entries known to require special handling
+ * around restore time. These entries are found in the BROADCAST_ON_RESTORE table within
+ * the provider's backup agent implementation.
+ *
+ * @see #EXTRA_SETTING_NAME
+ * @see #EXTRA_SETTING_PREVIOUS_VALUE
+ * @see #EXTRA_SETTING_NEW_VALUE
+ * {@hide}
+ */
+ public static final String ACTION_SETTING_RESTORED = "android.os.action.SETTING_RESTORED";
+
+ /** {@hide} */
+ public static final String EXTRA_SETTING_NAME = "setting_name";
+ /** {@hide} */
+ public static final String EXTRA_SETTING_PREVIOUS_VALUE = "previous_value";
+ /** {@hide} */
+ public static final String EXTRA_SETTING_NEW_VALUE = "new_value";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4c452aa8a5a77..bb0959eac2572 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5374,6 +5374,7 @@ public final class Settings {
ACCESSIBILITY_SCRIPT_INJECTION,
BACKUP_AUTO_RESTORE,
ENABLED_ACCESSIBILITY_SERVICES,
+ ENABLED_NOTIFICATION_LISTENERS,
TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
TOUCH_EXPLORATION_ENABLED,
ACCESSIBILITY_ENABLED,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ed4776bab5254..4d6b5f68aa770 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -90,6 +90,8 @@
+
+
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index eac83d8e2563e..7f826ef961db8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -793,7 +793,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
}
// Figure out the white list and redirects to the global table.
- String[] whitelist = null;
+ final String[] whitelist;
if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
whitelist = Settings.Secure.SETTINGS_TO_BACKUP;
} else if (contentUri.equals(Settings.System.CONTENT_URI)) {
@@ -809,6 +809,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
Map cachedEntries = new HashMap();
ContentValues contentValues = new ContentValues(2);
SettingsHelper settingsHelper = mSettingsHelper;
+ ContentResolver cr = getContentResolver();
final int whiteListSize = whitelist.length;
for (int i = 0; i < whiteListSize; i++) {
@@ -841,14 +842,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
final Uri destination = (movedToGlobal != null && movedToGlobal.contains(key))
? Settings.Global.CONTENT_URI
: contentUri;
-
- // The helper doesn't care what namespace the keys are in
- if (settingsHelper.restoreValue(key, value)) {
- contentValues.clear();
- contentValues.put(Settings.NameValueTable.NAME, key);
- contentValues.put(Settings.NameValueTable.VALUE, value);
- getContentResolver().insert(destination, contentValues);
- }
+ settingsHelper.restoreValue(this, cr, contentValues, destination, key, value);
if (DEBUG) {
Log.d(TAG, "Restored setting: " + destination + " : "+ key + "=" + value);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 4144c803dbd32..1cad610e794f5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -19,7 +19,10 @@ package com.android.providers.settings;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.backup.IBackupManager;
+import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.location.LocationManager;
import android.media.AudioManager;
@@ -28,10 +31,12 @@ import android.net.Uri;
import android.os.IPowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.ArraySet;
import java.util.Locale;
@@ -41,6 +46,48 @@ public class SettingsHelper {
private AudioManager mAudioManager;
private TelephonyManager mTelephonyManager;
+ /**
+ * A few settings elements are special in that a restore of those values needs to
+ * be post-processed by relevant parts of the OS. A restore of any settings element
+ * mentioned in this table will therefore cause the system to send a broadcast with
+ * the {@link Intent#ACTION_SETTING_RESTORED} action, with extras naming the
+ * affected setting and supplying its pre-restore value for comparison.
+ *
+ * @see Intent#ACTION_SETTING_RESTORED
+ * @see System#SETTINGS_TO_BACKUP
+ * @see Secure#SETTINGS_TO_BACKUP
+ * @see Global#SETTINGS_TO_BACKUP
+ *
+ * {@hide}
+ */
+ private static final ArraySet sBroadcastOnRestore;
+ static {
+ sBroadcastOnRestore = new ArraySet(1);
+ sBroadcastOnRestore.add(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+ }
+
+ private interface SettingsLookup {
+ public String lookup(ContentResolver resolver, String name, int userHandle);
+ }
+
+ private static SettingsLookup sSystemLookup = new SettingsLookup() {
+ public String lookup(ContentResolver resolver, String name, int userHandle) {
+ return Settings.System.getStringForUser(resolver, name, userHandle);
+ }
+ };
+
+ private static SettingsLookup sSecureLookup = new SettingsLookup() {
+ public String lookup(ContentResolver resolver, String name, int userHandle) {
+ return Settings.Secure.getStringForUser(resolver, name, userHandle);
+ }
+ };
+
+ private static SettingsLookup sGlobalLookup = new SettingsLookup() {
+ public String lookup(ContentResolver resolver, String name, int userHandle) {
+ return Settings.Global.getStringForUser(resolver, name, userHandle);
+ }
+ };
+
public SettingsHelper(Context context) {
mContext = context;
mAudioManager = (AudioManager) context
@@ -58,24 +105,67 @@ public class SettingsHelper {
* some cases the data will be written by the call to the appropriate API,
* and in some cases the property value needs to be modified before setting.
*/
- public boolean restoreValue(String name, String value) {
- if (Settings.System.SCREEN_BRIGHTNESS.equals(name)) {
- setBrightness(Integer.parseInt(value));
- } else if (Settings.System.SOUND_EFFECTS_ENABLED.equals(name)) {
- setSoundEffects(Integer.parseInt(value) == 1);
- } else if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
- setGpsLocation(value);
- return false;
- } else if (Settings.Secure.BACKUP_AUTO_RESTORE.equals(name)) {
- setAutoRestore(Integer.parseInt(value) == 1);
- } else if (isAlreadyConfiguredCriticalAccessibilitySetting(name)) {
- return false;
- } else if (Settings.System.RINGTONE.equals(name)
- || Settings.System.NOTIFICATION_SOUND.equals(name)) {
- setRingtone(name, value);
- return false;
+ public void restoreValue(Context context, ContentResolver cr, ContentValues contentValues,
+ Uri destination, String name, String value) {
+ // Will we need a post-restore broadcast for this element?
+ String oldValue = null;
+ boolean sendBroadcast = false;
+ final SettingsLookup table;
+
+ if (destination.equals(Settings.Secure.CONTENT_URI)) {
+ table = sSecureLookup;
+ } else if (destination.equals(Settings.System.CONTENT_URI)) {
+ table = sSystemLookup;
+ } else { /* must be GLOBAL; this was preflighted by the caller */
+ table = sGlobalLookup;
+ }
+
+ if (sBroadcastOnRestore.contains(name)) {
+ oldValue = table.lookup(cr, name, UserHandle.USER_OWNER);
+ sendBroadcast = true;
+ }
+
+ try {
+ if (Settings.System.SCREEN_BRIGHTNESS.equals(name)) {
+ setBrightness(Integer.parseInt(value));
+ // fall through to the ordinary write to settings
+ } else if (Settings.System.SOUND_EFFECTS_ENABLED.equals(name)) {
+ setSoundEffects(Integer.parseInt(value) == 1);
+ // fall through to the ordinary write to settings
+ } else if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
+ setGpsLocation(value);
+ return;
+ } else if (Settings.Secure.BACKUP_AUTO_RESTORE.equals(name)) {
+ setAutoRestore(Integer.parseInt(value) == 1);
+ } else if (isAlreadyConfiguredCriticalAccessibilitySetting(name)) {
+ return;
+ } else if (Settings.System.RINGTONE.equals(name)
+ || Settings.System.NOTIFICATION_SOUND.equals(name)) {
+ setRingtone(name, value);
+ return;
+ }
+
+ // Default case: write the restored value to settings
+ contentValues.clear();
+ contentValues.put(Settings.NameValueTable.NAME, name);
+ contentValues.put(Settings.NameValueTable.VALUE, value);
+ cr.insert(destination, contentValues);
+ } catch (Exception e) {
+ // If we fail to apply the setting, by definition nothing happened
+ sendBroadcast = false;
+ } finally {
+ // If this was an element of interest, send the "we just restored it"
+ // broadcast with the historical value now that the new value has
+ // been committed and observers kicked off.
+ if (sendBroadcast) {
+ Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
+ .setPackage("android").addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ .putExtra(Intent.EXTRA_SETTING_NAME, name)
+ .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, value)
+ .putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, oldValue);
+ context.sendBroadcastAsUser(intent, UserHandle.OWNER, null);
+ }
}
- return true;
}
public String onBackupValue(String name, String value) {
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 0c7d71b1d0e75..9ccb2eaf1fb3a 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -18,10 +18,12 @@ package com.android.server.notification;
import android.app.ActivityManager;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -51,6 +53,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -74,6 +77,7 @@ abstract public class ManagedServices {
private final UserProfiles mUserProfiles;
private final SettingsObserver mSettingsObserver;
private final Config mConfig;
+ private ArraySet mRestored;
// contains connections to all connected services, including app services
// and system services
@@ -91,6 +95,8 @@ abstract public class ManagedServices {
// user change).
private int[] mLastSeenProfileIds;
+ private final BroadcastReceiver mRestoreReceiver;
+
public ManagedServices(Context context, Handler handler, Object mutex,
UserProfiles userProfiles) {
mContext = context;
@@ -98,6 +104,24 @@ abstract public class ManagedServices {
mUserProfiles = userProfiles;
mConfig = getConfig();
mSettingsObserver = new SettingsObserver(handler);
+
+ mRestoreReceiver = new SettingRestoredReceiver();
+ IntentFilter filter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
+ context.registerReceiver(mRestoreReceiver, filter);
+ }
+
+ class SettingRestoredReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_SETTING_RESTORED.equals(intent.getAction())) {
+ String element = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
+ if (Objects.equals(element, mConfig.secureSettingName)) {
+ String prevValue = intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE);
+ String newValue = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE);
+ settingRestored(element, prevValue, newValue, getSendingUserId());
+ }
+ }
+ }
}
abstract protected Config getConfig();
@@ -140,6 +164,31 @@ abstract public class ManagedServices {
}
}
+ // By convention, restored settings are replicated to another settings
+ // entry, named similarly but with a disambiguation suffix.
+ public static final String restoredSettingName(Config config) {
+ return config.secureSettingName + ":restored";
+ }
+
+ // The OS has done a restore of this service's saved state. We clone it to the
+ // 'restored' reserve, and then once we return and the actual write to settings is
+ // performed, our observer will do the work of maintaining the restored vs live
+ // settings data.
+ public void settingRestored(String element, String oldValue, String newValue, int userid) {
+ if (DEBUG) Slog.d(TAG, "Restored managed service setting: " + element
+ + " ovalue=" + oldValue + " nvalue=" + newValue);
+ if (mConfig.secureSettingName.equals(element)) {
+ if (element != null) {
+ mRestored = null;
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ restoredSettingName(mConfig),
+ newValue,
+ userid);
+ disableNonexistentServices(userid);
+ }
+ }
+ }
+
public void onPackagesChanged(boolean queryReplace, String[] pkgList) {
if (DEBUG) Slog.d(TAG, "onPackagesChanged queryReplace=" + queryReplace
+ " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))
@@ -211,8 +260,23 @@ abstract public class ManagedServices {
}
private void disableNonexistentServices(int userId) {
+ final ContentResolver cr = mContext.getContentResolver();
+ boolean restoredChanged = false;
+ if (mRestored == null) {
+ String restoredSetting = Settings.Secure.getStringForUser(
+ cr,
+ restoredSettingName(mConfig),
+ userId);
+ if (!TextUtils.isEmpty(restoredSetting)) {
+ if (DEBUG) Slog.d(TAG, "restored: " + restoredSetting);
+ String[] restored = restoredSetting.split(ENABLED_SERVICES_SEPARATOR);
+ mRestored = new ArraySet(Arrays.asList(restored));
+ } else {
+ mRestored = new ArraySet();
+ }
+ }
String flatIn = Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
+ cr,
mConfig.secureSettingName,
userId);
if (!TextUtils.isEmpty(flatIn)) {
@@ -228,14 +292,16 @@ abstract public class ManagedServices {
ResolveInfo resolveInfo = installedServices.get(i);
ServiceInfo info = resolveInfo.serviceInfo;
+ ComponentName component = new ComponentName(info.packageName, info.name);
if (!mConfig.bindPermission.equals(info.permission)) {
Slog.w(TAG, "Skipping " + getCaption() + " service "
+ info.packageName + "/" + info.name
+ ": it does not require the permission "
+ mConfig.bindPermission);
+ restoredChanged |= mRestored.remove(component.flattenToString());
continue;
}
- installed.add(new ComponentName(info.packageName, info.name));
+ installed.add(component);
}
String flatOut = "";
@@ -246,16 +312,27 @@ abstract public class ManagedServices {
ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]);
if (installed.contains(enabledComponent)) {
remaining.add(enabled[i]);
+ restoredChanged |= mRestored.remove(enabled[i]);
}
}
+ remaining.addAll(mRestored);
flatOut = TextUtils.join(ENABLED_SERVICES_SEPARATOR, remaining);
}
if (DEBUG) Slog.v(TAG, "flat after: " + flatOut);
if (!flatIn.equals(flatOut)) {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.putStringForUser(cr,
mConfig.secureSettingName,
flatOut, userId);
}
+ if (restoredChanged) {
+ if (DEBUG) Slog.d(TAG, "restored changed; rewriting");
+ final String flatRestored = TextUtils.join(ENABLED_SERVICES_SEPARATOR,
+ mRestored.toArray());
+ Settings.Secure.putStringForUser(cr,
+ restoredSettingName(mConfig),
+ flatRestored,
+ userId);
+ }
}
}