ZenModeHelper.java revision 528dcd2fbe6556ca542a432b7aa26731663e3148
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; 21import static android.media.AudioAttributes.USAGE_UNKNOWN; 22 23import android.app.AppOpsManager; 24import android.app.Notification; 25import android.content.BroadcastReceiver; 26import android.content.ComponentName; 27import android.content.ContentResolver; 28import android.content.Context; 29import android.content.Intent; 30import android.content.IntentFilter; 31import android.content.res.Resources; 32import android.content.res.XmlResourceParser; 33import android.database.ContentObserver; 34import android.media.AudioAttributes; 35import android.media.AudioManager; 36import android.net.Uri; 37import android.os.Handler; 38import android.os.IBinder; 39import android.os.UserHandle; 40import android.provider.Settings.Global; 41import android.provider.Settings.Secure; 42import android.service.notification.NotificationListenerService; 43import android.service.notification.ZenModeConfig; 44import android.telecomm.TelecommManager; 45import android.util.Log; 46import android.util.Slog; 47 48import com.android.internal.R; 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 { 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 Handler 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 AudioManager mAudioManager; 79 private int mPreviousRingerMode = -1; 80 81 public ZenModeHelper(Context context, Handler handler) { 82 mContext = context; 83 mHandler = handler; 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 final IntentFilter filter = new IntentFilter(); 91 filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); 92 mContext.registerReceiver(mReceiver, filter); 93 } 94 95 public static ZenModeConfig readDefaultConfig(Resources resources) { 96 XmlResourceParser parser = null; 97 try { 98 parser = resources.getXml(R.xml.default_zen_mode_config); 99 while (parser.next() != XmlPullParser.END_DOCUMENT) { 100 final ZenModeConfig config = ZenModeConfig.readXml(parser); 101 if (config != null) return config; 102 } 103 } catch (Exception e) { 104 Slog.w(TAG, "Error reading default zen mode config from resource", e); 105 } finally { 106 IoUtils.closeQuietly(parser); 107 } 108 return new ZenModeConfig(); 109 } 110 111 public void addCallback(Callback callback) { 112 mCallbacks.add(callback); 113 } 114 115 public void setAudioManager(AudioManager audioManager) { 116 mAudioManager = audioManager; 117 } 118 119 public int getZenModeListenerHint() { 120 switch(mZenMode) { 121 case Global.ZEN_MODE_OFF: 122 return NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_ALL; 123 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: 124 return NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_PRIORITY; 125 case Global.ZEN_MODE_NO_INTERRUPTIONS: 126 return NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_NONE; 127 default: 128 return 0; 129 } 130 } 131 132 private static int zenFromListenerHint(int hints, int defValue) { 133 final int level = hints & NotificationListenerService.HOST_INTERRUPTION_LEVEL_MASK; 134 switch(level) { 135 case NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_ALL: 136 return Global.ZEN_MODE_OFF; 137 case NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_PRIORITY: 138 return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; 139 case NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_NONE: 140 return Global.ZEN_MODE_NO_INTERRUPTIONS; 141 default: 142 return defValue; 143 } 144 } 145 146 public void requestFromListener(int hints) { 147 final int newZen = zenFromListenerHint(hints, -1); 148 if (newZen != -1) { 149 setZenMode(newZen, "listener"); 150 } 151 } 152 153 public boolean shouldIntercept(NotificationRecord record) { 154 if (mZenMode != Global.ZEN_MODE_OFF) { 155 if (isSystem(record)) { 156 return false; 157 } 158 if (isAlarm(record)) { 159 if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { 160 ZenLog.traceIntercepted(record, "alarm"); 161 return true; 162 } 163 return false; 164 } 165 // allow user-prioritized packages through in priority mode 166 if (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { 167 if (record.getPackagePriority() == Notification.PRIORITY_MAX) { 168 ZenLog.traceNotIntercepted(record, "priorityApp"); 169 return false; 170 } 171 } 172 if (isCall(record)) { 173 if (!mConfig.allowCalls) { 174 ZenLog.traceIntercepted(record, "!allowCalls"); 175 return true; 176 } 177 return shouldInterceptAudience(record); 178 } 179 if (isMessage(record)) { 180 if (!mConfig.allowMessages) { 181 ZenLog.traceIntercepted(record, "!allowMessages"); 182 return true; 183 } 184 return shouldInterceptAudience(record); 185 } 186 ZenLog.traceIntercepted(record, "!allowed"); 187 return true; 188 } 189 return false; 190 } 191 192 private boolean shouldInterceptAudience(NotificationRecord record) { 193 if (!audienceMatches(record)) { 194 ZenLog.traceIntercepted(record, "!audienceMatches"); 195 return true; 196 } 197 return false; 198 } 199 200 public int getZenMode() { 201 return mZenMode; 202 } 203 204 public void setZenMode(int zenModeValue, String reason) { 205 ZenLog.traceSetZenMode(zenModeValue, reason); 206 Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenModeValue); 207 } 208 209 public void updateZenMode() { 210 final int mode = Global.getInt(mContext.getContentResolver(), 211 Global.ZEN_MODE, Global.ZEN_MODE_OFF); 212 if (mode != mZenMode) { 213 ZenLog.traceUpdateZenMode(mZenMode, mode); 214 } 215 mZenMode = mode; 216 final boolean zen = mZenMode != Global.ZEN_MODE_OFF; 217 final String[] exceptionPackages = null; // none (for now) 218 219 // call restrictions 220 final boolean muteCalls = zen && !mConfig.allowCalls; 221 mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_NOTIFICATION_RINGTONE, 222 muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 223 exceptionPackages); 224 mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, USAGE_NOTIFICATION_RINGTONE, 225 muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 226 exceptionPackages); 227 228 // restrict vibrations with no hints 229 mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_UNKNOWN, 230 zen ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 231 exceptionPackages); 232 233 // alarm restrictions 234 final boolean muteAlarms = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; 235 mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_ALARM, 236 muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 237 exceptionPackages); 238 mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, USAGE_ALARM, 239 muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 240 exceptionPackages); 241 242 // force ringer mode into compliance 243 if (mAudioManager != null) { 244 int ringerMode = mAudioManager.getRingerMode(); 245 int forcedRingerMode = -1; 246 if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { 247 if (ringerMode != AudioManager.RINGER_MODE_SILENT) { 248 mPreviousRingerMode = ringerMode; 249 if (DEBUG) Slog.d(TAG, "Silencing ringer"); 250 forcedRingerMode = AudioManager.RINGER_MODE_SILENT; 251 } 252 } else { 253 if (ringerMode == AudioManager.RINGER_MODE_SILENT) { 254 if (DEBUG) Slog.d(TAG, "Unsilencing ringer"); 255 forcedRingerMode = mPreviousRingerMode != -1 ? mPreviousRingerMode 256 : AudioManager.RINGER_MODE_NORMAL; 257 mPreviousRingerMode = -1; 258 } 259 } 260 if (forcedRingerMode != -1) { 261 mAudioManager.setRingerMode(forcedRingerMode); 262 ZenLog.traceSetRingerMode(forcedRingerMode); 263 } 264 } 265 dispatchOnZenModeChanged(); 266 } 267 268 public boolean allowDisable(int what, IBinder token, String pkg) { 269 // TODO(cwren): delete this API before the next release. Bug:15344099 270 boolean allowDisable = true; 271 String reason = null; 272 if (isDefaultPhoneApp(pkg)) { 273 allowDisable = mZenMode == Global.ZEN_MODE_OFF || mConfig.allowCalls; 274 reason = mZenMode == Global.ZEN_MODE_OFF ? "zenOff" : "allowCalls"; 275 } 276 ZenLog.traceAllowDisable(pkg, allowDisable, reason); 277 return allowDisable; 278 } 279 280 public void dump(PrintWriter pw, String prefix) { 281 pw.print(prefix); pw.print("mZenMode="); 282 pw.println(Global.zenModeToString(mZenMode)); 283 pw.print(prefix); pw.print("mConfig="); pw.println(mConfig); 284 pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig); 285 pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode); 286 pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp); 287 } 288 289 public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { 290 final ZenModeConfig config = ZenModeConfig.readXml(parser); 291 if (config != null) { 292 setConfig(config); 293 } 294 } 295 296 public void writeXml(XmlSerializer out) throws IOException { 297 mConfig.writeXml(out); 298 } 299 300 public ZenModeConfig getConfig() { 301 return mConfig; 302 } 303 304 public boolean setConfig(ZenModeConfig config) { 305 if (config == null || !config.isValid()) return false; 306 if (config.equals(mConfig)) return true; 307 ZenLog.traceConfig(mConfig, config); 308 mConfig = config; 309 dispatchOnConfigChanged(); 310 final String val = Integer.toString(mConfig.hashCode()); 311 Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); 312 updateZenMode(); 313 return true; 314 } 315 316 private void handleRingerModeChanged() { 317 if (mAudioManager != null) { 318 // follow ringer mode if necessary 319 final int ringerMode = mAudioManager.getRingerMode(); 320 int newZen = -1; 321 if (ringerMode == AudioManager.RINGER_MODE_SILENT) { 322 if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS) { 323 newZen = Global.ZEN_MODE_NO_INTERRUPTIONS; 324 } 325 } else if ((ringerMode == AudioManager.RINGER_MODE_NORMAL 326 || ringerMode == AudioManager.RINGER_MODE_VIBRATE) 327 && mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { 328 newZen = Global.ZEN_MODE_OFF; 329 } 330 if (newZen != -1) { 331 ZenLog.traceFollowRingerMode(ringerMode, mZenMode, newZen); 332 setZenMode(newZen, "ringerMode"); 333 } 334 } 335 } 336 337 private void dispatchOnConfigChanged() { 338 for (Callback callback : mCallbacks) { 339 callback.onConfigChanged(); 340 } 341 } 342 343 private void dispatchOnZenModeChanged() { 344 for (Callback callback : mCallbacks) { 345 callback.onZenModeChanged(); 346 } 347 } 348 349 private boolean isSystem(NotificationRecord record) { 350 return record.isCategory(Notification.CATEGORY_SYSTEM); 351 } 352 353 private boolean isAlarm(NotificationRecord record) { 354 return record.isCategory(Notification.CATEGORY_ALARM) 355 || record.isCategory(Notification.CATEGORY_EVENT) 356 || record.isAudioStream(AudioManager.STREAM_ALARM) 357 || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM); 358 } 359 360 private boolean isCall(NotificationRecord record) { 361 return isDefaultPhoneApp(record.sbn.getPackageName()) 362 || record.isCategory(Notification.CATEGORY_CALL); 363 } 364 365 private boolean isDefaultPhoneApp(String pkg) { 366 if (mDefaultPhoneApp == null) { 367 final TelecommManager telecomm = 368 (TelecommManager) mContext.getSystemService(Context.TELECOMM_SERVICE); 369 mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null; 370 if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); 371 } 372 return pkg != null && mDefaultPhoneApp != null 373 && pkg.equals(mDefaultPhoneApp.getPackageName()); 374 } 375 376 private boolean isDefaultMessagingApp(NotificationRecord record) { 377 final int userId = record.getUserId(); 378 if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false; 379 final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(), 380 Secure.SMS_DEFAULT_APPLICATION, userId); 381 return Objects.equals(defaultApp, record.sbn.getPackageName()); 382 } 383 384 private boolean isMessage(NotificationRecord record) { 385 return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record); 386 } 387 388 private boolean audienceMatches(NotificationRecord record) { 389 switch (mConfig.allowFrom) { 390 case ZenModeConfig.SOURCE_ANYONE: 391 return true; 392 case ZenModeConfig.SOURCE_CONTACT: 393 return record.getContactAffinity() >= ValidateNotificationPeople.VALID_CONTACT; 394 case ZenModeConfig.SOURCE_STAR: 395 return record.getContactAffinity() >= ValidateNotificationPeople.STARRED_CONTACT; 396 default: 397 Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom); 398 return true; 399 } 400 } 401 402 private final Runnable mRingerModeChanged = new Runnable() { 403 @Override 404 public void run() { 405 handleRingerModeChanged(); 406 } 407 }; 408 409 private class SettingsObserver extends ContentObserver { 410 private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE); 411 412 public SettingsObserver(Handler handler) { 413 super(handler); 414 } 415 416 public void observe() { 417 final ContentResolver resolver = mContext.getContentResolver(); 418 resolver.registerContentObserver(ZEN_MODE, false /*notifyForDescendents*/, this); 419 update(null); 420 } 421 422 @Override 423 public void onChange(boolean selfChange, Uri uri) { 424 update(uri); 425 } 426 427 public void update(Uri uri) { 428 if (ZEN_MODE.equals(uri)) { 429 updateZenMode(); 430 } 431 } 432 } 433 434 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 435 @Override 436 public void onReceive(Context context, Intent intent) { 437 mHandler.post(mRingerModeChanged); 438 } 439 }; 440 441 public static class Callback { 442 void onConfigChanged() {} 443 void onZenModeChanged() {} 444 } 445} 446