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