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