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.ActivityManager; 20import android.app.AlarmManager; 21import android.app.AlarmManager.AlarmClockInfo; 22import android.app.PendingIntent; 23import android.content.BroadcastReceiver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.os.Handler; 28import android.os.Message; 29import android.os.PowerManager; 30import android.os.UserHandle; 31import android.text.format.DateFormat; 32import android.util.Log; 33import android.util.Slog; 34import android.util.TimeUtils; 35 36import com.android.server.notification.NotificationManagerService.DumpFilter; 37 38import java.io.PrintWriter; 39import java.util.ArrayList; 40import java.util.Locale; 41 42/** Helper for tracking updates to the current user's next alarm. */ 43public class NextAlarmTracker { 44 private static final String TAG = "NextAlarmTracker"; 45 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 46 47 private static final String ACTION_TRIGGER = TAG + ".trigger"; 48 private static final String EXTRA_TRIGGER = "trigger"; 49 private static final int REQUEST_CODE = 100; 50 51 private static final long SECONDS = 1000; 52 private static final long MINUTES = 60 * SECONDS; 53 private static final long NEXT_ALARM_UPDATE_DELAY = 1 * SECONDS; // treat clear+set as update 54 private static final long EARLY = 5 * SECONDS; // fire early, ensure alarm stream is unmuted 55 private static final long WAIT_AFTER_INIT = 5 * MINUTES;// for initial alarm re-registration 56 private static final long WAIT_AFTER_BOOT = 20 * SECONDS; // for initial alarm re-registration 57 58 private final Context mContext; 59 private final H mHandler = new H(); 60 private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); 61 62 private long mInit; 63 private boolean mRegistered; 64 private AlarmManager mAlarmManager; 65 private int mCurrentUserId; 66 private long mScheduledAlarmTime; 67 private long mBootCompleted; 68 private PowerManager.WakeLock mWakeLock; 69 70 public NextAlarmTracker(Context context) { 71 mContext = context; 72 } 73 74 public void dump(PrintWriter pw, DumpFilter filter) { 75 pw.println(" NextAlarmTracker:"); 76 pw.print(" len(mCallbacks)="); pw.println(mCallbacks.size()); 77 pw.print(" mRegistered="); pw.println(mRegistered); 78 pw.print(" mInit="); pw.println(mInit); 79 pw.print(" mBootCompleted="); pw.println(mBootCompleted); 80 pw.print(" mCurrentUserId="); pw.println(mCurrentUserId); 81 pw.print(" mScheduledAlarmTime="); pw.println(formatAlarmDebug(mScheduledAlarmTime)); 82 pw.print(" mWakeLock="); pw.println(mWakeLock); 83 } 84 85 public void addCallback(Callback callback) { 86 mCallbacks.add(callback); 87 } 88 89 public void removeCallback(Callback callback) { 90 mCallbacks.remove(callback); 91 } 92 93 public int getCurrentUserId() { 94 return mCurrentUserId; 95 } 96 97 public AlarmClockInfo getNextAlarm() { 98 return mAlarmManager.getNextAlarmClock(mCurrentUserId); 99 } 100 101 public void onUserSwitched() { 102 reset(); 103 } 104 105 public void init() { 106 mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 107 final PowerManager p = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 108 mWakeLock = p.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 109 mInit = System.currentTimeMillis(); 110 reset(); 111 } 112 113 public void reset() { 114 if (mRegistered) { 115 mContext.unregisterReceiver(mReceiver); 116 } 117 mCurrentUserId = ActivityManager.getCurrentUser(); 118 final IntentFilter filter = new IntentFilter(); 119 filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); 120 filter.addAction(ACTION_TRIGGER); 121 filter.addAction(Intent.ACTION_TIME_CHANGED); 122 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 123 filter.addAction(Intent.ACTION_BOOT_COMPLETED); 124 mContext.registerReceiverAsUser(mReceiver, new UserHandle(mCurrentUserId), filter, null, 125 null); 126 mRegistered = true; 127 evaluate(); 128 } 129 130 public void destroy() { 131 if (mRegistered) { 132 mContext.unregisterReceiver(mReceiver); 133 mRegistered = false; 134 } 135 } 136 137 public void evaluate() { 138 mHandler.postEvaluate(0); 139 } 140 141 private void fireEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { 142 for (Callback callback : mCallbacks) { 143 callback.onEvaluate(nextAlarm, wakeupTime, booted); 144 } 145 } 146 147 private void handleEvaluate() { 148 final AlarmClockInfo nextAlarm = mAlarmManager.getNextAlarmClock(mCurrentUserId); 149 final long triggerTime = getEarlyTriggerTime(nextAlarm); 150 final long now = System.currentTimeMillis(); 151 final boolean alarmUpcoming = triggerTime > now; 152 final boolean booted = isDoneWaitingAfterBoot(now); 153 if (DEBUG) Slog.d(TAG, "handleEvaluate nextAlarm=" + formatAlarmDebug(triggerTime) 154 + " alarmUpcoming=" + alarmUpcoming 155 + " booted=" + booted); 156 fireEvaluate(nextAlarm, triggerTime, booted); 157 if (!booted) { 158 // recheck after boot 159 final long recheckTime = (mBootCompleted > 0 ? mBootCompleted : now) + WAIT_AFTER_BOOT; 160 rescheduleAlarm(recheckTime); 161 return; 162 } 163 if (alarmUpcoming) { 164 // wake up just before the next alarm 165 rescheduleAlarm(triggerTime); 166 } 167 } 168 169 public static long getEarlyTriggerTime(AlarmClockInfo alarm) { 170 return alarm != null ? (alarm.getTriggerTime() - EARLY) : 0; 171 } 172 173 private boolean isDoneWaitingAfterBoot(long time) { 174 if (mBootCompleted > 0) return (time - mBootCompleted) > WAIT_AFTER_BOOT; 175 if (mInit > 0) return (time - mInit) > WAIT_AFTER_INIT; 176 return true; 177 } 178 179 public static String formatDuration(long millis) { 180 final StringBuilder sb = new StringBuilder(); 181 TimeUtils.formatDuration(millis, sb); 182 return sb.toString(); 183 } 184 185 public String formatAlarm(AlarmClockInfo alarm) { 186 return alarm != null ? formatAlarm(alarm.getTriggerTime()) : null; 187 } 188 189 private String formatAlarm(long time) { 190 return formatAlarm(time, "Hm", "hma"); 191 } 192 193 private String formatAlarm(long time, String skeleton24, String skeleton12) { 194 final String skeleton = DateFormat.is24HourFormat(mContext) ? skeleton24 : skeleton12; 195 final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); 196 return DateFormat.format(pattern, time).toString(); 197 } 198 199 public String formatAlarmDebug(AlarmClockInfo alarm) { 200 return formatAlarmDebug(alarm != null ? alarm.getTriggerTime() : 0); 201 } 202 203 public String formatAlarmDebug(long time) { 204 if (time <= 0) return Long.toString(time); 205 return String.format("%s (%s)", time, formatAlarm(time, "Hms", "hmsa")); 206 } 207 208 private void rescheduleAlarm(long time) { 209 if (DEBUG) Slog.d(TAG, "rescheduleAlarm " + time); 210 final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 211 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE, 212 new Intent(ACTION_TRIGGER) 213 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) 214 .putExtra(EXTRA_TRIGGER, time), 215 PendingIntent.FLAG_UPDATE_CURRENT); 216 alarms.cancel(pendingIntent); 217 mScheduledAlarmTime = time; 218 if (time > 0) { 219 if (DEBUG) Slog.d(TAG, String.format("Scheduling alarm for %s (in %s)", 220 formatAlarmDebug(time), formatDuration(time - System.currentTimeMillis()))); 221 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); 222 } 223 } 224 225 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 226 @Override 227 public void onReceive(Context context, Intent intent) { 228 final String action = intent.getAction(); 229 if (DEBUG) Slog.d(TAG, "onReceive " + action); 230 long delay = 0; 231 if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) { 232 delay = NEXT_ALARM_UPDATE_DELAY; 233 if (DEBUG) Slog.d(TAG, String.format(" next alarm for user %s: %s", 234 mCurrentUserId, 235 formatAlarmDebug(mAlarmManager.getNextAlarmClock(mCurrentUserId)))); 236 } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { 237 mBootCompleted = System.currentTimeMillis(); 238 } 239 mHandler.postEvaluate(delay); 240 mWakeLock.acquire(delay + 5000); // stay awake during evaluate 241 } 242 }; 243 244 private class H extends Handler { 245 private static final int MSG_EVALUATE = 1; 246 247 public void postEvaluate(long delay) { 248 removeMessages(MSG_EVALUATE); 249 sendEmptyMessageDelayed(MSG_EVALUATE, delay); 250 } 251 252 @Override 253 public void handleMessage(Message msg) { 254 if (msg.what == MSG_EVALUATE) { 255 handleEvaluate(); 256 } 257 } 258 } 259 260 public interface Callback { 261 void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted); 262 } 263} 264