DowntimeConditionProvider.java revision d208c79b3eb86042838700b5191922429571e80c
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 day = ((getDayOfWeek(time) + daysOffset - 1) % Calendar.SATURDAY) + 1; 197 start = addDays(start, daysOffset); 198 end = addDays(end, daysOffset); 199 return mDays.contains(day) && time >= start && time < end; 200 } 201 202 private void reevaluateDowntime() { 203 final boolean inDowntime = isInDowntime(System.currentTimeMillis()); 204 if (DEBUG) Slog.d(TAG, "inDowntime=" + inDowntime); 205 if (inDowntime == mInDowntime) return; 206 Slog.i(TAG, (inDowntime ? "Entering" : "Exiting" ) + " downtime"); 207 mInDowntime = inDowntime; 208 ZenLog.traceDowntime(mInDowntime, getDayOfWeek(System.currentTimeMillis()), mDays); 209 fireDowntimeChanged(); 210 } 211 212 private void fireDowntimeChanged() { 213 if (mCallback != null) { 214 mCallback.onDowntimeChanged(mInDowntime); 215 } 216 } 217 218 private void updateAlarms() { 219 if (mConfig == null) return; 220 updateAlarm(ENTER_ACTION, ENTER_CODE, mConfig.sleepStartHour, mConfig.sleepStartMinute); 221 updateAlarm(EXIT_ACTION, EXIT_CODE, mConfig.sleepEndHour, mConfig.sleepEndMinute); 222 } 223 224 private int getDayOfWeek(long time) { 225 mCalendar.setTimeInMillis(time); 226 return mCalendar.get(Calendar.DAY_OF_WEEK); 227 } 228 229 private long getTime(long millis, int hour, int min) { 230 mCalendar.setTimeInMillis(millis); 231 mCalendar.set(Calendar.HOUR_OF_DAY, hour); 232 mCalendar.set(Calendar.MINUTE, min); 233 mCalendar.set(Calendar.SECOND, 0); 234 mCalendar.set(Calendar.MILLISECOND, 0); 235 return mCalendar.getTimeInMillis(); 236 } 237 238 private long addDays(long time, int days) { 239 mCalendar.setTimeInMillis(time); 240 mCalendar.add(Calendar.DATE, days); 241 return mCalendar.getTimeInMillis(); 242 } 243 244 private void updateAlarm(String action, int requestCode, int hr, int min) { 245 final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 246 final long now = System.currentTimeMillis(); 247 mCalendar.setTimeInMillis(now); 248 mCalendar.set(Calendar.HOUR_OF_DAY, hr); 249 mCalendar.set(Calendar.MINUTE, min); 250 mCalendar.set(Calendar.SECOND, 0); 251 mCalendar.set(Calendar.MILLISECOND, 0); 252 long time = mCalendar.getTimeInMillis(); 253 if (time <= now) { 254 time = addDays(time, 1); 255 } 256 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode, 257 new Intent(action).putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT); 258 alarms.cancel(pendingIntent); 259 if (mConfig.sleepMode != null) { 260 if (DEBUG) Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s", 261 action, ts(time), time - now, ts(now))); 262 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); 263 } 264 } 265 266 private static String ts(long time) { 267 return new Date(time) + " (" + time + ")"; 268 } 269 270 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 271 @Override 272 public void onReceive(Context context, Intent intent) { 273 final String action = intent.getAction(); 274 final long now = System.currentTimeMillis(); 275 if (ENTER_ACTION.equals(action) || EXIT_ACTION.equals(action)) { 276 final long schTime = intent.getLongExtra(EXTRA_TIME, 0); 277 if (DEBUG) Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", 278 action, ts(schTime), ts(now), now - schTime)); 279 } else if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { 280 if (DEBUG) Slog.d(TAG, "timezone changed to " + TimeZone.getDefault()); 281 mCalendar.setTimeZone(TimeZone.getDefault()); 282 } else { 283 if (DEBUG) Slog.d(TAG, action + " fired at " + now); 284 } 285 reevaluateDowntime(); 286 updateAlarms(); 287 } 288 }; 289 290 public interface Callback { 291 void onDowntimeChanged(boolean inDowntime); 292 } 293} 294