ConditionProviders.java revision fb8eb00bcd56429fc6a4f1f349ee09a45074ebbf
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.app.AlarmManager; 20import android.app.PendingIntent; 21import android.content.BroadcastReceiver; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.net.Uri; 27import android.os.Handler; 28import android.os.IBinder; 29import android.os.IInterface; 30import android.os.RemoteException; 31import android.provider.Settings; 32import android.provider.Settings.Global; 33import android.service.notification.Condition; 34import android.service.notification.ConditionProviderService; 35import android.service.notification.IConditionListener; 36import android.service.notification.IConditionProvider; 37import android.service.notification.ZenModeConfig; 38import android.text.format.DateUtils; 39import android.util.ArrayMap; 40import android.util.ArraySet; 41import android.util.Slog; 42 43import com.android.internal.R; 44 45import java.io.PrintWriter; 46import java.util.ArrayList; 47import java.util.Arrays; 48import java.util.Date; 49 50public class ConditionProviders extends ManagedServices { 51 private static final Condition[] NO_CONDITIONS = new Condition[0]; 52 53 private final ZenModeHelper mZenModeHelper; 54 private final ArrayMap<IBinder, IConditionListener> mListeners 55 = new ArrayMap<IBinder, IConditionListener>(); 56 private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>(); 57 private final CountdownConditionHelper mCountdownHelper = new CountdownConditionHelper(); 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) { 81 super.dump(pw); 82 synchronized(mMutex) { 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 pw.print(" mRecords("); pw.print(mRecords.size()); pw.println("):"); 88 for (int i = 0; i < mRecords.size(); i++) { 89 pw.print(" "); pw.println(mRecords.get(i)); 90 } 91 pw.print(" mCountdownHelper: "); 92 pw.println(mCountdownHelper.getCurrentConditionDescription()); 93 } 94 } 95 96 @Override 97 protected IInterface asInterface(IBinder binder) { 98 return IConditionProvider.Stub.asInterface(binder); 99 } 100 101 @Override 102 protected void onServiceAdded(ManagedServiceInfo info) { 103 Slog.d(TAG, "onServiceAdded " + info); 104 final IConditionProvider provider = provider(info); 105 try { 106 provider.onConnected(); 107 } catch (RemoteException e) { 108 // we tried 109 } 110 synchronized (mMutex) { 111 final int N = mRecords.size(); 112 for(int i = 0; i < N; i++) { 113 final ConditionRecord r = mRecords.get(i); 114 if (!r.component.equals(info.component)) continue; 115 r.info = info; 116 // if automatic, auto-subscribe 117 if (r.isAutomatic) { 118 try { 119 final Uri id = r.id; 120 if (DEBUG) Slog.d(TAG, "Auto-subscribing to configured condition " + id); 121 provider.onSubscribe(id); 122 } catch (RemoteException e) { 123 // we tried 124 } 125 } 126 } 127 } 128 } 129 130 @Override 131 protected void onServiceRemovedLocked(ManagedServiceInfo removed) { 132 if (removed == null) return; 133 for (int i = mRecords.size() - 1; i >= 0; i--) { 134 final ConditionRecord r = mRecords.get(i); 135 if (!r.component.equals(removed.component)) continue; 136 if (r.isManual) { 137 // removing the current manual condition, exit zen 138 mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF); 139 } 140 if (r.isAutomatic) { 141 // removing an automatic condition, exit zen 142 mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF); 143 } 144 mRecords.remove(i); 145 } 146 } 147 148 public ManagedServiceInfo checkServiceToken(IConditionProvider provider) { 149 synchronized(mMutex) { 150 return checkServiceTokenLocked(provider); 151 } 152 } 153 154 public void requestZenModeConditions(IConditionListener callback, int relevance) { 155 synchronized(mMutex) { 156 if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback 157 + " relevance=" + Condition.relevanceToString(relevance)); 158 if (callback == null) return; 159 relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS); 160 if (relevance != 0) { 161 mListeners.put(callback.asBinder(), callback); 162 requestConditionsLocked(relevance); 163 } else { 164 mListeners.remove(callback.asBinder()); 165 if (mListeners.isEmpty()) { 166 requestConditionsLocked(0); 167 } 168 } 169 } 170 } 171 172 private Condition[] validateConditions(String pkg, Condition[] conditions) { 173 if (conditions == null || conditions.length == 0) return null; 174 final int N = conditions.length; 175 final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N); 176 for (int i = 0; i < N; i++) { 177 final Uri id = conditions[i].id; 178 if (!Condition.isValidId(id, pkg)) { 179 Slog.w(TAG, "Ignoring condition from " + pkg + " for invalid id: " + id); 180 continue; 181 } 182 if (valid.containsKey(id)) { 183 Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id); 184 continue; 185 } 186 valid.put(id, conditions[i]); 187 } 188 if (valid.size() == 0) return null; 189 if (valid.size() == N) return conditions; 190 final Condition[] rt = new Condition[valid.size()]; 191 for (int i = 0; i < rt.length; i++) { 192 rt[i] = valid.valueAt(i); 193 } 194 return rt; 195 } 196 197 private ConditionRecord getRecordLocked(Uri id, ComponentName component) { 198 final int N = mRecords.size(); 199 for (int i = 0; i < N; i++) { 200 final ConditionRecord r = mRecords.get(i); 201 if (r.id.equals(id) && r.component.equals(component)) { 202 return r; 203 } 204 } 205 final ConditionRecord r = new ConditionRecord(id, component); 206 mRecords.add(r); 207 return r; 208 } 209 210 public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) { 211 synchronized(mMutex) { 212 if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions=" 213 + (conditions == null ? null : Arrays.asList(conditions))); 214 conditions = validateConditions(pkg, conditions); 215 if (conditions == null || conditions.length == 0) return; 216 final int N = conditions.length; 217 for (IConditionListener listener : mListeners.values()) { 218 try { 219 listener.onConditionsReceived(conditions); 220 } catch (RemoteException e) { 221 Slog.w(TAG, "Error sending conditions to listener " + listener, e); 222 } 223 } 224 for (int i = 0; i < N; i++) { 225 final Condition c = conditions[i]; 226 final ConditionRecord r = getRecordLocked(c.id, info.component); 227 r.info = info; 228 r.condition = c; 229 // if manual, exit zen if false (or failed) 230 if (r.isManual) { 231 if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) { 232 final boolean failed = c.state == Condition.STATE_ERROR; 233 if (failed) { 234 Slog.w(TAG, "Exit zen: manual condition failed: " + c); 235 } else if (DEBUG) { 236 Slog.d(TAG, "Exit zen: manual condition false: " + c); 237 } 238 mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF); 239 unsubscribeLocked(r); 240 r.isManual = false; 241 } 242 } 243 // if automatic, exit zen if false (or failed), enter zen if true 244 if (r.isAutomatic) { 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: automatic condition failed: " + c); 249 } else if (DEBUG) { 250 Slog.d(TAG, "Exit zen: automatic condition false: " + c); 251 } 252 mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF); 253 } else if (c.state == Condition.STATE_TRUE) { 254 Slog.d(TAG, "Enter zen: automatic condition true: " + c); 255 mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_ON); 256 } 257 } 258 } 259 } 260 } 261 262 public void setZenModeCondition(Uri conditionId) { 263 if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId); 264 synchronized(mMutex) { 265 final int N = mRecords.size(); 266 for (int i = 0; i < N; i++) { 267 final ConditionRecord r = mRecords.get(i); 268 final boolean idEqual = r.id.equals(conditionId); 269 if (r.isManual && !idEqual) { 270 // was previous manual condition, unsubscribe 271 unsubscribeLocked(r); 272 r.isManual = false; 273 } else if (idEqual && !r.isManual) { 274 // is new manual condition, subscribe 275 subscribeLocked(r); 276 r.isManual = true; 277 } 278 } 279 } 280 mCountdownHelper.setZenModeCondition(conditionId); 281 } 282 283 private void subscribeLocked(ConditionRecord r) { 284 if (DEBUG) Slog.d(TAG, "subscribeLocked " + r); 285 final IConditionProvider provider = provider(r); 286 if (provider == null) { 287 Slog.w(TAG, "subscribeLocked: no provider"); 288 return; 289 } 290 try { 291 provider.onSubscribe(r.id); 292 } catch (RemoteException e) { 293 Slog.w(TAG, "Error subscribing to " + r, e); 294 } 295 } 296 297 private static <T> ArraySet<T> safeSet(T... items) { 298 final ArraySet<T> rt = new ArraySet<T>(); 299 if (items == null || items.length == 0) return rt; 300 final int N = items.length; 301 for (int i = 0; i < N; i++) { 302 final T item = items[i]; 303 if (item != null) { 304 rt.add(item); 305 } 306 } 307 return rt; 308 } 309 310 public void setAutomaticZenModeConditions(Uri[] conditionIds) { 311 setAutomaticZenModeConditions(conditionIds, true /*save*/); 312 } 313 314 private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) { 315 if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions " 316 + (conditionIds == null ? null : Arrays.asList(conditionIds))); 317 synchronized(mMutex) { 318 final ArraySet<Uri> newIds = safeSet(conditionIds); 319 final int N = mRecords.size(); 320 boolean changed = false; 321 for (int i = 0; i < N; i++) { 322 final ConditionRecord r = mRecords.get(i); 323 final boolean automatic = newIds.contains(r.id); 324 if (!r.isAutomatic && automatic) { 325 // subscribe to new automatic 326 subscribeLocked(r); 327 r.isAutomatic = true; 328 changed = true; 329 } else if (r.isAutomatic && !automatic) { 330 // unsubscribe from old automatic 331 unsubscribeLocked(r); 332 r.isAutomatic = false; 333 changed = true; 334 } 335 } 336 if (save && changed) { 337 saveZenConfigLocked(); 338 } 339 } 340 } 341 342 public Condition[] getAutomaticZenModeConditions() { 343 synchronized(mMutex) { 344 final int N = mRecords.size(); 345 ArrayList<Condition> rt = null; 346 for (int i = 0; i < N; i++) { 347 final ConditionRecord r = mRecords.get(i); 348 if (r.isAutomatic && r.condition != null) { 349 if (rt == null) rt = new ArrayList<Condition>(); 350 rt.add(r.condition); 351 } 352 } 353 return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]); 354 } 355 } 356 357 private void unsubscribeLocked(ConditionRecord r) { 358 if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r); 359 final IConditionProvider provider = provider(r); 360 if (provider == null) { 361 Slog.w(TAG, "unsubscribeLocked: no provider"); 362 return; 363 } 364 try { 365 provider.onUnsubscribe(r.id); 366 } catch (RemoteException e) { 367 Slog.w(TAG, "Error unsubscribing to " + r, e); 368 } 369 } 370 371 private static IConditionProvider provider(ConditionRecord r) { 372 return r == null ? null : provider(r.info); 373 } 374 375 private static IConditionProvider provider(ManagedServiceInfo info) { 376 return info == null ? null : (IConditionProvider) info.service; 377 } 378 379 private void requestConditionsLocked(int flags) { 380 for (ManagedServiceInfo info : mServices) { 381 final IConditionProvider provider = provider(info); 382 if (provider == null) continue; 383 try { 384 provider.onRequestConditions(flags); 385 } catch (RemoteException e) { 386 Slog.w(TAG, "Error requesting conditions from " + info.component, e); 387 } 388 } 389 } 390 391 private void loadZenConfig() { 392 final ZenModeConfig config = mZenModeHelper.getConfig(); 393 if (config == null) { 394 if (DEBUG) Slog.d(TAG, "loadZenConfig: no config"); 395 return; 396 } 397 synchronized (mMutex) { 398 if (config.conditionComponents == null || config.conditionIds == null 399 || config.conditionComponents.length != config.conditionIds.length) { 400 if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions"); 401 setAutomaticZenModeConditions(null, false /*save*/); 402 return; 403 } 404 final ArraySet<Uri> newIds = new ArraySet<Uri>(); 405 final int N = config.conditionComponents.length; 406 for (int i = 0; i < N; i++) { 407 final ComponentName component = config.conditionComponents[i]; 408 final Uri id = config.conditionIds[i]; 409 if (component != null && id != null) { 410 getRecordLocked(id, component); // ensure record exists 411 newIds.add(id); 412 } 413 } 414 if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N); 415 setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/); 416 } 417 } 418 419 private void saveZenConfigLocked() { 420 ZenModeConfig config = mZenModeHelper.getConfig(); 421 if (config == null) return; 422 config = config.copy(); 423 final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>(); 424 final int automaticN = mRecords.size(); 425 for (int i = 0; i < automaticN; i++) { 426 final ConditionRecord r = mRecords.get(i); 427 if (r.isAutomatic) { 428 automatic.add(r); 429 } 430 } 431 if (automatic.isEmpty()) { 432 config.conditionComponents = null; 433 config.conditionIds = null; 434 } else { 435 final int N = automatic.size(); 436 config.conditionComponents = new ComponentName[N]; 437 config.conditionIds = new Uri[N]; 438 for (int i = 0; i < N; i++) { 439 final ConditionRecord r = automatic.get(i); 440 config.conditionComponents[i] = r.component; 441 config.conditionIds[i] = r.id; 442 } 443 } 444 if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config); 445 mZenModeHelper.setConfig(config); 446 } 447 448 private final class CountdownConditionHelper extends BroadcastReceiver { 449 private static final String ACTION = "CountdownConditionHelper"; 450 private static final int REQUEST_CODE = 100; 451 private static final String EXTRA_TIME = "time"; 452 453 private long mCurrent; 454 455 public CountdownConditionHelper() { 456 mContext.registerReceiver(this, new IntentFilter(ACTION)); 457 } 458 459 public void setZenModeCondition(Uri conditionId) { 460 final long time = tryParseCondition(conditionId); 461 final AlarmManager alarms = (AlarmManager) 462 mContext.getSystemService(Context.ALARM_SERVICE); 463 final Intent intent = new Intent(ACTION).putExtra(EXTRA_TIME, time) 464 .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 465 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE, 466 intent, PendingIntent.FLAG_UPDATE_CURRENT); 467 alarms.cancel(pendingIntent); 468 mCurrent = time; 469 if (time > 0) { 470 final long now = System.currentTimeMillis(); 471 final CharSequence span = 472 DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS); 473 Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future (%s), now=%s", 474 ACTION, ts(time), time - now, span, ts(now))); 475 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); 476 } 477 } 478 479 public String getCurrentConditionDescription() { 480 if (mCurrent == 0) return null; 481 final long time = mCurrent; 482 final long now = System.currentTimeMillis(); 483 final CharSequence span = 484 DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS); 485 return String.format("Scheduled for %s, %s in the future (%s), now=%s", 486 ts(time), time - now, span, ts(now)); 487 } 488 489 private String ts(long time) { 490 return new Date(time) + " (" + time + ")"; 491 } 492 493 @Override 494 public void onReceive(Context context, Intent intent) { 495 if (ACTION.equals(intent.getAction())) { 496 final long time = intent.getLongExtra(EXTRA_TIME, 0); 497 Slog.d(TAG, "Countdown condition fired. time=" + time + " mCurrent=" + mCurrent); 498 if (time > 0 && time == mCurrent) { 499 // countdown condition is still the manual condition, leave zen 500 mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF); 501 ConditionProviders.this.setZenModeCondition(null); 502 } 503 } 504 } 505 506 private long tryParseCondition(Uri conditionId) { 507 // condition://android/countdown/1399917958951 508 if (!Condition.isValidId(conditionId, "android")) return 0; 509 if (conditionId.getPathSegments().size() != 2 510 || !"countdown".equals(conditionId.getPathSegments().get(0))) return 0; 511 try { 512 return Long.parseLong(conditionId.getPathSegments().get(1)); 513 } catch (RuntimeException e) { 514 Slog.w(TAG, "Error parsing countdown condition: " + conditionId, e); 515 return 0; 516 } 517 } 518 } 519 520 private class ZenModeHelperCallback extends ZenModeHelper.Callback { 521 @Override 522 void onConfigChanged() { 523 loadZenConfig(); 524 } 525 526 @Override 527 void onZenModeChanged() { 528 final int mode = mZenModeHelper.getZenMode(); 529 if (mode == Global.ZEN_MODE_OFF) { 530 // ensure any manual condition is cleared 531 setZenModeCondition(null); 532 } 533 } 534 } 535 536 private static class ConditionRecord { 537 public final Uri id; 538 public final ComponentName component; 539 public Condition condition; 540 public ManagedServiceInfo info; 541 public boolean isAutomatic; 542 public boolean isManual; 543 544 private ConditionRecord(Uri id, ComponentName component) { 545 this.id = id; 546 this.component = component; 547 } 548 549 @Override 550 public String toString() { 551 final StringBuilder sb = new StringBuilder("ConditionRecord[id=") 552 .append(id).append(",component=").append(component); 553 if (isAutomatic) sb.append(",automatic"); 554 if (isManual) sb.append(",manual"); 555 return sb.append(']').toString(); 556 } 557 } 558} 559