blob: a4a94c29abfa924d086e7e281c25cf8508afadcb [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;
Julia Reynolds7034caf2016-03-09 10:41:00 -050030import android.os.Process;
John Spurlock1b8b22b2015-05-20 09:47:13 -040031import android.os.UserHandle;
32import android.os.UserManager;
John Spurlockd60258f2015-04-30 09:30:52 -040033import android.service.notification.Condition;
34import android.service.notification.IConditionProvider;
35import android.service.notification.ZenModeConfig;
John Spurlock2f096ed2015-05-04 11:58:26 -040036import android.service.notification.ZenModeConfig.EventInfo;
John Spurlockd60258f2015-04-30 09:30:52 -040037import android.util.ArraySet;
38import android.util.Log;
39import android.util.Slog;
John Spurlock1b8b22b2015-05-20 09:47:13 -040040import android.util.SparseArray;
John Spurlockd60258f2015-04-30 09:30:52 -040041
John Spurlock2f096ed2015-05-04 11:58:26 -040042import com.android.server.notification.CalendarTracker.CheckEventResult;
John Spurlockd60258f2015-04-30 09:30:52 -040043import com.android.server.notification.NotificationManagerService.DumpFilter;
44
45import java.io.PrintWriter;
Julia Reynolds2c93da92016-03-15 16:39:24 -040046import java.util.ArrayList;
47import java.util.List;
John Spurlockd60258f2015-04-30 09:30:52 -040048
49/**
50 * Built-in zen condition provider for calendar event-based conditions.
51 */
52public class EventConditionProvider extends SystemConditionProviderService {
John Spurlock2f096ed2015-05-04 11:58:26 -040053 private static final String TAG = "ConditionProviders.ECP";
54 private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG);
John Spurlockd60258f2015-04-30 09:30:52 -040055
56 public static final ComponentName COMPONENT =
57 new ComponentName("android", EventConditionProvider.class.getName());
58 private static final String NOT_SHOWN = "...";
John Spurlock2f096ed2015-05-04 11:58:26 -040059 private static final String SIMPLE_NAME = EventConditionProvider.class.getSimpleName();
60 private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE";
61 private static final int REQUEST_CODE_EVALUATE = 1;
62 private static final String EXTRA_TIME = "time";
John Spurlock1b8b22b2015-05-20 09:47:13 -040063 private static final long CHANGE_DELAY = 2 * 1000; // coalesce chatty calendar changes
John Spurlockd60258f2015-04-30 09:30:52 -040064
John Spurlock2f096ed2015-05-04 11:58:26 -040065 private final Context mContext = this;
John Spurlockd60258f2015-04-30 09:30:52 -040066 private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
John Spurlock1b8b22b2015-05-20 09:47:13 -040067 private final SparseArray<CalendarTracker> mTrackers = new SparseArray<>();
68 private final Handler mWorker;
Julia Reynolds7034caf2016-03-09 10:41:00 -050069 private final HandlerThread mThread;
John Spurlockd60258f2015-04-30 09:30:52 -040070
71 private boolean mConnected;
72 private boolean mRegistered;
John Spurlock2f096ed2015-05-04 11:58:26 -040073 private boolean mBootComplete; // don't hammer the calendar provider until boot completes.
John Spurlock1b8b22b2015-05-20 09:47:13 -040074 private long mNextAlarmTime;
John Spurlockd60258f2015-04-30 09:30:52 -040075
Julia Reynolds7034caf2016-03-09 10:41:00 -050076 public EventConditionProvider() {
John Spurlock2f096ed2015-05-04 11:58:26 -040077 if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()");
Julia Reynolds7034caf2016-03-09 10:41:00 -050078 mThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
79 mThread.start();
80 mWorker = new Handler(mThread.getLooper());
John Spurlockd60258f2015-04-30 09:30:52 -040081 }
82
83 @Override
84 public ComponentName getComponent() {
85 return COMPONENT;
86 }
87
88 @Override
89 public boolean isValidConditionId(Uri id) {
90 return ZenModeConfig.isValidEventConditionId(id);
91 }
92
93 @Override
94 public void dump(PrintWriter pw, DumpFilter filter) {
John Spurlock2f096ed2015-05-04 11:58:26 -040095 pw.print(" "); pw.print(SIMPLE_NAME); pw.println(":");
John Spurlockd60258f2015-04-30 09:30:52 -040096 pw.print(" mConnected="); pw.println(mConnected);
97 pw.print(" mRegistered="); pw.println(mRegistered);
John Spurlock2f096ed2015-05-04 11:58:26 -040098 pw.print(" mBootComplete="); pw.println(mBootComplete);
John Spurlock1b8b22b2015-05-20 09:47:13 -040099 dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, System.currentTimeMillis());
Julia Reynolds2c93da92016-03-15 16:39:24 -0400100 synchronized (mSubscriptions) {
101 pw.println(" mSubscriptions=");
102 for (Uri conditionId : mSubscriptions) {
103 pw.print(" ");
104 pw.println(conditionId);
105 }
John Spurlockd60258f2015-04-30 09:30:52 -0400106 }
John Spurlock1b8b22b2015-05-20 09:47:13 -0400107 pw.println(" mTrackers=");
108 for (int i = 0; i < mTrackers.size(); i++) {
109 pw.print(" user="); pw.println(mTrackers.keyAt(i));
110 mTrackers.valueAt(i).dump(" ", pw);
111 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400112 }
113
114 @Override
115 public void onBootComplete() {
116 if (DEBUG) Slog.d(TAG, "onBootComplete");
117 if (mBootComplete) return;
118 mBootComplete = true;
John Spurlock1b8b22b2015-05-20 09:47:13 -0400119 final IntentFilter filter = new IntentFilter();
120 filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
121 filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
122 mContext.registerReceiver(new BroadcastReceiver() {
123 @Override
124 public void onReceive(Context context, Intent intent) {
125 reloadTrackers();
126 }
127 }, filter);
128 reloadTrackers();
John Spurlockd60258f2015-04-30 09:30:52 -0400129 }
130
131 @Override
132 public void onConnected() {
133 if (DEBUG) Slog.d(TAG, "onConnected");
134 mConnected = true;
135 }
136
137 @Override
138 public void onDestroy() {
139 super.onDestroy();
140 if (DEBUG) Slog.d(TAG, "onDestroy");
141 mConnected = false;
142 }
143
144 @Override
John Spurlockd60258f2015-04-30 09:30:52 -0400145 public void onSubscribe(Uri conditionId) {
146 if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
147 if (!ZenModeConfig.isValidEventConditionId(conditionId)) {
Julia Reynolds2c93da92016-03-15 16:39:24 -0400148 notifyCondition(createCondition(conditionId, Condition.STATE_FALSE));
John Spurlockd60258f2015-04-30 09:30:52 -0400149 return;
150 }
Julia Reynolds2c93da92016-03-15 16:39:24 -0400151 synchronized (mSubscriptions) {
152 if (mSubscriptions.add(conditionId)) {
153 evaluateSubscriptions();
154 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400155 }
John Spurlockd60258f2015-04-30 09:30:52 -0400156 }
157
158 @Override
159 public void onUnsubscribe(Uri conditionId) {
160 if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
Julia Reynolds2c93da92016-03-15 16:39:24 -0400161 synchronized (mSubscriptions) {
162 if (mSubscriptions.remove(conditionId)) {
163 evaluateSubscriptions();
164 }
John Spurlockd60258f2015-04-30 09:30:52 -0400165 }
166 }
167
168 @Override
169 public void attachBase(Context base) {
170 attachBaseContext(base);
171 }
172
173 @Override
174 public IConditionProvider asInterface() {
175 return (IConditionProvider) onBind(null);
176 }
177
John Spurlock1b8b22b2015-05-20 09:47:13 -0400178 private void reloadTrackers() {
179 if (DEBUG) Slog.d(TAG, "reloadTrackers");
180 for (int i = 0; i < mTrackers.size(); i++) {
181 mTrackers.valueAt(i).setCallback(null);
182 }
183 mTrackers.clear();
184 for (UserHandle user : UserManager.get(mContext).getUserProfiles()) {
Xiaohui Chen66556302015-10-21 11:19:42 -0700185 final Context context = user.isSystem() ? mContext : getContextForUser(mContext, user);
John Spurlock1b8b22b2015-05-20 09:47:13 -0400186 if (context == null) {
187 Slog.w(TAG, "Unable to create context for user " + user.getIdentifier());
188 continue;
189 }
190 mTrackers.put(user.getIdentifier(), new CalendarTracker(mContext, context));
191 }
192 evaluateSubscriptions();
193 }
194
John Spurlockd60258f2015-04-30 09:30:52 -0400195 private void evaluateSubscriptions() {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400196 if (!mWorker.hasCallbacks(mEvaluateSubscriptionsW)) {
197 mWorker.post(mEvaluateSubscriptionsW);
198 }
199 }
200
201 private void evaluateSubscriptionsW() {
202 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions");
John Spurlock2f096ed2015-05-04 11:58:26 -0400203 if (!mBootComplete) {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400204 if (DEBUG) Slog.d(TAG, "Skipping evaluate before boot complete");
John Spurlock2f096ed2015-05-04 11:58:26 -0400205 return;
John Spurlockd60258f2015-04-30 09:30:52 -0400206 }
John Spurlock2f096ed2015-05-04 11:58:26 -0400207 final long now = System.currentTimeMillis();
Julia Reynolds2c93da92016-03-15 16:39:24 -0400208 List<Condition> conditionsToNotify = new ArrayList<>();
209 synchronized (mSubscriptions) {
210 for (int i = 0; i < mTrackers.size(); i++) {
211 mTrackers.valueAt(i).setCallback(
212 mSubscriptions.isEmpty() ? null : mTrackerCallback);
John Spurlock2f096ed2015-05-04 11:58:26 -0400213 }
Julia Reynolds2c93da92016-03-15 16:39:24 -0400214 setRegistered(!mSubscriptions.isEmpty());
215 long reevaluateAt = 0;
216 for (Uri conditionId : mSubscriptions) {
217 final EventInfo event = ZenModeConfig.tryParseEventConditionId(conditionId);
218 if (event == null) {
219 conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE));
John Spurlock1b8b22b2015-05-20 09:47:13 -0400220 continue;
221 }
Julia Reynolds2c93da92016-03-15 16:39:24 -0400222 CheckEventResult result = null;
Beverlycf0fbcec2018-09-07 16:52:16 -0400223 if (event.calName == null) { // any calendar
Julia Reynolds2c93da92016-03-15 16:39:24 -0400224 // event could exist on any tracker
225 for (int i = 0; i < mTrackers.size(); i++) {
226 final CalendarTracker tracker = mTrackers.valueAt(i);
227 final CheckEventResult r = tracker.checkEvent(event, now);
228 if (result == null) {
229 result = r;
230 } else {
231 result.inEvent |= r.inEvent;
232 result.recheckAt = Math.min(result.recheckAt, r.recheckAt);
233 }
234 }
235 } else {
236 // event should exist on one tracker
237 final int userId = EventInfo.resolveUserId(event.userId);
238 final CalendarTracker tracker = mTrackers.get(userId);
239 if (tracker == null) {
240 Slog.w(TAG, "No calendar tracker found for user " + userId);
241 conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE));
242 continue;
243 }
244 result = tracker.checkEvent(event, now);
245 }
246 if (result.recheckAt != 0
247 && (reevaluateAt == 0 || result.recheckAt < reevaluateAt)) {
248 reevaluateAt = result.recheckAt;
249 }
250 if (!result.inEvent) {
251 conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE));
252 continue;
253 }
254 conditionsToNotify.add(createCondition(conditionId, Condition.STATE_TRUE));
John Spurlock1b8b22b2015-05-20 09:47:13 -0400255 }
Julia Reynolds2c93da92016-03-15 16:39:24 -0400256 rescheduleAlarm(now, reevaluateAt);
John Spurlock2f096ed2015-05-04 11:58:26 -0400257 }
Julia Reynolds2c93da92016-03-15 16:39:24 -0400258 for (Condition condition : conditionsToNotify) {
259 if (condition != null) {
260 notifyCondition(condition);
261 }
262 }
John Spurlock1b8b22b2015-05-20 09:47:13 -0400263 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions took " + (System.currentTimeMillis() - now));
John Spurlock2f096ed2015-05-04 11:58:26 -0400264 }
265
John Spurlock1b8b22b2015-05-20 09:47:13 -0400266 private void rescheduleAlarm(long now, long time) {
267 mNextAlarmTime = time;
John Spurlock2f096ed2015-05-04 11:58:26 -0400268 final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
269 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
270 REQUEST_CODE_EVALUATE,
271 new Intent(ACTION_EVALUATE)
272 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
273 .putExtra(EXTRA_TIME, time),
274 PendingIntent.FLAG_UPDATE_CURRENT);
275 alarms.cancel(pendingIntent);
276 if (time == 0 || time < now) {
277 if (DEBUG) Slog.d(TAG, "Not scheduling evaluate: " + (time == 0 ? "no time specified"
278 : "specified time in the past"));
279 return;
280 }
281 if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s",
282 ts(time), formatDuration(time - now), ts(now)));
283 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
John Spurlockd60258f2015-04-30 09:30:52 -0400284 }
285
John Spurlockd60258f2015-04-30 09:30:52 -0400286 private Condition createCondition(Uri id, int state) {
287 final String summary = NOT_SHOWN;
288 final String line1 = NOT_SHOWN;
289 final String line2 = NOT_SHOWN;
290 return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS);
291 }
292
John Spurlock2f096ed2015-05-04 11:58:26 -0400293 private void setRegistered(boolean registered) {
294 if (mRegistered == registered) return;
295 if (DEBUG) Slog.d(TAG, "setRegistered " + registered);
296 mRegistered = registered;
297 if (mRegistered) {
298 final IntentFilter filter = new IntentFilter();
299 filter.addAction(Intent.ACTION_TIME_CHANGED);
300 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
301 filter.addAction(ACTION_EVALUATE);
302 registerReceiver(mReceiver, filter);
303 } else {
304 unregisterReceiver(mReceiver);
305 }
306 }
307
John Spurlock1b8b22b2015-05-20 09:47:13 -0400308 private static Context getContextForUser(Context context, UserHandle user) {
309 try {
310 return context.createPackageContextAsUser(context.getPackageName(), 0, user);
311 } catch (NameNotFoundException e) {
312 return null;
313 }
314 }
315
John Spurlock2f096ed2015-05-04 11:58:26 -0400316 private final CalendarTracker.Callback mTrackerCallback = new CalendarTracker.Callback() {
317 @Override
318 public void onChanged() {
John Spurlock1b8b22b2015-05-20 09:47:13 -0400319 if (DEBUG) Slog.d(TAG, "mTrackerCallback.onChanged");
320 mWorker.removeCallbacks(mEvaluateSubscriptionsW);
321 mWorker.postDelayed(mEvaluateSubscriptionsW, CHANGE_DELAY);
John Spurlock2f096ed2015-05-04 11:58:26 -0400322 }
323 };
324
John Spurlock1b8b22b2015-05-20 09:47:13 -0400325 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
John Spurlock2f096ed2015-05-04 11:58:26 -0400326 @Override
327 public void onReceive(Context context, Intent intent) {
328 if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
329 evaluateSubscriptions();
330 }
331 };
John Spurlock1b8b22b2015-05-20 09:47:13 -0400332
333 private final Runnable mEvaluateSubscriptionsW = new Runnable() {
334 @Override
335 public void run() {
336 evaluateSubscriptionsW();
337 }
338 };
John Spurlockd60258f2015-04-30 09:30:52 -0400339}