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 17 package com.android.server.notification; 18 19 import android.app.AlarmManager; 20 import android.app.PendingIntent; 21 import android.content.BroadcastReceiver; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.content.pm.PackageManager.NameNotFoundException; 27 import android.net.Uri; 28 import android.os.Handler; 29 import android.os.HandlerThread; 30 import android.os.Process; 31 import android.os.UserHandle; 32 import android.os.UserManager; 33 import android.service.notification.Condition; 34 import android.service.notification.IConditionProvider; 35 import android.service.notification.ZenModeConfig; 36 import android.service.notification.ZenModeConfig.EventInfo; 37 import android.util.ArraySet; 38 import android.util.Log; 39 import android.util.Slog; 40 import android.util.SparseArray; 41 42 import com.android.server.notification.CalendarTracker.CheckEventResult; 43 import com.android.server.notification.NotificationManagerService.DumpFilter; 44 45 import java.io.PrintWriter; 46 import java.util.ArrayList; 47 import java.util.List; 48 49 /** 50 * Built-in zen condition provider for calendar event-based conditions. 51 */ 52 public class EventConditionProvider extends SystemConditionProviderService { 53 private static final String TAG = "ConditionProviders.ECP"; 54 private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); 55 56 public static final ComponentName COMPONENT = 57 new ComponentName("android", EventConditionProvider.class.getName()); 58 private static final String NOT_SHOWN = "..."; 59 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"; 63 private static final long CHANGE_DELAY = 2 * 1000; // coalesce chatty calendar changes 64 65 private final Context mContext = this; 66 private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>(); 67 private final SparseArray<CalendarTracker> mTrackers = new SparseArray<>(); 68 private final Handler mWorker; 69 private final HandlerThread mThread; 70 71 private boolean mConnected; 72 private boolean mRegistered; 73 private boolean mBootComplete; // don't hammer the calendar provider until boot completes. 74 private long mNextAlarmTime; 75 EventConditionProvider()76 public EventConditionProvider() { 77 if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()"); 78 mThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); 79 mThread.start(); 80 mWorker = new Handler(mThread.getLooper()); 81 } 82 83 @Override getComponent()84 public ComponentName getComponent() { 85 return COMPONENT; 86 } 87 88 @Override isValidConditionId(Uri id)89 public boolean isValidConditionId(Uri id) { 90 return ZenModeConfig.isValidEventConditionId(id); 91 } 92 93 @Override dump(PrintWriter pw, DumpFilter filter)94 public void dump(PrintWriter pw, DumpFilter filter) { 95 pw.print(" "); pw.print(SIMPLE_NAME); pw.println(":"); 96 pw.print(" mConnected="); pw.println(mConnected); 97 pw.print(" mRegistered="); pw.println(mRegistered); 98 pw.print(" mBootComplete="); pw.println(mBootComplete); 99 dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, System.currentTimeMillis()); 100 synchronized (mSubscriptions) { 101 pw.println(" mSubscriptions="); 102 for (Uri conditionId : mSubscriptions) { 103 pw.print(" "); 104 pw.println(conditionId); 105 } 106 } 107 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 } 112 } 113 114 @Override onBootComplete()115 public void onBootComplete() { 116 if (DEBUG) Slog.d(TAG, "onBootComplete"); 117 if (mBootComplete) return; 118 mBootComplete = true; 119 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(); 129 } 130 131 @Override onConnected()132 public void onConnected() { 133 if (DEBUG) Slog.d(TAG, "onConnected"); 134 mConnected = true; 135 } 136 137 @Override onDestroy()138 public void onDestroy() { 139 super.onDestroy(); 140 if (DEBUG) Slog.d(TAG, "onDestroy"); 141 mConnected = false; 142 } 143 144 @Override onSubscribe(Uri conditionId)145 public void onSubscribe(Uri conditionId) { 146 if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId); 147 if (!ZenModeConfig.isValidEventConditionId(conditionId)) { 148 notifyCondition(createCondition(conditionId, Condition.STATE_FALSE)); 149 return; 150 } 151 synchronized (mSubscriptions) { 152 if (mSubscriptions.add(conditionId)) { 153 evaluateSubscriptions(); 154 } 155 } 156 } 157 158 @Override onUnsubscribe(Uri conditionId)159 public void onUnsubscribe(Uri conditionId) { 160 if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId); 161 synchronized (mSubscriptions) { 162 if (mSubscriptions.remove(conditionId)) { 163 evaluateSubscriptions(); 164 } 165 } 166 } 167 168 @Override attachBase(Context base)169 public void attachBase(Context base) { 170 attachBaseContext(base); 171 } 172 173 @Override asInterface()174 public IConditionProvider asInterface() { 175 return (IConditionProvider) onBind(null); 176 } 177 reloadTrackers()178 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()) { 185 final Context context = user.isSystem() ? mContext : getContextForUser(mContext, user); 186 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 evaluateSubscriptions()195 private void evaluateSubscriptions() { 196 if (!mWorker.hasCallbacks(mEvaluateSubscriptionsW)) { 197 mWorker.post(mEvaluateSubscriptionsW); 198 } 199 } 200 evaluateSubscriptionsW()201 private void evaluateSubscriptionsW() { 202 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions"); 203 if (!mBootComplete) { 204 if (DEBUG) Slog.d(TAG, "Skipping evaluate before boot complete"); 205 return; 206 } 207 final long now = System.currentTimeMillis(); 208 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); 213 } 214 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)); 220 continue; 221 } 222 CheckEventResult result = null; 223 if (event.calName == null) { // any calendar 224 // 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)); 255 } 256 rescheduleAlarm(now, reevaluateAt); 257 } 258 for (Condition condition : conditionsToNotify) { 259 if (condition != null) { 260 notifyCondition(condition); 261 } 262 } 263 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions took " + (System.currentTimeMillis() - now)); 264 } 265 rescheduleAlarm(long now, long time)266 private void rescheduleAlarm(long now, long time) { 267 mNextAlarmTime = time; 268 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 | PendingIntent.FLAG_IMMUTABLE); 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); 284 } 285 createCondition(Uri id, int state)286 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 setRegistered(boolean registered)293 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 getContextForUser(Context context, UserHandle user)308 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 316 private final CalendarTracker.Callback mTrackerCallback = new CalendarTracker.Callback() { 317 @Override 318 public void onChanged() { 319 if (DEBUG) Slog.d(TAG, "mTrackerCallback.onChanged"); 320 mWorker.removeCallbacks(mEvaluateSubscriptionsW); 321 mWorker.postDelayed(mEvaluateSubscriptionsW, CHANGE_DELAY); 322 } 323 }; 324 325 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 326 @Override 327 public void onReceive(Context context, Intent intent) { 328 if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction()); 329 evaluateSubscriptions(); 330 } 331 }; 332 333 private final Runnable mEvaluateSubscriptionsW = new Runnable() { 334 @Override 335 public void run() { 336 evaluateSubscriptionsW(); 337 } 338 }; 339 } 340