ZenModeHelper.java revision e5b42d97f6fd3eb0220ea84f21e60d530d93fc46
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.BroadcastReceiver; 25import android.content.ComponentName; 26import android.content.ContentResolver; 27import android.content.Context; 28import android.content.Intent; 29import android.content.IntentFilter; 30import android.content.res.Resources; 31import android.content.res.XmlResourceParser; 32import android.database.ContentObserver; 33import android.media.AudioAttributes; 34import android.media.AudioManager; 35import android.net.Uri; 36import android.os.Bundle; 37import android.os.Handler; 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; 48 49import libcore.io.IoUtils; 50 51import org.xmlpull.v1.XmlPullParser; 52import org.xmlpull.v1.XmlPullParserException; 53import org.xmlpull.v1.XmlSerializer; 54 55import java.io.IOException; 56import java.io.PrintWriter; 57import java.util.ArrayList; 58import java.util.Objects; 59 60/** 61 * NotificationManagerService helper for functionality related to zen mode. 62 */ 63public class ZenModeHelper { 64 private static final String TAG = "ZenModeHelper"; 65 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 66 67 private final Context mContext; 68 private final Handler mHandler; 69 private final SettingsObserver mSettingsObserver; 70 private final AppOpsManager mAppOps; 71 private final ZenModeConfig mDefaultConfig; 72 private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); 73 74 private ComponentName mDefaultPhoneApp; 75 private int mZenMode; 76 private ZenModeConfig mConfig; 77 private AudioManager mAudioManager; 78 private int mPreviousRingerMode = -1; 79 80 public ZenModeHelper(Context context, Handler handler) { 81 mContext = context; 82 mHandler = handler; 83 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 84 mDefaultConfig = readDefaultConfig(context.getResources()); 85 mConfig = mDefaultConfig; 86 mSettingsObserver = new SettingsObserver(mHandler); 87 mSettingsObserver.observe(); 88 89 final IntentFilter filter = new IntentFilter(); 90 filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); 91 mContext.registerReceiver(mReceiver, filter); 92 } 93 94 public static ZenModeConfig readDefaultConfig(Resources resources) { 95 XmlResourceParser parser = null; 96 try { 97 parser = resources.getXml(R.xml.default_zen_mode_config); 98 while (parser.next() != XmlPullParser.END_DOCUMENT) { 99 final ZenModeConfig config = ZenModeConfig.readXml(parser); 100 if (config != null) return config; 101 } 102 } catch (Exception e) { 103 Slog.w(TAG, "Error reading default zen mode config from resource", e); 104 } finally { 105 IoUtils.closeQuietly(parser); 106 } 107 return new ZenModeConfig(); 108 } 109 110 public void addCallback(Callback callback) { 111 mCallbacks.add(callback); 112 } 113 114 public void setAudioManager(AudioManager audioManager) { 115 mAudioManager = audioManager; 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(int interruptionFilter) { 146 final int newZen = zenModeFromListenerInterruptionFilter(interruptionFilter, -1); 147 if (newZen != -1) { 148 setZenMode(newZen, "listener"); 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 mode = Global.getInt(mContext.getContentResolver(), 218 Global.ZEN_MODE, Global.ZEN_MODE_OFF); 219 if (mode != mZenMode) { 220 ZenLog.traceUpdateZenMode(mZenMode, mode); 221 } 222 mZenMode = mode; 223 final boolean zen = mZenMode != Global.ZEN_MODE_OFF; 224 final String[] exceptionPackages = null; // none (for now) 225 226 // call restrictions 227 final boolean muteCalls = zen && !mConfig.allowCalls; 228 mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_NOTIFICATION_RINGTONE, 229 muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 230 exceptionPackages); 231 mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, USAGE_NOTIFICATION_RINGTONE, 232 muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 233 exceptionPackages); 234 235 // alarm restrictions 236 final boolean muteAlarms = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; 237 mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_ALARM, 238 muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 239 exceptionPackages); 240 mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, USAGE_ALARM, 241 muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 242 exceptionPackages); 243 244 // force ringer mode into compliance 245 if (mAudioManager != null) { 246 int ringerMode = mAudioManager.getRingerMode(); 247 int forcedRingerMode = -1; 248 if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { 249 if (ringerMode != AudioManager.RINGER_MODE_SILENT) { 250 mPreviousRingerMode = ringerMode; 251 if (DEBUG) Slog.d(TAG, "Silencing ringer"); 252 forcedRingerMode = AudioManager.RINGER_MODE_SILENT; 253 } 254 } else { 255 if (ringerMode == AudioManager.RINGER_MODE_SILENT) { 256 if (DEBUG) Slog.d(TAG, "Unsilencing ringer"); 257 forcedRingerMode = mPreviousRingerMode != -1 ? mPreviousRingerMode 258 : AudioManager.RINGER_MODE_NORMAL; 259 mPreviousRingerMode = -1; 260 } 261 } 262 if (forcedRingerMode != -1) { 263 mAudioManager.setRingerMode(forcedRingerMode, false /*checkZen*/); 264 ZenLog.traceSetRingerMode(forcedRingerMode); 265 } 266 } 267 dispatchOnZenModeChanged(); 268 } 269 270 public void dump(PrintWriter pw, String prefix) { 271 pw.print(prefix); pw.print("mZenMode="); 272 pw.println(Global.zenModeToString(mZenMode)); 273 pw.print(prefix); pw.print("mConfig="); pw.println(mConfig); 274 pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig); 275 pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode); 276 pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp); 277 } 278 279 public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { 280 final ZenModeConfig config = ZenModeConfig.readXml(parser); 281 if (config != null) { 282 setConfig(config); 283 } 284 } 285 286 public void writeXml(XmlSerializer out) throws IOException { 287 mConfig.writeXml(out); 288 } 289 290 public ZenModeConfig getConfig() { 291 return mConfig; 292 } 293 294 public boolean setConfig(ZenModeConfig config) { 295 if (config == null || !config.isValid()) return false; 296 if (config.equals(mConfig)) return true; 297 ZenLog.traceConfig(mConfig, config); 298 mConfig = config; 299 dispatchOnConfigChanged(); 300 final String val = Integer.toString(mConfig.hashCode()); 301 Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); 302 updateZenMode(); 303 return true; 304 } 305 306 private void handleRingerModeChanged() { 307 if (mAudioManager != null) { 308 // follow ringer mode if necessary 309 final int ringerMode = mAudioManager.getRingerMode(); 310 int newZen = -1; 311 if (ringerMode == AudioManager.RINGER_MODE_SILENT) { 312 if (mZenMode == Global.ZEN_MODE_OFF) { 313 newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; 314 } 315 } else if ((ringerMode == AudioManager.RINGER_MODE_NORMAL 316 || ringerMode == AudioManager.RINGER_MODE_VIBRATE) 317 && mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { 318 newZen = Global.ZEN_MODE_OFF; 319 } 320 if (newZen != -1) { 321 ZenLog.traceFollowRingerMode(ringerMode, mZenMode, newZen); 322 setZenMode(newZen, "ringerMode"); 323 } 324 } 325 } 326 327 private void dispatchOnConfigChanged() { 328 for (Callback callback : mCallbacks) { 329 callback.onConfigChanged(); 330 } 331 } 332 333 private void dispatchOnZenModeChanged() { 334 for (Callback callback : mCallbacks) { 335 callback.onZenModeChanged(); 336 } 337 } 338 339 private static boolean isSystem(NotificationRecord record) { 340 return record.isCategory(Notification.CATEGORY_SYSTEM); 341 } 342 343 private static boolean isAlarm(NotificationRecord record) { 344 return record.isCategory(Notification.CATEGORY_ALARM) 345 || record.isAudioStream(AudioManager.STREAM_ALARM) 346 || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM); 347 } 348 349 private static boolean isEvent(NotificationRecord record) { 350 return record.isCategory(Notification.CATEGORY_EVENT); 351 } 352 353 public boolean isCall(NotificationRecord record) { 354 return record != null && (isDefaultPhoneApp(record.sbn.getPackageName()) 355 || record.isCategory(Notification.CATEGORY_CALL)); 356 } 357 358 private boolean isDefaultPhoneApp(String pkg) { 359 if (mDefaultPhoneApp == null) { 360 final TelecomManager telecomm = 361 (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); 362 mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null; 363 if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); 364 } 365 return pkg != null && mDefaultPhoneApp != null 366 && pkg.equals(mDefaultPhoneApp.getPackageName()); 367 } 368 369 private boolean isDefaultMessagingApp(NotificationRecord record) { 370 final int userId = record.getUserId(); 371 if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false; 372 final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(), 373 Secure.SMS_DEFAULT_APPLICATION, userId); 374 return Objects.equals(defaultApp, record.sbn.getPackageName()); 375 } 376 377 private boolean isMessage(NotificationRecord record) { 378 return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record); 379 } 380 381 /** 382 * @param extras extras of the notification with EXTRA_PEOPLE populated 383 * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response 384 * @param timeoutAffinity affinity to return when the timeout specified via 385 * <code>contactsTimeoutMs</code> is hit 386 */ 387 public boolean matchesCallFilter(UserHandle userHandle, Bundle extras, 388 ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) { 389 final int zen = mZenMode; 390 if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through 391 if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { 392 if (!mConfig.allowCalls) return false; // no calls get through 393 if (validator != null) { 394 final float contactAffinity = validator.getContactAffinity(userHandle, extras, 395 contactsTimeoutMs, timeoutAffinity); 396 return audienceMatches(contactAffinity); 397 } 398 } 399 return true; 400 } 401 402 private boolean audienceMatches(float contactAffinity) { 403 switch (mConfig.allowFrom) { 404 case ZenModeConfig.SOURCE_ANYONE: 405 return true; 406 case ZenModeConfig.SOURCE_CONTACT: 407 return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT; 408 case ZenModeConfig.SOURCE_STAR: 409 return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT; 410 default: 411 Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom); 412 return true; 413 } 414 } 415 416 private final Runnable mRingerModeChanged = new Runnable() { 417 @Override 418 public void run() { 419 handleRingerModeChanged(); 420 } 421 }; 422 423 private class SettingsObserver extends ContentObserver { 424 private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE); 425 426 public SettingsObserver(Handler handler) { 427 super(handler); 428 } 429 430 public void observe() { 431 final ContentResolver resolver = mContext.getContentResolver(); 432 resolver.registerContentObserver(ZEN_MODE, false /*notifyForDescendents*/, this); 433 update(null); 434 } 435 436 @Override 437 public void onChange(boolean selfChange, Uri uri) { 438 update(uri); 439 } 440 441 public void update(Uri uri) { 442 if (ZEN_MODE.equals(uri)) { 443 updateZenMode(); 444 } 445 } 446 } 447 448 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 449 @Override 450 public void onReceive(Context context, Intent intent) { 451 mHandler.post(mRingerModeChanged); 452 } 453 }; 454 455 public static class Callback { 456 void onConfigChanged() {} 457 void onZenModeChanged() {} 458 } 459} 460