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