Notification listener backup & restore

We now back up & restore the set of enabled notification listeners.  Post-
restore, a listener that had been enabled on the ancestral device will be
enabled on the current device as soon as it's installed, matching the
user's previous configuration.  After this has happened the enable/disable
state for that app is not "sticky"; disabling it again will work as
expected.

The infrastructure for accomplishing this is general: it can be leveraged
by any ManagedServices derivative.  There's a bit of extra wiring in the
settings provider to support the restore-time information flow as well.
This is because ManagedServices -- like many other parts of the system --
monitors writes to the settings provider and does work in response to new
writes of the elements that it cares about.  Unfortunately this means that
there is no way to use the BackupAgent's restoreFinished() hook to post-
process the restored data: by the time it is run, the ManagedService's
observers have already executed and culled any unknown components from
the description that was just pushed into settings.

As of this patch, the settings provider's restore logic knows that a
particular settings element will require a message to interested observers
about the restore-driven change.  The message is delivered as a broadcast,
and is sent after the new value has been committed to the settings db.
Adding other system ManagedService handling that parallels this will only
require adding a new corresponding entry to the table of individual settings
for which the relevant "this settings element is being restored" broadcast
is sent, found in SettingsHelper.

(It isn't sent for all settings elements because very few settings elements
have semantics that require it; 3rd party code won't be running yet during
platform restore anyway; and sending up to hundreds of broadcasts during
setup & restore is far from ideal.)

Bug 19254153

Change-Id: Ib8268c6cb273862a3ee089d2764f3bff4a299103
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 0c7d71b..9ccb2ea 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 @@
 
 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.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -74,6 +77,7 @@
     private final UserProfiles mUserProfiles;
     private final SettingsObserver mSettingsObserver;
     private final Config mConfig;
+    private ArraySet<String> mRestored;
 
     // contains connections to all connected services, including app services
     // and system services
@@ -91,6 +95,8 @@
     // 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 @@
         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 @@
         }
     }
 
+    // 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 @@
     }
 
     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<String>(Arrays.asList(restored));
+            } else {
+                mRestored = new ArraySet<String>();
+            }
+        }
         String flatIn = Settings.Secure.getStringForUser(
-                mContext.getContentResolver(),
+                cr,
                 mConfig.secureSettingName,
                 userId);
         if (!TextUtils.isEmpty(flatIn)) {
@@ -228,14 +292,16 @@
                 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 @@
                     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);
+            }
         }
     }