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