ZenModeHelper.java revision d8afe3c41e65a8f6ff4283c124ba250c92cf50c6
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.AlarmManager; 24import android.app.AppOpsManager; 25import android.app.Notification; 26import android.app.PendingIntent; 27import android.content.BroadcastReceiver; 28import android.content.ComponentName; 29import android.content.ContentResolver; 30import android.content.Context; 31import android.content.Intent; 32import android.content.IntentFilter; 33import android.content.res.Resources; 34import android.content.res.XmlResourceParser; 35import android.database.ContentObserver; 36import android.media.AudioAttributes; 37import android.media.AudioManager; 38import android.net.Uri; 39import android.os.Handler; 40import android.os.IBinder; 41import android.os.UserHandle; 42import android.provider.Settings.Global; 43import android.provider.Settings.Secure; 44import android.service.notification.NotificationListenerService; 45import android.service.notification.ZenModeConfig; 46import android.telecomm.TelecommManager; 47import android.util.Slog; 48 49import com.android.internal.R; 50 51import libcore.io.IoUtils; 52 53import org.xmlpull.v1.XmlPullParser; 54import org.xmlpull.v1.XmlPullParserException; 55import org.xmlpull.v1.XmlSerializer; 56 57import java.io.IOException; 58import java.io.PrintWriter; 59import java.util.ArrayList; 60import java.util.Calendar; 61import java.util.Date; 62import java.util.Objects; 63 64/** 65 * NotificationManagerService helper for functionality related to zen mode. 66 */ 67public class ZenModeHelper { 68 private static final String TAG = "ZenModeHelper"; 69 70 private static final String ACTION_ENTER_ZEN = "enter_zen"; 71 private static final int REQUEST_CODE_ENTER = 100; 72 private static final String ACTION_EXIT_ZEN = "exit_zen"; 73 private static final int REQUEST_CODE_EXIT = 101; 74 private static final String EXTRA_TIME = "time"; 75 76 private final Context mContext; 77 private final Handler mHandler; 78 private final SettingsObserver mSettingsObserver; 79 private final AppOpsManager mAppOps; 80 private final ZenModeConfig mDefaultConfig; 81 private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); 82 83 private ComponentName mDefaultPhoneApp; 84 private int mZenMode; 85 private ZenModeConfig mConfig; 86 private AudioManager mAudioManager; 87 private int mPreviousRingerMode = -1; 88 89 public ZenModeHelper(Context context, Handler handler) { 90 mContext = context; 91 mHandler = handler; 92 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 93 mDefaultConfig = readDefaultConfig(context.getResources()); 94 mConfig = mDefaultConfig; 95 mSettingsObserver = new SettingsObserver(mHandler); 96 mSettingsObserver.observe(); 97 98 final IntentFilter filter = new IntentFilter(); 99 filter.addAction(ACTION_ENTER_ZEN); 100 filter.addAction(ACTION_EXIT_ZEN); 101 filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); 102 mContext.registerReceiver(new ZenBroadcastReceiver(), filter); 103 } 104 105 public static ZenModeConfig readDefaultConfig(Resources resources) { 106 XmlResourceParser parser = null; 107 try { 108 parser = resources.getXml(R.xml.default_zen_mode_config); 109 while (parser.next() != XmlPullParser.END_DOCUMENT) { 110 final ZenModeConfig config = ZenModeConfig.readXml(parser); 111 if (config != null) return config; 112 } 113 } catch (Exception e) { 114 Slog.w(TAG, "Error reading default zen mode config from resource", e); 115 } finally { 116 IoUtils.closeQuietly(parser); 117 } 118 return new ZenModeConfig(); 119 } 120 121 public void addCallback(Callback callback) { 122 mCallbacks.add(callback); 123 } 124 125 public void setAudioManager(AudioManager audioManager) { 126 mAudioManager = audioManager; 127 } 128 129 public int getZenModeListenerHint() { 130 switch(mZenMode) { 131 case Global.ZEN_MODE_OFF: 132 return NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_ALL; 133 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: 134 return NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_PRIORITY; 135 case Global.ZEN_MODE_NO_INTERRUPTIONS: 136 return NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_NONE; 137 default: 138 return 0; 139 } 140 } 141 142 private static int zenFromListenerHint(int hints, int defValue) { 143 final int level = hints & NotificationListenerService.HOST_INTERRUPTION_LEVEL_MASK; 144 switch(level) { 145 case NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_ALL: 146 return Global.ZEN_MODE_OFF; 147 case NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_PRIORITY: 148 return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; 149 case NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_NONE: 150 return Global.ZEN_MODE_NO_INTERRUPTIONS; 151 default: 152 return defValue; 153 } 154 } 155 156 public void requestFromListener(int hints) { 157 final int newZen = zenFromListenerHint(hints, -1); 158 if (newZen != -1) { 159 setZenMode(newZen); 160 } 161 } 162 163 public boolean shouldIntercept(NotificationRecord record) { 164 if (mZenMode != Global.ZEN_MODE_OFF) { 165 if (isSystem(record)) { 166 return false; 167 } 168 if (isAlarm(record)) { 169 if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { 170 ZenLog.traceIntercepted(record, "alarm"); 171 return true; 172 } 173 return false; 174 } 175 // audience has veto power over all following rules 176 if (!audienceMatches(record)) { 177 ZenLog.traceIntercepted(record, "!audienceMatches"); 178 return true; 179 } 180 if (isCall(record)) { 181 if (!mConfig.allowCalls) { 182 ZenLog.traceIntercepted(record, "!allowCalls"); 183 return true; 184 } 185 return false; 186 } 187 if (isMessage(record)) { 188 if (!mConfig.allowMessages) { 189 ZenLog.traceIntercepted(record, "!allowMessages"); 190 return true; 191 } 192 return false; 193 } 194 ZenLog.traceIntercepted(record, "!allowed"); 195 return true; 196 } 197 return false; 198 } 199 200 public int getZenMode() { 201 return mZenMode; 202 } 203 204 public void setZenMode(int zenModeValue) { 205 Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenModeValue); 206 } 207 208 public void updateZenMode() { 209 final int mode = Global.getInt(mContext.getContentResolver(), 210 Global.ZEN_MODE, Global.ZEN_MODE_OFF); 211 if (mode != mZenMode) { 212 Slog.d(TAG, String.format("updateZenMode: %s -> %s", 213 Global.zenModeToString(mZenMode), 214 Global.zenModeToString(mode))); 215 ZenLog.traceUpdateZenMode(mZenMode, mode); 216 } 217 mZenMode = mode; 218 final boolean zen = mZenMode != Global.ZEN_MODE_OFF; 219 final String[] exceptionPackages = null; // none (for now) 220 221 // call restrictions 222 final boolean muteCalls = zen && !mConfig.allowCalls; 223 mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_NOTIFICATION_RINGTONE, 224 muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 225 exceptionPackages); 226 mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, USAGE_NOTIFICATION_RINGTONE, 227 muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 228 exceptionPackages); 229 230 // restrict vibrations with no hints 231 mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_UNKNOWN, 232 zen ? 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 Slog.d(TAG, "Silencing ringer"); 252 forcedRingerMode = AudioManager.RINGER_MODE_SILENT; 253 } 254 } else { 255 if (ringerMode == AudioManager.RINGER_MODE_SILENT) { 256 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); 264 ZenLog.traceSetRingerMode(forcedRingerMode); 265 } 266 } 267 dispatchOnZenModeChanged(); 268 } 269 270 public boolean allowDisable(int what, IBinder token, String pkg) { 271 // TODO(cwren): delete this API before the next release. Bug:15344099 272 boolean allowDisable = true; 273 String reason = null; 274 if (isDefaultPhoneApp(pkg)) { 275 allowDisable = mZenMode == Global.ZEN_MODE_OFF || mConfig.allowCalls; 276 reason = mZenMode == Global.ZEN_MODE_OFF ? "zenOff" : "allowCalls"; 277 } 278 ZenLog.traceAllowDisable(pkg, allowDisable, reason); 279 return allowDisable; 280 } 281 282 public void dump(PrintWriter pw, String prefix) { 283 pw.print(prefix); pw.print("mZenMode="); 284 pw.println(Global.zenModeToString(mZenMode)); 285 pw.print(prefix); pw.print("mConfig="); pw.println(mConfig); 286 pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig); 287 pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode); 288 pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp); 289 } 290 291 public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { 292 final ZenModeConfig config = ZenModeConfig.readXml(parser); 293 if (config != null) { 294 setConfig(config); 295 } 296 } 297 298 public void writeXml(XmlSerializer out) throws IOException { 299 mConfig.writeXml(out); 300 } 301 302 public ZenModeConfig getConfig() { 303 return mConfig; 304 } 305 306 public boolean setConfig(ZenModeConfig config) { 307 if (config == null || !config.isValid()) return false; 308 if (config.equals(mConfig)) return true; 309 ZenLog.traceConfig(mConfig, config); 310 mConfig = config; 311 dispatchOnConfigChanged(); 312 final String val = Integer.toString(mConfig.hashCode()); 313 Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); 314 updateAlarms(); 315 updateZenMode(); 316 return true; 317 } 318 319 private void handleRingerModeChanged() { 320 if (mAudioManager != null) { 321 // follow ringer mode if necessary 322 final int ringerMode = mAudioManager.getRingerMode(); 323 int newZen = -1; 324 if (ringerMode == AudioManager.RINGER_MODE_SILENT) { 325 if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS) { 326 newZen = Global.ZEN_MODE_NO_INTERRUPTIONS; 327 } 328 } else if ((ringerMode == AudioManager.RINGER_MODE_NORMAL 329 || ringerMode == AudioManager.RINGER_MODE_VIBRATE) 330 && mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { 331 newZen = Global.ZEN_MODE_OFF; 332 } 333 if (newZen != -1) { 334 ZenLog.traceFollowRingerMode(ringerMode, mZenMode, newZen); 335 setZenMode(newZen); 336 } 337 } 338 } 339 340 private void dispatchOnConfigChanged() { 341 for (Callback callback : mCallbacks) { 342 callback.onConfigChanged(); 343 } 344 } 345 346 private void dispatchOnZenModeChanged() { 347 for (Callback callback : mCallbacks) { 348 callback.onZenModeChanged(); 349 } 350 } 351 352 private boolean isSystem(NotificationRecord record) { 353 return record.isCategory(Notification.CATEGORY_SYSTEM); 354 } 355 356 private boolean isAlarm(NotificationRecord record) { 357 return record.isCategory(Notification.CATEGORY_ALARM) 358 || record.isCategory(Notification.CATEGORY_EVENT) 359 || record.isAudioStream(AudioManager.STREAM_ALARM) 360 || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM); 361 } 362 363 private boolean isCall(NotificationRecord record) { 364 return isDefaultPhoneApp(record.sbn.getPackageName()) 365 || record.isCategory(Notification.CATEGORY_CALL); 366 } 367 368 private boolean isDefaultPhoneApp(String pkg) { 369 if (mDefaultPhoneApp == null) { 370 final TelecommManager telecomm = 371 (TelecommManager) mContext.getSystemService(Context.TELECOMM_SERVICE); 372 mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null; 373 Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); 374 } 375 return pkg != null && mDefaultPhoneApp != null 376 && pkg.equals(mDefaultPhoneApp.getPackageName()); 377 } 378 379 private boolean isDefaultMessagingApp(NotificationRecord record) { 380 final int userId = record.getUserId(); 381 if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false; 382 final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(), 383 Secure.SMS_DEFAULT_APPLICATION, userId); 384 return Objects.equals(defaultApp, record.sbn.getPackageName()); 385 } 386 387 private boolean isMessage(NotificationRecord record) { 388 return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record); 389 } 390 391 private boolean audienceMatches(NotificationRecord record) { 392 switch (mConfig.allowFrom) { 393 case ZenModeConfig.SOURCE_ANYONE: 394 return true; 395 case ZenModeConfig.SOURCE_CONTACT: 396 return record.getContactAffinity() >= ValidateNotificationPeople.VALID_CONTACT; 397 case ZenModeConfig.SOURCE_STAR: 398 return record.getContactAffinity() >= ValidateNotificationPeople.STARRED_CONTACT; 399 default: 400 Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom); 401 return true; 402 } 403 } 404 405 private void updateAlarms() { 406 updateAlarm(ACTION_ENTER_ZEN, REQUEST_CODE_ENTER, 407 mConfig.sleepStartHour, mConfig.sleepStartMinute); 408 updateAlarm(ACTION_EXIT_ZEN, REQUEST_CODE_EXIT, 409 mConfig.sleepEndHour, mConfig.sleepEndMinute); 410 } 411 412 private void updateAlarm(String action, int requestCode, int hr, int min) { 413 final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 414 final long now = System.currentTimeMillis(); 415 final Calendar c = Calendar.getInstance(); 416 c.setTimeInMillis(now); 417 c.set(Calendar.HOUR_OF_DAY, hr); 418 c.set(Calendar.MINUTE, min); 419 c.set(Calendar.SECOND, 0); 420 c.set(Calendar.MILLISECOND, 0); 421 if (c.getTimeInMillis() <= now) { 422 c.add(Calendar.DATE, 1); 423 } 424 final long time = c.getTimeInMillis(); 425 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode, 426 new Intent(action).putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT); 427 alarms.cancel(pendingIntent); 428 if (mConfig.sleepMode != null) { 429 Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s", 430 action, ts(time), time - now, ts(now))); 431 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); 432 } 433 } 434 435 private static String ts(long time) { 436 return new Date(time) + " (" + time + ")"; 437 } 438 439 private final Runnable mRingerModeChanged = new Runnable() { 440 @Override 441 public void run() { 442 handleRingerModeChanged(); 443 } 444 }; 445 446 private class SettingsObserver extends ContentObserver { 447 private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE); 448 449 public SettingsObserver(Handler handler) { 450 super(handler); 451 } 452 453 public void observe() { 454 final ContentResolver resolver = mContext.getContentResolver(); 455 resolver.registerContentObserver(ZEN_MODE, false /*notifyForDescendents*/, this); 456 update(null); 457 } 458 459 @Override 460 public void onChange(boolean selfChange, Uri uri) { 461 update(uri); 462 } 463 464 public void update(Uri uri) { 465 if (ZEN_MODE.equals(uri)) { 466 updateZenMode(); 467 } 468 } 469 } 470 471 private class ZenBroadcastReceiver extends BroadcastReceiver { 472 private final Calendar mCalendar = Calendar.getInstance(); 473 474 @Override 475 public void onReceive(Context context, Intent intent) { 476 if (ACTION_ENTER_ZEN.equals(intent.getAction())) { 477 setZenMode(intent, Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); 478 } else if (ACTION_EXIT_ZEN.equals(intent.getAction())) { 479 setZenMode(intent, Global.ZEN_MODE_OFF); 480 } else if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(intent.getAction())) { 481 mHandler.post(mRingerModeChanged); 482 } 483 } 484 485 private void setZenMode(Intent intent, int zenModeValue) { 486 final long schTime = intent.getLongExtra(EXTRA_TIME, 0); 487 final long now = System.currentTimeMillis(); 488 Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", 489 intent.getAction(), ts(schTime), ts(now), now - schTime)); 490 491 final int[] days = ZenModeConfig.tryParseDays(mConfig.sleepMode); 492 boolean enter = false; 493 final int day = getDayOfWeek(schTime); 494 if (days != null) { 495 for (int i = 0; i < days.length; i++) { 496 if (days[i] == day) { 497 enter = true; 498 ZenModeHelper.this.setZenMode(zenModeValue); 499 break; 500 } 501 } 502 } 503 ZenLog.traceDowntime(enter, day, days); 504 updateAlarms(); 505 } 506 507 private int getDayOfWeek(long time) { 508 mCalendar.setTimeInMillis(time); 509 return mCalendar.get(Calendar.DAY_OF_WEEK); 510 } 511 } 512 513 public static class Callback { 514 void onConfigChanged() {} 515 void onZenModeChanged() {} 516 } 517} 518