blob: a4d5bce3363c502ce09fb4114cf6cb51f3bc7b41 [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;
29import android.os.Looper;
30import android.os.UserHandle;
31import android.os.UserManager;
John Spurlockd60258f2015-04-30 09:30:52 -040032import android.service.notification.Condition;
33import android.service.notification.IConditionProvider;
34import android.service.notification.ZenModeConfig;
John Spurlock2f096ed2015-05-04 11:58:26 -040035import android.service.notification.ZenModeConfig.EventInfo;
John Spurlockd60258f2015-04-30 09:30:52 -040036import android.util.ArraySet;
37import android.util.Log;
38import android.util.Slog;
John Spurlock1b8b22b2015-05-20 09:47:13 -040039import android.util.SparseArray;
John Spurlockd60258f2015-04-30 09:30:52 -040040
John Spurlock2f096ed2015-05-04 11:58:26 -040041import com.android.server.notification.CalendarTracker.CheckEventResult;
John Spurlockd60258f2015-04-30 09:30:52 -040042import com.android.server.notification.NotificationManagerService.DumpFilter;
43
44import java.io.PrintWriter;
45
46/**
47 * Built-in zen condition provider for calendar event-based conditions.
48 */
49public class EventConditionProvider extends SystemConditionProviderService {
John Spurlock2f096ed2015-05-04 11:58:26 -040050 private static final String TAG = "ConditionProviders.ECP";
51 private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG);
John Spurlockd60258f2015-04-30 09:30:52 -040052
53 public static final ComponentName COMPONENT =
54 new ComponentName("android", EventConditionProvider.class.getName());
55 private static final String NOT_SHOWN = "...";
John Spurlock2f096ed2015-05-04 11:58:26 -040056 private static final String SIMPLE_NAME = EventConditionProvider.class.getSimpleName();
57 private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE";
58 private static final int REQUEST_CODE_EVALUATE = 1;
59 private static final String EXTRA_TIME = "time";
John Spurlock1b8b22b2015-05-20 09:47:13 -040060 private static final long CHANGE_DELAY = 2 * 1000; // coalesce chatty calendar changes
John Spurlockd60258f2015-04-30 09:30:52 -040061
John Spurlock2f096ed2015-05-04 11:58:26 -040062 private final Context mContext = this;
John Spurlockd60258f2015-04-30 09:30:52 -040063 private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
John Spurlock1b8b22b2015-05-20 09:47:13 -040064 private final SparseArray<CalendarTracker> mTrackers = new SparseArray<>();
65 private final Handler mWorker;
John Spurlockd60258f2015-04-30 09:30:52 -040066
67 private boolean mConnected;
68 private boolean mRegistered;
John Spurlock2f096ed2015-05-04 11:58:26 -040069 private boolean mBootComplete; // don't hammer the calendar provider until boot completes.
John Spurlock1b8b22b2015-05-20 09:47:13 -040070 private long mNextAlarmTime;
John Spurlockd60258f2015-04-30 09:30:52 -040071
John Spurlock1b8b22b2015-05-20 09:47:13 -040072 public EventConditionProvider(Looper worker) {
John Spurlock2f096ed2015-05-04 11:58:26 -040073 if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()");
John Spurlock1b8b22b2015-05-20 09:47:13 -040074 mWorker = new Handler(worker);
John Spurlockd60258f2015-04-30 09:30:52 -040075 }
76
77 @Override
78 public ComponentName getComponent() {
79 return COMPONENT;
80 }
81
82 @Override
83 public boolean isValidConditionId(Uri id) {
84 return ZenModeConfig.isValidEventConditionId(id);
85 }
86
87 @Override
88 public void dump(PrintWriter pw, DumpFilter filter) {
John Spurlock2f096ed2015-05-04 11:58:26 -040089 pw.print(" "); pw.print(SIMPLE_NAME); pw.println(":");
John Spurlockd60258f2015-04-30 09:30:52 -040090 pw.print(" mConnected="); pw.println(mConnected);
91 pw.print(" mRegistered="); pw.println(mRegistered);
John Spurlock2f096ed2015-05-04 11:58:26 -040092 pw.print(" mBootComplete="); pw.println(mBootComplete);
John Spurlock1b8b22b2015-05-20 09:47:13 -040093 dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, System.currentTimeMillis());
John Spurlockd60258f2015-04-30 09:30:52 -040094 pw.println(" mSubscriptions=");
95 for (Uri conditionId : mSubscriptions) {
96 pw.print(" ");
97 pw.println(conditionId);
98 }
John Spurlock1b8b22b2015-05-20 09:47:13 -040099 pw.println(" mTrackers=");
100 for (int i = 0; i < mTrackers.size(); i++) {
101 pw.print(" user="); pw.println(mTrackers.keyAt(i));
102 mTrackers.valueAt(i).dump(" ", pw);
103 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400104 }
105
106 @Override
107 public void onBootComplete() {
108 if (DEBUG) Slog.d(TAG, "onBootComplete");
109 if (mBootComplete) return;
110 mBootComplete = true;
John Spurlock1b8b22b2015-05-20 09:47:13 -0400111 final IntentFilter filter = new IntentFilter();
112 filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
113 filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
114 mContext.registerReceiver(new BroadcastReceiver() {
115 @Override
116 public void onReceive(Context context, Intent intent) {
117 reloadTrackers();
118 }
119 }, filter);
120 reloadTrackers();
John Spurlockd60258f2015-04-30 09:30:52 -0400121 }
122
123 @Override
124 public void onConnected() {
125 if (DEBUG) Slog.d(TAG, "onConnected");
126 mConnected = true;
127 }
128
129 @Override
130 public void onDestroy() {
131 super.onDestroy();
132 if (DEBUG) Slog.d(TAG, "onDestroy");
133 mConnected = false;
134 }
135
136 @Override
137 public void onRequestConditions(int relevance) {
138 if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
139 // does not advertise conditions
140 }
141
142 @Override
143 public void onSubscribe(Uri conditionId) {
144 if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
145 if (!ZenModeConfig.isValidEventConditionId(conditionId)) {
146 notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition");
147 return;
148 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400149 if (mSubscriptions.add(conditionId)) {
150 evaluateSubscriptions();
151 }
John Spurlockd60258f2015-04-30 09:30:52 -0400152 }
153
154 @Override
155 public void onUnsubscribe(Uri conditionId) {
156 if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
157 if (mSubscriptions.remove(conditionId)) {
158 evaluateSubscriptions();
159 }
160 }
161
162 @Override
163 public void attachBase(Context base) {
164 attachBaseContext(base);
165 }
166
167 @Override
168 public IConditionProvider asInterface() {
169 return (IConditionProvider) onBind(null);
170 }
171
John Spurlock1b8b22b2015-05-20 09:47:13 -0400172 private void reloadTrackers() {
173 if (DEBUG) Slog.d(TAG, "reloadTrackers");
174 for (int i = 0; i < mTrackers.size(); i++) {
175 mTrackers.valueAt(i).setCallback(null);
176 }
177 mTrackers.clear();
178 for (UserHandle user : UserManager.get(mContext).getUserProfiles()) {
Xiaohui Chen66556302015-10-21 11:19:42 -0700179 final Context context = user.isSystem() ? mContext : getContextForUser(mContext, user);
John Spurlock1b8b22b2015-05-20 09:47:13 -0400180 if (context == null) {
181 Slog.w(TAG, "Unable to create context for user " + user.getIdentifier());
182 continue;
183 }
184 mTrackers.put(user.getIdentifier(), new CalendarTracker(mContext, context));
185 }
186 evaluateSubscriptions();
187 }
188
John Spurlockd60258f2015-04-30 09:30:52 -0400189 private void evaluateSubscriptions() {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400190 if (!mWorker.hasCallbacks(mEvaluateSubscriptionsW)) {
191 mWorker.post(mEvaluateSubscriptionsW);
192 }
193 }
194
195 private void evaluateSubscriptionsW() {
196 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions");
John Spurlock2f096ed2015-05-04 11:58:26 -0400197 if (!mBootComplete) {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400198 if (DEBUG) Slog.d(TAG, "Skipping evaluate before boot complete");
John Spurlock2f096ed2015-05-04 11:58:26 -0400199 return;
John Spurlockd60258f2015-04-30 09:30:52 -0400200 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400201 final long now = System.currentTimeMillis();
John Spurlock1b8b22b2015-05-20 09:47:13 -0400202 for (int i = 0; i < mTrackers.size(); i++) {
203 mTrackers.valueAt(i).setCallback(mSubscriptions.isEmpty() ? null : mTrackerCallback);
204 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400205 setRegistered(!mSubscriptions.isEmpty());
206 long reevaluateAt = 0;
207 for (Uri conditionId : mSubscriptions) {
208 final EventInfo event = ZenModeConfig.tryParseEventConditionId(conditionId);
209 if (event == null) {
210 notifyCondition(conditionId, Condition.STATE_FALSE, "badConditionId");
211 continue;
212 }
John Spurlock1b8b22b2015-05-20 09:47:13 -0400213 CheckEventResult result = null;
John Spurlock995a7492015-05-28 22:13:03 -0400214 if (event.calendar == null) { // any calendar
John Spurlock1b8b22b2015-05-20 09:47:13 -0400215 // event could exist on any tracker
216 for (int i = 0; i < mTrackers.size(); i++) {
217 final CalendarTracker tracker = mTrackers.valueAt(i);
218 final CheckEventResult r = tracker.checkEvent(event, now);
219 if (result == null) {
220 result = r;
221 } else {
222 result.inEvent |= r.inEvent;
223 result.recheckAt = Math.min(result.recheckAt, r.recheckAt);
224 }
225 }
226 } else {
227 // event should exist on one tracker
228 final int userId = EventInfo.resolveUserId(event.userId);
229 final CalendarTracker tracker = mTrackers.get(userId);
230 if (tracker == null) {
231 Slog.w(TAG, "No calendar tracker found for user " + userId);
232 notifyCondition(conditionId, Condition.STATE_FALSE, "badUserId");
233 continue;
234 }
235 result = tracker.checkEvent(event, now);
236 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400237 if (result.recheckAt != 0 && (reevaluateAt == 0 || result.recheckAt < reevaluateAt)) {
238 reevaluateAt = result.recheckAt;
239 }
240 if (!result.inEvent) {
241 notifyCondition(conditionId, Condition.STATE_FALSE, "!inEventNow");
242 continue;
243 }
244 notifyCondition(conditionId, Condition.STATE_TRUE, "inEventNow");
245 }
John Spurlock1b8b22b2015-05-20 09:47:13 -0400246 rescheduleAlarm(now, reevaluateAt);
247 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions took " + (System.currentTimeMillis() - now));
John Spurlock2f096ed2015-05-04 11:58:26 -0400248 }
249
John Spurlock1b8b22b2015-05-20 09:47:13 -0400250 private void rescheduleAlarm(long now, long time) {
251 mNextAlarmTime = time;
John Spurlock2f096ed2015-05-04 11:58:26 -0400252 final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
253 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
254 REQUEST_CODE_EVALUATE,
255 new Intent(ACTION_EVALUATE)
256 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
257 .putExtra(EXTRA_TIME, time),
258 PendingIntent.FLAG_UPDATE_CURRENT);
259 alarms.cancel(pendingIntent);
260 if (time == 0 || time < now) {
261 if (DEBUG) Slog.d(TAG, "Not scheduling evaluate: " + (time == 0 ? "no time specified"
262 : "specified time in the past"));
263 return;
264 }
265 if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s",
266 ts(time), formatDuration(time - now), ts(now)));
267 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
John Spurlockd60258f2015-04-30 09:30:52 -0400268 }
269
270 private void notifyCondition(Uri conditionId, int state, String reason) {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400271 if (DEBUG) Slog.d(TAG, "notifyCondition " + conditionId + " "
272 + Condition.stateToString(state) + " reason=" + reason);
John Spurlockd60258f2015-04-30 09:30:52 -0400273 notifyCondition(createCondition(conditionId, state));
274 }
275
276 private Condition createCondition(Uri id, int state) {
277 final String summary = NOT_SHOWN;
278 final String line1 = NOT_SHOWN;
279 final String line2 = NOT_SHOWN;
280 return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS);
281 }
282
John Spurlock2f096ed2015-05-04 11:58:26 -0400283 private void setRegistered(boolean registered) {
284 if (mRegistered == registered) return;
285 if (DEBUG) Slog.d(TAG, "setRegistered " + registered);
286 mRegistered = registered;
287 if (mRegistered) {
288 final IntentFilter filter = new IntentFilter();
289 filter.addAction(Intent.ACTION_TIME_CHANGED);
290 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
291 filter.addAction(ACTION_EVALUATE);
292 registerReceiver(mReceiver, filter);
293 } else {
294 unregisterReceiver(mReceiver);
295 }
296 }
297
John Spurlock1b8b22b2015-05-20 09:47:13 -0400298 private static Context getContextForUser(Context context, UserHandle user) {
299 try {
300 return context.createPackageContextAsUser(context.getPackageName(), 0, user);
301 } catch (NameNotFoundException e) {
302 return null;
303 }
304 }
305
John Spurlock2f096ed2015-05-04 11:58:26 -0400306 private final CalendarTracker.Callback mTrackerCallback = new CalendarTracker.Callback() {
307 @Override
308 public void onChanged() {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400309 if (DEBUG) Slog.d(TAG, "mTrackerCallback.onChanged");
310 mWorker.removeCallbacks(mEvaluateSubscriptionsW);
311 mWorker.postDelayed(mEvaluateSubscriptionsW, CHANGE_DELAY);
John Spurlock2f096ed2015-05-04 11:58:26 -0400312 }
313 };
314
John Spurlock1b8b22b2015-05-20 09:47:13 -0400315 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
John Spurlock2f096ed2015-05-04 11:58:26 -0400316 @Override
317 public void onReceive(Context context, Intent intent) {
318 if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
319 evaluateSubscriptions();
320 }
321 };
John Spurlock1b8b22b2015-05-20 09:47:13 -0400322
323 private final Runnable mEvaluateSubscriptionsW = new Runnable() {
324 @Override
325 public void run() {
326 evaluateSubscriptionsW();
327 }
328 };
John Spurlockd60258f2015-04-30 09:30:52 -0400329}