summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2018-01-10 08:26:43 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2018-01-10 08:26:43 +0000
commit73e9f5cfe89b2b8c45deb6215ee7593e98ce281f (patch)
treedebbba3a30da9182f9c644e8fa41afda6a37d477
parente8cbaa73a052d07d0c997dab6a0a9e1175063dc7 (diff)
parentd2fc86d779a667624fd576c6e1f107e4c8339873 (diff)
downloadandroid_packages_providers_CalendarProvider-73e9f5cfe89b2b8c45deb6215ee7593e98ce281f.tar.gz
android_packages_providers_CalendarProvider-73e9f5cfe89b2b8c45deb6215ee7593e98ce281f.tar.bz2
android_packages_providers_CalendarProvider-73e9f5cfe89b2b8c45deb6215ee7593e98ce281f.zip
Snap for 4535700 from d2fc86d779a667624fd576c6e1f107e4c8339873 to pi-release
Change-Id: I2df826156640cdf2442e92414bad5c04ca858ad1
-rw-r--r--AndroidManifest.xml1
-rw-r--r--src/com/android/providers/calendar/CalendarAlarmManager.java15
-rw-r--r--src/com/android/providers/calendar/CalendarProvider2.java8
-rw-r--r--src/com/android/providers/calendar/CalendarSanityChecker.java218
-rw-r--r--tests/src/com/android/providers/calendar/CalendarSanityCheckerTest.java128
5 files changed, 366 insertions, 4 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 255f39a..c4ee113 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -38,6 +38,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+ <uses-permission android:name="android.permission.USE_RESERVED_DISK" />
<application android:label="@string/calendar_storage"
android:allowBackup="false"
diff --git a/src/com/android/providers/calendar/CalendarAlarmManager.java b/src/com/android/providers/calendar/CalendarAlarmManager.java
index 8586e6b..9e0bb5f 100644
--- a/src/com/android/providers/calendar/CalendarAlarmManager.java
+++ b/src/com/android/providers/calendar/CalendarAlarmManager.java
@@ -106,6 +106,10 @@ public class CalendarAlarmManager {
"com.android.providers.calendar.intent.CalendarProvider2";
static final int ALARM_CHECK_DELAY_MILLIS = 5000;
+ /** 24 hours - 15 minutes. */
+ static final long NEXT_ALARM_CHECK_TIME_MS = DateUtils.DAY_IN_MILLIS -
+ (15 * DateUtils.MINUTE_IN_MILLIS);
+
/**
* Used for tracking if the next alarm is already scheduled
*/
@@ -123,9 +127,6 @@ public class CalendarAlarmManager {
public CalendarAlarmManager(Context context) {
initializeWithContext(context);
-
- PowerManager powerManager = (PowerManager) mContext.getSystemService(
- Context.POWER_SERVICE);
}
protected void initializeWithContext(Context context) {
@@ -207,6 +208,10 @@ public class CalendarAlarmManager {
setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTimeMillis, pending);
}
+ void scheduleNextAlarmCheckRightNow() {
+ scheduleNextAlarmCheck(System.currentTimeMillis());
+ }
+
void rescheduleMissedAlarms() {
rescheduleMissedAlarms(mContext.getContentResolver());
}
@@ -264,6 +269,8 @@ public class CalendarAlarmManager {
* @param cp2 TODO
*/
private void scheduleNextAlarmLocked(SQLiteDatabase db, CalendarProvider2 cp2) {
+ CalendarSanityChecker.getInstance(mContext).updateLastCheckTime();
+
Time time = new Time();
final long currentMillis = System.currentTimeMillis();
@@ -473,7 +480,7 @@ public class CalendarAlarmManager {
// inserted before the next alarm check, then this method will
// be run again when the new event is inserted.
if (!alarmScheduled) {
- scheduleNextAlarmCheck(end - (15 * DateUtils.MINUTE_IN_MILLIS));
+ scheduleNextAlarmCheck(currentMillis + NEXT_ALARM_CHECK_TIME_MS);
}
}
diff --git a/src/com/android/providers/calendar/CalendarProvider2.java b/src/com/android/providers/calendar/CalendarProvider2.java
index 55e8c6d..91d247d 100644
--- a/src/com/android/providers/calendar/CalendarProvider2.java
+++ b/src/com/android/providers/calendar/CalendarProvider2.java
@@ -805,6 +805,8 @@ public class CalendarProvider2 extends SQLiteContentProvider implements OnAccoun
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ CalendarSanityChecker.getInstance(mContext).checkLastCheckTime();
+
final long identity = clearCallingIdentityInternal();
try {
return queryInternal(uri, projection, selection, selectionArgs, sortOrder);
@@ -2076,6 +2078,8 @@ public class CalendarProvider2 extends SQLiteContentProvider implements OnAccoun
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "insertInTransaction: " + uri);
}
+ CalendarSanityChecker.getInstance(mContext).checkLastCheckTime();
+
validateUriParameters(uri.getQueryParameterNames());
final int match = sUriMatcher.match(uri);
verifyTransactionAllowed(TRANSACTION_INSERT, uri, values, callerIsSyncAdapter, match,
@@ -3053,6 +3057,8 @@ public class CalendarProvider2 extends SQLiteContentProvider implements OnAccoun
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "deleteInTransaction: " + uri);
}
+ CalendarSanityChecker.getInstance(mContext).checkLastCheckTime();
+
validateUriParameters(uri.getQueryParameterNames());
final int match = sUriMatcher.match(uri);
verifyTransactionAllowed(TRANSACTION_DELETE, uri, null, callerIsSyncAdapter, match,
@@ -3918,6 +3924,8 @@ public class CalendarProvider2 extends SQLiteContentProvider implements OnAccoun
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "updateInTransaction: " + uri);
}
+ CalendarSanityChecker.getInstance(mContext).checkLastCheckTime();
+
validateUriParameters(uri.getQueryParameterNames());
final int match = sUriMatcher.match(uri);
verifyTransactionAllowed(TRANSACTION_UPDATE, uri, values, callerIsSyncAdapter, match,
diff --git a/src/com/android/providers/calendar/CalendarSanityChecker.java b/src/com/android/providers/calendar/CalendarSanityChecker.java
new file mode 100644
index 0000000..f6f9b21
--- /dev/null
+++ b/src/com/android/providers/calendar/CalendarSanityChecker.java
@@ -0,0 +1,218 @@
+/*
+ * 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.
+ */
+
+package com.android.providers.calendar;
+
+import android.annotation.Nullable;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.SharedPreferences;
+import android.os.SystemClock;
+import android.os.UserManager;
+import android.provider.CalendarContract;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * We call {@link #checkLastCheckTime} at the provider public entry points to make sure
+ * {@link CalendarAlarmManager#scheduleNextAlarmLocked} has been called recently enough.
+ *
+ * atest tests/src/com/android/providers/calendar/CalendarSanityCheckerTest.java
+ */
+public class CalendarSanityChecker {
+ private static final String TAG = "CalendarSanityChecker";
+
+ private static final boolean DEBUG = false;
+
+ private static final long MAX_ALLOWED_CHECK_INTERVAL_MS =
+ CalendarAlarmManager.NEXT_ALARM_CHECK_TIME_MS;
+
+ /**
+ * If updateLastCheckTime isn't called after user unlock within this time,
+ * we call scheduleNextAlarmCheckRightNow.
+ */
+ private static final long MAX_ALLOWED_REAL_TIME_AFTER_UNLOCK_MS =
+ 15 * DateUtils.MINUTE_IN_MILLIS;
+
+ /**
+ * Minimum interval between WTFs.
+ */
+ private static final long WTF_INTERVAL_MS = 60 * DateUtils.MINUTE_IN_MILLIS;
+
+ private static final String PREF_NAME = "sanity";
+ private static final String LAST_CHECK_REALTIME_PREF_KEY = "last_check_realtime";
+ private static final String LAST_CHECK_BOOT_COUNT_PREF_KEY = "last_check_boot_count";
+ private static final String LAST_WTF_REALTIME_PREF_KEY = "last_wtf_realtime";
+
+ private static CalendarSanityChecker sInstance;
+ private final Context mContext;
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ @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
+ protected long getRealtimeMillis() {
+ return SystemClock.elapsedRealtime();
+ }
+
+ @VisibleForTesting
+ protected long getBootCount() {
+ return Settings.Global.getLong(mContext.getContentResolver(), Global.BOOT_COUNT, 0);
+ }
+
+ @VisibleForTesting
+ protected long getUserUnlockTime() {
+ final UserManager um = mContext.getSystemService(UserManager.class);
+ final long startTime = um.getUserStartRealtime();
+ final long unlockTime = um.getUserUnlockRealtime();
+ if (DEBUG) {
+ Log.d(TAG, String.format("User start/unlock time=%d/%d", startTime, unlockTime));
+ }
+ return unlockTime;
+ }
+
+ public static synchronized CalendarSanityChecker getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new CalendarSanityChecker(context);
+ }
+ return sInstance;
+ }
+
+ /**
+ * Called by {@link CalendarAlarmManager#scheduleNextAlarmLocked}
+ */
+ public final void updateLastCheckTime() {
+ final long now = getRealtimeMillis();
+ if (DEBUG) {
+ Log.d(TAG, "updateLastCheckTime: now=" + now);
+ }
+ synchronized (mLock) {
+ mPrefs.edit()
+ .putLong(LAST_CHECK_REALTIME_PREF_KEY, now)
+ .putLong(LAST_CHECK_BOOT_COUNT_PREF_KEY, getBootCount())
+ .apply();
+ }
+ }
+
+ /**
+ * 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()}.
+ */
+ public final boolean checkLastCheckTime() {
+ final long lastBootCount;
+ final long lastCheckTime;
+ final long lastWtfTime;
+
+ synchronized (mLock) {
+ lastBootCount = mPrefs.getLong(LAST_CHECK_BOOT_COUNT_PREF_KEY, -1);
+ lastCheckTime = mPrefs.getLong(LAST_CHECK_REALTIME_PREF_KEY, -1);
+ lastWtfTime = mPrefs.getLong(LAST_WTF_REALTIME_PREF_KEY, 0);
+
+ final long nowBootCount = getBootCount();
+ final long nowRealtime = getRealtimeMillis();
+
+ final long unlockTime = getUserUnlockTime();
+
+ if (DEBUG) {
+ Log.d(TAG, String.format("isStateValid: %d/%d %d/%d unlocked=%d lastWtf=%d",
+ lastBootCount, nowBootCount, lastCheckTime, nowRealtime, unlockTime,
+ lastWtfTime));
+ }
+
+ if (lastBootCount != nowBootCount) {
+ // This branch means updateLastCheckTime() hasn't been called since boot.
+
+ debug("checkLastCheckTime: Last check time not set.");
+
+ if (unlockTime == 0) {
+ debug("checkLastCheckTime: unlockTime=0."); // This shouldn't happen though.
+ return true;
+ }
+
+ if ((nowRealtime - unlockTime) <= MAX_ALLOWED_REAL_TIME_AFTER_UNLOCK_MS) {
+ debug("checkLastCheckTime: nowRealtime okay.");
+ return true;
+ }
+ debug("checkLastCheckTime: nowRealtime too old");
+ } else {
+ // This branch means updateLastCheckTime() has been called since boot.
+
+ if ((nowRealtime - lastWtfTime) <= WTF_INTERVAL_MS) {
+ debug("checkLastCheckTime: Last WTF recent, skipping check.");
+ return true;
+ }
+
+ if ((nowRealtime - lastCheckTime) <= MAX_ALLOWED_CHECK_INTERVAL_MS) {
+ debug("checkLastCheckTime: Last check was recent, okay.");
+ return true;
+ }
+ }
+ Slog.wtf(TAG, String.format("Last check time %d was too old. now=%d (boot count=%d/%d)",
+ lastCheckTime, nowRealtime, lastBootCount, nowBootCount));
+
+ mPrefs.edit()
+ .putLong(LAST_CHECK_REALTIME_PREF_KEY, 0)
+ .putLong(LAST_WTF_REALTIME_PREF_KEY, nowRealtime)
+ .putLong(LAST_CHECK_BOOT_COUNT_PREF_KEY, getBootCount())
+ .apply();
+
+ // Note mCalendarProvider2 really shouldn't be null.
+ if (mCalendarProvider2 != null) {
+ mCalendarProvider2.getOrCreateCalendarAlarmManager()
+ .scheduleNextAlarmCheckRightNow();
+ }
+ }
+ return false;
+ }
+
+ void debug(String message) {
+ if (DEBUG) {
+ Log.d(TAG, message);
+ }
+ }
+}
diff --git a/tests/src/com/android/providers/calendar/CalendarSanityCheckerTest.java b/tests/src/com/android/providers/calendar/CalendarSanityCheckerTest.java
new file mode 100644
index 0000000..1586c61
--- /dev/null
+++ b/tests/src/com/android/providers/calendar/CalendarSanityCheckerTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+package com.android.providers.calendar;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.text.format.DateUtils;
+
+public class CalendarSanityCheckerTest extends AndroidTestCase {
+ private class CalendarSanityCheckerTestable extends CalendarSanityChecker {
+ protected CalendarSanityCheckerTestable(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected long getRealtimeMillis() {
+ return mInjectedRealtimeMillis;
+ }
+
+ @Override
+ protected long getBootCount() {
+ return mInjectedBootCount;
+ }
+
+ @Override
+ protected long getUserUnlockTime() {
+ return mInjectedUnlockTime;
+ }
+ }
+
+ private long mInjectedRealtimeMillis = 1000000L;
+ private long mInjectedBootCount = 1000;
+ private long mInjectedUnlockTime = 0;
+
+ public void testWithoutLastCheckTime() {
+ CalendarSanityCheckerTestable target = new CalendarSanityCheckerTestable(getContext());
+ target.mPrefs.edit().clear().commit();
+
+ assertTrue(target.checkLastCheckTime());
+
+ // Unlock.
+ mInjectedUnlockTime = mInjectedRealtimeMillis;
+
+ mInjectedRealtimeMillis += 15 * 60 * 1000;
+ assertTrue(target.checkLastCheckTime());
+
+ mInjectedRealtimeMillis += 1;
+ assertFalse(target.checkLastCheckTime());
+ }
+
+ public void testWithLastCheckTime() {
+ CalendarSanityCheckerTestable target = new CalendarSanityCheckerTestable(getContext());
+ target.mPrefs.edit().clear().commit();
+
+ assertTrue(target.checkLastCheckTime());
+
+ mInjectedUnlockTime = mInjectedRealtimeMillis;
+
+ // Update the last check time.
+ mInjectedRealtimeMillis += 1 * 60 * 1000;
+ target.updateLastCheckTime();
+
+ // Right after, okay.
+ assertTrue(target.checkLastCheckTime());
+
+ // Still okay.
+ mInjectedRealtimeMillis += DateUtils.DAY_IN_MILLIS - (15 * DateUtils.MINUTE_IN_MILLIS);
+ assertTrue(target.checkLastCheckTime());
+
+ mInjectedRealtimeMillis += 1;
+ assertFalse(target.checkLastCheckTime());
+
+ // Repeat the same thing.
+ mInjectedRealtimeMillis += 1 * 60 * 1000;
+ target.updateLastCheckTime();
+
+ // Right after, okay.
+ assertTrue(target.checkLastCheckTime());
+
+ // Still okay.
+ mInjectedRealtimeMillis += DateUtils.DAY_IN_MILLIS - (15 * DateUtils.MINUTE_IN_MILLIS);
+ assertTrue(target.checkLastCheckTime());
+
+ mInjectedRealtimeMillis += 1;
+ assertFalse(target.checkLastCheckTime());
+
+ // Check again right after. This should pass because of WTF_INTERVAL_MS.
+ assertTrue(target.checkLastCheckTime());
+
+ mInjectedRealtimeMillis += 60 * 60 * 1000;
+
+ // Still okay.
+ assertTrue(target.checkLastCheckTime());
+
+ // Now WTF again.
+ mInjectedRealtimeMillis += 1;
+ assertFalse(target.checkLastCheckTime());
+
+ // Reboot.
+ mInjectedRealtimeMillis = 1000000L;
+ mInjectedBootCount++;
+
+ // Unlock.
+ mInjectedUnlockTime = mInjectedRealtimeMillis;
+
+ mInjectedRealtimeMillis += 15 * 60 * 1000;
+ assertTrue(target.checkLastCheckTime());
+
+ mInjectedRealtimeMillis += 1;
+ assertFalse(target.checkLastCheckTime());
+
+ // Check again right after. This should pass because of WTF_INTERVAL_MS.
+ assertTrue(target.checkLastCheckTime());
+ }
+}