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