blob: b13fec19cc66c562c0a11a35b0cde3cb956a01f1 [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;
47
48/**
49 * Built-in zen condition provider for calendar event-based conditions.
50 */
51public class EventConditionProvider extends SystemConditionProviderService {
John Spurlock2f096ed2015-05-04 11:58:26 -040052 private static final String TAG = "ConditionProviders.ECP";
53 private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG);
John Spurlockd60258f2015-04-30 09:30:52 -040054
55 public static final ComponentName COMPONENT =
56 new ComponentName("android", EventConditionProvider.class.getName());
57 private static final String NOT_SHOWN = "...";
John Spurlock2f096ed2015-05-04 11:58:26 -040058 private static final String SIMPLE_NAME = EventConditionProvider.class.getSimpleName();
59 private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE";
60 private static final int REQUEST_CODE_EVALUATE = 1;
61 private static final String EXTRA_TIME = "time";
John Spurlock1b8b22b2015-05-20 09:47:13 -040062 private static final long CHANGE_DELAY = 2 * 1000; // coalesce chatty calendar changes
John Spurlockd60258f2015-04-30 09:30:52 -040063
John Spurlock2f096ed2015-05-04 11:58:26 -040064 private final Context mContext = this;
John Spurlockd60258f2015-04-30 09:30:52 -040065 private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
John Spurlock1b8b22b2015-05-20 09:47:13 -040066 private final SparseArray<CalendarTracker> mTrackers = new SparseArray<>();
67 private final Handler mWorker;
Julia Reynolds7034caf2016-03-09 10:41:00 -050068 private final HandlerThread mThread;
John Spurlockd60258f2015-04-30 09:30:52 -040069
70 private boolean mConnected;
71 private boolean mRegistered;
John Spurlock2f096ed2015-05-04 11:58:26 -040072 private boolean mBootComplete; // don't hammer the calendar provider until boot completes.
John Spurlock1b8b22b2015-05-20 09:47:13 -040073 private long mNextAlarmTime;
John Spurlockd60258f2015-04-30 09:30:52 -040074
Julia Reynolds7034caf2016-03-09 10:41:00 -050075 public EventConditionProvider() {
John Spurlock2f096ed2015-05-04 11:58:26 -040076 if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()");
Julia Reynolds7034caf2016-03-09 10:41:00 -050077 mThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
78 mThread.start();
79 mWorker = new Handler(mThread.getLooper());
John Spurlockd60258f2015-04-30 09:30:52 -040080 }
81
82 @Override
83 public ComponentName getComponent() {
84 return COMPONENT;
85 }
86
87 @Override
88 public boolean isValidConditionId(Uri id) {
89 return ZenModeConfig.isValidEventConditionId(id);
90 }
91
92 @Override
93 public void dump(PrintWriter pw, DumpFilter filter) {
John Spurlock2f096ed2015-05-04 11:58:26 -040094 pw.print(" "); pw.print(SIMPLE_NAME); pw.println(":");
John Spurlockd60258f2015-04-30 09:30:52 -040095 pw.print(" mConnected="); pw.println(mConnected);
96 pw.print(" mRegistered="); pw.println(mRegistered);
John Spurlock2f096ed2015-05-04 11:58:26 -040097 pw.print(" mBootComplete="); pw.println(mBootComplete);
John Spurlock1b8b22b2015-05-20 09:47:13 -040098 dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, System.currentTimeMillis());
John Spurlockd60258f2015-04-30 09:30:52 -040099 pw.println(" mSubscriptions=");
100 for (Uri conditionId : mSubscriptions) {
101 pw.print(" ");
102 pw.println(conditionId);
103 }
John Spurlock1b8b22b2015-05-20 09:47:13 -0400104 pw.println(" mTrackers=");
105 for (int i = 0; i < mTrackers.size(); i++) {
106 pw.print(" user="); pw.println(mTrackers.keyAt(i));
107 mTrackers.valueAt(i).dump(" ", pw);
108 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400109 }
110
111 @Override
112 public void onBootComplete() {
113 if (DEBUG) Slog.d(TAG, "onBootComplete");
114 if (mBootComplete) return;
115 mBootComplete = true;
John Spurlock1b8b22b2015-05-20 09:47:13 -0400116 final IntentFilter filter = new IntentFilter();
117 filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
118 filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
119 mContext.registerReceiver(new BroadcastReceiver() {
120 @Override
121 public void onReceive(Context context, Intent intent) {
122 reloadTrackers();
123 }
124 }, filter);
125 reloadTrackers();
John Spurlockd60258f2015-04-30 09:30:52 -0400126 }
127
128 @Override
129 public void onConnected() {
130 if (DEBUG) Slog.d(TAG, "onConnected");
131 mConnected = true;
132 }
133
134 @Override
135 public void onDestroy() {
136 super.onDestroy();
137 if (DEBUG) Slog.d(TAG, "onDestroy");
138 mConnected = false;
139 }
140
141 @Override
John Spurlockd60258f2015-04-30 09:30:52 -0400142 public void onSubscribe(Uri conditionId) {
143 if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
144 if (!ZenModeConfig.isValidEventConditionId(conditionId)) {
145 notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition");
146 return;
147 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400148 if (mSubscriptions.add(conditionId)) {
149 evaluateSubscriptions();
150 }
John Spurlockd60258f2015-04-30 09:30:52 -0400151 }
152
153 @Override
154 public void onUnsubscribe(Uri conditionId) {
155 if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
156 if (mSubscriptions.remove(conditionId)) {
157 evaluateSubscriptions();
158 }
159 }
160
161 @Override
162 public void attachBase(Context base) {
163 attachBaseContext(base);
164 }
165
166 @Override
167 public IConditionProvider asInterface() {
168 return (IConditionProvider) onBind(null);
169 }
170
John Spurlock1b8b22b2015-05-20 09:47:13 -0400171 private void reloadTrackers() {
172 if (DEBUG) Slog.d(TAG, "reloadTrackers");
173 for (int i = 0; i < mTrackers.size(); i++) {
174 mTrackers.valueAt(i).setCallback(null);
175 }
176 mTrackers.clear();
177 for (UserHandle user : UserManager.get(mContext).getUserProfiles()) {
Xiaohui Chen66556302015-10-21 11:19:42 -0700178 final Context context = user.isSystem() ? mContext : getContextForUser(mContext, user);
John Spurlock1b8b22b2015-05-20 09:47:13 -0400179 if (context == null) {
180 Slog.w(TAG, "Unable to create context for user " + user.getIdentifier());
181 continue;
182 }
183 mTrackers.put(user.getIdentifier(), new CalendarTracker(mContext, context));
184 }
185 evaluateSubscriptions();
186 }
187
John Spurlockd60258f2015-04-30 09:30:52 -0400188 private void evaluateSubscriptions() {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400189 if (!mWorker.hasCallbacks(mEvaluateSubscriptionsW)) {
190 mWorker.post(mEvaluateSubscriptionsW);
191 }
192 }
193
194 private void evaluateSubscriptionsW() {
195 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions");
John Spurlock2f096ed2015-05-04 11:58:26 -0400196 if (!mBootComplete) {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400197 if (DEBUG) Slog.d(TAG, "Skipping evaluate before boot complete");
John Spurlock2f096ed2015-05-04 11:58:26 -0400198 return;
John Spurlockd60258f2015-04-30 09:30:52 -0400199 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400200 final long now = System.currentTimeMillis();
John Spurlock1b8b22b2015-05-20 09:47:13 -0400201 for (int i = 0; i < mTrackers.size(); i++) {
202 mTrackers.valueAt(i).setCallback(mSubscriptions.isEmpty() ? null : mTrackerCallback);
203 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400204 setRegistered(!mSubscriptions.isEmpty());
205 long reevaluateAt = 0;
206 for (Uri conditionId : mSubscriptions) {
207 final EventInfo event = ZenModeConfig.tryParseEventConditionId(conditionId);
208 if (event == null) {
209 notifyCondition(conditionId, Condition.STATE_FALSE, "badConditionId");
210 continue;
211 }
John Spurlock1b8b22b2015-05-20 09:47:13 -0400212 CheckEventResult result = null;
John Spurlock995a7492015-05-28 22:13:03 -0400213 if (event.calendar == null) { // any calendar
John Spurlock1b8b22b2015-05-20 09:47:13 -0400214 // event could exist on any tracker
215 for (int i = 0; i < mTrackers.size(); i++) {
216 final CalendarTracker tracker = mTrackers.valueAt(i);
217 final CheckEventResult r = tracker.checkEvent(event, now);
218 if (result == null) {
219 result = r;
220 } else {
221 result.inEvent |= r.inEvent;
222 result.recheckAt = Math.min(result.recheckAt, r.recheckAt);
223 }
224 }
225 } else {
226 // event should exist on one tracker
227 final int userId = EventInfo.resolveUserId(event.userId);
228 final CalendarTracker tracker = mTrackers.get(userId);
229 if (tracker == null) {
230 Slog.w(TAG, "No calendar tracker found for user " + userId);
231 notifyCondition(conditionId, Condition.STATE_FALSE, "badUserId");
232 continue;
233 }
234 result = tracker.checkEvent(event, now);
235 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400236 if (result.recheckAt != 0 && (reevaluateAt == 0 || result.recheckAt < reevaluateAt)) {
237 reevaluateAt = result.recheckAt;
238 }
239 if (!result.inEvent) {
240 notifyCondition(conditionId, Condition.STATE_FALSE, "!inEventNow");
241 continue;
242 }
243 notifyCondition(conditionId, Condition.STATE_TRUE, "inEventNow");
244 }
John Spurlock1b8b22b2015-05-20 09:47:13 -0400245 rescheduleAlarm(now, reevaluateAt);
246 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions took " + (System.currentTimeMillis() - now));
John Spurlock2f096ed2015-05-04 11:58:26 -0400247 }
248
John Spurlock1b8b22b2015-05-20 09:47:13 -0400249 private void rescheduleAlarm(long now, long time) {
250 mNextAlarmTime = time;
John Spurlock2f096ed2015-05-04 11:58:26 -0400251 final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
252 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
253 REQUEST_CODE_EVALUATE,
254 new Intent(ACTION_EVALUATE)
255 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
256 .putExtra(EXTRA_TIME, time),
257 PendingIntent.FLAG_UPDATE_CURRENT);
258 alarms.cancel(pendingIntent);
259 if (time == 0 || time < now) {
260 if (DEBUG) Slog.d(TAG, "Not scheduling evaluate: " + (time == 0 ? "no time specified"
261 : "specified time in the past"));
262 return;
263 }
264 if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s",
265 ts(time), formatDuration(time - now), ts(now)));
266 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
John Spurlockd60258f2015-04-30 09:30:52 -0400267 }
268
269 private void notifyCondition(Uri conditionId, int state, String reason) {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400270 if (DEBUG) Slog.d(TAG, "notifyCondition " + conditionId + " "
271 + Condition.stateToString(state) + " reason=" + reason);
John Spurlockd60258f2015-04-30 09:30:52 -0400272 notifyCondition(createCondition(conditionId, state));
273 }
274
275 private Condition createCondition(Uri id, int state) {
276 final String summary = NOT_SHOWN;
277 final String line1 = NOT_SHOWN;
278 final String line2 = NOT_SHOWN;
279 return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS);
280 }
281
John Spurlock2f096ed2015-05-04 11:58:26 -0400282 private void setRegistered(boolean registered) {
283 if (mRegistered == registered) return;
284 if (DEBUG) Slog.d(TAG, "setRegistered " + registered);
285 mRegistered = registered;
286 if (mRegistered) {
287 final IntentFilter filter = new IntentFilter();
288 filter.addAction(Intent.ACTION_TIME_CHANGED);
289 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
290 filter.addAction(ACTION_EVALUATE);
291 registerReceiver(mReceiver, filter);
292 } else {
293 unregisterReceiver(mReceiver);
294 }
295 }
296
John Spurlock1b8b22b2015-05-20 09:47:13 -0400297 private static Context getContextForUser(Context context, UserHandle user) {
298 try {
299 return context.createPackageContextAsUser(context.getPackageName(), 0, user);
300 } catch (NameNotFoundException e) {
301 return null;
302 }
303 }
304
John Spurlock2f096ed2015-05-04 11:58:26 -0400305 private final CalendarTracker.Callback mTrackerCallback = new CalendarTracker.Callback() {
306 @Override
307 public void onChanged() {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400308 if (DEBUG) Slog.d(TAG, "mTrackerCallback.onChanged");
309 mWorker.removeCallbacks(mEvaluateSubscriptionsW);
310 mWorker.postDelayed(mEvaluateSubscriptionsW, CHANGE_DELAY);
John Spurlock2f096ed2015-05-04 11:58:26 -0400311 }
312 };
313
John Spurlock1b8b22b2015-05-20 09:47:13 -0400314 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
John Spurlock2f096ed2015-05-04 11:58:26 -0400315 @Override
316 public void onReceive(Context context, Intent intent) {
317 if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
318 evaluateSubscriptions();
319 }
320 };
John Spurlock1b8b22b2015-05-20 09:47:13 -0400321
322 private final Runnable mEvaluateSubscriptionsW = new Runnable() {
323 @Override
324 public void run() {
325 evaluateSubscriptionsW();
326 }
327 };
John Spurlockd60258f2015-04-30 09:30:52 -0400328}