ZenModeHelper.java revision 530052a2fe3b6a6a4246ce28ab0ced647fe7f470
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 static android.media.AudioAttributes.USAGE_ALARM; 20import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; 21 22import android.app.AppOpsManager; 23import android.app.Notification; 24import android.content.ComponentName; 25import android.content.ContentResolver; 26import android.content.Context; 27import android.content.res.Resources; 28import android.content.res.XmlResourceParser; 29import android.database.ContentObserver; 30import android.media.AudioAttributes; 31import android.media.AudioManager; 32import android.media.AudioManagerInternal; 33import android.net.Uri; 34import android.os.Bundle; 35import android.os.Handler; 36import android.os.Looper; 37import android.os.Message; 38import android.os.UserHandle; 39import android.provider.Settings.Global; 40import android.provider.Settings.Secure; 41import android.service.notification.NotificationListenerService; 42import android.service.notification.ZenModeConfig; 43import android.telecom.TelecomManager; 44import android.util.Log; 45import android.util.Slog; 46 47import com.android.internal.R; 48import com.android.server.LocalServices; 49 50import libcore.io.IoUtils; 51 52import org.xmlpull.v1.XmlPullParser; 53import org.xmlpull.v1.XmlPullParserException; 54import org.xmlpull.v1.XmlSerializer; 55 56import java.io.IOException; 57import java.io.PrintWriter; 58import java.util.ArrayList; 59import java.util.Objects; 60 61/** 62 * NotificationManagerService helper for functionality related to zen mode. 63 */ 64public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate { 65 private static final String TAG = "ZenModeHelper"; 66 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 67 68 private final Context mContext; 69 private final H mHandler; 70 private final SettingsObserver mSettingsObserver; 71 private final AppOpsManager mAppOps; 72 private final ZenModeConfig mDefaultConfig; 73 private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); 74 75 private ComponentName mDefaultPhoneApp; 76 private int mZenMode; 77 private ZenModeConfig mConfig; 78 private AudioManagerInternal mAudioManager; 79 private int mPreviousRingerMode = -1; 80 81 public ZenModeHelper(Context context, Looper looper) { 82 mContext = context; 83 mHandler = new H(looper); 84 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 85 mDefaultConfig = readDefaultConfig(context.getResources()); 86 mConfig = mDefaultConfig; 87 mSettingsObserver = new SettingsObserver(mHandler); 88 mSettingsObserver.observe(); 89 } 90 91 public static ZenModeConfig readDefaultConfig(Resources resources) { 92 XmlResourceParser parser = null; 93 try { 94 parser = resources.getXml(R.xml.default_zen_mode_config); 95 while (parser.next() != XmlPullParser.END_DOCUMENT) { 96 final ZenModeConfig config = ZenModeConfig.readXml(parser); 97 if (config != null) return config; 98 } 99 } catch (Exception e) { 100 Slog.w(TAG, "Error reading default zen mode config from resource", e); 101 } finally { 102 IoUtils.closeQuietly(parser); 103 } 104 return new ZenModeConfig(); 105 } 106 107 public void addCallback(Callback callback) { 108 mCallbacks.add(callback); 109 } 110 111 public void removeCallback(Callback callback) { 112 mCallbacks.remove(callback); 113 } 114 115 public void onSystemReady() { 116 mAudioManager = LocalServices.getService(AudioManagerInternal.class); 117 if (mAudioManager != null) { 118 mAudioManager.setRingerModeDelegate(this); 119 } 120 } 121 122 public int getZenModeListenerInterruptionFilter() { 123 switch (mZenMode) { 124 case Global.ZEN_MODE_OFF: 125 return NotificationListenerService.INTERRUPTION_FILTER_ALL; 126 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: 127 return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY; 128 case Global.ZEN_MODE_NO_INTERRUPTIONS: 129 return NotificationListenerService.INTERRUPTION_FILTER_NONE; 130 default: 131 return 0; 132 } 133 } 134 135 private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter, 136 int defValue) { 137 switch (listenerInterruptionFilter) { 138 case NotificationListenerService.INTERRUPTION_FILTER_ALL: 139 return Global.ZEN_MODE_OFF; 140 case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY: 141 return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; 142 case NotificationListenerService.INTERRUPTION_FILTER_NONE: 143 return Global.ZEN_MODE_NO_INTERRUPTIONS; 144 default: 145 return defValue; 146 } 147 } 148 149 public void requestFromListener(ComponentName name, int interruptionFilter) { 150 final int newZen = zenModeFromListenerInterruptionFilter(interruptionFilter, -1); 151 if (newZen != -1) { 152 setZenMode(newZen, "listener:" + (name != null ? name.flattenToShortString() : null)); 153 } 154 } 155 156 public boolean shouldIntercept(NotificationRecord record) { 157 if (isSystem(record)) { 158 return false; 159 } 160 switch (mZenMode) { 161 case Global.ZEN_MODE_NO_INTERRUPTIONS: 162 // #notevenalarms 163 ZenLog.traceIntercepted(record, "none"); 164 return true; 165 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: 166 if (isAlarm(record)) { 167 // Alarms are always priority 168 return false; 169 } 170 // allow user-prioritized packages through in priority mode 171 if (record.getPackagePriority() == Notification.PRIORITY_MAX) { 172 ZenLog.traceNotIntercepted(record, "priorityApp"); 173 return false; 174 } 175 if (isCall(record)) { 176 if (!mConfig.allowCalls) { 177 ZenLog.traceIntercepted(record, "!allowCalls"); 178 return true; 179 } 180 return shouldInterceptAudience(record); 181 } 182 if (isMessage(record)) { 183 if (!mConfig.allowMessages) { 184 ZenLog.traceIntercepted(record, "!allowMessages"); 185 return true; 186 } 187 return shouldInterceptAudience(record); 188 } 189 if (isEvent(record)) { 190 if (!mConfig.allowEvents) { 191 ZenLog.traceIntercepted(record, "!allowEvents"); 192 return true; 193 } 194 return false; 195 } 196 ZenLog.traceIntercepted(record, "!priority"); 197 return true; 198 default: 199 return false; 200 } 201 } 202 203 private boolean shouldInterceptAudience(NotificationRecord record) { 204 if (!audienceMatches(record.getContactAffinity())) { 205 ZenLog.traceIntercepted(record, "!audienceMatches"); 206 return true; 207 } 208 return false; 209 } 210 211 public int getZenMode() { 212 return mZenMode; 213 } 214 215 public void setZenMode(int zenMode, String reason) { 216 ZenLog.traceSetZenMode(zenMode, reason); 217 Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenMode); 218 } 219 220 public void updateZenMode() { 221 final int oldMode = mZenMode; 222 final int newMode = Global.getInt(mContext.getContentResolver(), 223 Global.ZEN_MODE, Global.ZEN_MODE_OFF); 224 if (oldMode != newMode) { 225 ZenLog.traceUpdateZenMode(oldMode, newMode); 226 } 227 mZenMode = newMode; 228 final boolean zen = mZenMode != Global.ZEN_MODE_OFF; 229 final String[] exceptionPackages = null; // none (for now) 230 231 // call restrictions 232 final boolean muteCalls = zen && !mConfig.allowCalls; 233 mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_NOTIFICATION_RINGTONE, 234 muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 235 exceptionPackages); 236 mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, USAGE_NOTIFICATION_RINGTONE, 237 muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 238 exceptionPackages); 239 240 // alarm restrictions 241 final boolean muteAlarms = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; 242 mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_ALARM, 243 muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 244 exceptionPackages); 245 mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, USAGE_ALARM, 246 muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 247 exceptionPackages); 248 249 onZenUpdated(oldMode, newMode); 250 dispatchOnZenModeChanged(); 251 } 252 253 public void dump(PrintWriter pw, String prefix) { 254 pw.print(prefix); pw.print("mZenMode="); 255 pw.println(Global.zenModeToString(mZenMode)); 256 pw.print(prefix); pw.print("mConfig="); pw.println(mConfig); 257 pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig); 258 pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode); 259 pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp); 260 } 261 262 public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { 263 final ZenModeConfig config = ZenModeConfig.readXml(parser); 264 if (config != null) { 265 setConfig(config); 266 } 267 } 268 269 public void writeXml(XmlSerializer out) throws IOException { 270 mConfig.writeXml(out); 271 } 272 273 public ZenModeConfig getConfig() { 274 return mConfig; 275 } 276 277 public boolean setConfig(ZenModeConfig config) { 278 if (config == null || !config.isValid()) return false; 279 if (config.equals(mConfig)) return true; 280 ZenLog.traceConfig(mConfig, config); 281 mConfig = config; 282 dispatchOnConfigChanged(); 283 final String val = Integer.toString(mConfig.hashCode()); 284 Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); 285 updateZenMode(); 286 return true; 287 } 288 289 private void onZenUpdated(int oldZen, int newZen) { 290 if (mAudioManager == null) return; 291 if (oldZen == newZen) return; 292 293 // force the ringer mode into compliance 294 final int ringerModeInternal = mAudioManager.getRingerModeInternal(); 295 int newRingerModeInternal = ringerModeInternal; 296 switch (newZen) { 297 case Global.ZEN_MODE_NO_INTERRUPTIONS: 298 if (ringerModeInternal != AudioManager.RINGER_MODE_SILENT) { 299 mPreviousRingerMode = ringerModeInternal; 300 newRingerModeInternal = AudioManager.RINGER_MODE_SILENT; 301 } 302 break; 303 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: 304 case Global.ZEN_MODE_OFF: 305 if (ringerModeInternal == AudioManager.RINGER_MODE_SILENT) { 306 newRingerModeInternal = mPreviousRingerMode != -1 ? mPreviousRingerMode 307 : AudioManager.RINGER_MODE_NORMAL; 308 mPreviousRingerMode = -1; 309 } 310 break; 311 } 312 if (newRingerModeInternal != -1) { 313 mAudioManager.setRingerModeInternal(newRingerModeInternal, TAG); 314 } 315 } 316 317 @Override // RingerModeDelegate 318 public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller, 319 int ringerModeExternal) { 320 final boolean isChange = ringerModeOld != ringerModeNew; 321 322 int ringerModeExternalOut = ringerModeNew; 323 324 int newZen = -1; 325 switch(ringerModeNew) { 326 case AudioManager.RINGER_MODE_SILENT: 327 if (isChange) { 328 if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS) { 329 newZen = Global.ZEN_MODE_NO_INTERRUPTIONS; 330 } 331 } 332 break; 333 case AudioManager.RINGER_MODE_VIBRATE: 334 case AudioManager.RINGER_MODE_NORMAL: 335 if (mZenMode != Global.ZEN_MODE_OFF) { 336 ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT; 337 } 338 break; 339 } 340 if (newZen != -1) { 341 mHandler.postSetZenMode(newZen, "ringerModeInternal"); 342 } 343 344 if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) { 345 ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller, 346 ringerModeExternal, ringerModeExternalOut); 347 } 348 return ringerModeExternalOut; 349 } 350 351 @Override // RingerModeDelegate 352 public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller, 353 int ringerModeInternal) { 354 int ringerModeInternalOut = ringerModeNew; 355 final boolean isChange = ringerModeOld != ringerModeNew; 356 final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE; 357 358 int newZen = -1; 359 switch(ringerModeNew) { 360 case AudioManager.RINGER_MODE_SILENT: 361 if (isChange) { 362 if (mZenMode == Global.ZEN_MODE_OFF) { 363 newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; 364 } 365 ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE 366 : AudioManager.RINGER_MODE_NORMAL; 367 } else { 368 ringerModeInternalOut = ringerModeInternal; 369 } 370 break; 371 case AudioManager.RINGER_MODE_VIBRATE: 372 case AudioManager.RINGER_MODE_NORMAL: 373 if (mZenMode != Global.ZEN_MODE_OFF) { 374 newZen = Global.ZEN_MODE_OFF; 375 } 376 break; 377 } 378 if (newZen != -1) { 379 mHandler.postSetZenMode(newZen, "ringerModeExternal"); 380 } 381 382 ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller, ringerModeInternal, 383 ringerModeInternalOut); 384 return ringerModeInternalOut; 385 } 386 387 private void dispatchOnConfigChanged() { 388 for (Callback callback : mCallbacks) { 389 callback.onConfigChanged(); 390 } 391 } 392 393 private void dispatchOnZenModeChanged() { 394 for (Callback callback : mCallbacks) { 395 callback.onZenModeChanged(); 396 } 397 } 398 399 private static boolean isSystem(NotificationRecord record) { 400 return record.isCategory(Notification.CATEGORY_SYSTEM); 401 } 402 403 private static boolean isAlarm(NotificationRecord record) { 404 return record.isCategory(Notification.CATEGORY_ALARM) 405 || record.isAudioStream(AudioManager.STREAM_ALARM) 406 || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM); 407 } 408 409 private static boolean isEvent(NotificationRecord record) { 410 return record.isCategory(Notification.CATEGORY_EVENT); 411 } 412 413 public boolean isCall(NotificationRecord record) { 414 return record != null && (isDefaultPhoneApp(record.sbn.getPackageName()) 415 || record.isCategory(Notification.CATEGORY_CALL)); 416 } 417 418 private boolean isDefaultPhoneApp(String pkg) { 419 if (mDefaultPhoneApp == null) { 420 final TelecomManager telecomm = 421 (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); 422 mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null; 423 if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); 424 } 425 return pkg != null && mDefaultPhoneApp != null 426 && pkg.equals(mDefaultPhoneApp.getPackageName()); 427 } 428 429 private boolean isDefaultMessagingApp(NotificationRecord record) { 430 final int userId = record.getUserId(); 431 if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false; 432 final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(), 433 Secure.SMS_DEFAULT_APPLICATION, userId); 434 return Objects.equals(defaultApp, record.sbn.getPackageName()); 435 } 436 437 private boolean isMessage(NotificationRecord record) { 438 return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record); 439 } 440 441 /** 442 * @param extras extras of the notification with EXTRA_PEOPLE populated 443 * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response 444 * @param timeoutAffinity affinity to return when the timeout specified via 445 * <code>contactsTimeoutMs</code> is hit 446 */ 447 public boolean matchesCallFilter(UserHandle userHandle, Bundle extras, 448 ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) { 449 final int zen = mZenMode; 450 if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through 451 if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { 452 if (!mConfig.allowCalls) return false; // no calls get through 453 if (validator != null) { 454 final float contactAffinity = validator.getContactAffinity(userHandle, extras, 455 contactsTimeoutMs, timeoutAffinity); 456 return audienceMatches(contactAffinity); 457 } 458 } 459 return true; 460 } 461 462 @Override 463 public String toString() { 464 return TAG; 465 } 466 467 private boolean audienceMatches(float contactAffinity) { 468 switch (mConfig.allowFrom) { 469 case ZenModeConfig.SOURCE_ANYONE: 470 return true; 471 case ZenModeConfig.SOURCE_CONTACT: 472 return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT; 473 case ZenModeConfig.SOURCE_STAR: 474 return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT; 475 default: 476 Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom); 477 return true; 478 } 479 } 480 481 private class SettingsObserver extends ContentObserver { 482 private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE); 483 484 public SettingsObserver(Handler handler) { 485 super(handler); 486 } 487 488 public void observe() { 489 final ContentResolver resolver = mContext.getContentResolver(); 490 resolver.registerContentObserver(ZEN_MODE, false /*notifyForDescendents*/, this); 491 update(null); 492 } 493 494 @Override 495 public void onChange(boolean selfChange, Uri uri) { 496 update(uri); 497 } 498 499 public void update(Uri uri) { 500 if (ZEN_MODE.equals(uri)) { 501 updateZenMode(); 502 } 503 } 504 } 505 506 private class H extends Handler { 507 private static final int MSG_SET_ZEN = 1; 508 509 private H(Looper looper) { 510 super(looper); 511 } 512 513 private void postSetZenMode(int zen, String reason) { 514 obtainMessage(MSG_SET_ZEN, zen, 0, reason).sendToTarget(); 515 } 516 517 @Override 518 public void handleMessage(Message msg) { 519 switch(msg.what) { 520 case MSG_SET_ZEN: 521 setZenMode(msg.arg1, (String) msg.obj); 522 break; 523 } 524 } 525 } 526 527 public static class Callback { 528 void onConfigChanged() {} 529 void onZenModeChanged() {} 530 } 531} 532