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