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.content.Context;
20 import android.content.SharedPreferences;
21 import android.os.SystemClock;
22 import android.os.UserManager;
23 import android.provider.Settings;
24 import android.provider.Settings.Global;
25 import android.text.format.DateUtils;
26 import android.util.Log;
27 import android.util.Slog;
28 
29 import com.android.internal.annotations.GuardedBy;
30 import com.android.internal.annotations.VisibleForTesting;
31 
32 /**
33  * We call {@link #checkLastCheckTime} at the provider public entry points to make sure
34  * {@link CalendarAlarmManager#scheduleNextAlarmLocked} has been called recently enough.
35  *
36  * atest tests/src/com/android/providers/calendar/CalendarSanityCheckerTest.java
37  */
38 public class CalendarSanityChecker {
39     private static final String TAG = "CalendarSanityChecker";
40 
41     private static final boolean DEBUG = false;
42 
43     private static final long MAX_ALLOWED_CHECK_INTERVAL_MS =
44             CalendarAlarmManager.NEXT_ALARM_CHECK_TIME_MS;
45 
46     /**
47      * If updateLastCheckTime isn't called after user unlock within this time,
48      * we call scheduleNextAlarmCheckRightNow.
49      */
50     private static final long MAX_ALLOWED_REAL_TIME_AFTER_UNLOCK_MS =
51             15 * DateUtils.MINUTE_IN_MILLIS;
52 
53     /**
54      * Minimum interval between WTFs.
55      */
56     private static final long WTF_INTERVAL_MS = 60 * DateUtils.MINUTE_IN_MILLIS;
57 
58     private static final String PREF_NAME = "sanity";
59     private static final String LAST_CHECK_REALTIME_PREF_KEY = "last_check_realtime";
60     private static final String LAST_CHECK_BOOT_COUNT_PREF_KEY = "last_check_boot_count";
61     private static final String LAST_WTF_REALTIME_PREF_KEY = "last_wtf_realtime";
62 
63     private final Context mContext;
64 
65     private final Object mLock = new Object();
66 
67     @GuardedBy("mLock")
68     @VisibleForTesting
69     final SharedPreferences mPrefs;
70 
CalendarSanityChecker(Context context)71     public CalendarSanityChecker(Context context) {
72         mContext = context;
73         mPrefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
74     }
75 
76     @VisibleForTesting
getRealtimeMillis()77     protected long getRealtimeMillis() {
78         return SystemClock.elapsedRealtime();
79     }
80 
81     @VisibleForTesting
getBootCount()82     protected long getBootCount() {
83         return Settings.Global.getLong(mContext.getContentResolver(), Global.BOOT_COUNT, 0);
84     }
85 
86     @VisibleForTesting
getUserUnlockTime()87     protected long getUserUnlockTime() {
88         final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
89         final long startTime = um.getUserStartRealtime();
90         final long unlockTime = um.getUserUnlockRealtime();
91         if (DEBUG) {
92             Log.d(TAG, String.format("User start/unlock time=%d/%d", startTime, unlockTime));
93         }
94         return unlockTime;
95     }
96 
97     /**
98      * Called by {@link CalendarAlarmManager#scheduleNextAlarmLocked}
99      */
updateLastCheckTime()100     public final void updateLastCheckTime() {
101         final long now = getRealtimeMillis();
102         if (DEBUG) {
103             Log.d(TAG, "updateLastCheckTime: now=" + now);
104         }
105         synchronized (mLock) {
106             mPrefs.edit()
107                     .putLong(LAST_CHECK_REALTIME_PREF_KEY, now)
108                     .putLong(LAST_CHECK_BOOT_COUNT_PREF_KEY, getBootCount())
109                     .apply();
110         }
111     }
112 
113     /**
114      * Call this at public entry points. This will check if the last check time was recent enough,
115      * and otherwise it'll call {@link CalendarAlarmManager#checkNextAlarmCheckRightNow}.
116      */
checkLastCheckTime()117     public final boolean checkLastCheckTime() {
118         final long lastBootCount;
119         final long lastCheckTime;
120         final long lastWtfTime;
121 
122         synchronized (mLock) {
123             lastBootCount = mPrefs.getLong(LAST_CHECK_BOOT_COUNT_PREF_KEY, -1);
124             lastCheckTime = mPrefs.getLong(LAST_CHECK_REALTIME_PREF_KEY, -1);
125             lastWtfTime = mPrefs.getLong(LAST_WTF_REALTIME_PREF_KEY, 0);
126 
127             final long nowBootCount = getBootCount();
128             final long nowRealtime = getRealtimeMillis();
129 
130             final long unlockTime = getUserUnlockTime();
131 
132             if (DEBUG) {
133                 Log.d(TAG, String.format("isStateValid: %d/%d %d/%d unlocked=%d lastWtf=%d",
134                         lastBootCount, nowBootCount, lastCheckTime, nowRealtime, unlockTime,
135                         lastWtfTime));
136             }
137 
138             if (lastBootCount != nowBootCount) {
139                 // This branch means updateLastCheckTime() hasn't been called since boot.
140 
141                 debug("checkLastCheckTime: Last check time not set.");
142 
143                 if (unlockTime == 0) {
144                     debug("checkLastCheckTime: unlockTime=0."); // This shouldn't happen though.
145                     return true;
146                 }
147 
148                 if ((nowRealtime - unlockTime) <= MAX_ALLOWED_REAL_TIME_AFTER_UNLOCK_MS) {
149                     debug("checkLastCheckTime: nowRealtime okay.");
150                     return true;
151                 }
152                 debug("checkLastCheckTime: nowRealtime too old");
153             } else {
154                 // This branch means updateLastCheckTime() has been called since boot.
155 
156                 if ((nowRealtime - lastWtfTime) <= WTF_INTERVAL_MS) {
157                     debug("checkLastCheckTime: Last WTF recent, skipping check.");
158                     return true;
159                 }
160 
161                 if ((nowRealtime - lastCheckTime) <= MAX_ALLOWED_CHECK_INTERVAL_MS) {
162                     debug("checkLastCheckTime: Last check was recent, okay.");
163                     return true;
164                 }
165             }
166             Slog.wtf(TAG, String.format("Last check time %d was too old. now=%d (boot count=%d/%d)",
167                     lastCheckTime, nowRealtime, lastBootCount, nowBootCount));
168 
169             mPrefs.edit()
170                     .putLong(LAST_CHECK_REALTIME_PREF_KEY, 0)
171                     .putLong(LAST_WTF_REALTIME_PREF_KEY, nowRealtime)
172                     .putLong(LAST_CHECK_BOOT_COUNT_PREF_KEY, getBootCount())
173                     .apply();
174 
175             // Note mCalendarProvider2 really shouldn't be null.
176             CalendarAlarmManager.checkNextAlarmCheckRightNow(mContext);
177         }
178         return false;
179     }
180 
debug(String message)181     void debug(String message) {
182         if (DEBUG) {
183             Log.d(TAG, message);
184         }
185     }
186 }
187