Merge "Zen: Exit downtime on next alarm (if mode=none)." into lmp-mr1-dev
diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
index 24fd155..0fb5732 100644
--- a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
+++ b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
@@ -18,6 +18,7 @@
 
 import android.app.AlarmManager;
 import android.app.PendingIntent;
+import android.app.AlarmManager.AlarmClockInfo;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -63,8 +64,10 @@
     private final Calendar mCalendar = Calendar.getInstance();
     private final Context mContext = this;
     private final ArraySet<Integer> mDays = new ArraySet<Integer>();
+    private final ArraySet<Long> mFiredAlarms = new ArraySet<Long>();
 
     private boolean mConnected;
+    private NextAlarmTracker mTracker;
     private int mDowntimeMode;
     private ZenModeConfig mConfig;
     private Callback mCallback;
@@ -77,6 +80,7 @@
         pw.println("    DowntimeConditionProvider:");
         pw.print("      mConnected="); pw.println(mConnected);
         pw.print("      mDowntimeMode="); pw.println(Global.zenModeToString(mDowntimeMode));
+        pw.print("      mFiredAlarms="); pw.println(mFiredAlarms);
     }
 
     public void attachBase(Context base) {
@@ -101,12 +105,15 @@
         filter.addAction(Intent.ACTION_TIME_CHANGED);
         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
         mContext.registerReceiver(mReceiver, filter);
+        mTracker = mCallback.getNextAlarmTracker();
+        mTracker.addCallback(mTrackerCallback);
         init();
     }
 
     @Override
     public void onDestroy() {
         if (DEBUG) Slog.d(TAG, "onDestroy");
+        mTracker.removeCallback(mTrackerCallback);
         mConnected = false;
     }
 
@@ -183,35 +190,52 @@
         }
     }
 
-    private int computeDowntimeMode(long time) {
-        if (mConfig == null || mDays.size() == 0) return Global.ZEN_MODE_OFF;
+    private boolean isInDowntime(long time) {
+        if (mConfig == null || mDays.size() == 0) return false;
         final long start = getTime(time, mConfig.sleepStartHour, mConfig.sleepStartMinute);
         long end = getTime(time, mConfig.sleepEndHour, mConfig.sleepEndMinute);
-        if (start == end) return Global.ZEN_MODE_OFF;
+        if (start == end) return false;
         if (end < start) {
             end = addDays(end, 1);
         }
-        final boolean inDowntime = isInDowntime(-1, time, start, end)
-                || isInDowntime(0, time, start, end);
-        return inDowntime ? (mConfig.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
-                : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) : Global.ZEN_MODE_OFF;
+        final boolean orAlarm = mConfig.sleepNone;
+        return isInDowntime(-1, time, start, end, orAlarm)
+                || isInDowntime(0, time, start, end, orAlarm);
     }
 
-    private boolean isInDowntime(int daysOffset, long time, long start, long end) {
+    private boolean isInDowntime(int daysOffset, long time, long start, long end, boolean orAlarm) {
         final int n = Calendar.SATURDAY;
         final int day = ((getDayOfWeek(time) - 1) + (daysOffset % n) + n) % n + 1;
         start = addDays(start, daysOffset);
         end = addDays(end, daysOffset);
+        if (orAlarm) {
+            end = findFiredAlarm(start, end);
+        }
         return mDays.contains(day) && time >= start && time < end;
     }
 
+    private long findFiredAlarm(long start, long end) {
+        final int N = mFiredAlarms.size();
+        for (int i = 0; i < N; i++) {
+            final long firedAlarm = mFiredAlarms.valueAt(i);
+            if (firedAlarm > start && firedAlarm < end) {
+                return firedAlarm;
+            }
+        }
+        return end;
+    }
+
     private void reevaluateDowntime() {
-        final int downtimeMode = computeDowntimeMode(System.currentTimeMillis());
+        final long now = System.currentTimeMillis();
+        final boolean inDowntimeNow = isInDowntime(now);
+        final int downtimeMode = inDowntimeNow ? (mConfig.sleepNone
+                ? Global.ZEN_MODE_NO_INTERRUPTIONS : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+                : Global.ZEN_MODE_OFF;
         if (DEBUG) Slog.d(TAG, "downtimeMode=" + downtimeMode);
         if (downtimeMode == mDowntimeMode) return;
         mDowntimeMode = downtimeMode;
         Slog.i(TAG, (isInDowntime() ? "Entering" : "Exiting" ) + " downtime");
-        ZenLog.traceDowntime(mDowntimeMode, getDayOfWeek(System.currentTimeMillis()), mDays);
+        ZenLog.traceDowntime(mDowntimeMode, getDayOfWeek(now), mDays);
         fireDowntimeChanged();
     }
 
@@ -266,8 +290,8 @@
                 PendingIntent.FLAG_UPDATE_CURRENT);
         alarms.cancel(pendingIntent);
         if (mConfig.sleepMode != null) {
-            if (DEBUG) Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s",
-                    action, ts(time), time - now, ts(now)));
+            if (DEBUG) Slog.d(TAG, String.format("Scheduling %s for %s, in %s, now=%s",
+                    action, ts(time), NextAlarmTracker.formatDuration(time - now), ts(now)));
             alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
         }
     }
@@ -276,6 +300,30 @@
         return new Date(time) + " (" + time + ")";
     }
 
+    private void onEvaluateNextAlarm(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
+        if (!booted) return;  // we don't know yet
+        if (nextAlarm == null) return;  // not fireable
+        if (DEBUG) Slog.d(TAG, "onEvaluateNextAlarm " + mTracker.formatAlarmDebug(nextAlarm));
+        if (System.currentTimeMillis() > wakeupTime) {
+            if (DEBUG) Slog.d(TAG, "Alarm fired: " + mTracker.formatAlarmDebug(wakeupTime));
+            trimFiredAlarms();
+            mFiredAlarms.add(wakeupTime);
+        }
+        reevaluateDowntime();
+    }
+
+    private void trimFiredAlarms() {
+        // remove fired alarms over 2 days old
+        final long keepAfter = System.currentTimeMillis() - 2 * 24 * 60 * 60 * 1000;
+        final int N = mFiredAlarms.size();
+        for (int i = N - 1; i >= 0; i--) {
+            final long firedAlarm = mFiredAlarms.valueAt(i);
+            if (firedAlarm < keepAfter) {
+                mFiredAlarms.removeAt(i);
+            }
+        }
+    }
+
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -296,6 +344,13 @@
         }
     };
 
+    private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
+        @Override
+        public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
+            DowntimeConditionProvider.this.onEvaluateNextAlarm(nextAlarm, wakeupTime, booted);
+        }
+    };
+
     public interface Callback {
         void onDowntimeChanged(int downtimeMode);
         NextAlarmTracker getNextAlarmTracker();
diff --git a/services/core/java/com/android/server/notification/NextAlarmTracker.java b/services/core/java/com/android/server/notification/NextAlarmTracker.java
index d197afd..234f545 100644
--- a/services/core/java/com/android/server/notification/NextAlarmTracker.java
+++ b/services/core/java/com/android/server/notification/NextAlarmTracker.java
@@ -176,7 +176,7 @@
         return true;
     }
 
-    private static String formatDuration(long millis) {
+    public static String formatDuration(long millis) {
         final StringBuilder sb = new StringBuilder();
         TimeUtils.formatDuration(millis, sb);
         return sb.toString();
@@ -196,7 +196,7 @@
         return DateFormat.format(pattern, time).toString();
     }
 
-    private String formatAlarmDebug(AlarmClockInfo alarm) {
+    public String formatAlarmDebug(AlarmClockInfo alarm) {
         return formatAlarmDebug(alarm != null ? alarm.getTriggerTime() : 0);
     }