ConditionProviders.java revision 530052a2fe3b6a6a4246ce28ab0ced647fe7f470
1/** 2 * Copyright (c) 2014, 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.content.ComponentName; 20import android.content.Context; 21import android.net.Uri; 22import android.os.Handler; 23import android.os.IBinder; 24import android.os.IInterface; 25import android.os.RemoteException; 26import android.os.UserHandle; 27import android.provider.Settings; 28import android.provider.Settings.Global; 29import android.service.notification.Condition; 30import android.service.notification.ConditionProviderService; 31import android.service.notification.IConditionListener; 32import android.service.notification.IConditionProvider; 33import android.service.notification.ZenModeConfig; 34import android.util.ArrayMap; 35import android.util.ArraySet; 36import android.util.Slog; 37 38import com.android.internal.R; 39import com.android.server.notification.NotificationManagerService.DumpFilter; 40 41import java.io.PrintWriter; 42import java.util.ArrayList; 43import java.util.Arrays; 44import java.util.Objects; 45 46public class ConditionProviders extends ManagedServices { 47 private static final Condition[] NO_CONDITIONS = new Condition[0]; 48 49 private final ZenModeHelper mZenModeHelper; 50 private final ArrayMap<IBinder, IConditionListener> mListeners 51 = new ArrayMap<IBinder, IConditionListener>(); 52 private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>(); 53 private final ArraySet<String> mSystemConditionProviders; 54 private final CountdownConditionProvider mCountdown; 55 private final DowntimeConditionProvider mDowntime; 56 private final NextAlarmConditionProvider mNextAlarm; 57 private final NextAlarmTracker mNextAlarmTracker; 58 59 private Condition mExitCondition; 60 private ComponentName mExitConditionComponent; 61 62 public ConditionProviders(Context context, Handler handler, 63 UserProfiles userProfiles, ZenModeHelper zenModeHelper) { 64 super(context, handler, new Object(), userProfiles); 65 mZenModeHelper = zenModeHelper; 66 mZenModeHelper.addCallback(new ZenModeHelperCallback()); 67 mSystemConditionProviders = safeSet(PropConfig.getStringArray(mContext, 68 "system.condition.providers", 69 R.array.config_system_condition_providers)); 70 final boolean countdown = mSystemConditionProviders.contains(ZenModeConfig.COUNTDOWN_PATH); 71 final boolean downtime = mSystemConditionProviders.contains(ZenModeConfig.DOWNTIME_PATH); 72 final boolean nextAlarm = mSystemConditionProviders.contains(ZenModeConfig.NEXT_ALARM_PATH); 73 mNextAlarmTracker = (downtime || nextAlarm) ? new NextAlarmTracker(mContext) : null; 74 mCountdown = countdown ? new CountdownConditionProvider() : null; 75 mDowntime = downtime ? new DowntimeConditionProvider(this, mNextAlarmTracker, 76 mZenModeHelper) : null; 77 mNextAlarm = nextAlarm ? new NextAlarmConditionProvider(mNextAlarmTracker) : null; 78 loadZenConfig(); 79 } 80 81 public boolean isSystemConditionProviderEnabled(String path) { 82 return mSystemConditionProviders.contains(path); 83 } 84 85 @Override 86 protected Config getConfig() { 87 Config c = new Config(); 88 c.caption = "condition provider"; 89 c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE; 90 c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS; 91 c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE; 92 c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS; 93 c.clientLabel = R.string.condition_provider_service_binding_label; 94 return c; 95 } 96 97 @Override 98 public void dump(PrintWriter pw, DumpFilter filter) { 99 super.dump(pw, filter); 100 synchronized(mMutex) { 101 if (filter == null) { 102 pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):"); 103 for (int i = 0; i < mListeners.size(); i++) { 104 pw.print(" "); pw.println(mListeners.keyAt(i)); 105 } 106 } 107 pw.print(" mRecords("); pw.print(mRecords.size()); pw.println("):"); 108 for (int i = 0; i < mRecords.size(); i++) { 109 final ConditionRecord r = mRecords.get(i); 110 if (filter != null && !filter.matches(r.component)) continue; 111 pw.print(" "); pw.println(r); 112 final String countdownDesc = CountdownConditionProvider.tryParseDescription(r.id); 113 if (countdownDesc != null) { 114 pw.print(" ("); pw.print(countdownDesc); pw.println(")"); 115 } 116 } 117 } 118 pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviders); 119 if (mCountdown != null) { 120 mCountdown.dump(pw, filter); 121 } 122 if (mDowntime != null) { 123 mDowntime.dump(pw, filter); 124 } 125 if (mNextAlarm != null) { 126 mNextAlarm.dump(pw, filter); 127 } 128 if (mNextAlarmTracker != null) { 129 mNextAlarmTracker.dump(pw, filter); 130 } 131 } 132 133 @Override 134 protected IInterface asInterface(IBinder binder) { 135 return IConditionProvider.Stub.asInterface(binder); 136 } 137 138 @Override 139 public void onBootPhaseAppsCanStart() { 140 super.onBootPhaseAppsCanStart(); 141 if (mNextAlarmTracker != null) { 142 mNextAlarmTracker.init(); 143 } 144 if (mCountdown != null) { 145 mCountdown.attachBase(mContext); 146 registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT, 147 UserHandle.USER_OWNER); 148 } 149 if (mDowntime != null) { 150 mDowntime.attachBase(mContext); 151 registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT, 152 UserHandle.USER_OWNER); 153 } 154 if (mNextAlarm != null) { 155 mNextAlarm.attachBase(mContext); 156 registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT, 157 UserHandle.USER_OWNER); 158 } 159 } 160 161 @Override 162 public void onUserSwitched() { 163 super.onUserSwitched(); 164 if (mNextAlarmTracker != null) { 165 mNextAlarmTracker.onUserSwitched(); 166 } 167 } 168 169 @Override 170 protected void onServiceAdded(ManagedServiceInfo info) { 171 Slog.d(TAG, "onServiceAdded " + info); 172 final IConditionProvider provider = provider(info); 173 try { 174 provider.onConnected(); 175 } catch (RemoteException e) { 176 // we tried 177 } 178 synchronized (mMutex) { 179 if (info.component.equals(mExitConditionComponent)) { 180 // ensure record exists, we'll wire it up and subscribe below 181 final ConditionRecord manualRecord = 182 getRecordLocked(mExitCondition.id, mExitConditionComponent); 183 manualRecord.isManual = true; 184 } 185 final int N = mRecords.size(); 186 for(int i = 0; i < N; i++) { 187 final ConditionRecord r = mRecords.get(i); 188 if (!r.component.equals(info.component)) continue; 189 r.info = info; 190 // if automatic or manual, auto-subscribe 191 if (r.isAutomatic || r.isManual) { 192 subscribeLocked(r); 193 } 194 } 195 } 196 } 197 198 @Override 199 protected void onServiceRemovedLocked(ManagedServiceInfo removed) { 200 if (removed == null) return; 201 for (int i = mRecords.size() - 1; i >= 0; i--) { 202 final ConditionRecord r = mRecords.get(i); 203 if (!r.component.equals(removed.component)) continue; 204 if (r.isManual) { 205 // removing the current manual condition, exit zen 206 onManualConditionClearing(); 207 mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "manualServiceRemoved"); 208 } 209 if (r.isAutomatic) { 210 // removing an automatic condition, exit zen 211 mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "automaticServiceRemoved"); 212 } 213 mRecords.remove(i); 214 } 215 } 216 217 public ManagedServiceInfo checkServiceToken(IConditionProvider provider) { 218 synchronized(mMutex) { 219 return checkServiceTokenLocked(provider); 220 } 221 } 222 223 public void requestZenModeConditions(IConditionListener callback, int relevance) { 224 synchronized(mMutex) { 225 if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback 226 + " relevance=" + Condition.relevanceToString(relevance)); 227 if (callback == null) return; 228 relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS); 229 if (relevance != 0) { 230 mListeners.put(callback.asBinder(), callback); 231 requestConditionsLocked(relevance); 232 } else { 233 mListeners.remove(callback.asBinder()); 234 if (mListeners.isEmpty()) { 235 requestConditionsLocked(0); 236 } 237 } 238 } 239 } 240 241 private Condition[] validateConditions(String pkg, Condition[] conditions) { 242 if (conditions == null || conditions.length == 0) return null; 243 final int N = conditions.length; 244 final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N); 245 for (int i = 0; i < N; i++) { 246 final Uri id = conditions[i].id; 247 if (!Condition.isValidId(id, pkg)) { 248 Slog.w(TAG, "Ignoring condition from " + pkg + " for invalid id: " + id); 249 continue; 250 } 251 if (valid.containsKey(id)) { 252 Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id); 253 continue; 254 } 255 valid.put(id, conditions[i]); 256 } 257 if (valid.size() == 0) return null; 258 if (valid.size() == N) return conditions; 259 final Condition[] rt = new Condition[valid.size()]; 260 for (int i = 0; i < rt.length; i++) { 261 rt[i] = valid.valueAt(i); 262 } 263 return rt; 264 } 265 266 private ConditionRecord getRecordLocked(Uri id, ComponentName component) { 267 final int N = mRecords.size(); 268 for (int i = 0; i < N; i++) { 269 final ConditionRecord r = mRecords.get(i); 270 if (r.id.equals(id) && r.component.equals(component)) { 271 return r; 272 } 273 } 274 final ConditionRecord r = new ConditionRecord(id, component); 275 mRecords.add(r); 276 return r; 277 } 278 279 public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) { 280 synchronized(mMutex) { 281 if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions=" 282 + (conditions == null ? null : Arrays.asList(conditions))); 283 conditions = validateConditions(pkg, conditions); 284 if (conditions == null || conditions.length == 0) return; 285 final int N = conditions.length; 286 for (IConditionListener listener : mListeners.values()) { 287 try { 288 listener.onConditionsReceived(conditions); 289 } catch (RemoteException e) { 290 Slog.w(TAG, "Error sending conditions to listener " + listener, e); 291 } 292 } 293 for (int i = 0; i < N; i++) { 294 final Condition c = conditions[i]; 295 final ConditionRecord r = getRecordLocked(c.id, info.component); 296 final Condition oldCondition = r.condition; 297 final boolean conditionUpdate = oldCondition != null && !oldCondition.equals(c); 298 r.info = info; 299 r.condition = c; 300 // if manual, exit zen if false (or failed), update if true (and changed) 301 if (r.isManual) { 302 if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) { 303 final boolean failed = c.state == Condition.STATE_ERROR; 304 if (failed) { 305 Slog.w(TAG, "Exit zen: manual condition failed: " + c); 306 } else if (DEBUG) { 307 Slog.d(TAG, "Exit zen: manual condition false: " + c); 308 } 309 onManualConditionClearing(); 310 mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF, 311 "manualConditionExit"); 312 unsubscribeLocked(r); 313 r.isManual = false; 314 } else if (c.state == Condition.STATE_TRUE && conditionUpdate) { 315 if (DEBUG) Slog.d(TAG, "Current condition updated, still true. old=" 316 + oldCondition + " new=" + c); 317 setZenModeCondition(c, "conditionUpdate"); 318 } 319 } 320 // if automatic, exit zen if false (or failed), enter zen if true 321 if (r.isAutomatic) { 322 if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) { 323 final boolean failed = c.state == Condition.STATE_ERROR; 324 if (failed) { 325 Slog.w(TAG, "Exit zen: automatic condition failed: " + c); 326 } else if (DEBUG) { 327 Slog.d(TAG, "Exit zen: automatic condition false: " + c); 328 } 329 mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF, 330 "automaticConditionExit"); 331 } else if (c.state == Condition.STATE_TRUE) { 332 Slog.d(TAG, "Enter zen: automatic condition true: " + c); 333 mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, 334 "automaticConditionEnter"); 335 } 336 } 337 } 338 } 339 } 340 341 private void ensureRecordExists(Condition condition, IConditionProvider provider, 342 ComponentName component) { 343 // constructed by convention, make sure the record exists... 344 final ConditionRecord r = getRecordLocked(condition.id, component); 345 if (r.info == null) { 346 // ... and is associated with the in-process service 347 r.info = checkServiceTokenLocked(provider); 348 } 349 } 350 351 public void setZenModeCondition(Condition condition, String reason) { 352 if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition + " reason=" + reason); 353 synchronized(mMutex) { 354 ComponentName conditionComponent = null; 355 if (condition != null) { 356 if (mCountdown != null && ZenModeConfig.isValidCountdownConditionId(condition.id)) { 357 ensureRecordExists(condition, mCountdown.asInterface(), 358 CountdownConditionProvider.COMPONENT); 359 } 360 if (mDowntime != null && ZenModeConfig.isValidDowntimeConditionId(condition.id)) { 361 ensureRecordExists(condition, mDowntime.asInterface(), 362 DowntimeConditionProvider.COMPONENT); 363 } 364 } 365 final int N = mRecords.size(); 366 for (int i = 0; i < N; i++) { 367 final ConditionRecord r = mRecords.get(i); 368 final boolean idEqual = condition != null && r.id.equals(condition.id); 369 if (r.isManual && !idEqual) { 370 // was previous manual condition, unsubscribe 371 unsubscribeLocked(r); 372 r.isManual = false; 373 } else if (idEqual && !r.isManual) { 374 // is new manual condition, subscribe 375 subscribeLocked(r); 376 r.isManual = true; 377 } 378 if (idEqual) { 379 conditionComponent = r.component; 380 } 381 } 382 if (!Objects.equals(mExitCondition, condition)) { 383 mExitCondition = condition; 384 mExitConditionComponent = conditionComponent; 385 ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason); 386 saveZenConfigLocked(); 387 } 388 } 389 } 390 391 private void subscribeLocked(ConditionRecord r) { 392 if (DEBUG) Slog.d(TAG, "subscribeLocked " + r); 393 final IConditionProvider provider = provider(r); 394 RemoteException re = null; 395 if (provider != null) { 396 try { 397 Slog.d(TAG, "Subscribing to " + r.id + " with " + provider); 398 provider.onSubscribe(r.id); 399 } catch (RemoteException e) { 400 Slog.w(TAG, "Error subscribing to " + r, e); 401 re = e; 402 } 403 } 404 ZenLog.traceSubscribe(r != null ? r.id : null, provider, re); 405 } 406 407 @SafeVarargs 408 private static <T> ArraySet<T> safeSet(T... items) { 409 final ArraySet<T> rt = new ArraySet<T>(); 410 if (items == null || items.length == 0) return rt; 411 final int N = items.length; 412 for (int i = 0; i < N; i++) { 413 final T item = items[i]; 414 if (item != null) { 415 rt.add(item); 416 } 417 } 418 return rt; 419 } 420 421 public void setAutomaticZenModeConditions(Uri[] conditionIds) { 422 setAutomaticZenModeConditions(conditionIds, true /*save*/); 423 } 424 425 private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) { 426 if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions " 427 + (conditionIds == null ? null : Arrays.asList(conditionIds))); 428 synchronized(mMutex) { 429 final ArraySet<Uri> newIds = safeSet(conditionIds); 430 final int N = mRecords.size(); 431 boolean changed = false; 432 for (int i = 0; i < N; i++) { 433 final ConditionRecord r = mRecords.get(i); 434 final boolean automatic = newIds.contains(r.id); 435 if (!r.isAutomatic && automatic) { 436 // subscribe to new automatic 437 subscribeLocked(r); 438 r.isAutomatic = true; 439 changed = true; 440 } else if (r.isAutomatic && !automatic) { 441 // unsubscribe from old automatic 442 unsubscribeLocked(r); 443 r.isAutomatic = false; 444 changed = true; 445 } 446 } 447 if (save && changed) { 448 saveZenConfigLocked(); 449 } 450 } 451 } 452 453 public Condition[] getAutomaticZenModeConditions() { 454 synchronized(mMutex) { 455 final int N = mRecords.size(); 456 ArrayList<Condition> rt = null; 457 for (int i = 0; i < N; i++) { 458 final ConditionRecord r = mRecords.get(i); 459 if (r.isAutomatic && r.condition != null) { 460 if (rt == null) rt = new ArrayList<Condition>(); 461 rt.add(r.condition); 462 } 463 } 464 return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]); 465 } 466 } 467 468 private void unsubscribeLocked(ConditionRecord r) { 469 if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r); 470 final IConditionProvider provider = provider(r); 471 RemoteException re = null; 472 if (provider != null) { 473 try { 474 provider.onUnsubscribe(r.id); 475 } catch (RemoteException e) { 476 Slog.w(TAG, "Error unsubscribing to " + r, e); 477 re = e; 478 } 479 } 480 ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re); 481 } 482 483 private static IConditionProvider provider(ConditionRecord r) { 484 return r == null ? null : provider(r.info); 485 } 486 487 private static IConditionProvider provider(ManagedServiceInfo info) { 488 return info == null ? null : (IConditionProvider) info.service; 489 } 490 491 private void requestConditionsLocked(int flags) { 492 for (ManagedServiceInfo info : mServices) { 493 final IConditionProvider provider = provider(info); 494 if (provider == null) continue; 495 // clear all stored conditions from this provider that we no longer care about 496 for (int i = mRecords.size() - 1; i >= 0; i--) { 497 final ConditionRecord r = mRecords.get(i); 498 if (r.info != info) continue; 499 if (r.isManual || r.isAutomatic) continue; 500 mRecords.remove(i); 501 } 502 try { 503 provider.onRequestConditions(flags); 504 } catch (RemoteException e) { 505 Slog.w(TAG, "Error requesting conditions from " + info.component, e); 506 } 507 } 508 } 509 510 private void loadZenConfig() { 511 final ZenModeConfig config = mZenModeHelper.getConfig(); 512 if (config == null) { 513 if (DEBUG) Slog.d(TAG, "loadZenConfig: no config"); 514 return; 515 } 516 synchronized (mMutex) { 517 final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition); 518 mExitCondition = config.exitCondition; 519 mExitConditionComponent = config.exitConditionComponent; 520 if (changingExit) { 521 ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config"); 522 } 523 if (mDowntime != null) { 524 mDowntime.setConfig(config); 525 } 526 if (config.conditionComponents == null || config.conditionIds == null 527 || config.conditionComponents.length != config.conditionIds.length) { 528 if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions"); 529 setAutomaticZenModeConditions(null, false /*save*/); 530 return; 531 } 532 final ArraySet<Uri> newIds = new ArraySet<Uri>(); 533 final int N = config.conditionComponents.length; 534 for (int i = 0; i < N; i++) { 535 final ComponentName component = config.conditionComponents[i]; 536 final Uri id = config.conditionIds[i]; 537 if (component != null && id != null) { 538 getRecordLocked(id, component); // ensure record exists 539 newIds.add(id); 540 } 541 } 542 if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N); 543 setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/); 544 } 545 } 546 547 private void saveZenConfigLocked() { 548 ZenModeConfig config = mZenModeHelper.getConfig(); 549 if (config == null) return; 550 config = config.copy(); 551 final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>(); 552 final int automaticN = mRecords.size(); 553 for (int i = 0; i < automaticN; i++) { 554 final ConditionRecord r = mRecords.get(i); 555 if (r.isAutomatic) { 556 automatic.add(r); 557 } 558 } 559 if (automatic.isEmpty()) { 560 config.conditionComponents = null; 561 config.conditionIds = null; 562 } else { 563 final int N = automatic.size(); 564 config.conditionComponents = new ComponentName[N]; 565 config.conditionIds = new Uri[N]; 566 for (int i = 0; i < N; i++) { 567 final ConditionRecord r = automatic.get(i); 568 config.conditionComponents[i] = r.component; 569 config.conditionIds[i] = r.id; 570 } 571 } 572 config.exitCondition = mExitCondition; 573 config.exitConditionComponent = mExitConditionComponent; 574 if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config); 575 mZenModeHelper.setConfig(config); 576 } 577 578 private void onManualConditionClearing() { 579 if (mDowntime != null) { 580 mDowntime.onManualConditionClearing(); 581 } 582 } 583 584 private class ZenModeHelperCallback extends ZenModeHelper.Callback { 585 @Override 586 void onConfigChanged() { 587 loadZenConfig(); 588 } 589 590 @Override 591 void onZenModeChanged() { 592 final int mode = mZenModeHelper.getZenMode(); 593 if (mode == Global.ZEN_MODE_OFF) { 594 // ensure any manual condition is cleared 595 setZenModeCondition(null, "zenOff"); 596 } 597 } 598 } 599 600 private static class ConditionRecord { 601 public final Uri id; 602 public final ComponentName component; 603 public Condition condition; 604 public ManagedServiceInfo info; 605 public boolean isAutomatic; 606 public boolean isManual; 607 608 private ConditionRecord(Uri id, ComponentName component) { 609 this.id = id; 610 this.component = component; 611 } 612 613 @Override 614 public String toString() { 615 final StringBuilder sb = new StringBuilder("ConditionRecord[id=") 616 .append(id).append(",component=").append(component); 617 if (isAutomatic) sb.append(",automatic"); 618 if (isManual) sb.append(",manual"); 619 return sb.append(']').toString(); 620 } 621 } 622} 623