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