blob: ba7fe784650700a3cffa954f0cb59b35ac55e41c [file] [log] [blame]
John Spurlockb2278d62015-04-07 12:47:12 -04001/*
2 * Copyright (C) 2015 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
17package com.android.server.notification;
18
Julia Reynolds1998ee52016-02-11 13:49:08 -050019import android.app.ActivityManager;
John Spurlockb2278d62015-04-07 12:47:12 -040020import android.app.AlarmManager;
21import android.app.PendingIntent;
22import android.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.net.Uri;
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -040028import android.os.Binder;
29import android.provider.Settings;
John Spurlockb2278d62015-04-07 12:47:12 -040030import android.service.notification.Condition;
31import android.service.notification.IConditionProvider;
Beverlybe6d3522017-11-20 11:01:59 -050032import android.service.notification.ScheduleCalendar;
John Spurlockb2278d62015-04-07 12:47:12 -040033import android.service.notification.ZenModeConfig;
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -040034import android.text.TextUtils;
Julia Reynolds1998ee52016-02-11 13:49:08 -050035import android.util.ArrayMap;
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -040036import android.util.ArraySet;
John Spurlockb2278d62015-04-07 12:47:12 -040037import android.util.Log;
38import android.util.Slog;
John Spurlockb2278d62015-04-07 12:47:12 -040039
Julia Reynolds4b318432017-11-22 10:56:18 -050040import com.android.internal.annotations.GuardedBy;
41import com.android.internal.annotations.VisibleForTesting;
John Spurlockb2278d62015-04-07 12:47:12 -040042import com.android.server.notification.NotificationManagerService.DumpFilter;
43
44import java.io.PrintWriter;
Julia Reynolds2ea7b092016-09-14 11:24:30 -040045import java.util.ArrayList;
Julia Reynolds83eec702016-05-25 12:29:55 -040046import java.util.Calendar;
Julia Reynolds2ea7b092016-09-14 11:24:30 -040047import java.util.List;
John Spurlockb2278d62015-04-07 12:47:12 -040048
49/**
50 * Built-in zen condition provider for daily scheduled time-based conditions.
51 */
52public class ScheduleConditionProvider extends SystemConditionProviderService {
Julia Reynolds9eb2c1e2016-06-02 12:53:42 -040053 static final String TAG = "ConditionProviders.SCP";
54 static final boolean DEBUG = true || Log.isLoggable("ConditionProviders", Log.DEBUG);
John Spurlockb2278d62015-04-07 12:47:12 -040055
56 public static final ComponentName COMPONENT =
57 new ComponentName("android", ScheduleConditionProvider.class.getName());
58 private static final String NOT_SHOWN = "...";
John Spurlock2f096ed2015-05-04 11:58:26 -040059 private static final String SIMPLE_NAME = ScheduleConditionProvider.class.getSimpleName();
60 private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE";
John Spurlockb2278d62015-04-07 12:47:12 -040061 private static final int REQUEST_CODE_EVALUATE = 1;
62 private static final String EXTRA_TIME = "time";
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -040063 private static final String SEPARATOR = ";";
64 private static final String SCP_SETTING = "snoozed_schedule_condition_provider";
65
John Spurlockb2278d62015-04-07 12:47:12 -040066 private final Context mContext = this;
Julia Reynolds1998ee52016-02-11 13:49:08 -050067 private final ArrayMap<Uri, ScheduleCalendar> mSubscriptions = new ArrayMap<>();
Julia Reynolds4b318432017-11-22 10:56:18 -050068 private ArraySet<Uri> mSnoozedForAlarm = new ArraySet<>();
John Spurlockb2278d62015-04-07 12:47:12 -040069
Julia Reynolds1998ee52016-02-11 13:49:08 -050070 private AlarmManager mAlarmManager;
John Spurlockb2278d62015-04-07 12:47:12 -040071 private boolean mConnected;
72 private boolean mRegistered;
John Spurlocka7d92b12015-05-13 14:48:02 -040073 private long mNextAlarmTime;
John Spurlockb2278d62015-04-07 12:47:12 -040074
75 public ScheduleConditionProvider() {
John Spurlock2f096ed2015-05-04 11:58:26 -040076 if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()");
John Spurlockb2278d62015-04-07 12:47:12 -040077 }
78
79 @Override
80 public ComponentName getComponent() {
81 return COMPONENT;
82 }
83
84 @Override
John Spurlockd60258f2015-04-30 09:30:52 -040085 public boolean isValidConditionId(Uri id) {
John Spurlockb2278d62015-04-07 12:47:12 -040086 return ZenModeConfig.isValidScheduleConditionId(id);
87 }
88
89 @Override
90 public void dump(PrintWriter pw, DumpFilter filter) {
John Spurlock2f096ed2015-05-04 11:58:26 -040091 pw.print(" "); pw.print(SIMPLE_NAME); pw.println(":");
John Spurlockb2278d62015-04-07 12:47:12 -040092 pw.print(" mConnected="); pw.println(mConnected);
93 pw.print(" mRegistered="); pw.println(mRegistered);
94 pw.println(" mSubscriptions=");
95 final long now = System.currentTimeMillis();
Julia Reynolds2ea7b092016-09-14 11:24:30 -040096 synchronized (mSubscriptions) {
97 for (Uri conditionId : mSubscriptions.keySet()) {
98 pw.print(" ");
99 pw.print(meetsSchedule(mSubscriptions.get(conditionId), now) ? "* " : " ");
100 pw.println(conditionId);
101 pw.print(" ");
102 pw.println(mSubscriptions.get(conditionId).toString());
103 }
John Spurlockb2278d62015-04-07 12:47:12 -0400104 }
Julia Reynolds4b318432017-11-22 10:56:18 -0500105 pw.println(" snoozed due to alarm: " + TextUtils.join(SEPARATOR, mSnoozedForAlarm));
John Spurlock1b8b22b2015-05-20 09:47:13 -0400106 dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, now);
John Spurlockb2278d62015-04-07 12:47:12 -0400107 }
108
109 @Override
110 public void onConnected() {
111 if (DEBUG) Slog.d(TAG, "onConnected");
112 mConnected = true;
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -0400113 readSnoozed();
John Spurlockb2278d62015-04-07 12:47:12 -0400114 }
115
116 @Override
John Spurlock2f096ed2015-05-04 11:58:26 -0400117 public void onBootComplete() {
118 // noop
119 }
120
121 @Override
John Spurlockb2278d62015-04-07 12:47:12 -0400122 public void onDestroy() {
123 super.onDestroy();
124 if (DEBUG) Slog.d(TAG, "onDestroy");
125 mConnected = false;
126 }
127
128 @Override
John Spurlockb2278d62015-04-07 12:47:12 -0400129 public void onSubscribe(Uri conditionId) {
130 if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
131 if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) {
Julia Reynolds4b318432017-11-22 10:56:18 -0500132 notifyCondition(createCondition(conditionId, Condition.STATE_ERROR, "invalidId"));
John Spurlockb2278d62015-04-07 12:47:12 -0400133 return;
134 }
Julia Reynolds2ea7b092016-09-14 11:24:30 -0400135 synchronized (mSubscriptions) {
Beverlybe6d3522017-11-20 11:01:59 -0500136 mSubscriptions.put(conditionId, ZenModeConfig.toScheduleCalendar(conditionId));
Julia Reynolds2ea7b092016-09-14 11:24:30 -0400137 }
John Spurlockb2278d62015-04-07 12:47:12 -0400138 evaluateSubscriptions();
139 }
140
141 @Override
142 public void onUnsubscribe(Uri conditionId) {
143 if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
Julia Reynolds2ea7b092016-09-14 11:24:30 -0400144 synchronized (mSubscriptions) {
145 mSubscriptions.remove(conditionId);
146 }
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -0400147 removeSnoozed(conditionId);
John Spurlockb2278d62015-04-07 12:47:12 -0400148 evaluateSubscriptions();
149 }
150
151 @Override
152 public void attachBase(Context base) {
153 attachBaseContext(base);
154 }
155
156 @Override
157 public IConditionProvider asInterface() {
158 return (IConditionProvider) onBind(null);
159 }
160
161 private void evaluateSubscriptions() {
Julia Reynolds1998ee52016-02-11 13:49:08 -0500162 if (mAlarmManager == null) {
163 mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
164 }
John Spurlockb2278d62015-04-07 12:47:12 -0400165 final long now = System.currentTimeMillis();
John Spurlocka7d92b12015-05-13 14:48:02 -0400166 mNextAlarmTime = 0;
Julia Reynolds1998ee52016-02-11 13:49:08 -0500167 long nextUserAlarmTime = getNextAlarm();
Julia Reynolds2ea7b092016-09-14 11:24:30 -0400168 List<Condition> conditionsToNotify = new ArrayList<>();
169 synchronized (mSubscriptions) {
170 setRegistered(!mSubscriptions.isEmpty());
171 for (Uri conditionId : mSubscriptions.keySet()) {
Julia Reynolds4b318432017-11-22 10:56:18 -0500172 Condition condition =
173 evaluateSubscriptionLocked(conditionId, mSubscriptions.get(conditionId),
174 now, nextUserAlarmTime);
175 if (condition != null) {
176 conditionsToNotify.add(condition);
John Spurlockb2278d62015-04-07 12:47:12 -0400177 }
178 }
179 }
Julia Reynolds2ea7b092016-09-14 11:24:30 -0400180 notifyConditions(conditionsToNotify.toArray(new Condition[conditionsToNotify.size()]));
John Spurlocka7d92b12015-05-13 14:48:02 -0400181 updateAlarm(now, mNextAlarmTime);
John Spurlockb2278d62015-04-07 12:47:12 -0400182 }
183
Julia Reynolds4b318432017-11-22 10:56:18 -0500184 @VisibleForTesting
185 @GuardedBy("mSubscriptions")
186 Condition evaluateSubscriptionLocked(Uri conditionId, ScheduleCalendar cal,
187 long now, long nextUserAlarmTime) {
188 Condition condition;
189 if (cal == null) {
190 condition = createCondition(conditionId, Condition.STATE_ERROR, "!invalidId");
191 removeSnoozed(conditionId);
192 return condition;
193 }
194 if (cal.isInSchedule(now)) {
195 if (conditionSnoozed(conditionId)) {
196 condition = createCondition(conditionId, Condition.STATE_FALSE, "snoozed");
197 } else if (cal.shouldExitForAlarm(now)) {
198 condition = createCondition(conditionId, Condition.STATE_FALSE, "alarmCanceled");
199 addSnoozed(conditionId);
200 } else {
201 condition = createCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule");
202 }
203 } else {
204 condition = createCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule");
205 removeSnoozed(conditionId);
206 }
207 cal.maybeSetNextAlarm(now, nextUserAlarmTime);
208 final long nextChangeTime = cal.getNextChangeTime(now);
209 if (nextChangeTime > 0 && nextChangeTime > now) {
210 if (mNextAlarmTime == 0 || nextChangeTime < mNextAlarmTime) {
211 mNextAlarmTime = nextChangeTime;
212 }
213 }
214 return condition;
215 }
216
John Spurlockb2278d62015-04-07 12:47:12 -0400217 private void updateAlarm(long now, long time) {
218 final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
219 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
220 REQUEST_CODE_EVALUATE,
221 new Intent(ACTION_EVALUATE)
222 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
223 .putExtra(EXTRA_TIME, time),
224 PendingIntent.FLAG_UPDATE_CURRENT);
225 alarms.cancel(pendingIntent);
226 if (time > now) {
227 if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s",
228 ts(time), formatDuration(time - now), ts(now)));
229 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
230 } else {
231 if (DEBUG) Slog.d(TAG, "Not scheduling evaluate");
232 }
233 }
234
Julia Reynolds1998ee52016-02-11 13:49:08 -0500235 public long getNextAlarm() {
236 final AlarmManager.AlarmClockInfo info = mAlarmManager.getNextAlarmClock(
237 ActivityManager.getCurrentUser());
238 return info != null ? info.getTriggerTime() : 0;
239 }
240
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -0400241 private boolean meetsSchedule(ScheduleCalendar cal, long time) {
John Spurlockb2278d62015-04-07 12:47:12 -0400242 return cal != null && cal.isInSchedule(time);
243 }
244
John Spurlockb2278d62015-04-07 12:47:12 -0400245 private void setRegistered(boolean registered) {
246 if (mRegistered == registered) return;
247 if (DEBUG) Slog.d(TAG, "setRegistered " + registered);
248 mRegistered = registered;
249 if (mRegistered) {
250 final IntentFilter filter = new IntentFilter();
251 filter.addAction(Intent.ACTION_TIME_CHANGED);
252 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
253 filter.addAction(ACTION_EVALUATE);
Julia Reynolds1998ee52016-02-11 13:49:08 -0500254 filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
John Spurlockb2278d62015-04-07 12:47:12 -0400255 registerReceiver(mReceiver, filter);
256 } else {
257 unregisterReceiver(mReceiver);
258 }
259 }
260
Julia Reynolds2ea7b092016-09-14 11:24:30 -0400261 private Condition createCondition(Uri id, int state, String reason) {
262 if (DEBUG) Slog.d(TAG, "notifyCondition " + id
John Spurlocka7d92b12015-05-13 14:48:02 -0400263 + " " + Condition.stateToString(state)
John Spurlockb2278d62015-04-07 12:47:12 -0400264 + " reason=" + reason);
John Spurlockb2278d62015-04-07 12:47:12 -0400265 final String summary = NOT_SHOWN;
266 final String line1 = NOT_SHOWN;
267 final String line2 = NOT_SHOWN;
268 return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS);
269 }
270
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -0400271 private boolean conditionSnoozed(Uri conditionId) {
Julia Reynolds4b318432017-11-22 10:56:18 -0500272 synchronized (mSnoozedForAlarm) {
273 return mSnoozedForAlarm.contains(conditionId);
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -0400274 }
275 }
276
Julia Reynolds4b318432017-11-22 10:56:18 -0500277 @VisibleForTesting
278 void addSnoozed(Uri conditionId) {
279 synchronized (mSnoozedForAlarm) {
280 mSnoozedForAlarm.add(conditionId);
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -0400281 saveSnoozedLocked();
282 }
283 }
284
285 private void removeSnoozed(Uri conditionId) {
Julia Reynolds4b318432017-11-22 10:56:18 -0500286 synchronized (mSnoozedForAlarm) {
287 mSnoozedForAlarm.remove(conditionId);
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -0400288 saveSnoozedLocked();
289 }
290 }
291
Julia Reynolds4b318432017-11-22 10:56:18 -0500292 private void saveSnoozedLocked() {
293 final String setting = TextUtils.join(SEPARATOR, mSnoozedForAlarm);
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -0400294 final int currentUser = ActivityManager.getCurrentUser();
295 Settings.Secure.putStringForUser(mContext.getContentResolver(),
296 SCP_SETTING,
297 setting,
298 currentUser);
299 }
300
Julia Reynolds4b318432017-11-22 10:56:18 -0500301 private void readSnoozed() {
302 synchronized (mSnoozedForAlarm) {
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -0400303 long identity = Binder.clearCallingIdentity();
304 try {
305 final String setting = Settings.Secure.getStringForUser(
306 mContext.getContentResolver(),
307 SCP_SETTING,
308 ActivityManager.getCurrentUser());
309 if (setting != null) {
310 final String[] tokens = setting.split(SEPARATOR);
311 for (int i = 0; i < tokens.length; i++) {
312 String token = tokens[i];
313 if (token != null) {
314 token = token.trim();
315 }
316 if (TextUtils.isEmpty(token)) {
317 continue;
318 }
Julia Reynolds4b318432017-11-22 10:56:18 -0500319 mSnoozedForAlarm.add(Uri.parse(token));
Julia Reynoldsfe58f1f2016-07-19 10:18:32 -0400320 }
321 }
322 } finally {
323 Binder.restoreCallingIdentity(identity);
324 }
325 }
326 }
327
John Spurlockb2278d62015-04-07 12:47:12 -0400328 private BroadcastReceiver mReceiver = new BroadcastReceiver() {
329 @Override
330 public void onReceive(Context context, Intent intent) {
331 if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
Julia Reynolds83eec702016-05-25 12:29:55 -0400332 if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
Julia Reynolds2ea7b092016-09-14 11:24:30 -0400333 synchronized (mSubscriptions) {
334 for (Uri conditionId : mSubscriptions.keySet()) {
335 final ScheduleCalendar cal = mSubscriptions.get(conditionId);
336 if (cal != null) {
337 cal.setTimeZone(Calendar.getInstance().getTimeZone());
338 }
Julia Reynolds83eec702016-05-25 12:29:55 -0400339 }
340 }
341 }
John Spurlockb2278d62015-04-07 12:47:12 -0400342 evaluateSubscriptions();
343 }
344 };
345
346}