diff options
author | 2014-11-10 19:53:14 +0000 | |
---|---|---|
committer | 2014-11-10 19:53:14 +0000 | |
commit | 551f0778c39b030ebf1fd51c882c152b3e7ac320 (patch) | |
tree | 22881e3b5c65b82e64b725854709c098d2de5224 | |
parent | 2ace3d4803fff437387d36f27dab05f1c159b710 (diff) | |
parent | 7936ca36d9dfd620df0bfead6c79b5eddc761c20 (diff) |
Merge "Zen: Pull next-alarm tracking out into separate helper." into lmp-mr1-dev
automerge: 7936ca3
* commit '7936ca36d9dfd620df0bfead6c79b5eddc761c20':
Zen: Pull next-alarm tracking out into separate helper.
4 files changed, 340 insertions, 205 deletions
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index 3ff9ff4b37d1..a1085d32580b 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -51,6 +51,7 @@ public class ConditionProviders extends ManagedServices { = new ArrayMap<IBinder, IConditionListener>(); private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>(); private final CountdownConditionProvider mCountdown = new CountdownConditionProvider(); + private final NextAlarmTracker mNextAlarmTracker; private final DowntimeConditionProvider mDowntime = new DowntimeConditionProvider(); private final NextAlarmConditionProvider mNextAlarm = new NextAlarmConditionProvider(); @@ -63,6 +64,7 @@ public class ConditionProviders extends ManagedServices { mZenModeHelper = zenModeHelper; mZenModeHelper.addCallback(new ZenModeHelperCallback()); loadZenConfig(); + mNextAlarmTracker = new NextAlarmTracker(context); } @Override @@ -101,6 +103,7 @@ public class ConditionProviders extends ManagedServices { mCountdown.dump(pw, filter); mDowntime.dump(pw, filter); mNextAlarm.dump(pw, filter); + mNextAlarmTracker.dump(pw, filter); } @Override @@ -111,6 +114,7 @@ public class ConditionProviders extends ManagedServices { @Override public void onBootPhaseAppsCanStart() { super.onBootPhaseAppsCanStart(); + mNextAlarmTracker.init(); mCountdown.attachBase(mContext); registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT, UserHandle.USER_OWNER); @@ -121,20 +125,13 @@ public class ConditionProviders extends ManagedServices { mNextAlarm.attachBase(mContext); registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT, UserHandle.USER_OWNER); - mNextAlarm.setCallback(new NextAlarmConditionProvider.Callback() { - @Override - public boolean isInDowntime() { - return mDowntime.isInDowntime(); - } - }); + mNextAlarm.setCallback(new NextAlarmCallback()); } @Override public void onUserSwitched() { super.onUserSwitched(); - if (mNextAlarm != null) { - mNextAlarm.onUserSwitched(); - } + mNextAlarmTracker.onUserSwitched(); } @Override @@ -572,6 +569,23 @@ public class ConditionProviders extends ManagedServices { mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "downtimeExit"); } } + + @Override + public NextAlarmTracker getNextAlarmTracker() { + return mNextAlarmTracker; + } + } + + private class NextAlarmCallback implements NextAlarmConditionProvider.Callback { + @Override + public boolean isInDowntime() { + return mDowntime.isInDowntime(); + } + + @Override + public NextAlarmTracker getNextAlarmTracker() { + return mNextAlarmTracker; + } } private static class ConditionRecord { diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java index 881c9ad7fb1a..24fd155ee234 100644 --- a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java +++ b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java @@ -298,5 +298,6 @@ public class DowntimeConditionProvider extends ConditionProviderService { public interface Callback { void onDowntimeChanged(int downtimeMode); + NextAlarmTracker getNextAlarmTracker(); } } diff --git a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java b/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java index 4a29573a1859..35bbaa053465 100644 --- a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java +++ b/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java @@ -16,34 +16,23 @@ package com.android.server.notification; -import android.app.ActivityManager; import android.app.AlarmManager; -import android.app.PendingIntent; import android.app.AlarmManager.AlarmClockInfo; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.net.Uri; -import android.os.Handler; -import android.os.Message; -import android.os.PowerManager; -import android.os.UserHandle; import android.service.notification.Condition; import android.service.notification.ConditionProviderService; import android.service.notification.IConditionProvider; import android.service.notification.ZenModeConfig; -import android.util.TimeUtils; -import android.text.format.DateFormat; import android.util.Log; import android.util.Slog; +import android.util.TimeUtils; import com.android.internal.R; import com.android.server.notification.NotificationManagerService.DumpFilter; import java.io.PrintWriter; -import java.util.Locale; /** * Built-in zen condition provider for alarm-clock-based conditions. @@ -62,33 +51,21 @@ public class NextAlarmConditionProvider extends ConditionProviderService { private static final String TAG = "NextAlarmConditions"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final String ACTION_TRIGGER = TAG + ".trigger"; - private static final String EXTRA_TRIGGER = "trigger"; - private static final int REQUEST_CODE = 100; private static final long SECONDS = 1000; private static final long MINUTES = 60 * SECONDS; private static final long HOURS = 60 * MINUTES; - private static final long NEXT_ALARM_UPDATE_DELAY = 1 * SECONDS; // treat clear+set as update - private static final long EARLY = 5 * SECONDS; // fire early, ensure alarm stream is unmuted - private static final long WAIT_AFTER_CONNECT = 5 * MINUTES;// for initial alarm re-registration - private static final long WAIT_AFTER_BOOT = 20 * SECONDS; // for initial alarm re-registration + private static final String NEXT_ALARM_PATH = "next_alarm"; public static final ComponentName COMPONENT = new ComponentName("android", NextAlarmConditionProvider.class.getName()); private final Context mContext = this; - private final H mHandler = new H(); - private long mConnected; - private boolean mRegistered; - private AlarmManager mAlarmManager; - private int mCurrentUserId; + private NextAlarmTracker mTracker; + private boolean mConnected; private long mLookaheadThreshold; - private long mScheduledAlarmTime; private Callback mCallback; private Uri mCurrentSubscription; - private PowerManager.WakeLock mWakeLock; - private long mBootCompleted; public NextAlarmConditionProvider() { if (DEBUG) Slog.d(TAG, "new NextAlarmConditionProvider()"); @@ -97,14 +74,9 @@ public class NextAlarmConditionProvider extends ConditionProviderService { public void dump(PrintWriter pw, DumpFilter filter) { pw.println(" NextAlarmConditionProvider:"); pw.print(" mConnected="); pw.println(mConnected); - pw.print(" mBootCompleted="); pw.println(mBootCompleted); - pw.print(" mRegistered="); pw.println(mRegistered); - pw.print(" mCurrentUserId="); pw.println(mCurrentUserId); - pw.print(" mScheduledAlarmTime="); pw.println(formatAlarmDebug(mScheduledAlarmTime)); pw.print(" mLookaheadThreshold="); pw.print(mLookaheadThreshold); pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")"); pw.print(" mCurrentSubscription="); pw.println(mCurrentSubscription); - pw.print(" mWakeLock="); pw.println(mWakeLock); } public void setCallback(Callback callback) { @@ -114,38 +86,26 @@ public class NextAlarmConditionProvider extends ConditionProviderService { @Override public void onConnected() { if (DEBUG) Slog.d(TAG, "onConnected"); - mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - final PowerManager p = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - mWakeLock = p.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mLookaheadThreshold = mContext.getResources() .getInteger(R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS; - init(); - mConnected = System.currentTimeMillis(); - } - - public void onUserSwitched() { - if (DEBUG) Slog.d(TAG, "onUserSwitched"); - if (mConnected != 0) { - init(); - } + mConnected = true; + mTracker = mCallback.getNextAlarmTracker(); + mTracker.addCallback(mTrackerCallback); } @Override public void onDestroy() { super.onDestroy(); if (DEBUG) Slog.d(TAG, "onDestroy"); - if (mRegistered) { - mContext.unregisterReceiver(mReceiver); - mRegistered = false; - } - mConnected = 0; + mTracker.removeCallback(mTrackerCallback); + mConnected = false; } @Override public void onRequestConditions(int relevance) { - if (mConnected == 0 || (relevance & Condition.FLAG_RELEVANT_NOW) == 0) return; + if (!mConnected || (relevance & Condition.FLAG_RELEVANT_NOW) == 0) return; - final AlarmClockInfo nextAlarm = mAlarmManager.getNextAlarmClock(mCurrentUserId); + final AlarmClockInfo nextAlarm = mTracker.getNextAlarm(); if (nextAlarm == null) return; // no next alarm if (mCallback != null && mCallback.isInDowntime()) return; // prefer downtime condition if (!isWithinLookaheadThreshold(nextAlarm)) return; // alarm not within window @@ -154,12 +114,6 @@ public class NextAlarmConditionProvider extends ConditionProviderService { notifyCondition(newConditionId(), nextAlarm, Condition.STATE_TRUE, "request"); } - private boolean isWithinLookaheadThreshold(AlarmClockInfo alarm) { - if (alarm == null) return false; - final long delta = getEarlyTriggerTime(alarm) - System.currentTimeMillis(); - return delta > 0 && (mLookaheadThreshold <= 0 || delta < mLookaheadThreshold); - } - @Override public void onSubscribe(Uri conditionId) { if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId); @@ -168,73 +122,33 @@ public class NextAlarmConditionProvider extends ConditionProviderService { return; } mCurrentSubscription = conditionId; - mHandler.postEvaluate(0); - } - - private static long getEarlyTriggerTime(AlarmClockInfo alarm) { - return alarm != null ? (alarm.getTriggerTime() - EARLY) : 0; + mTracker.evaluate(); } - private boolean isDoneWaitingAfterBoot(long time) { - if (mBootCompleted > 0) return (time - mBootCompleted) > WAIT_AFTER_BOOT; - if (mConnected > 0) return (time - mConnected) > WAIT_AFTER_CONNECT; - return true; - } - - private void handleEvaluate() { - final AlarmClockInfo nextAlarm = mAlarmManager.getNextAlarmClock(mCurrentUserId); - final long triggerTime = getEarlyTriggerTime(nextAlarm); - final boolean withinThreshold = isWithinLookaheadThreshold(nextAlarm); - final long now = System.currentTimeMillis(); - final boolean booted = isDoneWaitingAfterBoot(now); - if (DEBUG) Slog.d(TAG, "handleEvaluate mCurrentSubscription=" + mCurrentSubscription - + " nextAlarm=" + formatAlarmDebug(triggerTime) - + " withinThreshold=" + withinThreshold - + " booted=" + booted); - if (mCurrentSubscription == null) return; // no one cares - if (!booted) { - // we don't know yet - notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_UNKNOWN, "!booted"); - final long recheckTime = (mBootCompleted > 0 ? mBootCompleted : now) + WAIT_AFTER_BOOT; - rescheduleAlarm(recheckTime); - return; - } - if (!withinThreshold) { - // triggertime invalid or in the past, condition = false - notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_FALSE, "!within"); + @Override + public void onUnsubscribe(Uri conditionId) { + if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId); + if (conditionId != null && conditionId.equals(mCurrentSubscription)) { mCurrentSubscription = null; - return; } - // triggertime in the future, condition = true, schedule alarm - notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_TRUE, "within"); - rescheduleAlarm(triggerTime); } - private static String formatDuration(long millis) { - final StringBuilder sb = new StringBuilder(); - TimeUtils.formatDuration(millis, sb); - return sb.toString(); + public void attachBase(Context base) { + attachBaseContext(base); } - private void rescheduleAlarm(long time) { - if (DEBUG) Slog.d(TAG, "rescheduleAlarm " + time); - final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE, - new Intent(ACTION_TRIGGER) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) - .putExtra(EXTRA_TRIGGER, time), - PendingIntent.FLAG_UPDATE_CURRENT); - alarms.cancel(pendingIntent); - mScheduledAlarmTime = time; - if (time > 0) { - if (DEBUG) Slog.d(TAG, String.format("Scheduling alarm for %s (in %s)", - formatAlarmDebug(time), formatDuration(time - System.currentTimeMillis()))); - alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); - } + public IConditionProvider asInterface() { + return (IConditionProvider) onBind(null); + } + + private boolean isWithinLookaheadThreshold(AlarmClockInfo alarm) { + if (alarm == null) return false; + final long delta = NextAlarmTracker.getEarlyTriggerTime(alarm) - System.currentTimeMillis(); + return delta > 0 && (mLookaheadThreshold <= 0 || delta < mLookaheadThreshold); } private void notifyCondition(Uri id, AlarmClockInfo alarm, int state, String reason) { - final String formattedAlarm = alarm == null ? "" : formatAlarm(alarm.getTriggerTime()); + final String formattedAlarm = alarm == null ? "" : mTracker.formatAlarm(alarm); if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state) + " alarm=" + formattedAlarm + " reason=" + reason); notifyCondition(new Condition(id, @@ -243,28 +157,11 @@ public class NextAlarmConditionProvider extends ConditionProviderService { formattedAlarm, 0, state, Condition.FLAG_RELEVANT_NOW)); } - @Override - public void onUnsubscribe(Uri conditionId) { - if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId); - if (conditionId != null && conditionId.equals(mCurrentSubscription)) { - mCurrentSubscription = null; - rescheduleAlarm(0); - } - } - - public void attachBase(Context base) { - attachBaseContext(base); - } - - public IConditionProvider asInterface() { - return (IConditionProvider) onBind(null); - } - private Uri newConditionId() { return new Uri.Builder().scheme(Condition.SCHEME) .authority(ZenModeConfig.SYSTEM_AUTHORITY) .appendPath(NEXT_ALARM_PATH) - .appendPath(Integer.toString(mCurrentUserId)) + .appendPath(Integer.toString(mTracker.getCurrentUserId())) .build(); } @@ -273,81 +170,41 @@ public class NextAlarmConditionProvider extends ConditionProviderService { && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY) && conditionId.getPathSegments().size() == 2 && conditionId.getPathSegments().get(0).equals(NEXT_ALARM_PATH) - && conditionId.getPathSegments().get(1).equals(Integer.toString(mCurrentUserId)); + && conditionId.getPathSegments().get(1) + .equals(Integer.toString(mTracker.getCurrentUserId())); } - private void init() { - if (mRegistered) { - mContext.unregisterReceiver(mReceiver); + private void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { + final boolean withinThreshold = isWithinLookaheadThreshold(nextAlarm); + if (DEBUG) Slog.d(TAG, "onEvaluate mCurrentSubscription=" + mCurrentSubscription + + " nextAlarmWakeup=" + mTracker.formatAlarmDebug(wakeupTime) + + " withinThreshold=" + withinThreshold + + " booted=" + booted); + if (mCurrentSubscription == null) return; // no one cares + if (!booted) { + // we don't know yet + notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_UNKNOWN, "!booted"); + return; } - mCurrentUserId = ActivityManager.getCurrentUser(); - final IntentFilter filter = new IntentFilter(); - filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); - filter.addAction(ACTION_TRIGGER); - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - filter.addAction(Intent.ACTION_BOOT_COMPLETED); - mContext.registerReceiverAsUser(mReceiver, new UserHandle(mCurrentUserId), filter, null, - null); - mRegistered = true; - mHandler.postEvaluate(0); - } - - private String formatAlarm(long time) { - return formatAlarm(time, "Hm", "hma"); - } - - private String formatAlarm(long time, String skeleton24, String skeleton12) { - final String skeleton = DateFormat.is24HourFormat(mContext) ? skeleton24 : skeleton12; - final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); - return DateFormat.format(pattern, time).toString(); - } - - private String formatAlarmDebug(AlarmClockInfo alarm) { - return formatAlarmDebug(alarm != null ? alarm.getTriggerTime() : 0); - } - - private String formatAlarmDebug(long time) { - if (time <= 0) return Long.toString(time); - return String.format("%s (%s)", time, formatAlarm(time, "Hms", "hmsa")); + if (!withinThreshold) { + // next alarm outside threshold or in the past, condition = false + notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_FALSE, "!within"); + mCurrentSubscription = null; + return; + } + // next alarm in the future and within threshold, condition = true + notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_TRUE, "within"); } - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() { @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (DEBUG) Slog.d(TAG, "onReceive " + action); - long delay = 0; - if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) { - delay = NEXT_ALARM_UPDATE_DELAY; - if (DEBUG) Slog.d(TAG, String.format(" next alarm for user %s: %s", - mCurrentUserId, - formatAlarmDebug(mAlarmManager.getNextAlarmClock(mCurrentUserId)))); - } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { - mBootCompleted = System.currentTimeMillis(); - } - mHandler.postEvaluate(delay); - mWakeLock.acquire(delay + 5000); // stay awake during evaluate + public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { + NextAlarmConditionProvider.this.onEvaluate(nextAlarm, wakeupTime, booted); } }; public interface Callback { boolean isInDowntime(); - } - - private class H extends Handler { - private static final int MSG_EVALUATE = 1; - - public void postEvaluate(long delay) { - removeMessages(MSG_EVALUATE); - sendEmptyMessageDelayed(MSG_EVALUATE, delay); - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_EVALUATE) { - handleEvaluate(); - } - } + NextAlarmTracker getNextAlarmTracker(); } } diff --git a/services/core/java/com/android/server/notification/NextAlarmTracker.java b/services/core/java/com/android/server/notification/NextAlarmTracker.java new file mode 100644 index 000000000000..d197afd3f9d7 --- /dev/null +++ b/services/core/java/com/android/server/notification/NextAlarmTracker.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import android.app.ActivityManager; +import android.app.AlarmManager; +import android.app.AlarmManager.AlarmClockInfo; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.Message; +import android.os.PowerManager; +import android.os.UserHandle; +import android.text.format.DateFormat; +import android.util.Log; +import android.util.Slog; +import android.util.TimeUtils; + +import com.android.server.notification.NotificationManagerService.DumpFilter; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Locale; + +/** Helper for tracking updates to the current user's next alarm. */ +public class NextAlarmTracker { + private static final String TAG = "NextAlarmTracker"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private static final String ACTION_TRIGGER = TAG + ".trigger"; + private static final String EXTRA_TRIGGER = "trigger"; + private static final int REQUEST_CODE = 100; + + private static final long SECONDS = 1000; + private static final long MINUTES = 60 * SECONDS; + private static final long NEXT_ALARM_UPDATE_DELAY = 1 * SECONDS; // treat clear+set as update + private static final long EARLY = 5 * SECONDS; // fire early, ensure alarm stream is unmuted + private static final long WAIT_AFTER_INIT = 5 * MINUTES;// for initial alarm re-registration + private static final long WAIT_AFTER_BOOT = 20 * SECONDS; // for initial alarm re-registration + + private final Context mContext; + private final H mHandler = new H(); + private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); + + private long mInit; + private boolean mRegistered; + private AlarmManager mAlarmManager; + private int mCurrentUserId; + private long mScheduledAlarmTime; + private long mBootCompleted; + private PowerManager.WakeLock mWakeLock; + + public NextAlarmTracker(Context context) { + mContext = context; + } + + public void dump(PrintWriter pw, DumpFilter filter) { + pw.println(" NextAlarmTracker:"); + pw.print(" len(mCallbacks)="); pw.println(mCallbacks.size()); + pw.print(" mRegistered="); pw.println(mRegistered); + pw.print(" mInit="); pw.println(mInit); + pw.print(" mBootCompleted="); pw.println(mBootCompleted); + pw.print(" mCurrentUserId="); pw.println(mCurrentUserId); + pw.print(" mScheduledAlarmTime="); pw.println(formatAlarmDebug(mScheduledAlarmTime)); + pw.print(" mWakeLock="); pw.println(mWakeLock); + } + + public void addCallback(Callback callback) { + mCallbacks.add(callback); + } + + public void removeCallback(Callback callback) { + mCallbacks.remove(callback); + } + + public int getCurrentUserId() { + return mCurrentUserId; + } + + public AlarmClockInfo getNextAlarm() { + return mAlarmManager.getNextAlarmClock(mCurrentUserId); + } + + public void onUserSwitched() { + reset(); + } + + public void init() { + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + final PowerManager p = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = p.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mInit = System.currentTimeMillis(); + reset(); + } + + public void reset() { + if (mRegistered) { + mContext.unregisterReceiver(mReceiver); + } + mCurrentUserId = ActivityManager.getCurrentUser(); + final IntentFilter filter = new IntentFilter(); + filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); + filter.addAction(ACTION_TRIGGER); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + filter.addAction(Intent.ACTION_BOOT_COMPLETED); + mContext.registerReceiverAsUser(mReceiver, new UserHandle(mCurrentUserId), filter, null, + null); + mRegistered = true; + evaluate(); + } + + public void destroy() { + if (mRegistered) { + mContext.unregisterReceiver(mReceiver); + mRegistered = false; + } + } + + public void evaluate() { + mHandler.postEvaluate(0); + } + + private void fireEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { + for (Callback callback : mCallbacks) { + callback.onEvaluate(nextAlarm, wakeupTime, booted); + } + } + + private void handleEvaluate() { + final AlarmClockInfo nextAlarm = mAlarmManager.getNextAlarmClock(mCurrentUserId); + final long triggerTime = getEarlyTriggerTime(nextAlarm); + final long now = System.currentTimeMillis(); + final boolean alarmUpcoming = triggerTime > now; + final boolean booted = isDoneWaitingAfterBoot(now); + if (DEBUG) Slog.d(TAG, "handleEvaluate nextAlarm=" + formatAlarmDebug(triggerTime) + + " alarmUpcoming=" + alarmUpcoming + + " booted=" + booted); + fireEvaluate(nextAlarm, triggerTime, booted); + if (!booted) { + // recheck after boot + final long recheckTime = (mBootCompleted > 0 ? mBootCompleted : now) + WAIT_AFTER_BOOT; + rescheduleAlarm(recheckTime); + return; + } + if (alarmUpcoming) { + // wake up just before the next alarm + rescheduleAlarm(triggerTime); + } + } + + public static long getEarlyTriggerTime(AlarmClockInfo alarm) { + return alarm != null ? (alarm.getTriggerTime() - EARLY) : 0; + } + + private boolean isDoneWaitingAfterBoot(long time) { + if (mBootCompleted > 0) return (time - mBootCompleted) > WAIT_AFTER_BOOT; + if (mInit > 0) return (time - mInit) > WAIT_AFTER_INIT; + return true; + } + + private static String formatDuration(long millis) { + final StringBuilder sb = new StringBuilder(); + TimeUtils.formatDuration(millis, sb); + return sb.toString(); + } + + public String formatAlarm(AlarmClockInfo alarm) { + return alarm != null ? formatAlarm(alarm.getTriggerTime()) : null; + } + + private String formatAlarm(long time) { + return formatAlarm(time, "Hm", "hma"); + } + + private String formatAlarm(long time, String skeleton24, String skeleton12) { + final String skeleton = DateFormat.is24HourFormat(mContext) ? skeleton24 : skeleton12; + final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); + return DateFormat.format(pattern, time).toString(); + } + + private String formatAlarmDebug(AlarmClockInfo alarm) { + return formatAlarmDebug(alarm != null ? alarm.getTriggerTime() : 0); + } + + public String formatAlarmDebug(long time) { + if (time <= 0) return Long.toString(time); + return String.format("%s (%s)", time, formatAlarm(time, "Hms", "hmsa")); + } + + private void rescheduleAlarm(long time) { + if (DEBUG) Slog.d(TAG, "rescheduleAlarm " + time); + final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE, + new Intent(ACTION_TRIGGER) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .putExtra(EXTRA_TRIGGER, time), + PendingIntent.FLAG_UPDATE_CURRENT); + alarms.cancel(pendingIntent); + mScheduledAlarmTime = time; + if (time > 0) { + if (DEBUG) Slog.d(TAG, String.format("Scheduling alarm for %s (in %s)", + formatAlarmDebug(time), formatDuration(time - System.currentTimeMillis()))); + alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); + } + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (DEBUG) Slog.d(TAG, "onReceive " + action); + long delay = 0; + if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) { + delay = NEXT_ALARM_UPDATE_DELAY; + if (DEBUG) Slog.d(TAG, String.format(" next alarm for user %s: %s", + mCurrentUserId, + formatAlarmDebug(mAlarmManager.getNextAlarmClock(mCurrentUserId)))); + } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { + mBootCompleted = System.currentTimeMillis(); + } + mHandler.postEvaluate(delay); + mWakeLock.acquire(delay + 5000); // stay awake during evaluate + } + }; + + private class H extends Handler { + private static final int MSG_EVALUATE = 1; + + public void postEvaluate(long delay) { + removeMessages(MSG_EVALUATE); + sendEmptyMessageDelayed(MSG_EVALUATE, delay); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_EVALUATE) { + handleEvaluate(); + } + } + } + + public interface Callback { + void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted); + } +} |