Doze: Refactor v1

Pulls appart UI, state, power, display and trigger management
into individual components controlled by a state machine.

Also adds tests.

Test: runtest -x packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
Change-Id: I6ee7e79d58a5a3ebeb975775af44ad1e5aaccf93
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 2bb1d6a..94cbdd4 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -16,468 +16,46 @@
 
 package com.android.systemui.doze;
 
-import android.app.AlarmManager;
-import android.app.UiModeManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
 import android.service.dreams.DreamService;
 import android.util.Log;
-import android.view.Display;
-
-import com.android.internal.hardware.AmbientDisplayConfiguration;
-import com.android.systemui.SystemUIApplication;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.Assert;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
 
-public class DozeService extends DreamService implements DozeSensors.Callback {
+public class DozeService extends DreamService implements DozeMachine.Service {
     private static final String TAG = "DozeService";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final String ACTION_BASE = "com.android.systemui.doze";
-    private static final String PULSE_ACTION = ACTION_BASE + ".pulse";
-
-    /**
-     * If true, reregisters all trigger sensors when the screen turns off.
-     */
-    private static final boolean REREGISTER_ALL_SENSORS_ON_SCREEN_OFF = true;
-
-    private final String mTag = String.format(TAG + ".%08x", hashCode());
-    private final Context mContext = this;
-    private final DozeParameters mDozeParameters = new DozeParameters(mContext);
-    private final Handler mHandler = new Handler();
-
-    private DozeHost mHost;
-    private DozeSensors mDozeSensors;
-    private SensorManager mSensorManager;
-    private PowerManager mPowerManager;
-    private PowerManager.WakeLock mWakeLock;
-    private UiModeManager mUiModeManager;
-    private boolean mDreaming;
-    private boolean mPulsing;
-    private boolean mBroadcastReceiverRegistered;
-    private boolean mDisplayStateSupported;
-    private boolean mPowerSaveActive;
-    private boolean mCarMode;
-    private long mNotificationPulseTime;
-
-    private AmbientDisplayConfiguration mConfig;
-    private AlarmManager mAlarmManager;
+    private DozeMachine mDozeMachine;
 
     public DozeService() {
-        if (DEBUG) Log.d(mTag, "new DozeService()");
         setDebug(DEBUG);
     }
 
     @Override
-    protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
-        super.dumpOnHandler(fd, pw, args);
-        pw.print("  mDreaming: "); pw.println(mDreaming);
-        pw.print("  mPulsing: "); pw.println(mPulsing);
-        pw.print("  mWakeLock: held="); pw.println(mWakeLock.isHeld());
-        pw.print("  mHost: "); pw.println(mHost);
-        pw.print("  mBroadcastReceiverRegistered: "); pw.println(mBroadcastReceiverRegistered);
-        pw.print("  mDisplayStateSupported: "); pw.println(mDisplayStateSupported);
-        pw.print("  mPowerSaveActive: "); pw.println(mPowerSaveActive);
-        pw.print("  mCarMode: "); pw.println(mCarMode);
-        pw.print("  mNotificationPulseTime: "); pw.println(
-                DozeLog.FORMAT.format(new Date(mNotificationPulseTime
-                        - SystemClock.elapsedRealtime() + System.currentTimeMillis())));
-        mDozeParameters.dump(pw);
-    }
-
-    @Override
     public void onCreate() {
-        if (DEBUG) Log.d(mTag, "onCreate");
         super.onCreate();
 
-        if (getApplication() instanceof SystemUIApplication) {
-            final SystemUIApplication app = (SystemUIApplication) getApplication();
-            mHost = app.getComponent(DozeHost.class);
-        }
-        if (mHost == null) Log.w(TAG, "No doze service host found.");
-
         setWindowless(true);
 
-        mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
-        mAlarmManager = (AlarmManager) mContext.getSystemService(AlarmManager.class);
-        mConfig = new AmbientDisplayConfiguration(mContext);
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
-        mWakeLock.setReferenceCounted(true);
-        mDisplayStateSupported = mDozeParameters.getDisplayStateSupported();
-        mUiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
-        turnDisplayOff();
-        mDozeSensors = new DozeSensors(mContext, mSensorManager, mDozeParameters,
-                mConfig, mWakeLock, this);
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        if (DEBUG) Log.d(mTag, "onAttachedToWindow");
-        super.onAttachedToWindow();
+        mDozeMachine = DozeFactory.assembleMachine(this);
     }
 
     @Override
     public void onDreamingStarted() {
         super.onDreamingStarted();
-
-        if (mHost == null) {
-            finish();
-            return;
-        }
-
-        mPowerSaveActive = mHost.isPowerSaveActive();
-        mCarMode = mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR;
-        if (DEBUG) Log.d(mTag, "onDreamingStarted canDoze=" + canDoze() + " mPowerSaveActive="
-                + mPowerSaveActive + " mCarMode=" + mCarMode);
-        if (mPowerSaveActive) {
-            finishToSavePower();
-            return;
-        }
-        if (mCarMode) {
-            finishForCarMode();
-            return;
-        }
-
-        mDreaming = true;
-        listenForPulseSignals(true);
-
-        // Ask the host to get things ready to start dozing.
-        // Once ready, we call startDozing() at which point the CPU may suspend
-        // and we will need to acquire a wakelock to do work.
-        mHost.startDozing(mWakeLock.wrap(() -> {
-            if (mDreaming) {
-                startDozing();
-
-                // From this point until onDreamingStopped we will need to hold a
-                // wakelock whenever we are doing work.  Note that we never call
-                // stopDozing because can we just keep dozing until the bitter end.
-            }
-        }));
-
-        if (mDozeParameters.getAlwaysOn()) {
-            mTimeTick.onAlarm();
-        }
+        mDozeMachine.requestState(DozeMachine.State.INITIALIZED);
+        startDozing();
     }
 
     @Override
     public void onDreamingStopped() {
-        if (DEBUG) Log.d(mTag, "onDreamingStopped isDozing=" + isDozing());
         super.onDreamingStopped();
-
-        if (mHost == null) {
-            return;
-        }
-
-        mDreaming = false;
-        listenForPulseSignals(false);
-
-        // Tell the host that it's over.
-        mHost.stopDozing();
-        mAlarmManager.cancel(mTimeTick);
-    }
-
-    private void requestPulse(final int reason) {
-        requestPulse(reason, false /* performedProxCheck */);
-    }
-
-    private void requestPulse(final int reason, boolean performedProxCheck) {
-        Assert.isMainThread();
-        if (mHost != null && mDreaming && !mPulsing) {
-            // Let the host know we want to pulse.  Wait for it to be ready, then
-            // turn the screen on.  When finished, turn the screen off again.
-            // Here we need a wakelock to stay awake until the pulse is finished.
-            mWakeLock.acquire();
-            mPulsing = true;
-            if (!mDozeParameters.getProxCheckBeforePulse()) {
-                // skip proximity check
-                continuePulsing(reason);
-                return;
-            }
-            final long start = SystemClock.uptimeMillis();
-            if (performedProxCheck) {
-                // the caller already performed a successful proximity check; we'll only do one to
-                // capture statistics, continue pulsing immediately.
-                continuePulsing(reason);
-            }
-            // perform a proximity check
-            new ProximityCheck() {
-                @Override
-                public void onProximityResult(int result) {
-                    final boolean isNear = result == RESULT_NEAR;
-                    final long end = SystemClock.uptimeMillis();
-                    DozeLog.traceProximityResult(mContext, isNear, end - start, reason);
-                    if (performedProxCheck) {
-                        // we already continued
-                        return;
-                    }
-                    // avoid pulsing in pockets
-                    if (isNear) {
-                        mPulsing = false;
-                        mWakeLock.release();
-                        return;
-                    }
-
-                    // not in-pocket, continue pulsing
-                    continuePulsing(reason);
-                }
-            }.check();
-        }
-    }
-
-    private void continuePulsing(int reason) {
-        if (mHost.isPulsingBlocked()) {
-            mPulsing = false;
-            mWakeLock.release();
-            return;
-        }
-        mHost.pulseWhileDozing(new DozeHost.PulseCallback() {
-            @Override
-            public void onPulseStarted() {
-                if (mPulsing && mDreaming) {
-                    turnDisplayOn();
-                }
-            }
-
-            @Override
-            public void onPulseFinished() {
-                if (mPulsing && mDreaming) {
-                    mPulsing = false;
-                    if (REREGISTER_ALL_SENSORS_ON_SCREEN_OFF) {
-                        mDozeSensors.reregisterAllSensors();
-                    }
-                    turnDisplayOff();
-                }
-                mWakeLock.release(); // needs to be unconditional to balance acquire
-            }
-        }, reason);
-    }
-
-    private void turnDisplayOff() {
-        if (DEBUG) Log.d(mTag, "Display off");
-        if (mDozeParameters.getAlwaysOn()) {
-            turnDisplayOn();
-        } else {
-            setDozeScreenState(Display.STATE_OFF);
-        }
-    }
-
-    private void turnDisplayOn() {
-        if (DEBUG) Log.d(mTag, "Display on");
-        setDozeScreenState(mDisplayStateSupported ? Display.STATE_DOZE : Display.STATE_ON);
-    }
-
-    private void finishToSavePower() {
-        Log.w(mTag, "Exiting ambient mode due to low power battery saver");
-        finish();
-    }
-
-    private void finishForCarMode() {
-        Log.w(mTag, "Exiting ambient mode, not allowed in car mode");
-        finish();
-    }
-
-    private void listenForPulseSignals(boolean listen) {
-        if (DEBUG) Log.d(mTag, "listenForPulseSignals: " + listen);
-        mDozeSensors.setListening(listen);
-        listenForBroadcasts(listen);
-        listenForNotifications(listen);
-    }
-
-    private void listenForBroadcasts(boolean listen) {
-        if (listen) {
-            final IntentFilter filter = new IntentFilter(PULSE_ACTION);
-            filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE);
-            filter.addAction(Intent.ACTION_USER_SWITCHED);
-            mContext.registerReceiver(mBroadcastReceiver, filter);
-
-            mBroadcastReceiverRegistered = true;
-        } else {
-            if (mBroadcastReceiverRegistered) {
-                mContext.unregisterReceiver(mBroadcastReceiver);
-            }
-            mBroadcastReceiverRegistered = false;
-        }
-    }
-
-    private void listenForNotifications(boolean listen) {
-        if (listen) {
-            mHost.addCallback(mHostCallback);
-        } else {
-            mHost.removeCallback(mHostCallback);
-        }
-    }
-
-    private void requestNotificationPulse() {
-        if (DEBUG) Log.d(mTag, "requestNotificationPulse");
-        if (!mConfig.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) return;
-        mNotificationPulseTime = SystemClock.elapsedRealtime();
-        requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
+        mDozeMachine.requestState(DozeMachine.State.FINISH);
     }
 
     @Override
-    public void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck) {
-        requestPulse(pulseReason, sensorPerformedProxCheck);
-
-        if (pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP) {
-            final long timeSinceNotification =
-                    SystemClock.elapsedRealtime() - mNotificationPulseTime;
-            final boolean withinVibrationThreshold =
-                    timeSinceNotification < mDozeParameters.getPickupVibrationThreshold();
-            DozeLog.tracePickupPulse(mContext, withinVibrationThreshold);
-        }
-
-    }
-
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (PULSE_ACTION.equals(intent.getAction())) {
-                if (DEBUG) Log.d(mTag, "Received pulse intent");
-                requestPulse(DozeLog.PULSE_REASON_INTENT);
-            }
-            if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
-                mCarMode = true;
-                if (mCarMode && mDreaming) {
-                    finishForCarMode();
-                }
-            }
-            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
-                mDozeSensors.onUserSwitched();
-            }
-        }
-    };
-
-    private AlarmManager.OnAlarmListener mTimeTick = new AlarmManager.OnAlarmListener() {
-        @Override
-        public void onAlarm() {
-            mHost.dozeTimeTick();
-
-            // Keep wakelock until a frame has been pushed.
-            mHandler.post(mWakeLock.wrap(()->{}));
-
-            Calendar calendar = GregorianCalendar.getInstance();
-            calendar.setTimeInMillis(System.currentTimeMillis());
-            calendar.set(Calendar.MILLISECOND, 0);
-            calendar.set(Calendar.SECOND, 0);
-            calendar.add(Calendar.MINUTE, 1);
-
-            long delta = calendar.getTimeInMillis() - System.currentTimeMillis();
-            mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                    SystemClock.elapsedRealtime() + delta, "doze_time_tick", mTimeTick, mHandler);
-        }
-    };
-
-    private final DozeHost.Callback mHostCallback = new DozeHost.Callback() {
-        @Override
-        public void onNewNotifications() {
-            if (DEBUG) Log.d(mTag, "onNewNotifications (noop)");
-            // noop for now
-        }
-
-        @Override
-        public void onBuzzBeepBlinked() {
-            if (DEBUG) Log.d(mTag, "onBuzzBeepBlinked");
-            requestNotificationPulse();
-        }
-
-        @Override
-        public void onNotificationLight(boolean on) {
-            if (DEBUG) Log.d(mTag, "onNotificationLight (noop) on=" + on);
-            // noop for now
-        }
-
-        @Override
-        public void onPowerSaveChanged(boolean active) {
-            mPowerSaveActive = active;
-            if (mPowerSaveActive && mDreaming) {
-                finishToSavePower();
-            }
-        }
-    };
-
-    private abstract class ProximityCheck implements SensorEventListener, Runnable {
-        private static final int TIMEOUT_DELAY_MS = 500;
-
-        protected static final int RESULT_UNKNOWN = 0;
-        protected static final int RESULT_NEAR = 1;
-        protected static final int RESULT_FAR = 2;
-
-        private final String mTag = DozeService.this.mTag + ".ProximityCheck";
-
-        private boolean mRegistered;
-        private boolean mFinished;
-        private float mMaxRange;
-
-        abstract public void onProximityResult(int result);
-
-        public void check() {
-            if (mFinished || mRegistered) return;
-            final Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
-            if (sensor == null) {
-                if (DEBUG) Log.d(mTag, "No sensor found");
-                finishWithResult(RESULT_UNKNOWN);
-                return;
-            }
-            mDozeSensors.setDisableSensorsInterferingWithProximity(true);
-
-            mMaxRange = sensor.getMaximumRange();
-            mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0,
-                    mHandler);
-            mHandler.postDelayed(this, TIMEOUT_DELAY_MS);
-            mRegistered = true;
-        }
-
-        @Override
-        public void onSensorChanged(SensorEvent event) {
-            if (event.values.length == 0) {
-                if (DEBUG) Log.d(mTag, "Event has no values!");
-                finishWithResult(RESULT_UNKNOWN);
-            } else {
-                if (DEBUG) Log.d(mTag, "Event: value=" + event.values[0] + " max=" + mMaxRange);
-                final boolean isNear = event.values[0] < mMaxRange;
-                finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR);
-            }
-        }
-
-        @Override
-        public void run() {
-            if (DEBUG) Log.d(mTag, "No event received before timeout");
-            finishWithResult(RESULT_UNKNOWN);
-        }
-
-        private void finishWithResult(int result) {
-            if (mFinished) return;
-            if (mRegistered) {
-                mHandler.removeCallbacks(this);
-                mSensorManager.unregisterListener(this);
-                mDozeSensors.setDisableSensorsInterferingWithProximity(false);
-                mRegistered = false;
-            }
-            onProximityResult(result);
-            mFinished = true;
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-            // noop
-        }
+    protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mDozeMachine.dump(pw);
     }
 }