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