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