EventConditionProvider.java revision 1b8b22b1a412539020f78a132cff7c8fa7fae258
1/* 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 19import android.app.AlarmManager; 20import android.app.PendingIntent; 21import android.content.BroadcastReceiver; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.content.pm.PackageManager.NameNotFoundException; 27import android.net.Uri; 28import android.os.Handler; 29import android.os.Looper; 30import android.os.UserHandle; 31import android.os.UserManager; 32import android.service.notification.Condition; 33import android.service.notification.IConditionProvider; 34import android.service.notification.ZenModeConfig; 35import android.service.notification.ZenModeConfig.EventInfo; 36import android.util.ArraySet; 37import android.util.Log; 38import android.util.Slog; 39import android.util.SparseArray; 40 41import com.android.server.notification.CalendarTracker.CheckEventResult; 42import 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 { 50 private static final String TAG = "ConditionProviders.ECP"; 51 private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); 52 53 public static final ComponentName COMPONENT = 54 new ComponentName("android", EventConditionProvider.class.getName()); 55 private static final String NOT_SHOWN = "..."; 56 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"; 60 private static final long CHANGE_DELAY = 2 * 1000; // coalesce chatty calendar changes 61 62 private final Context mContext = this; 63 private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>(); 64 private final SparseArray<CalendarTracker> mTrackers = new SparseArray<>(); 65 private final Handler mWorker; 66 67 private boolean mConnected; 68 private boolean mRegistered; 69 private boolean mBootComplete; // don't hammer the calendar provider until boot completes. 70 private long mNextAlarmTime; 71 72 public EventConditionProvider(Looper worker) { 73 if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()"); 74 mWorker = new Handler(worker); 75 } 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) { 89 pw.print(" "); pw.print(SIMPLE_NAME); pw.println(":"); 90 pw.print(" mConnected="); pw.println(mConnected); 91 pw.print(" mRegistered="); pw.println(mRegistered); 92 pw.print(" mBootComplete="); pw.println(mBootComplete); 93 dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, System.currentTimeMillis()); 94 pw.println(" mSubscriptions="); 95 for (Uri conditionId : mSubscriptions) { 96 pw.print(" "); 97 pw.println(conditionId); 98 } 99 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 } 104 } 105 106 @Override 107 public void onBootComplete() { 108 if (DEBUG) Slog.d(TAG, "onBootComplete"); 109 if (mBootComplete) return; 110 mBootComplete = true; 111 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(); 121 } 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 } 149 if (mSubscriptions.add(conditionId)) { 150 evaluateSubscriptions(); 151 } 152 } 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 172 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()) { 179 final Context context = user.isOwner() ? mContext : getContextForUser(mContext, user); 180 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 189 private void evaluateSubscriptions() { 190 if (!mWorker.hasCallbacks(mEvaluateSubscriptionsW)) { 191 mWorker.post(mEvaluateSubscriptionsW); 192 } 193 } 194 195 private void evaluateSubscriptionsW() { 196 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions"); 197 if (!mBootComplete) { 198 if (DEBUG) Slog.d(TAG, "Skipping evaluate before boot complete"); 199 return; 200 } 201 final long now = System.currentTimeMillis(); 202 for (int i = 0; i < mTrackers.size(); i++) { 203 mTrackers.valueAt(i).setCallback(mSubscriptions.isEmpty() ? null : mTrackerCallback); 204 } 205 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 } 213 CheckEventResult result = null; 214 if (event.calendar == EventInfo.ANY_CALENDAR) { 215 // 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 } 237 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 } 246 rescheduleAlarm(now, reevaluateAt); 247 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions took " + (System.currentTimeMillis() - now)); 248 } 249 250 private void rescheduleAlarm(long now, long time) { 251 mNextAlarmTime = time; 252 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); 268 } 269 270 private void notifyCondition(Uri conditionId, int state, String reason) { 271 if (DEBUG) Slog.d(TAG, "notifyCondition " + conditionId + " " 272 + Condition.stateToString(state) + " reason=" + reason); 273 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 283 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 298 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 306 private final CalendarTracker.Callback mTrackerCallback = new CalendarTracker.Callback() { 307 @Override 308 public void onChanged() { 309 if (DEBUG) Slog.d(TAG, "mTrackerCallback.onChanged"); 310 mWorker.removeCallbacks(mEvaluateSubscriptionsW); 311 mWorker.postDelayed(mEvaluateSubscriptionsW, CHANGE_DELAY); 312 } 313 }; 314 315 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 316 @Override 317 public void onReceive(Context context, Intent intent) { 318 if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction()); 319 evaluateSubscriptions(); 320 } 321 }; 322 323 private final Runnable mEvaluateSubscriptionsW = new Runnable() { 324 @Override 325 public void run() { 326 evaluateSubscriptionsW(); 327 } 328 }; 329} 330