Merge "Zen: Pull next-alarm tracking out into separate helper." into lmp-mr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
7936ca36d9
@@ -51,6 +51,7 @@ public class ConditionProviders extends ManagedServices {
|
|||||||
= new ArrayMap<IBinder, IConditionListener>();
|
= new ArrayMap<IBinder, IConditionListener>();
|
||||||
private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
|
private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
|
||||||
private final CountdownConditionProvider mCountdown = new CountdownConditionProvider();
|
private final CountdownConditionProvider mCountdown = new CountdownConditionProvider();
|
||||||
|
private final NextAlarmTracker mNextAlarmTracker;
|
||||||
private final DowntimeConditionProvider mDowntime = new DowntimeConditionProvider();
|
private final DowntimeConditionProvider mDowntime = new DowntimeConditionProvider();
|
||||||
private final NextAlarmConditionProvider mNextAlarm = new NextAlarmConditionProvider();
|
private final NextAlarmConditionProvider mNextAlarm = new NextAlarmConditionProvider();
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ public class ConditionProviders extends ManagedServices {
|
|||||||
mZenModeHelper = zenModeHelper;
|
mZenModeHelper = zenModeHelper;
|
||||||
mZenModeHelper.addCallback(new ZenModeHelperCallback());
|
mZenModeHelper.addCallback(new ZenModeHelperCallback());
|
||||||
loadZenConfig();
|
loadZenConfig();
|
||||||
|
mNextAlarmTracker = new NextAlarmTracker(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -101,6 +103,7 @@ public class ConditionProviders extends ManagedServices {
|
|||||||
mCountdown.dump(pw, filter);
|
mCountdown.dump(pw, filter);
|
||||||
mDowntime.dump(pw, filter);
|
mDowntime.dump(pw, filter);
|
||||||
mNextAlarm.dump(pw, filter);
|
mNextAlarm.dump(pw, filter);
|
||||||
|
mNextAlarmTracker.dump(pw, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -111,6 +114,7 @@ public class ConditionProviders extends ManagedServices {
|
|||||||
@Override
|
@Override
|
||||||
public void onBootPhaseAppsCanStart() {
|
public void onBootPhaseAppsCanStart() {
|
||||||
super.onBootPhaseAppsCanStart();
|
super.onBootPhaseAppsCanStart();
|
||||||
|
mNextAlarmTracker.init();
|
||||||
mCountdown.attachBase(mContext);
|
mCountdown.attachBase(mContext);
|
||||||
registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT,
|
registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT,
|
||||||
UserHandle.USER_OWNER);
|
UserHandle.USER_OWNER);
|
||||||
@@ -121,20 +125,13 @@ public class ConditionProviders extends ManagedServices {
|
|||||||
mNextAlarm.attachBase(mContext);
|
mNextAlarm.attachBase(mContext);
|
||||||
registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT,
|
registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT,
|
||||||
UserHandle.USER_OWNER);
|
UserHandle.USER_OWNER);
|
||||||
mNextAlarm.setCallback(new NextAlarmConditionProvider.Callback() {
|
mNextAlarm.setCallback(new NextAlarmCallback());
|
||||||
@Override
|
|
||||||
public boolean isInDowntime() {
|
|
||||||
return mDowntime.isInDowntime();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUserSwitched() {
|
public void onUserSwitched() {
|
||||||
super.onUserSwitched();
|
super.onUserSwitched();
|
||||||
if (mNextAlarm != null) {
|
mNextAlarmTracker.onUserSwitched();
|
||||||
mNextAlarm.onUserSwitched();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -572,6 +569,23 @@ public class ConditionProviders extends ManagedServices {
|
|||||||
mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "downtimeExit");
|
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 {
|
private static class ConditionRecord {
|
||||||
|
|||||||
@@ -298,5 +298,6 @@ public class DowntimeConditionProvider extends ConditionProviderService {
|
|||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
void onDowntimeChanged(int downtimeMode);
|
void onDowntimeChanged(int downtimeMode);
|
||||||
|
NextAlarmTracker getNextAlarmTracker();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,34 +16,23 @@
|
|||||||
|
|
||||||
package com.android.server.notification;
|
package com.android.server.notification;
|
||||||
|
|
||||||
import android.app.ActivityManager;
|
|
||||||
import android.app.AlarmManager;
|
import android.app.AlarmManager;
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.AlarmManager.AlarmClockInfo;
|
import android.app.AlarmManager.AlarmClockInfo;
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.net.Uri;
|
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.Condition;
|
||||||
import android.service.notification.ConditionProviderService;
|
import android.service.notification.ConditionProviderService;
|
||||||
import android.service.notification.IConditionProvider;
|
import android.service.notification.IConditionProvider;
|
||||||
import android.service.notification.ZenModeConfig;
|
import android.service.notification.ZenModeConfig;
|
||||||
import android.util.TimeUtils;
|
|
||||||
import android.text.format.DateFormat;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
import android.util.TimeUtils;
|
||||||
|
|
||||||
import com.android.internal.R;
|
import com.android.internal.R;
|
||||||
import com.android.server.notification.NotificationManagerService.DumpFilter;
|
import com.android.server.notification.NotificationManagerService.DumpFilter;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Built-in zen condition provider for alarm-clock-based conditions.
|
* 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 String TAG = "NextAlarmConditions";
|
||||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
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 SECONDS = 1000;
|
||||||
private static final long MINUTES = 60 * SECONDS;
|
private static final long MINUTES = 60 * SECONDS;
|
||||||
private static final long HOURS = 60 * MINUTES;
|
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";
|
private static final String NEXT_ALARM_PATH = "next_alarm";
|
||||||
public static final ComponentName COMPONENT =
|
public static final ComponentName COMPONENT =
|
||||||
new ComponentName("android", NextAlarmConditionProvider.class.getName());
|
new ComponentName("android", NextAlarmConditionProvider.class.getName());
|
||||||
|
|
||||||
private final Context mContext = this;
|
private final Context mContext = this;
|
||||||
private final H mHandler = new H();
|
|
||||||
|
|
||||||
private long mConnected;
|
private NextAlarmTracker mTracker;
|
||||||
private boolean mRegistered;
|
private boolean mConnected;
|
||||||
private AlarmManager mAlarmManager;
|
|
||||||
private int mCurrentUserId;
|
|
||||||
private long mLookaheadThreshold;
|
private long mLookaheadThreshold;
|
||||||
private long mScheduledAlarmTime;
|
|
||||||
private Callback mCallback;
|
private Callback mCallback;
|
||||||
private Uri mCurrentSubscription;
|
private Uri mCurrentSubscription;
|
||||||
private PowerManager.WakeLock mWakeLock;
|
|
||||||
private long mBootCompleted;
|
|
||||||
|
|
||||||
public NextAlarmConditionProvider() {
|
public NextAlarmConditionProvider() {
|
||||||
if (DEBUG) Slog.d(TAG, "new NextAlarmConditionProvider()");
|
if (DEBUG) Slog.d(TAG, "new NextAlarmConditionProvider()");
|
||||||
@@ -97,14 +74,9 @@ public class NextAlarmConditionProvider extends ConditionProviderService {
|
|||||||
public void dump(PrintWriter pw, DumpFilter filter) {
|
public void dump(PrintWriter pw, DumpFilter filter) {
|
||||||
pw.println(" NextAlarmConditionProvider:");
|
pw.println(" NextAlarmConditionProvider:");
|
||||||
pw.print(" mConnected="); pw.println(mConnected);
|
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(" mLookaheadThreshold="); pw.print(mLookaheadThreshold);
|
||||||
pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
|
pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
|
||||||
pw.print(" mCurrentSubscription="); pw.println(mCurrentSubscription);
|
pw.print(" mCurrentSubscription="); pw.println(mCurrentSubscription);
|
||||||
pw.print(" mWakeLock="); pw.println(mWakeLock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCallback(Callback callback) {
|
public void setCallback(Callback callback) {
|
||||||
@@ -114,38 +86,26 @@ public class NextAlarmConditionProvider extends ConditionProviderService {
|
|||||||
@Override
|
@Override
|
||||||
public void onConnected() {
|
public void onConnected() {
|
||||||
if (DEBUG) Slog.d(TAG, "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()
|
mLookaheadThreshold = mContext.getResources()
|
||||||
.getInteger(R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS;
|
.getInteger(R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS;
|
||||||
init();
|
mConnected = true;
|
||||||
mConnected = System.currentTimeMillis();
|
mTracker = mCallback.getNextAlarmTracker();
|
||||||
}
|
mTracker.addCallback(mTrackerCallback);
|
||||||
|
|
||||||
public void onUserSwitched() {
|
|
||||||
if (DEBUG) Slog.d(TAG, "onUserSwitched");
|
|
||||||
if (mConnected != 0) {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (DEBUG) Slog.d(TAG, "onDestroy");
|
if (DEBUG) Slog.d(TAG, "onDestroy");
|
||||||
if (mRegistered) {
|
mTracker.removeCallback(mTrackerCallback);
|
||||||
mContext.unregisterReceiver(mReceiver);
|
mConnected = false;
|
||||||
mRegistered = false;
|
|
||||||
}
|
|
||||||
mConnected = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestConditions(int relevance) {
|
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 (nextAlarm == null) return; // no next alarm
|
||||||
if (mCallback != null && mCallback.isInDowntime()) return; // prefer downtime condition
|
if (mCallback != null && mCallback.isInDowntime()) return; // prefer downtime condition
|
||||||
if (!isWithinLookaheadThreshold(nextAlarm)) return; // alarm not within window
|
if (!isWithinLookaheadThreshold(nextAlarm)) return; // alarm not within window
|
||||||
@@ -154,12 +114,6 @@ public class NextAlarmConditionProvider extends ConditionProviderService {
|
|||||||
notifyCondition(newConditionId(), nextAlarm, Condition.STATE_TRUE, "request");
|
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
|
@Override
|
||||||
public void onSubscribe(Uri conditionId) {
|
public void onSubscribe(Uri conditionId) {
|
||||||
if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
|
if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
|
||||||
@@ -168,79 +122,7 @@ public class NextAlarmConditionProvider extends ConditionProviderService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mCurrentSubscription = conditionId;
|
mCurrentSubscription = conditionId;
|
||||||
mHandler.postEvaluate(0);
|
mTracker.evaluate();
|
||||||
}
|
|
||||||
|
|
||||||
private 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 (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");
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 void notifyCondition(Uri id, AlarmClockInfo alarm, int state, String reason) {
|
|
||||||
final String formattedAlarm = alarm == null ? "" : formatAlarm(alarm.getTriggerTime());
|
|
||||||
if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state)
|
|
||||||
+ " alarm=" + formattedAlarm + " reason=" + reason);
|
|
||||||
notifyCondition(new Condition(id,
|
|
||||||
mContext.getString(R.string.zen_mode_next_alarm_summary, formattedAlarm),
|
|
||||||
mContext.getString(R.string.zen_mode_next_alarm_line_one),
|
|
||||||
formattedAlarm, 0, state, Condition.FLAG_RELEVANT_NOW));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -248,7 +130,6 @@ public class NextAlarmConditionProvider extends ConditionProviderService {
|
|||||||
if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
|
if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
|
||||||
if (conditionId != null && conditionId.equals(mCurrentSubscription)) {
|
if (conditionId != null && conditionId.equals(mCurrentSubscription)) {
|
||||||
mCurrentSubscription = null;
|
mCurrentSubscription = null;
|
||||||
rescheduleAlarm(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,11 +141,27 @@ public class NextAlarmConditionProvider extends ConditionProviderService {
|
|||||||
return (IConditionProvider) onBind(null);
|
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 ? "" : mTracker.formatAlarm(alarm);
|
||||||
|
if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state)
|
||||||
|
+ " alarm=" + formattedAlarm + " reason=" + reason);
|
||||||
|
notifyCondition(new Condition(id,
|
||||||
|
mContext.getString(R.string.zen_mode_next_alarm_summary, formattedAlarm),
|
||||||
|
mContext.getString(R.string.zen_mode_next_alarm_line_one),
|
||||||
|
formattedAlarm, 0, state, Condition.FLAG_RELEVANT_NOW));
|
||||||
|
}
|
||||||
|
|
||||||
private Uri newConditionId() {
|
private Uri newConditionId() {
|
||||||
return new Uri.Builder().scheme(Condition.SCHEME)
|
return new Uri.Builder().scheme(Condition.SCHEME)
|
||||||
.authority(ZenModeConfig.SYSTEM_AUTHORITY)
|
.authority(ZenModeConfig.SYSTEM_AUTHORITY)
|
||||||
.appendPath(NEXT_ALARM_PATH)
|
.appendPath(NEXT_ALARM_PATH)
|
||||||
.appendPath(Integer.toString(mCurrentUserId))
|
.appendPath(Integer.toString(mTracker.getCurrentUserId()))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,81 +170,41 @@ public class NextAlarmConditionProvider extends ConditionProviderService {
|
|||||||
&& conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
|
&& conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
|
||||||
&& conditionId.getPathSegments().size() == 2
|
&& conditionId.getPathSegments().size() == 2
|
||||||
&& conditionId.getPathSegments().get(0).equals(NEXT_ALARM_PATH)
|
&& 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() {
|
private void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
|
||||||
if (mRegistered) {
|
final boolean withinThreshold = isWithinLookaheadThreshold(nextAlarm);
|
||||||
mContext.unregisterReceiver(mReceiver);
|
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();
|
if (!withinThreshold) {
|
||||||
final IntentFilter filter = new IntentFilter();
|
// next alarm outside threshold or in the past, condition = false
|
||||||
filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
|
notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_FALSE, "!within");
|
||||||
filter.addAction(ACTION_TRIGGER);
|
mCurrentSubscription = null;
|
||||||
filter.addAction(Intent.ACTION_TIME_CHANGED);
|
return;
|
||||||
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
|
}
|
||||||
filter.addAction(Intent.ACTION_BOOT_COMPLETED);
|
// next alarm in the future and within threshold, condition = true
|
||||||
mContext.registerReceiverAsUser(mReceiver, new UserHandle(mCurrentUserId), filter, null,
|
notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_TRUE, "within");
|
||||||
null);
|
|
||||||
mRegistered = true;
|
|
||||||
mHandler.postEvaluate(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatAlarm(long time) {
|
private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
|
||||||
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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
|
||||||
final String action = intent.getAction();
|
NextAlarmConditionProvider.this.onEvaluate(nextAlarm, wakeupTime, booted);
|
||||||
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 interface Callback {
|
public interface Callback {
|
||||||
boolean isInDowntime();
|
boolean isInDowntime();
|
||||||
}
|
NextAlarmTracker getNextAlarmTracker();
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user