summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author John Spurlock <jspurlock@google.com> 2014-11-10 19:53:14 +0000
committer android-build-merger <android-build-merger@google.com> 2014-11-10 19:53:14 +0000
commit551f0778c39b030ebf1fd51c882c152b3e7ac320 (patch)
tree22881e3b5c65b82e64b725854709c098d2de5224
parent2ace3d4803fff437387d36f27dab05f1c159b710 (diff)
parent7936ca36d9dfd620df0bfead6c79b5eddc761c20 (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.
-rw-r--r--services/core/java/com/android/server/notification/ConditionProviders.java32
-rw-r--r--services/core/java/com/android/server/notification/DowntimeConditionProvider.java1
-rw-r--r--services/core/java/com/android/server/notification/NextAlarmConditionProvider.java249
-rw-r--r--services/core/java/com/android/server/notification/NextAlarmTracker.java263
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);
+ }
+}