Merge "Add OWNERS in packages/providers/CalendarProvider" am: 8da754fcd1
am: 5de0d41103

Change-Id: Ib30553c84f31876441ca382f9a2918c52c4bbad1
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9e92336..255f39a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -18,6 +18,8 @@
         package="com.android.providers.calendar"
         android:sharedUserId="android.uid.calendar">
 
+    <uses-sdk android:targetSdkVersion="25" android:minSdkVersion="25"/> <!-- TODO Remove it -->
+
     <uses-permission android:name="android.permission.READ_CALENDAR" />
     <uses-permission android:name="android.permission.WRITE_CALENDAR" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
@@ -52,13 +54,6 @@
                 android:readPermission="android.permission.READ_CALENDAR"
                 android:writePermission="android.permission.WRITE_CALENDAR" />
 
-        <!-- This is used to keep the provider alive long enough to send update
-             intent broadcasts. -->
-        <service android:name=".EmptyService" />
-        <!-- This is used to keep the provider alive long enough to clean up scheduled
-             alarms after boot. -->
-        <service android:name=".CalendarReceiver$RemoveScheduledAlarmsEmptyService" />
-
         <activity android:name="CalendarContentProviderTests" android:label="Calendar Content Provider"
                 android:exported="false">
             <intent-filter>
@@ -113,7 +108,6 @@
                 <data android:scheme="content" />
             </intent-filter>
         </receiver>
-        <service android:name="CalendarProviderIntentService"/>
 
     </application>
 </manifest>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az/strings.xml
similarity index 100%
rename from res/values-az-rAZ/strings.xml
rename to res/values-az/strings.xml
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn/strings.xml
similarity index 100%
rename from res/values-bn-rBD/strings.xml
rename to res/values-bn/strings.xml
diff --git a/res/values-et-rEE/strings.xml b/res/values-et/strings.xml
similarity index 100%
rename from res/values-et-rEE/strings.xml
rename to res/values-et/strings.xml
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu/strings.xml
similarity index 100%
rename from res/values-eu-rES/strings.xml
rename to res/values-eu/strings.xml
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl/strings.xml
similarity index 100%
rename from res/values-gl-rES/strings.xml
rename to res/values-gl/strings.xml
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu/strings.xml
similarity index 100%
rename from res/values-gu-rIN/strings.xml
rename to res/values-gu/strings.xml
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy/strings.xml
similarity index 100%
rename from res/values-hy-rAM/strings.xml
rename to res/values-hy/strings.xml
diff --git a/res/values-is-rIS/strings.xml b/res/values-is/strings.xml
similarity index 100%
rename from res/values-is-rIS/strings.xml
rename to res/values-is/strings.xml
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka/strings.xml
similarity index 100%
rename from res/values-ka-rGE/strings.xml
rename to res/values-ka/strings.xml
diff --git a/res/values-km-rKH/strings.xml b/res/values-km/strings.xml
similarity index 100%
rename from res/values-km-rKH/strings.xml
rename to res/values-km/strings.xml
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn/strings.xml
similarity index 100%
rename from res/values-kn-rIN/strings.xml
rename to res/values-kn/strings.xml
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo/strings.xml
similarity index 100%
rename from res/values-lo-rLA/strings.xml
rename to res/values-lo/strings.xml
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml/strings.xml
similarity index 100%
rename from res/values-ml-rIN/strings.xml
rename to res/values-ml/strings.xml
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn/strings.xml
similarity index 100%
rename from res/values-mn-rMN/strings.xml
rename to res/values-mn/strings.xml
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr/strings.xml
similarity index 100%
rename from res/values-mr-rIN/strings.xml
rename to res/values-mr/strings.xml
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms/strings.xml
similarity index 100%
rename from res/values-ms-rMY/strings.xml
rename to res/values-ms/strings.xml
diff --git a/res/values-my-rMM/strings.xml b/res/values-my/strings.xml
similarity index 100%
rename from res/values-my-rMM/strings.xml
rename to res/values-my/strings.xml
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne/strings.xml
similarity index 100%
rename from res/values-ne-rNP/strings.xml
rename to res/values-ne/strings.xml
diff --git a/res/values-si-rLK/strings.xml b/res/values-si/strings.xml
similarity index 100%
rename from res/values-si-rLK/strings.xml
rename to res/values-si/strings.xml
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta/strings.xml
similarity index 100%
rename from res/values-ta-rIN/strings.xml
rename to res/values-ta/strings.xml
diff --git a/res/values-te-rIN/strings.xml b/res/values-te/strings.xml
similarity index 100%
rename from res/values-te-rIN/strings.xml
rename to res/values-te/strings.xml
diff --git a/src/com/android/providers/calendar/CalendarAlarmManager.java b/src/com/android/providers/calendar/CalendarAlarmManager.java
index ac14713..c77b654 100644
--- a/src/com/android/providers/calendar/CalendarAlarmManager.java
+++ b/src/com/android/providers/calendar/CalendarAlarmManager.java
@@ -30,7 +30,6 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
 import android.os.SystemClock;
 import android.provider.CalendarContract;
 import android.provider.CalendarContract.CalendarAlerts;
@@ -127,10 +126,6 @@
      */
     @VisibleForTesting
     protected Object mAlarmLock;
-    /**
-     * Used to keep the process from getting killed while scheduling alarms
-     */
-    private final WakeLock mScheduleNextAlarmWakeLock;
 
     @VisibleForTesting
     protected Context mContext;
@@ -141,14 +136,6 @@
 
         PowerManager powerManager = (PowerManager) mContext.getSystemService(
                 Context.POWER_SERVICE);
-        // Create a wake lock that will be used when we are actually
-        // scheduling the next alarm
-        mScheduleNextAlarmWakeLock = powerManager.newWakeLock(
-                PowerManager.PARTIAL_WAKE_LOCK, SCHEDULE_NEXT_ALARM_WAKE_LOCK);
-        // We want the Wake Lock to be reference counted (so that we dont
-        // need to take care
-        // about its reference counting)
-        mScheduleNextAlarmWakeLock.setReferenceCounted(true);
     }
 
     protected void initializeWithContext(Context context) {
@@ -158,9 +145,10 @@
         mAlarmLock = new Object();
     }
 
-    private Intent getCheckNextAlarmIntent(boolean removeAlarms) {
+    @VisibleForTesting
+    static Intent getCheckNextAlarmIntent(Context context, boolean removeAlarms) {
         Intent intent = new Intent(CalendarAlarmManager.ACTION_CHECK_NEXT_ALARM);
-        intent.setClass(mContext, CalendarProviderBroadcastReceiver.class);
+        intent.setClass(context, CalendarProviderBroadcastReceiver.class);
         intent.putExtra(KEY_REMOVE_ALARMS, removeAlarms);
         return intent;
     }
@@ -183,7 +171,7 @@
             if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) {
                 Log.d(CalendarProvider2.TAG, "Scheduling check of next Alarm");
             }
-            Intent intent = getCheckNextAlarmIntent(removeAlarms);
+            Intent intent = getCheckNextAlarmIntent(mContext, removeAlarms);
             PendingIntent pending = PendingIntent.getBroadcast(mContext, 0 /* ignored */, intent,
                     PendingIntent.FLAG_NO_CREATE);
             if (pending != null) {
@@ -209,7 +197,7 @@
      * @param triggerTimeMillis Time to run the next alarm check, in milliseconds.
      */
     void scheduleNextAlarmCheck(long triggerTimeMillis) {
-        Intent intent = getCheckNextAlarmIntent(false /* removeAlarms*/);
+        Intent intent = getCheckNextAlarmIntent(mContext, false /* removeAlarms*/);
         PendingIntent pending = PendingIntent.getBroadcast(
                 mContext, 0, intent, PendingIntent.FLAG_NO_CREATE);
         if (pending != null) {
@@ -229,25 +217,6 @@
         setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTimeMillis, pending);
     }
 
-    PowerManager.WakeLock getScheduleNextAlarmWakeLock() {
-        return mScheduleNextAlarmWakeLock;
-    }
-
-    void acquireScheduleNextAlarmWakeLock() {
-        getScheduleNextAlarmWakeLock().acquire();
-    }
-
-    void releaseScheduleNextAlarmWakeLock() {
-        try {
-            getScheduleNextAlarmWakeLock().release();
-        } catch (RuntimeException e) {
-            if (!e.getMessage().startsWith("WakeLock under-locked ")) {
-              throw e;
-            }
-            Log.w(TAG, "WakeLock under-locked ignored.");
-        }
-    }
-
     void rescheduleMissedAlarms() {
         rescheduleMissedAlarms(mContext.getContentResolver());
     }
@@ -260,8 +229,9 @@
      * @param cp2
      */
     void runScheduleNextAlarm(boolean removeAlarms, CalendarProvider2 cp2) {
-        SQLiteDatabase db = cp2.mDb;
+        SQLiteDatabase db = cp2.getWritableDatabase();
         if (db == null) {
+            Log.wtf(CalendarProvider2.TAG, "Unable to get the database.");
             return;
         }
 
@@ -530,6 +500,10 @@
                 + CalendarAlerts.STATE_SCHEDULED, null /* whereArgs */);
     }
 
+    public void set(int type, long triggerAtTime, PendingIntent operation) {
+        mAlarmManager.set(type, triggerAtTime, operation);
+    }
+
     public void setExact(int type, long triggerAtTime, PendingIntent operation) {
         mAlarmManager.setExact(type, triggerAtTime, operation);
     }
diff --git a/src/com/android/providers/calendar/CalendarInstancesHelper.java b/src/com/android/providers/calendar/CalendarInstancesHelper.java
index 19b1faa..1ab5a01 100644
--- a/src/com/android/providers/calendar/CalendarInstancesHelper.java
+++ b/src/com/android/providers/calendar/CalendarInstancesHelper.java
@@ -58,9 +58,9 @@
     }
 
     private static final String TAG = "CalInstances";
-    private CalendarDatabaseHelper mDbHelper;
-    private MetaData mMetaData;
-    private CalendarCache mCalendarCache;
+    private final CalendarDatabaseHelper mDbHelper;
+    private final MetaData mMetaData;
+    private final CalendarCache mCalendarCache;
 
     private static final String SQL_WHERE_GET_EVENTS_ENTRIES =
             "((" + Events.DTSTART + " <= ? AND "
diff --git a/src/com/android/providers/calendar/CalendarProvider2.java b/src/com/android/providers/calendar/CalendarProvider2.java
index a85c45b..55e8c6d 100644
--- a/src/com/android/providers/calendar/CalendarProvider2.java
+++ b/src/com/android/providers/calendar/CalendarProvider2.java
@@ -20,7 +20,9 @@
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.OnAccountsUpdateListener;
+import android.app.AlarmManager;
 import android.app.AppOpsManager;
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.ContentUris;
@@ -37,9 +39,8 @@
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.Handler;
-import android.os.Message;
 import android.os.Process;
+import android.os.SystemClock;
 import android.provider.BaseColumns;
 import android.provider.CalendarContract;
 import android.provider.CalendarContract.Attendees;
@@ -441,28 +442,9 @@
     private Context mContext;
     private ContentResolver mContentResolver;
 
-    private static CalendarProvider2 mInstance;
-
     @VisibleForTesting
     protected CalendarAlarmManager mCalendarAlarm;
 
-    private final Handler mBroadcastHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            Context context = CalendarProvider2.this.mContext;
-            if (msg.what == UPDATE_BROADCAST_MSG) {
-                // Broadcast a provider changed intent
-                doSendUpdateNotification();
-                // Because the handler does not guarantee message delivery in
-                // the case that the provider is killed, we need to make sure
-                // that the provider stays alive long enough to deliver the
-                // notification. This empty service is sufficient to "wedge" the
-                // process until we stop it here.
-                context.stopService(new Intent(context, EmptyService.class));
-            }
-        }
-    };
-
     /**
      * Listens for timezone changes and disk-no-longer-full events
      */
@@ -492,10 +474,6 @@
         return CalendarDatabaseHelper.getInstance(context);
     }
 
-    protected static CalendarProvider2 getInstance() {
-        return mInstance;
-    }
-
     @Override
     public void shutdown() {
         if (mDbHelper != null) {
@@ -520,8 +498,6 @@
     }
 
     private boolean initialize() {
-        mInstance = this;
-
         mContext = getContext();
         mContentResolver = mContext.getContentResolver();
 
@@ -4537,11 +4513,9 @@
     }
 
     /**
-     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
+     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent with a delay.
      * This also provides a timeout, so any calls to this method will be batched
-     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.  The
-     * actual sending of the intent is done in
-     * {@link #doSendUpdateNotification()}.
+     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.
      *
      * TODO add support for eventId
      *
@@ -4550,47 +4524,24 @@
      */
     private void sendUpdateNotification(long eventId,
             boolean callerIsSyncAdapter) {
-        // Are there any pending broadcast requests?
-        if (mBroadcastHandler.hasMessages(UPDATE_BROADCAST_MSG)) {
-            // Delete any pending requests, before requeuing a fresh one
-            mBroadcastHandler.removeMessages(UPDATE_BROADCAST_MSG);
-        } else {
-            // Because the handler does not guarantee message delivery in
-            // the case that the provider is killed, we need to make sure
-            // that the provider stays alive long enough to deliver the
-            // notification. This empty service is sufficient to "wedge" the
-            // process until we stop it here.
-            mContext.startService(new Intent(mContext, EmptyService.class));
-        }
         // We use a much longer delay for sync-related updates, to prevent any
         // receivers from slowing down the sync
-        long delay = callerIsSyncAdapter ?
+        final long delay = callerIsSyncAdapter ?
                 SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS :
                 UPDATE_BROADCAST_TIMEOUT_MILLIS;
-        // Despite the fact that we actually only ever use one message at a time
-        // for now, it is really important to call obtainMessage() to get a
-        // clean instance.  This avoids potentially infinite loops resulting
-        // adding the same instance to the message queue twice, since the
-        // message queue implements its linked list using a field from Message.
-        Message msg = mBroadcastHandler.obtainMessage(UPDATE_BROADCAST_MSG);
-        mBroadcastHandler.sendMessageDelayed(msg, delay);
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "sendUpdateNotification: delay=" + delay);
+        }
+
+        mCalendarAlarm.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + delay,
+                PendingIntent.getBroadcast(mContext, 0, createProviderChangedBroadcast(),
+                        PendingIntent.FLAG_UPDATE_CURRENT));
     }
 
-    /**
-     * This method should not ever be called directly, to prevent sending too
-     * many potentially expensive broadcasts.  Instead, call
-     * {@link #sendUpdateNotification(boolean)} instead.
-     *
-     * @see #sendUpdateNotification(boolean)
-     */
-    private void doSendUpdateNotification() {
-        Intent intent = new Intent(Intent.ACTION_PROVIDER_CHANGED,
-                CalendarContract.CONTENT_URI);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        if (Log.isLoggable(TAG, Log.INFO)) {
-            Log.i(TAG, "Sending notification intent: " + intent);
-        }
-        mContext.sendBroadcast(intent, null);
+    private Intent createProviderChangedBroadcast() {
+        return new Intent(Intent.ACTION_PROVIDER_CHANGED, CalendarContract.CONTENT_URI)
+                .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
     }
 
     private static final int TRANSACTION_QUERY = 0;
diff --git a/src/com/android/providers/calendar/CalendarProviderBroadcastReceiver.java b/src/com/android/providers/calendar/CalendarProviderBroadcastReceiver.java
index af00bfa..10c76c0 100644
--- a/src/com/android/providers/calendar/CalendarProviderBroadcastReceiver.java
+++ b/src/com/android/providers/calendar/CalendarProviderBroadcastReceiver.java
@@ -18,28 +18,56 @@
 
 import android.app.Activity;
 import android.content.BroadcastReceiver;
+import android.content.ContentProvider;
 import android.content.Context;
+import android.content.IContentProvider;
 import android.content.Intent;
 import android.provider.CalendarContract;
 import android.util.Log;
+import android.util.Slog;
 
 public class CalendarProviderBroadcastReceiver extends BroadcastReceiver {
+    private static final String TAG = CalendarProvider2.TAG;
 
     @Override
     public void onReceive(Context context, Intent intent) {
         String action = intent.getAction();
         if (!CalendarAlarmManager.ACTION_CHECK_NEXT_ALARM.equals(action)
                 && !CalendarContract.ACTION_EVENT_REMINDER.equals(action)) {
+            Log.e(TAG, "Received invalid intent: " + intent);
             setResultCode(Activity.RESULT_CANCELED);
             return;
         }
-        final CalendarProvider2 provider = CalendarProvider2.getInstance();
-        // Acquire a wake lock that will be released when the launched Service is doing its work
-        provider.getOrCreateCalendarAlarmManager().acquireScheduleNextAlarmWakeLock();
-        // Set the result code
-        setResultCode(Activity.RESULT_OK);
-        // Launch the Service
-        intent.setClass(context, CalendarProviderIntentService.class);
-        context.startService(intent);
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Received intent: " + intent);
+        }
+        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 CalendarProviderBroadcastReceiver.");
+            return;
+        }
+
+        final CalendarProvider2 provider = (CalendarProvider2) cprovider;
+
+        final PendingResult result = goAsync();
+
+        new Thread(() -> {
+            // Schedule the next alarm. Please be noted that for ACTION_EVENT_REMINDER broadcast,
+            // we never remove scheduled alarms.
+            final boolean removeAlarms = intent
+                    .getBooleanExtra(CalendarAlarmManager.KEY_REMOVE_ALARMS, false);
+            provider.getOrCreateCalendarAlarmManager().runScheduleNextAlarm(removeAlarms, provider);
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Next alarm set.");
+            }
+
+            result.setResultCode(Activity.RESULT_OK);
+            result.finish();
+        }).start();
+
     }
 }
diff --git a/src/com/android/providers/calendar/CalendarProviderIntentService.java b/src/com/android/providers/calendar/CalendarProviderIntentService.java
deleted file mode 100644
index dcaf264..0000000
--- a/src/com/android/providers/calendar/CalendarProviderIntentService.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.providers.calendar;
-
-import android.app.IntentService;
-import android.content.Intent;
-import android.provider.CalendarContract;
-import android.util.Log;
-
-public class CalendarProviderIntentService extends IntentService {
-
-    private static final String TAG = CalendarProvider2.TAG;
-
-    public CalendarProviderIntentService() {
-        super("CalendarProviderIntentService");
-    }
-
-    @Override
-    protected void onHandleIntent(Intent intent) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "Received Intent: " + intent);
-        }
-        final CalendarProvider2 provider = CalendarProvider2.getInstance();
-        final String action = intent.getAction();
-        if (CalendarAlarmManager.ACTION_CHECK_NEXT_ALARM.equals(action)
-                || CalendarContract.ACTION_EVENT_REMINDER.equals(action)) {
-            // Schedule the next alarm. Please be noted that for ACTION_EVENT_REMINDER broadcast,
-            // we never remove scheduled alarms.
-            final boolean removeAlarms = intent
-                    .getBooleanExtra(CalendarAlarmManager.KEY_REMOVE_ALARMS, false);
-            provider.getOrCreateCalendarAlarmManager().runScheduleNextAlarm(removeAlarms, provider);
-            // Release the wake lock that was set in the Broadcast Receiver
-            provider.getOrCreateCalendarAlarmManager().releaseScheduleNextAlarmWakeLock();
-        } else if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "Invalid Intent action: " + action);
-        }
-    }
-}
diff --git a/src/com/android/providers/calendar/CalendarUpgradeReceiver.java b/src/com/android/providers/calendar/CalendarUpgradeReceiver.java
index 9402298..1f69f92 100644
--- a/src/com/android/providers/calendar/CalendarUpgradeReceiver.java
+++ b/src/com/android/providers/calendar/CalendarUpgradeReceiver.java
@@ -16,7 +16,6 @@
 
 package com.android.providers.calendar;
 
-import android.app.ActivityManagerNative;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
diff --git a/src/com/android/providers/calendar/EmptyService.java b/src/com/android/providers/calendar/EmptyService.java
deleted file mode 100644
index 70cf3d4..0000000
--- a/src/com/android/providers/calendar/EmptyService.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.android.providers.calendar;
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-/**
- * Background {@link Service} that is used to keep our process alive long enough
- * for background threads to finish. Started and stopped directly by specific
- * background tasks when needed.
- */
-public class EmptyService extends Service {
-    @Override
-    public IBinder onBind(Intent intent) {
-        return null;
-    }
-}
diff --git a/src/com/android/providers/calendar/SQLiteContentProvider.java b/src/com/android/providers/calendar/SQLiteContentProvider.java
index 53fc7e7..0b18ff4 100644
--- a/src/com/android/providers/calendar/SQLiteContentProvider.java
+++ b/src/com/android/providers/calendar/SQLiteContentProvider.java
@@ -329,4 +329,12 @@
             mOriginalCallingUid.set(null);
         }
     }
+
+    SQLiteDatabase getReadableDatabase() {
+        return mOpenHelper != null ? mOpenHelper.getReadableDatabase() : null;
+    }
+
+    SQLiteDatabase getWritableDatabase() {
+        return mOpenHelper != null ? mOpenHelper.getWritableDatabase() : null;
+    }
 }
diff --git a/tests/Android.mk b/tests/Android.mk
index 97e8d6d..86cac8a 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -8,6 +8,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CalendarProviderTests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 
 LOCAL_STATIC_JAVA_LIBRARIES := calendar-common junit legacy-android-test
 
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
new file mode 100644
index 0000000..eddab0b
--- /dev/null
+++ b/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Run calendar provider tests.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="CalendarProviderTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="CalendarProviderTests" />
+    <test class="com.android.tradefed.testtype.InstrumentationTest" >
+        <option name="package" value="com.android.providers.calendar.tests" />
+        <option name="runner" value="android.test.InstrumentationTestRunner" />
+    </test>
+</configuration>
diff --git a/tests/src/com/android/providers/calendar/CalendarProvider2ForTesting.java b/tests/src/com/android/providers/calendar/CalendarProvider2ForTesting.java
index 4d07bc1..f73168e 100644
--- a/tests/src/com/android/providers/calendar/CalendarProvider2ForTesting.java
+++ b/tests/src/com/android/providers/calendar/CalendarProvider2ForTesting.java
@@ -58,6 +58,10 @@
         }
 
         @Override
+        public void set(int type, long triggerAtTime, PendingIntent operation) {
+        }
+
+        @Override
         public void setExact(int type, long triggerAtTime, PendingIntent operation) {
         }
 
@@ -77,19 +81,5 @@
         @Override
         public void rescheduleMissedAlarms(ContentResolver cr) {
         }
-
-
-        @Override
-        PowerManager.WakeLock getScheduleNextAlarmWakeLock() {
-            return null;
-        }
-
-        @Override
-        void acquireScheduleNextAlarmWakeLock() {
-        }
-
-        @Override
-        void releaseScheduleNextAlarmWakeLock() {
-        }
     }
 }
diff --git a/tests/src/com/android/providers/calendar/CalendarProvider2Test.java b/tests/src/com/android/providers/calendar/CalendarProvider2Test.java
index 8694f6f..52cb8a3 100644
--- a/tests/src/com/android/providers/calendar/CalendarProvider2Test.java
+++ b/tests/src/com/android/providers/calendar/CalendarProvider2Test.java
@@ -2879,9 +2879,6 @@
     public static final Uri PROPERTIES_CONTENT_URI =
             Uri.parse("content://" + CalendarContract.AUTHORITY + "/properties");
 
-    public static final int COLUMN_KEY_INDEX = 0;
-    public static final int COLUMN_VALUE_INDEX = 1;
-
     public void testGetProviderProperties() throws CalendarCache.CacheException {
         CalendarDatabaseHelper helper = (CalendarDatabaseHelper) getProvider().getDatabaseHelper();
         cleanCalendarDataTable(helper);
@@ -2895,14 +2892,13 @@
         Cursor cursor = mResolver.query(PROPERTIES_CONTENT_URI, null, null, null, null);
         assertEquals(4, cursor.getCount());
 
-        assertEquals(CalendarCache.COLUMN_NAME_KEY, cursor.getColumnName(COLUMN_KEY_INDEX));
-        assertEquals(CalendarCache.COLUMN_NAME_VALUE, cursor.getColumnName(COLUMN_VALUE_INDEX));
-
+        final int keyColumnIndex = cursor.getColumnIndex(CalendarCache.COLUMN_NAME_KEY);
+        final int valueColumnIndex = cursor.getColumnIndex(CalendarCache.COLUMN_NAME_VALUE);
         Map<String, String> map = new HashMap<String, String>();
 
         while (cursor.moveToNext()) {
-            String key = cursor.getString(COLUMN_KEY_INDEX);
-            String value = cursor.getString(COLUMN_VALUE_INDEX);
+            String key = cursor.getString(keyColumnIndex);
+            String value = cursor.getString(valueColumnIndex);
             map.put(key, value);
         }
 
@@ -2941,8 +2937,10 @@
 
         assertEquals(1, cursor.getCount());
         assertTrue(cursor.moveToFirst());
-        assertEquals(cursor.getString(COLUMN_KEY_INDEX), key);
-        assertEquals(cursor.getString(COLUMN_VALUE_INDEX), value);
+        assertEquals(cursor.getString(cursor.getColumnIndex(CalendarCache.COLUMN_NAME_KEY)),
+                key);
+        assertEquals(cursor.getString(cursor.getColumnIndex(CalendarCache.COLUMN_NAME_VALUE)),
+                value);
 
         cursor.close();
     }
diff --git a/tests/src/com/android/providers/calendar/CalendarProviderBroadcastReceiverTest.java b/tests/src/com/android/providers/calendar/CalendarProviderBroadcastReceiverTest.java
new file mode 100644
index 0000000..09a19f5
--- /dev/null
+++ b/tests/src/com/android/providers/calendar/CalendarProviderBroadcastReceiverTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.calendar;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@LargeTest
+public class CalendarProviderBroadcastReceiverTest extends AndroidTestCase {
+    /**
+     * Actually send the broadcast and make sure the provider won't crash.
+     */
+    public void testBroadcastToRealProvider() throws Exception {
+        final Intent intent = CalendarAlarmManager.getCheckNextAlarmIntent(getContext(),
+                /* removeAlarms=*/ false);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+        final AtomicInteger resultCodeReceiver = new AtomicInteger(Integer.MIN_VALUE);
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        getContext().sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                resultCodeReceiver.set(getResultCode());
+
+                latch.countDown();
+            }
+        }, null, 0, null, null);
+
+        assertTrue("Didn't receive the result.", latch.await(1, TimeUnit.MINUTES));
+        assertEquals(Activity.RESULT_OK, resultCodeReceiver.get());
+    }
+}