blob: 5bc9b1c71f6819cd40c5295ab2f17c5cab792be7 [file] [log] [blame]
John Spurlockd60258f2015-04-30 09:30:52 -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
John Spurlock2f096ed2015-05-04 11:58:26 -040019import android.app.AlarmManager;
20import android.app.PendingIntent;
21import android.content.BroadcastReceiver;
John Spurlockd60258f2015-04-30 09:30:52 -040022import android.content.ComponentName;
23import android.content.Context;
John Spurlock2f096ed2015-05-04 11:58:26 -040024import android.content.Intent;
25import android.content.IntentFilter;
John Spurlock1b8b22b2015-05-20 09:47:13 -040026import android.content.pm.PackageManager.NameNotFoundException;
John Spurlockd60258f2015-04-30 09:30:52 -040027import android.net.Uri;
John Spurlock1b8b22b2015-05-20 09:47:13 -040028import android.os.Handler;
Julia Reynolds7034caf2016-03-09 10:41:00 -050029import android.os.HandlerThread;
John Spurlock1b8b22b2015-05-20 09:47:13 -040030import android.os.Looper;
Julia Reynolds7034caf2016-03-09 10:41:00 -050031import android.os.Process;
John Spurlock1b8b22b2015-05-20 09:47:13 -040032import android.os.UserHandle;
33import android.os.UserManager;
John Spurlockd60258f2015-04-30 09:30:52 -040034import android.service.notification.Condition;
35import android.service.notification.IConditionProvider;
36import android.service.notification.ZenModeConfig;
John Spurlock2f096ed2015-05-04 11:58:26 -040037import android.service.notification.ZenModeConfig.EventInfo;
John Spurlockd60258f2015-04-30 09:30:52 -040038import android.util.ArraySet;
39import android.util.Log;
40import android.util.Slog;
John Spurlock1b8b22b2015-05-20 09:47:13 -040041import android.util.SparseArray;
John Spurlockd60258f2015-04-30 09:30:52 -040042
John Spurlock2f096ed2015-05-04 11:58:26 -040043import com.android.server.notification.CalendarTracker.CheckEventResult;
John Spurlockd60258f2015-04-30 09:30:52 -040044import com.android.server.notification.NotificationManagerService.DumpFilter;
45
46import java.io.PrintWriter;
Julia Reynolds2c93da92016-03-15 16:39:24 -040047import java.util.ArrayList;
48import java.util.List;
John Spurlockd60258f2015-04-30 09:30:52 -040049
50/**
51 * Built-in zen condition provider for calendar event-based conditions.
52 */
53public class EventConditionProvider extends SystemConditionProviderService {
John Spurlock2f096ed2015-05-04 11:58:26 -040054 private static final String TAG = "ConditionProviders.ECP";
55 private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG);
John Spurlockd60258f2015-04-30 09:30:52 -040056
57 public static final ComponentName COMPONENT =
58 new ComponentName("android", EventConditionProvider.class.getName());
59 private static final String NOT_SHOWN = "...";
John Spurlock2f096ed2015-05-04 11:58:26 -040060 private static final String SIMPLE_NAME = EventConditionProvider.class.getSimpleName();
61 private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE";
62 private static final int REQUEST_CODE_EVALUATE = 1;
63 private static final String EXTRA_TIME = "time";
John Spurlock1b8b22b2015-05-20 09:47:13 -040064 private static final long CHANGE_DELAY = 2 * 1000; // coalesce chatty calendar changes
John Spurlockd60258f2015-04-30 09:30:52 -040065
John Spurlock2f096ed2015-05-04 11:58:26 -040066 private final Context mContext = this;
John Spurlockd60258f2015-04-30 09:30:52 -040067 private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
John Spurlock1b8b22b2015-05-20 09:47:13 -040068 private final SparseArray<CalendarTracker> mTrackers = new SparseArray<>();
69 private final Handler mWorker;
Julia Reynolds7034caf2016-03-09 10:41:00 -050070 private final HandlerThread mThread;
John Spurlockd60258f2015-04-30 09:30:52 -040071
72 private boolean mConnected;
73 private boolean mRegistered;
John Spurlock2f096ed2015-05-04 11:58:26 -040074 private boolean mBootComplete; // don't hammer the calendar provider until boot completes.
John Spurlock1b8b22b2015-05-20 09:47:13 -040075 private long mNextAlarmTime;
John Spurlockd60258f2015-04-30 09:30:52 -040076
Julia Reynolds7034caf2016-03-09 10:41:00 -050077 public EventConditionProvider() {
John Spurlock2f096ed2015-05-04 11:58:26 -040078 if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()");
Julia Reynolds7034caf2016-03-09 10:41:00 -050079 mThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
80 mThread.start();
81 mWorker = new Handler(mThread.getLooper());
John Spurlockd60258f2015-04-30 09:30:52 -040082 }
83
84 @Override
85 public ComponentName getComponent() {
86 return COMPONENT;
87 }
88
89 @Override
90 public boolean isValidConditionId(Uri id) {
91 return ZenModeConfig.isValidEventConditionId(id);
92 }
93
94 @Override
95 public void dump(PrintWriter pw, DumpFilter filter) {
John Spurlock2f096ed2015-05-04 11:58:26 -040096 pw.print(" "); pw.print(SIMPLE_NAME); pw.println(":");
John Spurlockd60258f2015-04-30 09:30:52 -040097 pw.print(" mConnected="); pw.println(mConnected);
98 pw.print(" mRegistered="); pw.println(mRegistered);
John Spurlock2f096ed2015-05-04 11:58:26 -040099 pw.print(" mBootComplete="); pw.println(mBootComplete);
John Spurlock1b8b22b2015-05-20 09:47:13 -0400100 dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, System.currentTimeMillis());
Julia Reynolds2c93da92016-03-15 16:39:24 -0400101 synchronized (mSubscriptions) {
102 pw.println(" mSubscriptions=");
103 for (Uri conditionId : mSubscriptions) {
104 pw.print(" ");
105 pw.println(conditionId);
106 }
John Spurlockd60258f2015-04-30 09:30:52 -0400107 }
John Spurlock1b8b22b2015-05-20 09:47:13 -0400108 pw.println(" mTrackers=");
109 for (int i = 0; i < mTrackers.size(); i++) {
110 pw.print(" user="); pw.println(mTrackers.keyAt(i));
111 mTrackers.valueAt(i).dump(" ", pw);
112 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400113 }
114
115 @Override
116 public void onBootComplete() {
117 if (DEBUG) Slog.d(TAG, "onBootComplete");
118 if (mBootComplete) return;
119 mBootComplete = true;
John Spurlock1b8b22b2015-05-20 09:47:13 -0400120 final IntentFilter filter = new IntentFilter();
121 filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
122 filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
123 mContext.registerReceiver(new BroadcastReceiver() {
124 @Override
125 public void onReceive(Context context, Intent intent) {
126 reloadTrackers();
127 }
128 }, filter);
129 reloadTrackers();
John Spurlockd60258f2015-04-30 09:30:52 -0400130 }
131
132 @Override
133 public void onConnected() {
134 if (DEBUG) Slog.d(TAG, "onConnected");
135 mConnected = true;
136 }
137
138 @Override
139 public void onDestroy() {
140 super.onDestroy();
141 if (DEBUG) Slog.d(TAG, "onDestroy");
142 mConnected = false;
143 }
144
145 @Override
John Spurlockd60258f2015-04-30 09:30:52 -0400146 public void onSubscribe(Uri conditionId) {
147 if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
148 if (!ZenModeConfig.isValidEventConditionId(conditionId)) {
Julia Reynolds2c93da92016-03-15 16:39:24 -0400149 notifyCondition(createCondition(conditionId, Condition.STATE_FALSE));
John Spurlockd60258f2015-04-30 09:30:52 -0400150 return;
151 }
Julia Reynolds2c93da92016-03-15 16:39:24 -0400152 synchronized (mSubscriptions) {
153 if (mSubscriptions.add(conditionId)) {
154 evaluateSubscriptions();
155 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400156 }
John Spurlockd60258f2015-04-30 09:30:52 -0400157 }
158
159 @Override
160 public void onUnsubscribe(Uri conditionId) {
161 if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
Julia Reynolds2c93da92016-03-15 16:39:24 -0400162 synchronized (mSubscriptions) {
163 if (mSubscriptions.remove(conditionId)) {
164 evaluateSubscriptions();
165 }
John Spurlockd60258f2015-04-30 09:30:52 -0400166 }
167 }
168
169 @Override
170 public void attachBase(Context base) {
171 attachBaseContext(base);
172 }
173
174 @Override
175 public IConditionProvider asInterface() {
176 return (IConditionProvider) onBind(null);
177 }
178
John Spurlock1b8b22b2015-05-20 09:47:13 -0400179 private void reloadTrackers() {
180 if (DEBUG) Slog.d(TAG, "reloadTrackers");
181 for (int i = 0; i < mTrackers.size(); i++) {
182 mTrackers.valueAt(i).setCallback(null);
183 }
184 mTrackers.clear();
185 for (UserHandle user : UserManager.get(mContext).getUserProfiles()) {
Xiaohui Chen66556302015-10-21 11:19:42 -0700186 final Context context = user.isSystem() ? mContext : getContextForUser(mContext, user);
John Spurlock1b8b22b2015-05-20 09:47:13 -0400187 if (context == null) {
188 Slog.w(TAG, "Unable to create context for user " + user.getIdentifier());
189 continue;
190 }
191 mTrackers.put(user.getIdentifier(), new CalendarTracker(mContext, context));
192 }
193 evaluateSubscriptions();
194 }
195
John Spurlockd60258f2015-04-30 09:30:52 -0400196 private void evaluateSubscriptions() {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400197 if (!mWorker.hasCallbacks(mEvaluateSubscriptionsW)) {
198 mWorker.post(mEvaluateSubscriptionsW);
199 }
200 }
201
202 private void evaluateSubscriptionsW() {
203 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions");
John Spurlock2f096ed2015-05-04 11:58:26 -0400204 if (!mBootComplete) {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400205 if (DEBUG) Slog.d(TAG, "Skipping evaluate before boot complete");
John Spurlock2f096ed2015-05-04 11:58:26 -0400206 return;
John Spurlockd60258f2015-04-30 09:30:52 -0400207 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400208 final long now = System.currentTimeMillis();
Julia Reynolds2c93da92016-03-15 16:39:24 -0400209 List<Condition> conditionsToNotify = new ArrayList<>();
210 synchronized (mSubscriptions) {
211 for (int i = 0; i < mTrackers.size(); i++) {
212 mTrackers.valueAt(i).setCallback(
213 mSubscriptions.isEmpty() ? null : mTrackerCallback);
John Spurlock2f096ed2015-05-04 11:58:26 -0400214 }
Julia Reynolds2c93da92016-03-15 16:39:24 -0400215 setRegistered(!mSubscriptions.isEmpty());
216 long reevaluateAt = 0;
217 for (Uri conditionId : mSubscriptions) {
218 final EventInfo event = ZenModeConfig.tryParseEventConditionId(conditionId);
219 if (event == null) {
220 conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE));
John Spurlock1b8b22b2015-05-20 09:47:13 -0400221 continue;
222 }
Julia Reynolds2c93da92016-03-15 16:39:24 -0400223 CheckEventResult result = null;
224 if (event.calendar == null) { // any calendar
225 // event could exist on any tracker
226 for (int i = 0; i < mTrackers.size(); i++) {
227 final CalendarTracker tracker = mTrackers.valueAt(i);
228 final CheckEventResult r = tracker.checkEvent(event, now);
229 if (result == null) {
230 result = r;
231 } else {
232 result.inEvent |= r.inEvent;
233 result.recheckAt = Math.min(result.recheckAt, r.recheckAt);
234 }
235 }
236 } else {
237 // event should exist on one tracker
238 final int userId = EventInfo.resolveUserId(event.userId);
239 final CalendarTracker tracker = mTrackers.get(userId);
240 if (tracker == null) {
241 Slog.w(TAG, "No calendar tracker found for user " + userId);
242 conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE));
243 continue;
244 }
245 result = tracker.checkEvent(event, now);
246 }
247 if (result.recheckAt != 0
248 && (reevaluateAt == 0 || result.recheckAt < reevaluateAt)) {
249 reevaluateAt = result.recheckAt;
250 }
251 if (!result.inEvent) {
252 conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE));
253 continue;
254 }
255 conditionsToNotify.add(createCondition(conditionId, Condition.STATE_TRUE));
John Spurlock1b8b22b2015-05-20 09:47:13 -0400256 }
Julia Reynolds2c93da92016-03-15 16:39:24 -0400257 rescheduleAlarm(now, reevaluateAt);
John Spurlock2f096ed2015-05-04 11:58:26 -0400258 }
Julia Reynolds2c93da92016-03-15 16:39:24 -0400259 for (Condition condition : conditionsToNotify) {
260 if (condition != null) {
261 notifyCondition(condition);
262 }
263 }
John Spurlock1b8b22b2015-05-20 09:47:13 -0400264 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions took " + (System.currentTimeMillis() - now));
John Spurlock2f096ed2015-05-04 11:58:26 -0400265 }
266
John Spurlock1b8b22b2015-05-20 09:47:13 -0400267 private void rescheduleAlarm(long now, long time) {
268 mNextAlarmTime = time;
John Spurlock2f096ed2015-05-04 11:58:26 -0400269 final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
270 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
271 REQUEST_CODE_EVALUATE,
272 new Intent(ACTION_EVALUATE)
273 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
274 .putExtra(EXTRA_TIME, time),
275 PendingIntent.FLAG_UPDATE_CURRENT);
276 alarms.cancel(pendingIntent);
277 if (time == 0 || time < now) {
278 if (DEBUG) Slog.d(TAG, "Not scheduling evaluate: " + (time == 0 ? "no time specified"
279 : "specified time in the past"));
280 return;
281 }
282 if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s",
283 ts(time), formatDuration(time - now), ts(now)));
284 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
John Spurlockd60258f2015-04-30 09:30:52 -0400285 }
286
John Spurlockd60258f2015-04-30 09:30:52 -0400287 private Condition createCondition(Uri id, int state) {
288 final String summary = NOT_SHOWN;
289 final String line1 = NOT_SHOWN;
290 final String line2 = NOT_SHOWN;
291 return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS);
292 }
293
John Spurlock2f096ed2015-05-04 11:58:26 -0400294 private void setRegistered(boolean registered) {
295 if (mRegistered == registered) return;
296 if (DEBUG) Slog.d(TAG, "setRegistered " + registered);
297 mRegistered = registered;
298 if (mRegistered) {
299 final IntentFilter filter = new IntentFilter();
300 filter.addAction(Intent.ACTION_TIME_CHANGED);
301 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
302 filter.addAction(ACTION_EVALUATE);
303 registerReceiver(mReceiver, filter);
304 } else {
305 unregisterReceiver(mReceiver);
306 }
307 }
308
John Spurlock1b8b22b2015-05-20 09:47:13 -0400309 private static Context getContextForUser(Context context, UserHandle user) {
310 try {
311 return context.createPackageContextAsUser(context.getPackageName(), 0, user);
312 } catch (NameNotFoundException e) {
313 return null;
314 }
315 }
316
John Spurlock2f096ed2015-05-04 11:58:26 -0400317 private final CalendarTracker.Callback mTrackerCallback = new CalendarTracker.Callback() {
318 @Override
319 public void onChanged() {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400320 if (DEBUG) Slog.d(TAG, "mTrackerCallback.onChanged");
321 mWorker.removeCallbacks(mEvaluateSubscriptionsW);
322 mWorker.postDelayed(mEvaluateSubscriptionsW, CHANGE_DELAY);
John Spurlock2f096ed2015-05-04 11:58:26 -0400323 }
324 };
325
John Spurlock1b8b22b2015-05-20 09:47:13 -0400326 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
John Spurlock2f096ed2015-05-04 11:58:26 -0400327 @Override
328 public void onReceive(Context context, Intent intent) {
329 if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
330 evaluateSubscriptions();
331 }
332 };
John Spurlock1b8b22b2015-05-20 09:47:13 -0400333
334 private final Runnable mEvaluateSubscriptionsW = new Runnable() {
335 @Override
336 public void run() {
337 evaluateSubscriptionsW();
338 }
339 };
John Spurlockd60258f2015-04-30 09:30:52 -0400340}