ZenModeHelper.java revision 056c519df1dfb8fdc57daddfdf09bc0e1ffddac4
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.ContentResolver; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.res.Resources; 29import android.content.res.XmlResourceParser; 30import android.database.ContentObserver; 31import android.media.AudioManager; 32import android.net.Uri; 33import android.os.Handler; 34import android.os.IBinder; 35import android.provider.Settings.Global; 36import android.service.notification.ZenModeConfig; 37import android.util.Slog; 38 39import com.android.internal.R; 40 41import libcore.io.IoUtils; 42 43import org.xmlpull.v1.XmlPullParser; 44import org.xmlpull.v1.XmlPullParserException; 45import org.xmlpull.v1.XmlSerializer; 46 47import java.io.IOException; 48import java.io.PrintWriter; 49import java.util.Arrays; 50import java.util.Calendar; 51import java.util.Date; 52import java.util.HashSet; 53import java.util.Set; 54 55/** 56 * NotificationManagerService helper for functionality related to zen mode. 57 */ 58public class ZenModeHelper { 59 private static final String TAG = "ZenModeHelper"; 60 61 private static final String ACTION_ENTER_ZEN = "enter_zen"; 62 private static final int REQUEST_CODE_ENTER = 100; 63 private static final String ACTION_EXIT_ZEN = "exit_zen"; 64 private static final int REQUEST_CODE_EXIT = 101; 65 private static final String EXTRA_TIME = "time"; 66 67 private final Context mContext; 68 private final Handler mHandler; 69 private final SettingsObserver mSettingsObserver; 70 private final AppOpsManager mAppOps; 71 private final ZenModeConfig mDefaultConfig; 72 73 private Callback mCallback; 74 private int mZenMode; 75 private ZenModeConfig mConfig; 76 77 // temporary, until we update apps to provide metadata 78 private static final Set<String> CALL_PACKAGES = new HashSet<String>(Arrays.asList( 79 "com.google.android.dialer", 80 "com.android.phone" 81 )); 82 private static final Set<String> MESSAGE_PACKAGES = new HashSet<String>(Arrays.asList( 83 "com.google.android.talk", 84 "com.android.mms" 85 )); 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 mContext.registerReceiver(new ZenBroadcastReceiver(), filter); 100 } 101 102 public static ZenModeConfig readDefaultConfig(Resources resources) { 103 XmlResourceParser parser = null; 104 try { 105 parser = resources.getXml(R.xml.default_zen_mode_config); 106 while (parser.next() != XmlPullParser.END_DOCUMENT) { 107 final ZenModeConfig config = ZenModeConfig.readXml(parser); 108 if (config != null) return config; 109 } 110 } catch (Exception e) { 111 Slog.w(TAG, "Error reading default zen mode config from resource", e); 112 } finally { 113 IoUtils.closeQuietly(parser); 114 } 115 return new ZenModeConfig(); 116 } 117 118 public void setCallback(Callback callback) { 119 mCallback = callback; 120 } 121 122 public boolean shouldIntercept(String pkg, Notification n) { 123 if (mZenMode != Global.ZEN_MODE_OFF) { 124 if (isCall(pkg, n)) { 125 return !mConfig.allowCalls; 126 } 127 if (isMessage(pkg, n)) { 128 return !mConfig.allowMessages; 129 } 130 return true; 131 } 132 return false; 133 } 134 135 public void updateZenMode() { 136 final int mode = Global.getInt(mContext.getContentResolver(), 137 Global.ZEN_MODE, Global.ZEN_MODE_OFF); 138 if (mode != mZenMode) { 139 Slog.d(TAG, String.format("updateZenMode: %s -> %s", 140 Global.zenModeToString(mZenMode), 141 Global.zenModeToString(mode))); 142 } 143 mZenMode = mode; 144 final boolean zen = mZenMode != Global.ZEN_MODE_OFF; 145 final String[] exceptionPackages = null; // none (for now) 146 147 // call restrictions 148 final boolean muteCalls = zen && !mConfig.allowCalls; 149 mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.STREAM_RING, 150 muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 151 exceptionPackages); 152 mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, AudioManager.STREAM_RING, 153 muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 154 exceptionPackages); 155 156 // restrict vibrations with no hints 157 mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.USE_DEFAULT_STREAM_TYPE, 158 zen ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, 159 exceptionPackages); 160 } 161 162 public boolean allowDisable(int what, IBinder token, String pkg) { 163 if (isCall(pkg, null)) { 164 return mZenMode == Global.ZEN_MODE_OFF || mConfig.allowCalls; 165 } 166 return true; 167 } 168 169 public void dump(PrintWriter pw, String prefix) { 170 pw.print(prefix); pw.print("mZenMode="); 171 pw.println(Global.zenModeToString(mZenMode)); 172 pw.print(prefix); pw.print("mConfig="); pw.println(mConfig); 173 pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig); 174 } 175 176 public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { 177 final ZenModeConfig config = ZenModeConfig.readXml(parser); 178 if (config != null) { 179 setConfig(config); 180 } 181 } 182 183 public void writeXml(XmlSerializer out) throws IOException { 184 mConfig.writeXml(out); 185 } 186 187 public ZenModeConfig getConfig() { 188 return mConfig; 189 } 190 191 public boolean setConfig(ZenModeConfig config) { 192 if (config == null || !config.isValid()) return false; 193 if (config.equals(mConfig)) return true; 194 mConfig = config; 195 Slog.d(TAG, "mConfig=" + mConfig); 196 if (mCallback != null) mCallback.onConfigChanged(); 197 final String val = Integer.toString(mConfig.hashCode()); 198 Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); 199 updateAlarms(); 200 updateZenMode(); 201 return true; 202 } 203 204 private boolean isCall(String pkg, Notification n) { 205 return CALL_PACKAGES.contains(pkg); 206 } 207 208 private boolean isMessage(String pkg, Notification n) { 209 return MESSAGE_PACKAGES.contains(pkg); 210 } 211 212 private void updateAlarms() { 213 updateAlarm(ACTION_ENTER_ZEN, REQUEST_CODE_ENTER, 214 mConfig.sleepStartHour, mConfig.sleepStartMinute); 215 updateAlarm(ACTION_EXIT_ZEN, REQUEST_CODE_EXIT, 216 mConfig.sleepEndHour, mConfig.sleepEndMinute); 217 } 218 219 private void updateAlarm(String action, int requestCode, int hr, int min) { 220 final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 221 final long now = System.currentTimeMillis(); 222 final Calendar c = Calendar.getInstance(); 223 c.setTimeInMillis(now); 224 c.set(Calendar.HOUR_OF_DAY, hr); 225 c.set(Calendar.MINUTE, min); 226 c.set(Calendar.SECOND, 0); 227 c.set(Calendar.MILLISECOND, 0); 228 if (c.getTimeInMillis() <= now) { 229 c.add(Calendar.DATE, 1); 230 } 231 final long time = c.getTimeInMillis(); 232 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode, 233 new Intent(action).putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT); 234 alarms.cancel(pendingIntent); 235 if (mConfig.sleepMode != null) { 236 Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s", 237 action, ts(time), time - now, ts(now))); 238 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); 239 } 240 } 241 242 private static String ts(long time) { 243 return new Date(time) + " (" + time + ")"; 244 } 245 246 public static boolean isWeekend(long time, int offsetDays) { 247 final Calendar c = Calendar.getInstance(); 248 c.setTimeInMillis(time); 249 if (offsetDays != 0) { 250 c.add(Calendar.DATE, offsetDays); 251 } 252 final int day = c.get(Calendar.DAY_OF_WEEK); 253 return day == Calendar.SATURDAY || day == Calendar.SUNDAY; 254 } 255 256 private class SettingsObserver extends ContentObserver { 257 private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE); 258 259 public SettingsObserver(Handler handler) { 260 super(handler); 261 } 262 263 public void observe() { 264 final ContentResolver resolver = mContext.getContentResolver(); 265 resolver.registerContentObserver(ZEN_MODE, false /*notifyForDescendents*/, this); 266 update(null); 267 } 268 269 @Override 270 public void onChange(boolean selfChange, Uri uri) { 271 update(uri); 272 } 273 274 public void update(Uri uri) { 275 if (ZEN_MODE.equals(uri)) { 276 updateZenMode(); 277 } 278 } 279 } 280 281 private class ZenBroadcastReceiver extends BroadcastReceiver { 282 @Override 283 public void onReceive(Context context, Intent intent) { 284 if (ACTION_ENTER_ZEN.equals(intent.getAction())) { 285 setZenMode(intent, 1, Global.ZEN_MODE_ON); 286 } else if (ACTION_EXIT_ZEN.equals(intent.getAction())) { 287 setZenMode(intent, 0, Global.ZEN_MODE_OFF); 288 } 289 } 290 291 private void setZenMode(Intent intent, int wkendOffsetDays, int zenModeValue) { 292 final long schTime = intent.getLongExtra(EXTRA_TIME, 0); 293 final long now = System.currentTimeMillis(); 294 Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", 295 intent.getAction(), ts(schTime), ts(now), now - schTime)); 296 297 final boolean skip = ZenModeConfig.SLEEP_MODE_WEEKNIGHTS.equals(mConfig.sleepMode) && 298 isWeekend(schTime, wkendOffsetDays); 299 300 if (skip) { 301 Slog.d(TAG, "Skipping zen mode update for the weekend"); 302 } else { 303 Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenModeValue); 304 } 305 updateAlarms(); 306 } 307 } 308 309 public interface Callback { 310 void onConfigChanged(); 311 } 312} 313