diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2018-01-10 08:26:43 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2018-01-10 08:26:43 +0000 |
commit | 73e9f5cfe89b2b8c45deb6215ee7593e98ce281f (patch) | |
tree | debbba3a30da9182f9c644e8fa41afda6a37d477 | |
parent | e8cbaa73a052d07d0c997dab6a0a9e1175063dc7 (diff) | |
parent | d2fc86d779a667624fd576c6e1f107e4c8339873 (diff) | |
download | android_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
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()); + } +} |