Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7936ca36 authored by John Spurlock's avatar John Spurlock Committed by Android (Google) Code Review
Browse files

Merge "Zen: Pull next-alarm tracking out into separate helper." into lmp-mr1-dev

parents 6c912b7d 6b0623a1
Loading
Loading
Loading
Loading
+23 −9
Original line number Diff line number Diff line
@@ -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 {
+1 −0
Original line number Diff line number Diff line
@@ -298,5 +298,6 @@ public class DowntimeConditionProvider extends ConditionProviderService {

    public interface Callback {
        void onDowntimeChanged(int downtimeMode);
        NextAlarmTracker getNextAlarmTracker();
    }
}
+53 −196
Original line number Diff line number Diff line
@@ -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));
    }

    private void init() {
        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;
        mHandler.postEvaluate(0);
                && conditionId.getPathSegments().get(1)
                        .equals(Integer.toString(mTracker.getCurrentUserId()));
    }

    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 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;
        }

    private String formatAlarmDebug(AlarmClockInfo alarm) {
        return formatAlarmDebug(alarm != null ? alarm.getTriggerTime() : 0);
        if (!withinThreshold) {
            // next alarm outside threshold or in the past, condition = false
            notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_FALSE, "!within");
            mCurrentSubscription = null;
            return;
        }

    private String formatAlarmDebug(long time) {
        if (time <= 0) return Long.toString(time);
        return String.format("%s (%s)", time, formatAlarm(time, "Hms", "hmsa"));
        // 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();
    }
}
+263 −0

File added.

Preview size limit exceeded, changes collapsed.