DowntimeConditionProvider.java revision cc30c8d729c9d846012ac6dd03c1e51262936e8f
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.PendingIntent; 21import android.content.BroadcastReceiver; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.net.Uri; 27import android.provider.Settings.Global; 28import android.service.notification.Condition; 29import android.service.notification.ConditionProviderService; 30import android.service.notification.IConditionProvider; 31import android.service.notification.ZenModeConfig; 32import android.service.notification.ZenModeConfig.DowntimeInfo; 33import android.text.format.DateFormat; 34import android.util.ArraySet; 35import android.util.Log; 36import android.util.Slog; 37 38import com.android.internal.R; 39import com.android.server.notification.NotificationManagerService.DumpFilter; 40 41import java.io.PrintWriter; 42import java.text.SimpleDateFormat; 43import java.util.Calendar; 44import java.util.Date; 45import java.util.Locale; 46import java.util.Objects; 47import java.util.TimeZone; 48 49/** Built-in zen condition provider for managing downtime */ 50public class DowntimeConditionProvider extends ConditionProviderService { 51 private static final String TAG = "DowntimeConditions"; 52 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 53 54 public static final ComponentName COMPONENT = 55 new ComponentName("android", DowntimeConditionProvider.class.getName()); 56 57 private static final String ENTER_ACTION = TAG + ".enter"; 58 private static final int ENTER_CODE = 100; 59 private static final String EXIT_ACTION = TAG + ".exit"; 60 private static final int EXIT_CODE = 101; 61 private static final String EXTRA_TIME = "time"; 62 63 private final Calendar mCalendar = Calendar.getInstance(); 64 private final Context mContext = this; 65 private final ArraySet<Integer> mDays = new ArraySet<Integer>(); 66 67 private boolean mConnected; 68 private int mDowntimeMode; 69 private ZenModeConfig mConfig; 70 private Callback mCallback; 71 72 public DowntimeConditionProvider() { 73 if (DEBUG) Slog.d(TAG, "new DowntimeConditionProvider()"); 74 } 75 76 public void dump(PrintWriter pw, DumpFilter filter) { 77 pw.println(" DowntimeConditionProvider:"); 78 pw.print(" mConnected="); pw.println(mConnected); 79 pw.print(" mDowntimeMode="); pw.println(Global.zenModeToString(mDowntimeMode)); 80 } 81 82 public void attachBase(Context base) { 83 attachBaseContext(base); 84 } 85 86 public IConditionProvider asInterface() { 87 return (IConditionProvider) onBind(null); 88 } 89 90 public void setCallback(Callback callback) { 91 mCallback = callback; 92 } 93 94 @Override 95 public void onConnected() { 96 if (DEBUG) Slog.d(TAG, "onConnected"); 97 mConnected = true; 98 final IntentFilter filter = new IntentFilter(); 99 filter.addAction(ENTER_ACTION); 100 filter.addAction(EXIT_ACTION); 101 filter.addAction(Intent.ACTION_TIME_CHANGED); 102 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 103 mContext.registerReceiver(mReceiver, filter); 104 init(); 105 } 106 107 @Override 108 public void onDestroy() { 109 if (DEBUG) Slog.d(TAG, "onDestroy"); 110 mConnected = false; 111 } 112 113 @Override 114 public void onRequestConditions(int relevance) { 115 if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance); 116 if ((relevance & Condition.FLAG_RELEVANT_NOW) != 0) { 117 if (isInDowntime() && mConfig != null) { 118 notifyCondition(createCondition(mConfig.toDowntimeInfo(), Condition.STATE_TRUE)); 119 } 120 } 121 } 122 123 @Override 124 public void onSubscribe(Uri conditionId) { 125 if (DEBUG) Slog.d(TAG, "onSubscribe conditionId=" + conditionId); 126 final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId); 127 if (downtime != null && mConfig != null) { 128 final int state = mConfig.toDowntimeInfo().equals(downtime) && isInDowntime() 129 ? Condition.STATE_TRUE : Condition.STATE_FALSE; 130 if (DEBUG) Slog.d(TAG, "notify condition state: " + Condition.stateToString(state)); 131 notifyCondition(createCondition(downtime, state)); 132 } 133 } 134 135 @Override 136 public void onUnsubscribe(Uri conditionId) { 137 if (DEBUG) Slog.d(TAG, "onUnsubscribe conditionId=" + conditionId); 138 } 139 140 public void setConfig(ZenModeConfig config) { 141 if (Objects.equals(mConfig, config)) return; 142 if (DEBUG) Slog.d(TAG, "setConfig"); 143 mConfig = config; 144 if (mConnected) { 145 init(); 146 } 147 } 148 149 public boolean isInDowntime() { 150 return mDowntimeMode != Global.ZEN_MODE_OFF; 151 } 152 153 public Condition createCondition(DowntimeInfo downtime, int state) { 154 if (downtime == null) return null; 155 final Uri id = ZenModeConfig.toDowntimeConditionId(downtime); 156 final String skeleton = DateFormat.is24HourFormat(mContext) ? "Hm" : "hma"; 157 final Locale locale = Locale.getDefault(); 158 final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton); 159 final long time = getTime(System.currentTimeMillis(), downtime.endHour, downtime.endMinute); 160 final String formatted = new SimpleDateFormat(pattern, locale).format(new Date(time)); 161 final String summary = mContext.getString(R.string.downtime_condition_summary, formatted); 162 final String line1 = mContext.getString(R.string.downtime_condition_line_one); 163 return new Condition(id, summary, line1, formatted, 0, state, Condition.FLAG_RELEVANT_NOW); 164 } 165 166 public boolean isDowntimeCondition(Condition condition) { 167 return condition != null && ZenModeConfig.isValidDowntimeConditionId(condition.id); 168 } 169 170 private void init() { 171 updateDays(); 172 reevaluateDowntime(); 173 updateAlarms(); 174 } 175 176 private void updateDays() { 177 mDays.clear(); 178 if (mConfig != null) { 179 final int[] days = ZenModeConfig.tryParseDays(mConfig.sleepMode); 180 for (int i = 0; days != null && i < days.length; i++) { 181 mDays.add(days[i]); 182 } 183 } 184 } 185 186 private int computeDowntimeMode(long time) { 187 if (mConfig == null || mDays.size() == 0) return Global.ZEN_MODE_OFF; 188 final long start = getTime(time, mConfig.sleepStartHour, mConfig.sleepStartMinute); 189 long end = getTime(time, mConfig.sleepEndHour, mConfig.sleepEndMinute); 190 if (start == end) return Global.ZEN_MODE_OFF; 191 if (end < start) { 192 end = addDays(end, 1); 193 } 194 final boolean inDowntime = isInDowntime(-1, time, start, end) 195 || isInDowntime(0, time, start, end); 196 return inDowntime ? (mConfig.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS 197 : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) : Global.ZEN_MODE_OFF; 198 } 199 200 private boolean isInDowntime(int daysOffset, long time, long start, long end) { 201 final int n = Calendar.SATURDAY; 202 final int day = ((getDayOfWeek(time) - 1) + (daysOffset % n) + n) % n + 1; 203 start = addDays(start, daysOffset); 204 end = addDays(end, daysOffset); 205 return mDays.contains(day) && time >= start && time < end; 206 } 207 208 private void reevaluateDowntime() { 209 final int downtimeMode = computeDowntimeMode(System.currentTimeMillis()); 210 if (DEBUG) Slog.d(TAG, "downtimeMode=" + downtimeMode); 211 if (downtimeMode == mDowntimeMode) return; 212 mDowntimeMode = downtimeMode; 213 Slog.i(TAG, (isInDowntime() ? "Entering" : "Exiting" ) + " downtime"); 214 ZenLog.traceDowntime(mDowntimeMode, getDayOfWeek(System.currentTimeMillis()), mDays); 215 fireDowntimeChanged(); 216 } 217 218 private void fireDowntimeChanged() { 219 if (mCallback != null) { 220 mCallback.onDowntimeChanged(mDowntimeMode); 221 } 222 } 223 224 private void updateAlarms() { 225 if (mConfig == null) return; 226 updateAlarm(ENTER_ACTION, ENTER_CODE, mConfig.sleepStartHour, mConfig.sleepStartMinute); 227 updateAlarm(EXIT_ACTION, EXIT_CODE, mConfig.sleepEndHour, mConfig.sleepEndMinute); 228 } 229 230 private int getDayOfWeek(long time) { 231 mCalendar.setTimeInMillis(time); 232 return mCalendar.get(Calendar.DAY_OF_WEEK); 233 } 234 235 private long getTime(long millis, int hour, int min) { 236 mCalendar.setTimeInMillis(millis); 237 mCalendar.set(Calendar.HOUR_OF_DAY, hour); 238 mCalendar.set(Calendar.MINUTE, min); 239 mCalendar.set(Calendar.SECOND, 0); 240 mCalendar.set(Calendar.MILLISECOND, 0); 241 return mCalendar.getTimeInMillis(); 242 } 243 244 private long addDays(long time, int days) { 245 mCalendar.setTimeInMillis(time); 246 mCalendar.add(Calendar.DATE, days); 247 return mCalendar.getTimeInMillis(); 248 } 249 250 private void updateAlarm(String action, int requestCode, int hr, int min) { 251 final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 252 final long now = System.currentTimeMillis(); 253 mCalendar.setTimeInMillis(now); 254 mCalendar.set(Calendar.HOUR_OF_DAY, hr); 255 mCalendar.set(Calendar.MINUTE, min); 256 mCalendar.set(Calendar.SECOND, 0); 257 mCalendar.set(Calendar.MILLISECOND, 0); 258 long time = mCalendar.getTimeInMillis(); 259 if (time <= now) { 260 time = addDays(time, 1); 261 } 262 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode, 263 new Intent(action) 264 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) 265 .putExtra(EXTRA_TIME, time), 266 PendingIntent.FLAG_UPDATE_CURRENT); 267 alarms.cancel(pendingIntent); 268 if (mConfig.sleepMode != null) { 269 if (DEBUG) Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s", 270 action, ts(time), time - now, ts(now))); 271 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); 272 } 273 } 274 275 private static String ts(long time) { 276 return new Date(time) + " (" + time + ")"; 277 } 278 279 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 280 @Override 281 public void onReceive(Context context, Intent intent) { 282 final String action = intent.getAction(); 283 final long now = System.currentTimeMillis(); 284 if (ENTER_ACTION.equals(action) || EXIT_ACTION.equals(action)) { 285 final long schTime = intent.getLongExtra(EXTRA_TIME, 0); 286 if (DEBUG) Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", 287 action, ts(schTime), ts(now), now - schTime)); 288 } else if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { 289 if (DEBUG) Slog.d(TAG, "timezone changed to " + TimeZone.getDefault()); 290 mCalendar.setTimeZone(TimeZone.getDefault()); 291 } else { 292 if (DEBUG) Slog.d(TAG, action + " fired at " + now); 293 } 294 reevaluateDowntime(); 295 updateAlarms(); 296 } 297 }; 298 299 public interface Callback { 300 void onDowntimeChanged(int downtimeMode); 301 } 302} 303