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