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

Commit 97de2b26 authored by Adrian Roos's avatar Adrian Roos
Browse files

Ambient: Pulse for every noisy notification exactly once

Also increases the pulse duration and switches timing to
elapsed realtime instead of wall time for robustness. Also
remove triggering for notification light for simplifying code.
Only noisy notifications are able to trigger the light anyway and
noisy notifications already trigger ambient on their own.

Change-Id: I4efbe530bb1a60cabaa46c2b04bcd5e12cdef680
Fixes: 31004875
parent 06165f53
Loading
Loading
Loading
Loading
+1 −7
Original line number Diff line number Diff line
@@ -218,12 +218,6 @@
    <!-- Doze: should notifications be used as a pulse signal? -->
    <bool name="doze_pulse_on_notifications">true</bool>

    <!-- Doze: when to pulse after a buzzworthy notification arrives -->
    <string name="doze_pulse_schedule" translatable="false">10s,30s,60s</string>

    <!-- Doze: maximum number of times the notification pulse schedule can be reset -->
    <integer name="doze_pulse_schedule_resets">2</integer>

    <!-- Doze: duration to avoid false pickup gestures triggered by notification vibrations -->
    <integer name="doze_pickup_vibration_threshold">2000</integer>

@@ -253,7 +247,7 @@
    <integer name="doze_pulse_duration_in_pickup">130</integer>

    <!-- Doze: pulse parameter - once faded in, how long does it stay visible? -->
    <integer name="doze_pulse_duration_visible">3000</integer>
    <integer name="doze_pulse_duration_visible">6000</integer>

    <!-- Doze: pulse parameter - how long does it take to fade out? -->
    <integer name="doze_pulse_duration_out">600</integer>
+1 −1
Original line number Diff line number Diff line
@@ -33,7 +33,7 @@ public class DozeLog {
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    private static final boolean ENABLED = true;
    private static final int SIZE = Build.IS_DEBUGGABLE ? 400 : 50;
    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
    static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");

    private static final int PULSE_REASONS = 4;

+13 −130
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package com.android.systemui.doze;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.UiModeManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -43,7 +41,6 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.DozeParameters.PulseSchedule;

import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -55,19 +52,6 @@ public class DozeService extends DreamService {

    private static final String ACTION_BASE = "com.android.systemui.doze";
    private static final String PULSE_ACTION = ACTION_BASE + ".pulse";
    private static final String NOTIFICATION_PULSE_ACTION = ACTION_BASE + ".notification_pulse";
    private static final String EXTRA_INSTANCE = "instance";

    /**
     * Earliest time we pulse due to a notification light after the service started.
     *
     * <p>Incoming notification light events during the blackout period are
     * delayed to the earliest time defined by this constant.</p>
     *
     * <p>This delay avoids a pulse immediately after screen off, at which
     * point the notification light is re-enabled again by NoMan.</p>
     */
    private static final int EARLIEST_LIGHT_PULSE_AFTER_START_MS = 10 * 1000;

    private final String mTag = String.format(TAG + ".%08x", hashCode());
    private final Context mContext = this;
@@ -80,19 +64,14 @@ public class DozeService extends DreamService {
    private TriggerSensor mPickupSensor;
    private PowerManager mPowerManager;
    private PowerManager.WakeLock mWakeLock;
    private AlarmManager mAlarmManager;
    private UiModeManager mUiModeManager;
    private boolean mDreaming;
    private boolean mPulsing;
    private boolean mBroadcastReceiverRegistered;
    private boolean mDisplayStateSupported;
    private boolean mNotificationLightOn;
    private boolean mPowerSaveActive;
    private boolean mCarMode;
    private long mNotificationPulseTime;
    private long mLastScheduleResetTime;
    private long mEarliestPulseDueToLight;
    private int mScheduleResetsRemaining;

    public DozeService() {
        if (DEBUG) Log.d(mTag, "new DozeService()");
@@ -110,11 +89,11 @@ public class DozeService extends DreamService {
        pw.print("  mSigMotionSensor: "); pw.println(mSigMotionSensor);
        pw.print("  mPickupSensor:"); pw.println(mPickupSensor);
        pw.print("  mDisplayStateSupported: "); pw.println(mDisplayStateSupported);
        pw.print("  mNotificationLightOn: "); pw.println(mNotificationLightOn);
        pw.print("  mPowerSaveActive: "); pw.println(mPowerSaveActive);
        pw.print("  mCarMode: "); pw.println(mCarMode);
        pw.print("  mNotificationPulseTime: "); pw.println(mNotificationPulseTime);
        pw.print("  mScheduleResetsRemaining: "); pw.println(mScheduleResetsRemaining);
        pw.print("  mNotificationPulseTime: "); pw.println(
                DozeLog.FORMAT.format(new Date(mNotificationPulseTime
                        - SystemClock.elapsedRealtime() + System.currentTimeMillis())));
        mDozeParameters.dump(pw);
    }

@@ -141,7 +120,6 @@ public class DozeService extends DreamService {
        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        mWakeLock.setReferenceCounted(true);
        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        mDisplayStateSupported = mDozeParameters.getDisplayStateSupported();
        mUiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
        turnDisplayOff();
@@ -176,8 +154,6 @@ public class DozeService extends DreamService {
        }

        mDreaming = true;
        rescheduleNotificationPulse(false /*predicate*/);  // cancel any pending pulse alarms
        mEarliestPulseDueToLight = System.currentTimeMillis() + EARLIEST_LIGHT_PULSE_AFTER_START_MS;
        listenForPulseSignals(true);

        // Ask the host to get things ready to start dozing.
@@ -316,7 +292,6 @@ public class DozeService extends DreamService {
    private void listenForBroadcasts(boolean listen) {
        if (listen) {
            final IntentFilter filter = new IntentFilter(PULSE_ACTION);
            filter.addAction(NOTIFICATION_PULSE_ACTION);
            filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE);
            mContext.registerReceiver(mBroadcastReceiver, filter);
            mBroadcastReceiverRegistered = true;
@@ -330,94 +305,18 @@ public class DozeService extends DreamService {

    private void listenForNotifications(boolean listen) {
        if (listen) {
            resetNotificationResets();
            mHost.addCallback(mHostCallback);

            // Continue to pulse for existing LEDs.
            mNotificationLightOn = mHost.isNotificationLightOn();
            if (mNotificationLightOn) {
                updateNotificationPulseDueToLight();
            }
        } else {
            mHost.removeCallback(mHostCallback);
        }
    }

    private void resetNotificationResets() {
        if (DEBUG) Log.d(mTag, "resetNotificationResets");
        mScheduleResetsRemaining = mDozeParameters.getPulseScheduleResets();
    }

    private void updateNotificationPulseDueToLight() {
        long timeMs = System.currentTimeMillis();
        timeMs = Math.max(timeMs, mEarliestPulseDueToLight);
        updateNotificationPulse(timeMs);
    }

    private void updateNotificationPulse(long notificationTimeMs) {
        if (DEBUG) Log.d(mTag, "updateNotificationPulse notificationTimeMs=" + notificationTimeMs);
    private void requestNotificationPulse() {
        if (DEBUG) Log.d(mTag, "requestNotificationPulse");
        if (!mDozeParameters.getPulseOnNotifications()) return;
        if (mScheduleResetsRemaining <= 0) {
            if (DEBUG) Log.d(mTag, "No more schedule resets remaining");
            return;
        }
        final long pulseDuration = mDozeParameters.getPulseDuration(false /*pickup*/);
        boolean pulseImmediately = System.currentTimeMillis() >= notificationTimeMs;
        if ((notificationTimeMs - mLastScheduleResetTime) >= pulseDuration) {
            mScheduleResetsRemaining--;
            mLastScheduleResetTime = notificationTimeMs;
        } else if (!pulseImmediately){
            if (DEBUG) Log.d(mTag, "Recently updated, not resetting schedule");
            return;
        }
        if (DEBUG) Log.d(mTag, "mScheduleResetsRemaining = " + mScheduleResetsRemaining);
        mNotificationPulseTime = notificationTimeMs;
        if (pulseImmediately) {
            DozeLog.traceNotificationPulse(mContext, 0);
        mNotificationPulseTime = SystemClock.elapsedRealtime();
        requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
    }
        // schedule the rest of the pulses
        rescheduleNotificationPulse(true /*predicate*/);
    }

    private PendingIntent notificationPulseIntent(long instance) {
        return PendingIntent.getBroadcast(mContext, 0,
                new Intent(NOTIFICATION_PULSE_ACTION)
                        .setPackage(getPackageName())
                        .putExtra(EXTRA_INSTANCE, instance)
                        .setFlags(Intent.FLAG_RECEIVER_FOREGROUND),
                PendingIntent.FLAG_UPDATE_CURRENT);
    }

    private void rescheduleNotificationPulse(boolean predicate) {
        if (DEBUG) Log.d(mTag, "rescheduleNotificationPulse predicate=" + predicate);
        final PendingIntent notificationPulseIntent = notificationPulseIntent(0);
        mAlarmManager.cancel(notificationPulseIntent);
        if (!predicate) {
            if (DEBUG) Log.d(mTag, "  don't reschedule: predicate is false");
            return;
        }
        final PulseSchedule schedule = mDozeParameters.getPulseSchedule();
        if (schedule == null) {
            if (DEBUG) Log.d(mTag, "  don't reschedule: schedule is null");
            return;
        }
        final long now = System.currentTimeMillis();
        final long time = schedule.getNextTime(now, mNotificationPulseTime);
        if (time <= 0) {
            if (DEBUG) Log.d(mTag, "  don't reschedule: time is " + time);
            return;
        }
        final long delta = time - now;
        if (delta <= 0) {
            if (DEBUG) Log.d(mTag, "  don't reschedule: delta is " + delta);
            return;
        }
        final long instance = time - mNotificationPulseTime;
        if (DEBUG) Log.d(mTag, "Scheduling pulse " + instance + " in " + delta + "ms for "
                + new Date(time));
        mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, time, notificationPulseIntent(instance));
    }

    private static String triggerEventToString(TriggerEvent event) {
        if (event == null) return null;
@@ -439,13 +338,6 @@ public class DozeService extends DreamService {
                if (DEBUG) Log.d(mTag, "Received pulse intent");
                requestPulse(DozeLog.PULSE_REASON_INTENT);
            }
            if (NOTIFICATION_PULSE_ACTION.equals(intent.getAction())) {
                final long instance = intent.getLongExtra(EXTRA_INSTANCE, -1);
                if (DEBUG) Log.d(mTag, "Received notification pulse intent instance=" + instance);
                DozeLog.traceNotificationPulse(mContext, instance);
                requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
                rescheduleNotificationPulse(mNotificationLightOn);
            }
            if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
                mCarMode = true;
                if (mCarMode && mDreaming) {
@@ -465,17 +357,13 @@ public class DozeService extends DreamService {
        @Override
        public void onBuzzBeepBlinked() {
            if (DEBUG) Log.d(mTag, "onBuzzBeepBlinked");
            updateNotificationPulse(System.currentTimeMillis());
            requestNotificationPulse();
        }

        @Override
        public void onNotificationLight(boolean on) {
            if (DEBUG) Log.d(mTag, "onNotificationLight on=" + on);
            if (mNotificationLightOn == on) return;
            mNotificationLightOn = on;
            if (mNotificationLightOn) {
                updateNotificationPulseDueToLight();
            }
            if (DEBUG) Log.d(mTag, "onNotificationLight (noop) on=" + on);
            // noop for now
        }

        @Override
@@ -564,17 +452,12 @@ public class DozeService extends DreamService {
                requestPulse(mPulseReason, sensorPerformsProxCheck);
                updateListener();  // reregister, this sensor only fires once

                // reset the notification pulse schedule, but only if we think we were not triggered
                // by a notification-related vibration
                final long timeSinceNotification = System.currentTimeMillis()
                // record pickup gesture, also keep track of whether we might have been triggered
                // by recent vibration.
                final long timeSinceNotification = SystemClock.elapsedRealtime()
                        - mNotificationPulseTime;
                final boolean withinVibrationThreshold =
                        timeSinceNotification < mDozeParameters.getPickupVibrationThreshold();
                if (withinVibrationThreshold) {
                   if (DEBUG) Log.d(mTag, "Not resetting schedule, recent notification");
                } else {
                    resetNotificationResets();
                }
                if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
                    DozeLog.tracePickupPulse(mContext, withinVibrationThreshold);
                }
+0 −56
Original line number Diff line number Diff line
@@ -38,8 +38,6 @@ public class DozeParameters {

    private final Context mContext;

    private static PulseSchedule sPulseSchedule;

    private static IntInOutMatcher sPickupSubtypePerformsProxMatcher;

    public DozeParameters(Context context) {
@@ -61,8 +59,6 @@ public class DozeParameters {
        pw.print("    getVibrateOnPickup(): "); pw.println(getVibrateOnPickup());
        pw.print("    getProxCheckBeforePulse(): "); pw.println(getProxCheckBeforePulse());
        pw.print("    getPulseOnNotifications(): "); pw.println(getPulseOnNotifications());
        pw.print("    getPulseSchedule(): "); pw.println(getPulseSchedule());
        pw.print("    getPulseScheduleResets(): "); pw.println(getPulseScheduleResets());
        pw.print("    getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold());
        pw.print("    getPickupSubtypePerformsProxCheck(): ");pw.println(
                dumpPickupSubtypePerformsProxCheck());
@@ -126,18 +122,6 @@ public class DozeParameters {
        return getBoolean("doze.pulse.notifications", R.bool.doze_pulse_on_notifications);
    }

    public PulseSchedule getPulseSchedule() {
        final String spec = getString("doze.pulse.schedule", R.string.doze_pulse_schedule);
        if (sPulseSchedule == null || !sPulseSchedule.mSpec.equals(spec)) {
            sPulseSchedule = PulseSchedule.parse(spec);
        }
        return sPulseSchedule;
    }

    public int getPulseScheduleResets() {
        return getInt("doze.pulse.schedule.resets", R.integer.doze_pulse_schedule_resets);
    }

    public int getPickupVibrationThreshold() {
        return getInt("doze.pickup.vibration.threshold", R.integer.doze_pickup_vibration_threshold);
    }
@@ -249,44 +233,4 @@ public class DozeParameters {
            return (mIsIn.get(value, mDefaultIsIn));
        }
    }

    public static class PulseSchedule {
        private static final Pattern PATTERN = Pattern.compile("(\\d+?)s", 0);

        private String mSpec;
        private int[] mSchedule;

        public static PulseSchedule parse(String spec) {
            if (TextUtils.isEmpty(spec)) return null;
            try {
                final PulseSchedule rt = new PulseSchedule();
                rt.mSpec = spec;
                final String[] tokens = spec.split(",");
                rt.mSchedule = new int[tokens.length];
                for (int i = 0; i < tokens.length; i++) {
                    final Matcher m = PATTERN.matcher(tokens[i]);
                    if (!m.matches()) throw new IllegalArgumentException("Bad token: " + tokens[i]);
                    rt.mSchedule[i] = Integer.parseInt(m.group(1));
                }
                if (DEBUG) Log.d(TAG, "Parsed spec [" + spec + "] as: " + rt);
                return rt;
            } catch (RuntimeException e) {
                Log.w(TAG, "Error parsing spec: " + spec, e);
                return null;
            }
        }

        @Override
        public String toString() {
            return Arrays.toString(mSchedule);
        }

        public long getNextTime(long now, long notificationTime) {
            for (int i = 0; i < mSchedule.length; i++) {
                final long time = notificationTime + mSchedule[i] * 1000;
                if (time > now) return time;
            }
            return 0;
        }
    }
}