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