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