1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.providers.calendar; 18 19 import android.annotation.Nullable; 20 import android.content.ContentProvider; 21 import android.content.Context; 22 import android.content.IContentProvider; 23 import android.content.SharedPreferences; 24 import android.os.SystemClock; 25 import android.os.UserManager; 26 import android.provider.CalendarContract; 27 import android.provider.Settings; 28 import android.provider.Settings.Global; 29 import android.text.format.DateUtils; 30 import android.util.Log; 31 import android.util.Slog; 32 33 import com.android.internal.annotations.GuardedBy; 34 import com.android.internal.annotations.VisibleForTesting; 35 36 /** 37 * We call {@link #checkLastCheckTime} at the provider public entry points to make sure 38 * {@link CalendarAlarmManager#scheduleNextAlarmLocked} has been called recently enough. 39 * 40 * atest tests/src/com/android/providers/calendar/CalendarSanityCheckerTest.java 41 */ 42 public class CalendarSanityChecker { 43 private static final String TAG = "CalendarSanityChecker"; 44 45 private static final boolean DEBUG = false; 46 47 private static final long MAX_ALLOWED_CHECK_INTERVAL_MS = 48 CalendarAlarmManager.NEXT_ALARM_CHECK_TIME_MS; 49 50 /** 51 * If updateLastCheckTime isn't called after user unlock within this time, 52 * we call scheduleNextAlarmCheckRightNow. 53 */ 54 private static final long MAX_ALLOWED_REAL_TIME_AFTER_UNLOCK_MS = 55 15 * DateUtils.MINUTE_IN_MILLIS; 56 57 /** 58 * Minimum interval between WTFs. 59 */ 60 private static final long WTF_INTERVAL_MS = 60 * DateUtils.MINUTE_IN_MILLIS; 61 62 private static final String PREF_NAME = "sanity"; 63 private static final String LAST_CHECK_REALTIME_PREF_KEY = "last_check_realtime"; 64 private static final String LAST_CHECK_BOOT_COUNT_PREF_KEY = "last_check_boot_count"; 65 private static final String LAST_WTF_REALTIME_PREF_KEY = "last_wtf_realtime"; 66 67 private static CalendarSanityChecker sInstance; 68 private final Context mContext; 69 70 private final Object mLock = new Object(); 71 72 @GuardedBy("mLock") 73 @VisibleForTesting 74 final SharedPreferences mPrefs; 75 CalendarSanityChecker(Context context)76 protected CalendarSanityChecker(Context context) { 77 mContext = context; 78 mPrefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); 79 } 80 81 @VisibleForTesting getRealtimeMillis()82 protected long getRealtimeMillis() { 83 return SystemClock.elapsedRealtime(); 84 } 85 86 @VisibleForTesting getBootCount()87 protected long getBootCount() { 88 return Settings.Global.getLong(mContext.getContentResolver(), Global.BOOT_COUNT, 0); 89 } 90 91 @VisibleForTesting getUserUnlockTime()92 protected long getUserUnlockTime() { 93 final UserManager um = mContext.getSystemService(UserManager.class); 94 final long startTime = um.getUserStartRealtime(); 95 final long unlockTime = um.getUserUnlockRealtime(); 96 if (DEBUG) { 97 Log.d(TAG, String.format("User start/unlock time=%d/%d", startTime, unlockTime)); 98 } 99 return unlockTime; 100 } 101 getInstance(Context context)102 public static synchronized CalendarSanityChecker getInstance(Context context) { 103 if (sInstance == null) { 104 sInstance = new CalendarSanityChecker(context); 105 } 106 return sInstance; 107 } 108 109 /** 110 * Called by {@link CalendarAlarmManager#scheduleNextAlarmLocked} 111 */ updateLastCheckTime()112 public final void updateLastCheckTime() { 113 final long now = getRealtimeMillis(); 114 if (DEBUG) { 115 Log.d(TAG, "updateLastCheckTime: now=" + now); 116 } 117 synchronized (mLock) { 118 mPrefs.edit() 119 .putLong(LAST_CHECK_REALTIME_PREF_KEY, now) 120 .putLong(LAST_CHECK_BOOT_COUNT_PREF_KEY, getBootCount()) 121 .apply(); 122 } 123 } 124 125 /** 126 * Call this at public entry points. This will check if the last check time was recent enough, 127 * and otherwise it'll call {@link CalendarAlarmManager#checkNextAlarmCheckRightNow}. 128 */ checkLastCheckTime()129 public final boolean checkLastCheckTime() { 130 final long lastBootCount; 131 final long lastCheckTime; 132 final long lastWtfTime; 133 134 synchronized (mLock) { 135 lastBootCount = mPrefs.getLong(LAST_CHECK_BOOT_COUNT_PREF_KEY, -1); 136 lastCheckTime = mPrefs.getLong(LAST_CHECK_REALTIME_PREF_KEY, -1); 137 lastWtfTime = mPrefs.getLong(LAST_WTF_REALTIME_PREF_KEY, 0); 138 139 final long nowBootCount = getBootCount(); 140 final long nowRealtime = getRealtimeMillis(); 141 142 final long unlockTime = getUserUnlockTime(); 143 144 if (DEBUG) { 145 Log.d(TAG, String.format("isStateValid: %d/%d %d/%d unlocked=%d lastWtf=%d", 146 lastBootCount, nowBootCount, lastCheckTime, nowRealtime, unlockTime, 147 lastWtfTime)); 148 } 149 150 if (lastBootCount != nowBootCount) { 151 // This branch means updateLastCheckTime() hasn't been called since boot. 152 153 debug("checkLastCheckTime: Last check time not set."); 154 155 if (unlockTime == 0) { 156 debug("checkLastCheckTime: unlockTime=0."); // This shouldn't happen though. 157 return true; 158 } 159 160 if ((nowRealtime - unlockTime) <= MAX_ALLOWED_REAL_TIME_AFTER_UNLOCK_MS) { 161 debug("checkLastCheckTime: nowRealtime okay."); 162 return true; 163 } 164 debug("checkLastCheckTime: nowRealtime too old"); 165 } else { 166 // This branch means updateLastCheckTime() has been called since boot. 167 168 if ((nowRealtime - lastWtfTime) <= WTF_INTERVAL_MS) { 169 debug("checkLastCheckTime: Last WTF recent, skipping check."); 170 return true; 171 } 172 173 if ((nowRealtime - lastCheckTime) <= MAX_ALLOWED_CHECK_INTERVAL_MS) { 174 debug("checkLastCheckTime: Last check was recent, okay."); 175 return true; 176 } 177 } 178 Slog.wtf(TAG, String.format("Last check time %d was too old. now=%d (boot count=%d/%d)", 179 lastCheckTime, nowRealtime, lastBootCount, nowBootCount)); 180 181 mPrefs.edit() 182 .putLong(LAST_CHECK_REALTIME_PREF_KEY, 0) 183 .putLong(LAST_WTF_REALTIME_PREF_KEY, nowRealtime) 184 .putLong(LAST_CHECK_BOOT_COUNT_PREF_KEY, getBootCount()) 185 .apply(); 186 187 // Note mCalendarProvider2 really shouldn't be null. 188 CalendarAlarmManager.checkNextAlarmCheckRightNow(mContext); 189 } 190 return false; 191 } 192 debug(String message)193 void debug(String message) { 194 if (DEBUG) { 195 Log.d(TAG, message); 196 } 197 } 198 } 199