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