Zen: Store notification zen policy per-user.

 - Keep a zen config per user in NoMan.
 - Add zen config for all users to xml policy storage mechanism.
 - Initialize config to default for new secondary users.
 - Re-evaluate global zen on user switch.
 - Remove some unused code in NoMan.
 - Make ZenModeHelper aware of multiple users, keep all configs,
   add to dump.
 - Log config diffs in addition to the config itself in ZenLog.

Bug: 15845915
Change-Id: Ic847451e5d111c74916def1ea0948db5a76966c9
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 33c666a..40956c1 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -262,6 +262,14 @@
         return null;
     }
 
+    public Condition findCondition(ComponentName component, Uri conditionId) {
+        if (component == null || conditionId == null) return null;
+        synchronized (mMutex) {
+            final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
+            return r != null ? r.condition : null;
+        }
+    }
+
     public void ensureRecordExists(ComponentName component, Uri conditionId,
             IConditionProvider provider) {
         // constructed by convention, make sure the record exists...
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2be409a..ba6088e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -126,7 +126,6 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map.Entry;
-import java.util.NoSuchElementException;
 import java.util.Objects;
 
 /** {@hide} */
@@ -244,15 +243,18 @@
 
     private Archive mArchive;
 
-    // Notification control database. For now just contains disabled packages.
+    // Persistent storage for notification policy
     private AtomicFile mPolicyFile;
+
+    // Temporary holder for <blocked-packages> config coming from old policy files.
     private HashSet<String> mBlockedPackages = new HashSet<String>();
 
     private static final int DB_VERSION = 1;
 
-    private static final String TAG_BODY = "notification-policy";
+    private static final String TAG_NOTIFICATION_POLICY = "notification-policy";
     private static final String ATTR_VERSION = "version";
 
+    // Obsolete:  converted if present, but not resaved to disk.
     private static final String TAG_BLOCKED_PKGS = "blocked-packages";
     private static final String TAG_PACKAGE = "package";
     private static final String ATTR_NAME = "name";
@@ -310,53 +312,9 @@
             mBuffer.addLast(nr.cloneLight());
         }
 
-        public void clear() {
-            mBuffer.clear();
-        }
-
         public Iterator<StatusBarNotification> descendingIterator() {
             return mBuffer.descendingIterator();
         }
-        public Iterator<StatusBarNotification> ascendingIterator() {
-            return mBuffer.iterator();
-        }
-        public Iterator<StatusBarNotification> filter(
-                final Iterator<StatusBarNotification> iter, final String pkg, final int userId) {
-            return new Iterator<StatusBarNotification>() {
-                StatusBarNotification mNext = findNext();
-
-                private StatusBarNotification findNext() {
-                    while (iter.hasNext()) {
-                        StatusBarNotification nr = iter.next();
-                        if ((pkg == null || nr.getPackageName() == pkg)
-                                && (userId == UserHandle.USER_ALL || nr.getUserId() == userId)) {
-                            return nr;
-                        }
-                    }
-                    return null;
-                }
-
-                @Override
-                public boolean hasNext() {
-                    return mNext == null;
-                }
-
-                @Override
-                public StatusBarNotification next() {
-                    StatusBarNotification next = mNext;
-                    if (next == null) {
-                        throw new NoSuchElementException();
-                    }
-                    mNext = findNext();
-                    return next;
-                }
-
-                @Override
-                public void remove() {
-                    iter.remove();
-                }
-            };
-        }
 
         public StatusBarNotification[] getArray(int count) {
             if (count == 0) count = mBufferSize;
@@ -370,21 +328,10 @@
             return a;
         }
 
-        public StatusBarNotification[] getArray(int count, String pkg, int userId) {
-            if (count == 0) count = mBufferSize;
-            final StatusBarNotification[] a
-                    = new StatusBarNotification[Math.min(count, mBuffer.size())];
-            Iterator<StatusBarNotification> iter = filter(descendingIterator(), pkg, userId);
-            int i=0;
-            while (iter.hasNext() && i < count) {
-                a[i++] = iter.next();
-            }
-            return a;
-        }
-
     }
 
     private void loadPolicyFile() {
+        if (DBG) Slog.d(TAG, "loadPolicyFile");
         synchronized(mPolicyFile) {
             mBlockedPackages.clear();
 
@@ -400,7 +347,7 @@
                 while ((type = parser.next()) != END_DOCUMENT) {
                     tag = parser.getName();
                     if (type == START_TAG) {
-                        if (TAG_BODY.equals(tag)) {
+                        if (TAG_NOTIFICATION_POLICY.equals(tag)) {
                             version = Integer.parseInt(
                                     parser.getAttributeValue(null, ATTR_VERSION));
                         } else if (TAG_BLOCKED_PKGS.equals(tag)) {
@@ -438,7 +385,7 @@
     }
 
     private void handleSavePolicyFile() {
-        Slog.d(TAG, "handleSavePolicyFile");
+        if (DBG) Slog.d(TAG, "handleSavePolicyFile");
         synchronized (mPolicyFile) {
             final FileOutputStream stream;
             try {
@@ -452,11 +399,11 @@
                 final XmlSerializer out = new FastXmlSerializer();
                 out.setOutput(stream, StandardCharsets.UTF_8.name());
                 out.startDocument(null, true);
-                out.startTag(null, TAG_BODY);
+                out.startTag(null, TAG_NOTIFICATION_POLICY);
                 out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
                 mZenModeHelper.writeXml(out);
                 mRankingHelper.writeXml(out);
-                out.endTag(null, TAG_BODY);
+                out.endTag(null, TAG_NOTIFICATION_POLICY);
                 out.endDocument();
                 mPolicyFile.finishWrite(stream);
             } catch (IOException e) {
@@ -806,8 +753,12 @@
                 // Refresh managed services
                 mConditionProviders.onUserSwitched(user);
                 mListeners.onUserSwitched(user);
+                mZenModeHelper.onUserSwitched(user);
             } else if (action.equals(Intent.ACTION_USER_ADDED)) {
                 mUserProfiles.updateCache(context);
+            } else if (action.equals(Intent.ACTION_USER_REMOVED)) {
+                final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+                mZenModeHelper.onUserRemoved(user);
             }
         }
     };
@@ -968,6 +919,7 @@
         filter.addAction(Intent.ACTION_USER_STOPPED);
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_USER_ADDED);
+        filter.addAction(Intent.ACTION_USER_REMOVED);
         getContext().registerReceiver(mIntentReceiver, filter);
 
         IntentFilter pkgFilter = new IntentFilter();
@@ -1418,8 +1370,6 @@
 
         @Override
         public void setNotificationsShownFromListener(INotificationListener token, String[] keys) {
-            final int callingUid = Binder.getCallingUid();
-            final int callingPid = Binder.getCallingPid();
             long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mNotificationList) {
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 44fbd2d..c45071b 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -115,8 +115,11 @@
         append(TYPE_UNSUBSCRIBE, uri + "," + subscribeResult(provider, e));
     }
 
-    public static void traceConfig(String reason, ZenModeConfig newConfig) {
-        append(TYPE_CONFIG, reason + "," + (newConfig != null ? newConfig.toString() : null));
+    public static void traceConfig(String reason, ZenModeConfig oldConfig,
+            ZenModeConfig newConfig) {
+        append(TYPE_CONFIG, reason
+                + "," + (newConfig != null ? newConfig.toString() : null)
+                + "," + ZenModeConfig.diff(oldConfig, newConfig));
     }
 
     public static void traceDisableEffects(NotificationRecord record, String reason) {
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index d6b7f2f..b89a654 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -63,7 +63,7 @@
         mConditionProviders.requestConditions(callback, relevance);
     }
 
-    public void evaluateConfig(ZenModeConfig config, boolean processSubscriptione) {
+    public void evaluateConfig(ZenModeConfig config, boolean processSubscriptions) {
         if (config == null) return;
         if (config.manualRule != null && config.manualRule.condition != null
                 && !config.manualRule.isTrueOrUnknown()) {
@@ -71,16 +71,16 @@
             config.manualRule = null;
         }
         final ArraySet<Uri> current = new ArraySet<>();
-        evaluateRule(config.manualRule, current, processSubscriptione);
+        evaluateRule(config.manualRule, current, processSubscriptions);
         for (ZenRule automaticRule : config.automaticRules.values()) {
-            evaluateRule(automaticRule, current, processSubscriptione);
+            evaluateRule(automaticRule, current, processSubscriptions);
             updateSnoozing(automaticRule);
         }
         final int N = mSubscriptions.size();
         for (int i = N - 1; i >= 0; i--) {
             final Uri id = mSubscriptions.keyAt(i);
             final ComponentName component = mSubscriptions.valueAt(i);
-            if (processSubscriptione) {
+            if (processSubscriptions) {
                 if (!current.contains(id)) {
                     mConditionProviders.unsubscribeIfNecessary(component, id);
                     mSubscriptions.removeAt(i);
@@ -157,6 +157,11 @@
                 if (DEBUG) Log.d(TAG, "zmc failed to subscribe");
             }
         }
+        if (rule.condition == null) {
+            rule.condition = mConditionProviders.findCondition(rule.component, rule.conditionId);
+            if (rule.condition != null && DEBUG) Log.d(TAG, "Found existing condition for: "
+                    + rule.conditionId);
+        }
     }
 
     private boolean isAutomaticActive(ComponentName component) {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 1860673..702c194 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -46,6 +46,7 @@
 import android.service.notification.ZenModeConfig.ZenRule;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.internal.R;
 import com.android.server.LocalServices;
@@ -77,8 +78,10 @@
     private final ZenModeFiltering mFiltering;
     private final RingerModeDelegate mRingerModeDelegate = new RingerModeDelegate();
     private final ZenModeConditions mConditions;
+    private final SparseArray<ZenModeConfig> mConfigs = new SparseArray<>();
 
     private int mZenMode;
+    private int mUser = UserHandle.USER_OWNER;
     private ZenModeConfig mConfig;
     private AudioManagerInternal mAudioManager;
     private int mPreviousRingerMode = -1;
@@ -92,6 +95,7 @@
         appendDefaultScheduleRules(mDefaultConfig);
         appendDefaultEventRules(mDefaultConfig);
         mConfig = mDefaultConfig;
+        mConfigs.put(UserHandle.USER_OWNER, mConfig);
         mSettingsObserver = new SettingsObserver(mHandler);
         mSettingsObserver.observe();
         mFiltering = new ZenModeFiltering(mContext);
@@ -142,6 +146,25 @@
         }
     }
 
+    public void onUserSwitched(int user) {
+        if (mUser == user || user < UserHandle.USER_OWNER) return;
+        mUser = user;
+        if (DEBUG) Log.d(TAG, "onUserSwitched u=" + user);
+        ZenModeConfig config = mConfigs.get(user);
+        if (config == null) {
+            if (DEBUG) Log.d(TAG, "onUserSwitched: generating default config for user " + user);
+            config = mDefaultConfig.copy();
+            config.user = user;
+        }
+        setConfig(config, "onUserSwitched");
+    }
+
+    public void onUserRemoved(int user) {
+        if (user < UserHandle.USER_OWNER) return;
+        if (DEBUG) Log.d(TAG, "onUserRemoved u=" + user);
+        mConfigs.remove(user);
+    }
+
     public void requestZenModeConditions(IConditionListener callback, int relevance) {
         mConditions.requestConditions(callback, relevance);
     }
@@ -200,8 +223,13 @@
     public void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("mZenMode=");
         pw.println(Global.zenModeToString(mZenMode));
-        dump(pw, prefix, "mConfig", mConfig);
         dump(pw, prefix, "mDefaultConfig", mDefaultConfig);
+        final int N = mConfigs.size();
+        for (int i = 0; i < N; i++) {
+            dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i));
+        }
+        pw.print(prefix); pw.print("mUser="); pw.println(mUser);
+        dump(pw, prefix, "mConfig", mConfig);
         pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
         pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
         mFiltering.dump(pw, prefix);
@@ -237,7 +265,10 @@
     }
 
     public void writeXml(XmlSerializer out) throws IOException {
-        mConfig.writeXml(out);
+        final int N = mConfigs.size();
+        for (int i = 0; i < N; i++) {
+            mConfigs.valueAt(i).writeXml(out);
+        }
     }
 
     public Policy getNotificationPolicy() {
@@ -268,10 +299,17 @@
             Log.w(TAG, "Invalid config in setConfig; " + config);
             return false;
         }
+        if (config.user != mUser) {
+            // simply store away for background users
+            mConfigs.put(config.user, config);
+            if (DEBUG) Log.d(TAG, "setConfig: store config for user " + config.user);
+            return true;
+        }
         mConditions.evaluateConfig(config, false /*processSubscriptions*/);  // may modify config
+        mConfigs.put(config.user, config);
         if (config.equals(mConfig)) return true;
         if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
-        ZenLog.traceConfig(reason, config);
+        ZenLog.traceConfig(reason, mConfig, config);
         final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
                 getNotificationPolicy(config));
         mConfig = config;