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