Snooze schedule rules that were canceled by alarms.

So a reboot or snoozed alarm doesn't cause them to
reactivate.

Bug: 30087850
Change-Id: I83fdb88009b515d442993944aec40df7365e830f
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 69960b0..e90a3ba 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -44,6 +44,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
+import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.Locale;
 import java.util.Objects;
@@ -896,9 +897,13 @@
                     ", endHour=" + endHour +
                     ", endMinute=" + endMinute +
                     ", exitAtAlarm=" + exitAtAlarm +
-                    ", nextAlarm=" + nextAlarm +
+                    ", nextAlarm=" + ts(nextAlarm) +
                     '}';
         }
+
+        protected static String ts(long time) {
+            return new Date(time) + " (" + time + ")";
+        }
     }
 
     // ==== Built-in system condition: event ====
diff --git a/services/core/java/com/android/server/notification/ScheduleCalendar.java b/services/core/java/com/android/server/notification/ScheduleCalendar.java
index 22ca702..9e8b2e3 100644
--- a/services/core/java/com/android/server/notification/ScheduleCalendar.java
+++ b/services/core/java/com/android/server/notification/ScheduleCalendar.java
@@ -82,15 +82,13 @@
         if (end <= start) {
             end = addDays(end, 1);
         }
-        boolean isInSchedule =
-                isInSchedule(-1, time, start, end) || isInSchedule(0, time, start, end);
-        if (isInSchedule && mSchedule.exitAtAlarm
+        return isInSchedule(-1, time, start, end) || isInSchedule(0, time, start, end);
+    }
+
+    public boolean shouldExitForAlarm(long time) {
+        return mSchedule.exitAtAlarm
                 && mSchedule.nextAlarm != 0
-                && time >= mSchedule.nextAlarm) {
-            return false;
-        } else {
-            return isInSchedule;
-        }
+                && time >= mSchedule.nextAlarm;
     }
 
     private boolean isInSchedule(int daysOffset, long time, long start, long end) {
diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
index 8197544..32d03ce 100644
--- a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
+++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
@@ -25,11 +25,15 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.Uri;
+import android.os.Binder;
+import android.provider.Settings;
 import android.service.notification.Condition;
 import android.service.notification.IConditionProvider;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 
@@ -53,9 +57,13 @@
     private static final String ACTION_EVALUATE =  SIMPLE_NAME + ".EVALUATE";
     private static final int REQUEST_CODE_EVALUATE = 1;
     private static final String EXTRA_TIME = "time";
+    private static final String SEPARATOR = ";";
+    private static final String SCP_SETTING = "snoozed_schedule_condition_provider";
+
 
     private final Context mContext = this;
     private final ArrayMap<Uri, ScheduleCalendar> mSubscriptions = new ArrayMap<>();
+    private ArraySet<Uri> mSnoozed = new ArraySet<>();
 
     private AlarmManager mAlarmManager;
     private boolean mConnected;
@@ -90,6 +98,7 @@
             pw.print("            ");
             pw.println(mSubscriptions.get(conditionId).toString());
         }
+        pw.println("      snoozed due to alarm: " + TextUtils.join(SEPARATOR, mSnoozed));
         dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, now);
     }
 
@@ -97,6 +106,7 @@
     public void onConnected() {
         if (DEBUG) Slog.d(TAG, "onConnected");
         mConnected = true;
+        readSnoozed();
     }
 
     @Override
@@ -126,6 +136,7 @@
     public void onUnsubscribe(Uri conditionId) {
         if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
         mSubscriptions.remove(conditionId);
+        removeSnoozed(conditionId);
         evaluateSubscriptions();
     }
 
@@ -150,10 +161,16 @@
         for (Uri conditionId : mSubscriptions.keySet()) {
             final ScheduleCalendar cal = mSubscriptions.get(conditionId);
             if (cal != null && cal.isInSchedule(now)) {
-                notifyCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule");
+                if (conditionSnoozed(conditionId) || cal.shouldExitForAlarm(now)) {
+                    notifyCondition(conditionId, Condition.STATE_FALSE, "alarmCanceled");
+                    addSnoozed(conditionId);
+                } else {
+                    notifyCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule");
+                }
                 cal.maybeSetNextAlarm(now, nextUserAlarmTime);
             } else {
                 notifyCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule");
+                removeSnoozed(conditionId);
                 if (nextUserAlarmTime == 0) {
                     cal.maybeSetNextAlarm(now, nextUserAlarmTime);
                 }
@@ -194,7 +211,7 @@
         return info != null ? info.getTriggerTime() : 0;
     }
 
-    private static boolean meetsSchedule(ScheduleCalendar cal, long time) {
+    private boolean meetsSchedule(ScheduleCalendar cal, long time) {
         return cal != null && cal.isInSchedule(time);
     }
 
@@ -237,6 +254,62 @@
         return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS);
     }
 
+    private boolean conditionSnoozed(Uri conditionId) {
+        synchronized (mSnoozed) {
+            return mSnoozed.contains(conditionId);
+        }
+    }
+
+    private void addSnoozed(Uri conditionId) {
+        synchronized (mSnoozed) {
+            mSnoozed.add(conditionId);
+            saveSnoozedLocked();
+        }
+    }
+
+    private void removeSnoozed(Uri conditionId) {
+        synchronized (mSnoozed) {
+            mSnoozed.remove(conditionId);
+            saveSnoozedLocked();
+        }
+    }
+
+    public void saveSnoozedLocked() {
+        final String setting = TextUtils.join(SEPARATOR, mSnoozed);
+        final int currentUser = ActivityManager.getCurrentUser();
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                SCP_SETTING,
+                setting,
+                currentUser);
+    }
+
+    public void readSnoozed() {
+        synchronized (mSnoozed) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                final String setting = Settings.Secure.getStringForUser(
+                        mContext.getContentResolver(),
+                        SCP_SETTING,
+                        ActivityManager.getCurrentUser());
+                if (setting != null) {
+                    final String[] tokens = setting.split(SEPARATOR);
+                    for (int i = 0; i < tokens.length; i++) {
+                        String token = tokens[i];
+                        if (token != null) {
+                            token = token.trim();
+                        }
+                        if (TextUtils.isEmpty(token)) {
+                            continue;
+                        }
+                        mSnoozed.add(Uri.parse(token));
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {