Use repeated alarm to check new events

Otherwise if the provider crashes when it receives a "check next event"
before it sets a next alarm, we'd be in trouble.



Change-Id: Ic0193fadfc07b065ef47b7a5aceb2b3d36c04b6b
Fixes: 79418474
Test: Verified behavior with "setprop debug.calendar.check_interval 5000"
diff --git a/src/com/android/providers/calendar/CalendarAlarmManager.java b/src/com/android/providers/calendar/CalendarAlarmManager.java
index 9e0bb5f..7019797 100644
--- a/src/com/android/providers/calendar/CalendarAlarmManager.java
+++ b/src/com/android/providers/calendar/CalendarAlarmManager.java
@@ -140,10 +140,16 @@
     static Intent getCheckNextAlarmIntent(Context context, boolean removeAlarms) {
         Intent intent = new Intent(CalendarAlarmManager.ACTION_CHECK_NEXT_ALARM);
         intent.setClass(context, CalendarProviderBroadcastReceiver.class);
-        intent.putExtra(KEY_REMOVE_ALARMS, removeAlarms);
+        if (removeAlarms) {
+            intent.putExtra(KEY_REMOVE_ALARMS, true);
+        }
         return intent;
     }
 
+    public static Intent getCheckNextAlarmIntentForBroadcast(Context context) {
+        return getCheckNextAlarmIntent(context, false);
+    }
+
     /**
      * Called by CalendarProvider to check the next alarm. A small delay is added before the real
      * checking happens in order to batch the requests.
@@ -179,37 +185,11 @@
         }
     }
 
-    /**
-     * Similar to {@link #checkNextAlarm}, but schedule the checking at specific {@code
-     * triggerTime}. In general, we do not need an alarm for scheduling. Instead we set the next
-     * alarm check immediately when a reminder is shown. The only use case for this
-     * is to schedule the next alarm check when there is no reminder within 1 day.
-     *
-     * @param triggerTimeMillis Time to run the next alarm check, in milliseconds.
-     */
-    void scheduleNextAlarmCheck(long triggerTimeMillis) {
-        Intent intent = getCheckNextAlarmIntent(mContext, false /* removeAlarms*/);
-        PendingIntent pending = PendingIntent.getBroadcast(
-                mContext, 0, intent, PendingIntent.FLAG_NO_CREATE);
-        if (pending != null) {
-            // Cancel any previous alarms that do the same thing.
-            cancel(pending);
-        }
-        pending = PendingIntent.getBroadcast(
-                mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
-
-        if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) {
-            Time time = new Time();
-            time.set(triggerTimeMillis);
-            String timeStr = time.format(" %a, %b %d, %Y %I:%M%P");
-            Log.d(CalendarProvider2.TAG,
-                    "scheduleNextAlarmCheck at: " + triggerTimeMillis + timeStr);
-        }
-        setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTimeMillis, pending);
-    }
-
-    void scheduleNextAlarmCheckRightNow() {
-        scheduleNextAlarmCheck(System.currentTimeMillis());
+    static void checkNextAlarmCheckRightNow(Context context) {
+        // We should probably call scheduleNextAlarmLocked() directly but we don't want
+        // to mix java synchronization and DB transactions that might cause deadlocks, so we
+        // just send a broadcast to serialize all the calls.
+        context.sendBroadcast(getCheckNextAlarmIntentForBroadcast(context));
     }
 
     void rescheduleMissedAlarms() {
@@ -474,14 +454,8 @@
         }
 
         // No event alarm is scheduled, check again in 24 hours - 15
-        // minutes. Scheduling the check 15 minutes earlier than 24
-        // hours to prevent the scheduler alarm from using up the
-        // alarms quota for reminders during dozing. If a new event is
-        // inserted before the next alarm check, then this method will
-        // be run again when the new event is inserted.
-        if (!alarmScheduled) {
-            scheduleNextAlarmCheck(currentMillis + NEXT_ALARM_CHECK_TIME_MS);
-        }
+        // minutes.
+        // We have a repeated alarm to check the next even every N hours, so nothing to do here.
     }
 
     /**
diff --git a/src/com/android/providers/calendar/CalendarReceiver.java b/src/com/android/providers/calendar/CalendarReceiver.java
index d1d8a5a..55b75e0 100644
--- a/src/com/android/providers/calendar/CalendarReceiver.java
+++ b/src/com/android/providers/calendar/CalendarReceiver.java
@@ -16,15 +16,17 @@
 
 package com.android.providers.calendar;
 
+import android.app.AlarmManager;
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.os.PowerManager;
+import android.os.SystemProperties;
 import android.util.Log;
 
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 /**
  * This IntentReceiver executes when the boot completes and ensures that
@@ -34,13 +36,26 @@
  * yet.
  */
 public class CalendarReceiver extends BroadcastReceiver {
-    private static final String TAG = "CalendarReceiver";
+    private static final String TAG = CalendarProvider2.TAG;
 
-    private final ExecutorService executor = Executors.newCachedThreadPool();
+    private static final long NEXT_EVENT_CHECK_INTERVAL =
+            SystemProperties.getLong("debug.calendar.check_interval", TimeUnit.HOURS.toMillis(6));
+    private static final int NEXT_EVENT_CHECK_PENDING_CODE = 100;
+
     private PowerManager.WakeLock mWakeLock;
 
     @Override
     public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+
+        if (!Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+            Log.w(TAG, "Unexpected broadcast: " + action);
+            return;
+        }
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "BOOT_COMPLETED");
+        }
+
         if (mWakeLock == null) {
             PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
             mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "CalendarReceiver_Provider");
@@ -48,19 +63,15 @@
         }
         mWakeLock.acquire();
 
-        final String action = intent.getAction();
         final ContentResolver cr = context.getContentResolver();
         final PendingResult result = goAsync();
-        executor.submit(new Runnable() {
-            @Override
-            public void run() {
-                if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
-                    removeScheduledAlarms(cr);
-                }
-                result.finish();
-                mWakeLock.release();
-            }
-        });
+
+        new Thread(() -> {
+            setCalendarCheckAlarm(context);
+            removeScheduledAlarms(cr);
+            result.finish();
+            mWakeLock.release();
+        }).start();
     }
 
     /*
@@ -75,10 +86,19 @@
      * worry about serializing the use of the service.
      */
     private void removeScheduledAlarms(ContentResolver resolver) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "Removing scheduled alarms");
-        }
         resolver.update(CalendarAlarmManager.SCHEDULE_ALARM_REMOVE_URI, null /* values */,
                 null /* where */, null /* selectionArgs */);
     }
+
+    private static void setCalendarCheckAlarm(Context context) {
+        final PendingIntent checkIntent = PendingIntent.getBroadcast(context,
+                NEXT_EVENT_CHECK_PENDING_CODE,
+                CalendarAlarmManager.getCheckNextAlarmIntentForBroadcast(context),
+                PendingIntent.FLAG_UPDATE_CURRENT);
+
+        final AlarmManager am = context.getSystemService(AlarmManager.class);
+
+        am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                NEXT_EVENT_CHECK_INTERVAL, NEXT_EVENT_CHECK_INTERVAL, checkIntent);
+    }
 }
diff --git a/src/com/android/providers/calendar/CalendarSanityChecker.java b/src/com/android/providers/calendar/CalendarSanityChecker.java
index f6f9b21..19cb5b1 100644
--- a/src/com/android/providers/calendar/CalendarSanityChecker.java
+++ b/src/com/android/providers/calendar/CalendarSanityChecker.java
@@ -73,26 +73,9 @@
     @VisibleForTesting
     final SharedPreferences mPrefs;
 
-    @Nullable // only null when initialization failed.
-    private final CalendarProvider2 mCalendarProvider2;
-
     protected CalendarSanityChecker(Context context) {
         mContext = context;
         mPrefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
-        mCalendarProvider2 = getProvider(mContext);
-    }
-
-    protected static CalendarProvider2 getProvider(Context context) {
-        final IContentProvider iprovider =
-                context.getContentResolver().acquireProvider(CalendarContract.AUTHORITY);
-        final ContentProvider cprovider = ContentProvider.coerceToLocalContentProvider(iprovider);
-
-        if (!(cprovider instanceof CalendarProvider2)) {
-            Slog.wtf(TAG, "CalendarProvider2 not found in CalendarSanityChecker.");
-            return null;
-        }
-
-        return (CalendarProvider2) cprovider;
     }
 
     @VisibleForTesting
@@ -141,7 +124,7 @@
 
     /**
      * Call this at public entry points. This will check if the last check time was recent enough,
-     * and otherwise it'll call {@link CalendarAlarmManager#scheduleNextAlarmCheckRightNow()}.
+     * and otherwise it'll call {@link CalendarAlarmManager#checkNextAlarmCheckRightNow}.
      */
     public final boolean checkLastCheckTime() {
         final long lastBootCount;
@@ -202,10 +185,7 @@
                     .apply();
 
             // Note mCalendarProvider2 really shouldn't be null.
-            if (mCalendarProvider2 != null) {
-                mCalendarProvider2.getOrCreateCalendarAlarmManager()
-                        .scheduleNextAlarmCheckRightNow();
-            }
+            CalendarAlarmManager.checkNextAlarmCheckRightNow(mContext);
         }
         return false;
     }