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 import com.android.server.pm.PackageManagerService; 45 46 import java.io.PrintWriter; 47 import java.util.ArrayList; 48 import java.util.List; 49 50 /** 51 * Built-in zen condition provider for calendar event-based conditions. 52 */ 53 public class EventConditionProvider extends SystemConditionProviderService { 54 private static final String TAG = "ConditionProviders.ECP"; 55 private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); 56 57 public static final ComponentName COMPONENT = 58 new ComponentName("android", EventConditionProvider.class.getName()); 59 private static final String NOT_SHOWN = "..."; 60 private static final String SIMPLE_NAME = EventConditionProvider.class.getSimpleName(); 61 private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE"; 62 private static final int REQUEST_CODE_EVALUATE = 1; 63 private static final String EXTRA_TIME = "time"; 64 private static final long CHANGE_DELAY = 2 * 1000; // coalesce chatty calendar changes 65 66 private final Context mContext = this; 67 private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>(); 68 private final SparseArray<CalendarTracker> mTrackers = new SparseArray<>(); 69 private final Handler mWorker; 70 private final HandlerThread mThread; 71 72 private boolean mConnected; 73 private boolean mRegistered; 74 private boolean mBootComplete; // don't hammer the calendar provider until boot completes. 75 private long mNextAlarmTime; 76 EventConditionProvider()77 public EventConditionProvider() { 78 if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()"); 79 mThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); 80 mThread.start(); 81 mWorker = new Handler(mThread.getLooper()); 82 } 83 84 @Override getComponent()85 public ComponentName getComponent() { 86 return COMPONENT; 87 } 88 89 @Override isValidConditionId(Uri id)90 public boolean isValidConditionId(Uri id) { 91 return ZenModeConfig.isValidEventConditionId(id); 92 } 93 94 @Override dump(PrintWriter pw, DumpFilter filter)95 public void dump(PrintWriter pw, DumpFilter filter) { 96 pw.print(" "); pw.print(SIMPLE_NAME); pw.println(":"); 97 pw.print(" mConnected="); pw.println(mConnected); 98 pw.print(" mRegistered="); pw.println(mRegistered); 99 pw.print(" mBootComplete="); pw.println(mBootComplete); 100 dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, System.currentTimeMillis()); 101 synchronized (mSubscriptions) { 102 pw.println(" mSubscriptions="); 103 for (Uri conditionId : mSubscriptions) { 104 pw.print(" "); 105 pw.println(conditionId); 106 } 107 } 108 pw.println(" mTrackers="); 109 for (int i = 0; i < mTrackers.size(); i++) { 110 pw.print(" user="); pw.println(mTrackers.keyAt(i)); 111 mTrackers.valueAt(i).dump(" ", pw); 112 } 113 } 114 115 @Override onBootComplete()116 public void onBootComplete() { 117 if (DEBUG) Slog.d(TAG, "onBootComplete"); 118 if (mBootComplete) return; 119 mBootComplete = true; 120 final IntentFilter filter = new IntentFilter(); 121 filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); 122 filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); 123 mContext.registerReceiver(new BroadcastReceiver() { 124 @Override 125 public void onReceive(Context context, Intent intent) { 126 reloadTrackers(); 127 } 128 }, filter); 129 reloadTrackers(); 130 } 131 132 @Override onConnected()133 public void onConnected() { 134 if (DEBUG) Slog.d(TAG, "onConnected"); 135 mConnected = true; 136 } 137 138 @Override onDestroy()139 public void onDestroy() { 140 super.onDestroy(); 141 if (DEBUG) Slog.d(TAG, "onDestroy"); 142 mConnected = false; 143 } 144 145 @Override onSubscribe(Uri conditionId)146 public void onSubscribe(Uri conditionId) { 147 if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId); 148 if (!ZenModeConfig.isValidEventConditionId(conditionId)) { 149 notifyCondition(createCondition(conditionId, Condition.STATE_FALSE)); 150 return; 151 } 152 synchronized (mSubscriptions) { 153 if (mSubscriptions.add(conditionId)) { 154 evaluateSubscriptions(); 155 } 156 } 157 } 158 159 @Override onUnsubscribe(Uri conditionId)160 public void onUnsubscribe(Uri conditionId) { 161 if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId); 162 synchronized (mSubscriptions) { 163 if (mSubscriptions.remove(conditionId)) { 164 evaluateSubscriptions(); 165 } 166 } 167 } 168 169 @Override attachBase(Context base)170 public void attachBase(Context base) { 171 attachBaseContext(base); 172 } 173 174 @Override asInterface()175 public IConditionProvider asInterface() { 176 return (IConditionProvider) onBind(null); 177 } 178 reloadTrackers()179 private void reloadTrackers() { 180 if (DEBUG) Slog.d(TAG, "reloadTrackers"); 181 for (int i = 0; i < mTrackers.size(); i++) { 182 mTrackers.valueAt(i).setCallback(null); 183 } 184 mTrackers.clear(); 185 for (UserHandle user : UserManager.get(mContext).getUserProfiles()) { 186 final Context context = user.isSystem() ? mContext : getContextForUser(mContext, user); 187 if (context == null) { 188 Slog.w(TAG, "Unable to create context for user " + user.getIdentifier()); 189 continue; 190 } 191 mTrackers.put(user.getIdentifier(), new CalendarTracker(mContext, context)); 192 } 193 evaluateSubscriptions(); 194 } 195 evaluateSubscriptions()196 private void evaluateSubscriptions() { 197 if (!mWorker.hasCallbacks(mEvaluateSubscriptionsW)) { 198 mWorker.post(mEvaluateSubscriptionsW); 199 } 200 } 201 evaluateSubscriptionsW()202 private void evaluateSubscriptionsW() { 203 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions"); 204 if (!mBootComplete) { 205 if (DEBUG) Slog.d(TAG, "Skipping evaluate before boot complete"); 206 return; 207 } 208 final long now = System.currentTimeMillis(); 209 List<Condition> conditionsToNotify = new ArrayList<>(); 210 synchronized (mSubscriptions) { 211 for (int i = 0; i < mTrackers.size(); i++) { 212 mTrackers.valueAt(i).setCallback( 213 mSubscriptions.isEmpty() ? null : mTrackerCallback); 214 } 215 setRegistered(!mSubscriptions.isEmpty()); 216 long reevaluateAt = 0; 217 for (Uri conditionId : mSubscriptions) { 218 final EventInfo event = ZenModeConfig.tryParseEventConditionId(conditionId); 219 if (event == null) { 220 conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE)); 221 continue; 222 } 223 CheckEventResult result = null; 224 if (event.calName == null) { // any calendar 225 // event could exist on any tracker 226 for (int i = 0; i < mTrackers.size(); i++) { 227 final CalendarTracker tracker = mTrackers.valueAt(i); 228 final CheckEventResult r = tracker.checkEvent(event, now); 229 if (result == null) { 230 result = r; 231 } else { 232 result.inEvent |= r.inEvent; 233 result.recheckAt = Math.min(result.recheckAt, r.recheckAt); 234 } 235 } 236 } else { 237 // event should exist on one tracker 238 final int userId = EventInfo.resolveUserId(event.userId); 239 final CalendarTracker tracker = mTrackers.get(userId); 240 if (tracker == null) { 241 Slog.w(TAG, "No calendar tracker found for user " + userId); 242 conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE)); 243 continue; 244 } 245 result = tracker.checkEvent(event, now); 246 } 247 if (result.recheckAt != 0 248 && (reevaluateAt == 0 || result.recheckAt < reevaluateAt)) { 249 reevaluateAt = result.recheckAt; 250 } 251 if (!result.inEvent) { 252 conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE)); 253 continue; 254 } 255 conditionsToNotify.add(createCondition(conditionId, Condition.STATE_TRUE)); 256 } 257 rescheduleAlarm(now, reevaluateAt); 258 } 259 for (Condition condition : conditionsToNotify) { 260 if (condition != null) { 261 notifyCondition(condition); 262 } 263 } 264 if (DEBUG) Slog.d(TAG, "evaluateSubscriptions took " + (System.currentTimeMillis() - now)); 265 } 266 rescheduleAlarm(long now, long time)267 private void rescheduleAlarm(long now, long time) { 268 mNextAlarmTime = time; 269 final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 270 final PendingIntent pendingIntent = getPendingIntent(time); 271 alarms.cancel(pendingIntent); 272 if (time == 0 || time < now) { 273 if (DEBUG) Slog.d(TAG, "Not scheduling evaluate: " + (time == 0 ? "no time specified" 274 : "specified time in the past")); 275 return; 276 } 277 if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s", 278 ts(time), formatDuration(time - now), ts(now))); 279 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); 280 } 281 getPendingIntent(long time)282 PendingIntent getPendingIntent(long time) { 283 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 284 REQUEST_CODE_EVALUATE, 285 new Intent(ACTION_EVALUATE) 286 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) 287 .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME) 288 .putExtra(EXTRA_TIME, time), 289 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); 290 return pendingIntent; 291 } 292 createCondition(Uri id, int state)293 private Condition createCondition(Uri id, int state) { 294 final String summary = NOT_SHOWN; 295 final String line1 = NOT_SHOWN; 296 final String line2 = NOT_SHOWN; 297 return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS); 298 } 299 setRegistered(boolean registered)300 private void setRegistered(boolean registered) { 301 if (mRegistered == registered) return; 302 if (DEBUG) Slog.d(TAG, "setRegistered " + registered); 303 mRegistered = registered; 304 if (mRegistered) { 305 final IntentFilter filter = new IntentFilter(); 306 filter.addAction(Intent.ACTION_TIME_CHANGED); 307 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 308 filter.addAction(ACTION_EVALUATE); 309 registerReceiver(mReceiver, filter, 310 Context.RECEIVER_EXPORTED_UNAUDITED); 311 } else { 312 unregisterReceiver(mReceiver); 313 } 314 } 315 getContextForUser(Context context, UserHandle user)316 private static Context getContextForUser(Context context, UserHandle user) { 317 try { 318 return context.createPackageContextAsUser(context.getPackageName(), 0, user); 319 } catch (NameNotFoundException e) { 320 return null; 321 } 322 } 323 324 private final CalendarTracker.Callback mTrackerCallback = new CalendarTracker.Callback() { 325 @Override 326 public void onChanged() { 327 if (DEBUG) Slog.d(TAG, "mTrackerCallback.onChanged"); 328 mWorker.removeCallbacks(mEvaluateSubscriptionsW); 329 mWorker.postDelayed(mEvaluateSubscriptionsW, CHANGE_DELAY); 330 } 331 }; 332 333 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 334 @Override 335 public void onReceive(Context context, Intent intent) { 336 if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction()); 337 evaluateSubscriptions(); 338 } 339 }; 340 341 private final Runnable mEvaluateSubscriptionsW = new Runnable() { 342 @Override 343 public void run() { 344 evaluateSubscriptionsW(); 345 } 346 }; 347 } 348