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