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