diff options
author | 2016-11-07 22:16:58 +0000 | |
---|---|---|
committer | 2016-11-07 22:17:01 +0000 | |
commit | ca67fa44ffc8a39701bfa0ca155cfa087d8806ce (patch) | |
tree | c0f23cce19d03c08bddba97e40100691a88f6676 | |
parent | e6022a40ad79ae59aa602bb251d92572c7db27aa (diff) | |
parent | ff2c4563cdee60576847e161678549bc501e8d84 (diff) |
Merge "Doze: Refactor v1"
11 files changed, 1168 insertions, 498 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java new file mode 100644 index 000000000000..4cfc811de1a0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 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.systemui.doze; + +import android.app.Application; +import android.content.Context; +import android.hardware.SensorManager; +import android.os.Handler; +import android.os.PowerManager; + +import com.android.internal.hardware.AmbientDisplayConfiguration; +import com.android.systemui.SystemUIApplication; +import com.android.systemui.statusbar.phone.DozeParameters; + +public class DozeFactory { + + /** Creates a DozeMachine with its parts for {@code dozeService}. */ + public static DozeMachine assembleMachine(DozeService dozeService) { + Context context = dozeService; + SensorManager sensorManager = context.getSystemService(SensorManager.class); + PowerManager powerManager = context.getSystemService(PowerManager.class); + + DozeHost host = getHost(dozeService); + AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(context); + DozeParameters params = new DozeParameters(context); + Handler handler = new Handler(); + DozeFactory.WakeLock wakeLock = new DozeFactory.WakeLock(powerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, "Doze")); + + DozeMachine machine = new DozeMachine(dozeService, params, wakeLock); + machine.setParts(new DozeMachine.Part[]{ + new DozeTriggers(context, machine, host, config, params, + sensorManager, handler, wakeLock), + new DozeUi(context, machine, wakeLock, host), + }); + + return machine; + } + + private static DozeHost getHost(DozeService service) { + Application appCandidate = service.getApplication(); + final SystemUIApplication app = (SystemUIApplication) appCandidate; + return app.getComponent(DozeHost.class); + } + + /** A wrapper around {@link PowerManager.WakeLock} for testability. */ + public static class WakeLock { + private final PowerManager.WakeLock mInner; + + public WakeLock(PowerManager.WakeLock inner) { + mInner = inner; + } + + /** @see PowerManager.WakeLock#acquire() */ + public void acquire() { + mInner.acquire(); + } + + /** @see PowerManager.WakeLock#release() */ + public void release() { + mInner.release(); + } + + /** @see PowerManager.WakeLock#wrap(Runnable) */ + public Runnable wrap(Runnable runnable) { + return mInner.wrap(runnable); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java index 02a98b0df7fc..fb940b511b24 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java @@ -24,7 +24,7 @@ import android.annotation.NonNull; public interface DozeHost { void addCallback(@NonNull Callback callback); void removeCallback(@NonNull Callback callback); - void startDozing(@NonNull Runnable ready); + void startDozing(); void pulseWhileDozing(@NonNull PulseCallback callback, int reason); void stopDozing(); void dozeTimeTick(); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java new file mode 100644 index 000000000000..c633aa15c8e3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2016 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.systemui.doze; + +import android.annotation.MainThread; +import android.util.Log; +import android.view.Display; + +import com.android.internal.util.Preconditions; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.util.Assert; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Orchestrates all things doze. + * + * DozeMachine implements a state machine that orchestrates how the UI and triggers work and + * interfaces with the power and screen states. + * + * During state transitions and in certain states, DozeMachine holds a wake lock. + */ +public class DozeMachine { + + static final String TAG = "DozeMachine"; + static final boolean DEBUG = DozeService.DEBUG; + + enum State { + /** Default state. Transition to INITIALIZED to get Doze going. */ + UNINITIALIZED, + /** Doze components are set up. Followed by transition to DOZE or DOZE_AOD. */ + INITIALIZED, + /** Regular doze. Device is asleep and listening for pulse triggers. */ + DOZE, + /** Always-on doze. Device is asleep, showing UI and listening for pulse triggers. */ + DOZE_AOD, + /** Pulse has been requested. Device is awake and preparing UI */ + DOZE_REQUEST_PULSE, + /** Pulse is showing. Device is awake and showing UI. */ + DOZE_PULSING, + /** Pulse is done showing. Followed by transition to DOZE or DOZE_AOD. */ + DOZE_PULSE_DONE, + /** Doze is done. DozeService is finished. */ + FINISH, + } + + private final Service mDozeService; + private final DozeFactory.WakeLock mWakeLock; + private final DozeParameters mParams; + private Part[] mParts; + + private final ArrayList<State> mQueuedRequests = new ArrayList<>(); + private State mState = State.UNINITIALIZED; + private boolean mWakeLockHeldForCurrentState = false; + + public DozeMachine(Service service, DozeParameters params, DozeFactory.WakeLock wakeLock) { + mDozeService = service; + mParams = params; + mWakeLock = wakeLock; + } + + /** Initializes the set of {@link Part}s. Must be called exactly once after construction. */ + public void setParts(Part[] parts) { + Preconditions.checkState(mParts == null); + mParts = parts; + } + + /** + * Requests transitioning to {@code requestedState}. + * + * This can be called during a state transition, in which case it will be queued until all + * queued state transitions are done. + * + * A wake lock is held while the transition is happening. + * + * Note that {@link #transitionPolicy} can modify what state will be transitioned to. + */ + @MainThread + public void requestState(State requestedState) { + Assert.isMainThread(); + if (DEBUG) { + Log.i(TAG, "request: current=" + mState + " req=" + requestedState, + new Throwable("here")); + } + + boolean runNow = !isExecutingTransition(); + mQueuedRequests.add(requestedState); + if (runNow) { + mWakeLock.acquire(); + for (int i = 0; i < mQueuedRequests.size(); i++) { + // Transitions in Parts can call back into requestState, which will + // cause mQueuedRequests to grow. + transitionTo(mQueuedRequests.get(i)); + } + mQueuedRequests.clear(); + mWakeLock.release(); + } + } + + /** + * @return the current state. + * + * This must not be called during a transition. + */ + @MainThread + public State getState() { + Assert.isMainThread(); + Preconditions.checkState(!isExecutingTransition()); + return mState; + } + + private boolean isExecutingTransition() { + return !mQueuedRequests.isEmpty(); + } + + private void transitionTo(State requestedState) { + State newState = transitionPolicy(requestedState); + + if (DEBUG) { + Log.i(TAG, "transition: old=" + mState + " req=" + requestedState + " new=" + newState); + } + + if (newState == mState) { + return; + } + + validateTransition(newState); + + State oldState = mState; + mState = newState; + + performTransitionOnComponents(oldState, newState); + updateScreenState(newState); + updateWakeLockState(newState); + + resolveIntermediateState(newState); + } + + private void performTransitionOnComponents(State oldState, State newState) { + for (Part p : mParts) { + p.transitionTo(oldState, newState); + } + + switch (newState) { + case FINISH: + mDozeService.finish(); + break; + default: + } + } + + private void validateTransition(State newState) { + switch (mState) { + case FINISH: + Preconditions.checkState(newState == State.FINISH); + break; + case UNINITIALIZED: + Preconditions.checkState(newState == State.INITIALIZED); + break; + } + switch (newState) { + case UNINITIALIZED: + throw new IllegalArgumentException("can't go to UNINITIALIZED"); + case INITIALIZED: + Preconditions.checkState(mState == State.UNINITIALIZED); + break; + case DOZE_PULSING: + Preconditions.checkState(mState == State.DOZE_REQUEST_PULSE); + break; + case DOZE_PULSE_DONE: + Preconditions.checkState(mState == State.DOZE_PULSING); + break; + default: + break; + } + } + + private int screenPolicy(State newState) { + switch (newState) { + case UNINITIALIZED: + case INITIALIZED: + case DOZE: + return Display.STATE_OFF; + case DOZE_PULSING: + case DOZE_AOD: + return Display.STATE_DOZE; // TODO: use STATE_ON if appropriate. + default: + return Display.STATE_UNKNOWN; + } + } + + private boolean wakeLockPolicy(State newState) { + switch (newState) { + case DOZE_REQUEST_PULSE: + case DOZE_PULSING: + return true; + default: + return false; + } + } + + private State transitionPolicy(State requestedState) { + if (mState == State.FINISH) { + return State.FINISH; + } + return requestedState; + } + + private void updateWakeLockState(State newState) { + boolean newPolicy = wakeLockPolicy(newState); + if (mWakeLockHeldForCurrentState && !newPolicy) { + mWakeLock.release(); + } else if (!mWakeLockHeldForCurrentState && newPolicy) { + mWakeLock.acquire(); + } + } + + private void updateScreenState(State newState) { + int state = screenPolicy(newState); + if (state != Display.STATE_UNKNOWN) { + mDozeService.setDozeScreenState(state); + } + } + + private void resolveIntermediateState(State state) { + switch (state) { + case INITIALIZED: + case DOZE_PULSE_DONE: + transitionTo(mParams.getAlwaysOn() + ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE); + break; + default: + break; + } + } + + /** Dumps the current state */ + public void dump(PrintWriter pw) { + pw.print(" state="); pw.println(mState); + pw.print(" wakeLockHeldForCurrentState="); pw.println(mWakeLockHeldForCurrentState); + pw.println("Parts:"); + for (Part p : mParts) { + p.dump(pw); + } + } + + /** A part of the DozeMachine that needs to be notified about state changes. */ + public interface Part { + /** + * Transition from {@code oldState} to {@code newState}. + * + * This method is guaranteed to only be called while a wake lock is held. + */ + void transitionTo(State oldState, State newState); + + /** Dump current state. For debugging only. */ + default void dump(PrintWriter pw) {} + } + + /** A wrapper interface for {@link android.service.dreams.DreamService} */ + public interface Service { + /** Finish dreaming. */ + void finish(); + + /** Request a display state. See {@link android.view.Display#STATE_DOZE}. */ + void setDozeScreenState(int state); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 81dfafc8d3dd..bb4ea2d7ee02 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -11,16 +11,11 @@ * 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 + * limitations under the License. */ package com.android.systemui.doze; -import com.android.internal.hardware.AmbientDisplayConfiguration; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.MetricsProto; -import com.android.systemui.statusbar.phone.DozeParameters; - import android.annotation.AnyThread; import android.app.ActivityManager; import android.content.ContentResolver; @@ -32,12 +27,17 @@ import android.hardware.TriggerEvent; import android.hardware.TriggerEventListener; import android.net.Uri; import android.os.Handler; -import android.os.PowerManager; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import com.android.internal.hardware.AmbientDisplayConfiguration; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.MetricsProto; +import com.android.systemui.statusbar.phone.DozeParameters; + +import java.io.PrintWriter; import java.util.List; public class DozeSensors { @@ -53,14 +53,14 @@ public class DozeSensors { private final TriggerSensor mPickupSensor; private final DozeParameters mDozeParameters; private final AmbientDisplayConfiguration mConfig; - private final PowerManager.WakeLock mWakeLock; + private final DozeFactory.WakeLock mWakeLock; private final Callback mCallback; private final Handler mHandler = new Handler(); public DozeSensors(Context context, SensorManager sensorManager, DozeParameters dozeParameters, - AmbientDisplayConfiguration config, PowerManager.WakeLock wakeLock, Callback callback) { + AmbientDisplayConfiguration config, DozeFactory.WakeLock wakeLock, Callback callback) { mContext = context; mSensorManager = sensorManager; mDozeParameters = dozeParameters; @@ -144,6 +144,13 @@ public class DozeSensors { mPickupSensor.setDisabled(disable); } + /** Dump current state */ + public void dump(PrintWriter pw) { + for (TriggerSensor s : mSensors) { + pw.print("Sensor: "); pw.println(s.toString()); + } + } + private class TriggerSensor extends TriggerEventListener { final Sensor mSensor; final boolean mConfigured; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index 2bb1d6a111ee..94cbdd41e220 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); } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java new file mode 100644 index 000000000000..9df8113b7ba6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2016 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.systemui.doze; + +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.SystemClock; +import android.os.UserHandle; +import android.text.format.Formatter; +import android.util.Log; + +import com.android.internal.hardware.AmbientDisplayConfiguration; +import com.android.internal.util.Preconditions; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.util.Assert; + +import java.io.PrintWriter; + +/** + * Handles triggers for ambient state changes. + */ +public class DozeTriggers implements DozeMachine.Part { + + private static final String TAG = "DozeTriggers"; + + /** adb shell am broadcast -a com.android.systemui.doze.pulse com.android.systemui */ + private static final String PULSE_ACTION = "com.android.systemui.doze.pulse"; + + private final Context mContext; + private final DozeMachine mMachine; + private final DozeSensors mDozeSensors; + private final DozeHost mDozeHost; + private final AmbientDisplayConfiguration mConfig; + private final DozeParameters mDozeParameters; + private final SensorManager mSensorManager; + private final Handler mHandler; + private final DozeFactory.WakeLock mWakeLock; + private final UiModeManager mUiModeManager; + private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver(); + + private long mNotificationPulseTime; + private boolean mPulsePending; + + + public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost, + AmbientDisplayConfiguration config, + DozeParameters dozeParameters, SensorManager sensorManager, Handler handler, + DozeFactory.WakeLock wakeLock) { + mContext = context; + mMachine = machine; + mDozeHost = dozeHost; + mConfig = config; + mDozeParameters = dozeParameters; + mSensorManager = sensorManager; + mHandler = handler; + mWakeLock = wakeLock; + mDozeSensors = new DozeSensors(context, mSensorManager, dozeParameters, config, + wakeLock, this::onSensor); + mUiModeManager = mContext.getSystemService(UiModeManager.class); + } + + private void onNotification() { + if (DozeMachine.DEBUG) Log.d(TAG, "requestNotificationPulse"); + mNotificationPulseTime = SystemClock.elapsedRealtime(); + if (!mConfig.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) return; + requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */); + } + + private void onWhisper() { + requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */); + } + + private void onSensor(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 void onCarMode() { + mMachine.requestState(DozeMachine.State.FINISH); + } + + private void onPowerSave() { + mMachine.requestState(DozeMachine.State.FINISH); + } + + @Override + public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { + switch (newState) { + case INITIALIZED: + mBroadcastReceiver.register(mContext); + mDozeHost.addCallback(mHostCallback); + checkTriggersAtInit(); + break; + case DOZE: + case DOZE_AOD: + mDozeSensors.setListening(true); + if (oldState != DozeMachine.State.INITIALIZED) { + mDozeSensors.reregisterAllSensors(); + } + break; + case FINISH: + mBroadcastReceiver.unregister(mContext); + mDozeHost.removeCallback(mHostCallback); + mDozeSensors.setListening(false); + break; + default: + } + } + + private void checkTriggersAtInit() { + if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) { + onCarMode(); + } + if (mDozeHost.isPowerSaveActive()) { + onPowerSave(); + } + } + + private void requestPulse(final int reason, boolean performedProxCheck) { + Assert.isMainThread(); + if (mPulsePending || !canPulse()) { + return; + } + + mPulsePending = true; + if (!mDozeParameters.getProxCheckBeforePulse() || performedProxCheck) { + // skip proximity check + continuePulseRequest(reason); + return; + } + + final long start = SystemClock.uptimeMillis(); + new ProximityCheck() { + @Override + public void onProximityResult(int result) { + final long end = SystemClock.uptimeMillis(); + DozeLog.traceProximityResult(mContext, result == RESULT_NEAR, + end - start, reason); + if (performedProxCheck) { + // we already continued + return; + } + // avoid pulsing in pockets + if (result == RESULT_NEAR) { + return; + } + + // not in-pocket, continue pulsing + continuePulseRequest(reason); + } + }.check(); + } + + private boolean canPulse() { + return mMachine.getState() == DozeMachine.State.DOZE + || mMachine.getState() == DozeMachine.State.DOZE_AOD; + } + + private void continuePulseRequest(int reason) { + mPulsePending = false; + if (mDozeHost.isPulsingBlocked() || !canPulse()) { + return; + } + mMachine.requestState(DozeMachine.State.DOZE_REQUEST_PULSE); + } + + @Override + public void dump(PrintWriter pw) { + pw.print(" notificationPulseTime="); + pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime)); + + pw.print(" pulsePending="); pw.println(mPulsePending); + pw.println("DozeSensors:"); + mDozeSensors.dump(pw); + } + + 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 boolean mRegistered; + private boolean mFinished; + private float mMaxRange; + + protected abstract void onProximityResult(int result); + + public void check() { + Preconditions.checkState(!mFinished && !mRegistered); + final Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); + if (sensor == null) { + if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: 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); + mWakeLock.acquire(); + mRegistered = true; + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.values.length == 0) { + if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: Event has no values!"); + finishWithResult(RESULT_UNKNOWN); + } else { + if (DozeMachine.DEBUG) { + Log.d(TAG, "ProxCheck: 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 (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No event received before timeout"); + finishWithResult(RESULT_UNKNOWN); + } + + private void finishWithResult(int result) { + if (mFinished) return; + boolean wasRegistered = mRegistered; + if (mRegistered) { + mHandler.removeCallbacks(this); + mSensorManager.unregisterListener(this); + mDozeSensors.setDisableSensorsInterferingWithProximity(false); + mRegistered = false; + } + onProximityResult(result); + if (wasRegistered) { + mWakeLock.release(); + } + mFinished = true; + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // noop + } + } + + private class TriggerReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (PULSE_ACTION.equals(intent.getAction())) { + if (DozeMachine.DEBUG) Log.d(TAG, "Received pulse intent"); + requestPulse(DozeLog.PULSE_REASON_INTENT, false /* performedProxCheck */); + } + if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) { + onCarMode(); + } + if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { + mDozeSensors.onUserSwitched(); + } + } + + public void register(Context context) { + IntentFilter filter = new IntentFilter(PULSE_ACTION); + filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE); + filter.addAction(Intent.ACTION_USER_SWITCHED); + context.registerReceiver(this, filter); + } + + public void unregister(Context context) { + context.unregisterReceiver(this); + } + } + + private DozeHost.Callback mHostCallback = new DozeHost.Callback() { + @Override + public void onNewNotifications() { + } + + @Override + public void onBuzzBeepBlinked() { + onNotification(); + } + + @Override + public void onNotificationLight(boolean on) { + + } + + @Override + public void onPowerSaveChanged(boolean active) { + if (active) { + onPowerSave(); + } + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java new file mode 100644 index 000000000000..95e49cea62f2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 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.systemui.doze; + +import android.content.Context; + +/** + * The policy controlling doze. + */ +public class DozeUi implements DozeMachine.Part { + + private final Context mContext; + private final DozeHost mHost; + private DozeFactory.WakeLock mWakeLock; + private DozeMachine mMachine; + + public DozeUi(Context context, DozeMachine machine, DozeFactory.WakeLock wakeLock, + DozeHost host) { + mContext = context; + mMachine = machine; + mWakeLock = wakeLock; + mHost = host; + } + + private void pulseWhileDozing(int reason) { + mHost.pulseWhileDozing( + new DozeHost.PulseCallback() { + @Override + public void onPulseStarted() { + mMachine.requestState(DozeMachine.State.DOZE_PULSING); + } + + @Override + public void onPulseFinished() { + mMachine.requestState(DozeMachine.State.DOZE_PULSE_DONE); + } + }, reason); + } + + @Override + public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { + switch (newState) { + case DOZE_REQUEST_PULSE: + pulseWhileDozing(DozeLog.PULSE_REASON_NOTIFICATION /* TODO */); + break; + case INITIALIZED: + mHost.startDozing(); + break; + case FINISH: + mHost.stopDozing(); + break; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 669a5125c66e..c33d91a9a29d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -5059,7 +5059,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private static final long PROCESSING_TIME = 500; private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); - private final H mHandler = new H(); // Keeps the last reported state by fireNotificationLight. private boolean mNotificationLightOn; @@ -5105,51 +5104,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void startDozing(@NonNull Runnable ready) { - mHandler.obtainMessage(H.MSG_START_DOZING, ready).sendToTarget(); - } - - @Override - public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) { - mHandler.obtainMessage(H.MSG_PULSE_WHILE_DOZING, reason, 0, callback).sendToTarget(); - } - - @Override - public void stopDozing() { - mHandler.obtainMessage(H.MSG_STOP_DOZING).sendToTarget(); - } - - @Override - public void dozeTimeTick() { - mKeyguardStatusView.refreshTime(); - } - - @Override - public boolean isPowerSaveActive() { - return mBatteryController != null && mBatteryController.isPowerSave(); - } - - @Override - public boolean isPulsingBlocked() { - return mFingerprintUnlockController.getMode() - == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK; - } - - @Override - public boolean isNotificationLightOn() { - return mNotificationLightOn; - } - - private void handleStartDozing(@NonNull Runnable ready) { + public void startDozing() { if (!mDozingRequested) { mDozingRequested = true; DozeLog.traceDozing(mContext, mDozing); updateDozing(); } - ready.run(); } - private void handlePulseWhileDozing(@NonNull PulseCallback callback, int reason) { + @Override + public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) { mDozeScrimController.pulse(new PulseCallback() { @Override @@ -5166,7 +5130,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, }, reason); } - private void handleStopDozing() { + @Override + public void stopDozing() { if (mDozingRequested) { mDozingRequested = false; DozeLog.traceDozing(mContext, mDozing); @@ -5174,25 +5139,26 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - private final class H extends Handler { - private static final int MSG_START_DOZING = 1; - private static final int MSG_PULSE_WHILE_DOZING = 2; - private static final int MSG_STOP_DOZING = 3; + @Override + public void dozeTimeTick() { + mKeyguardStatusView.refreshTime(); + } - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_START_DOZING: - handleStartDozing((Runnable) msg.obj); - break; - case MSG_PULSE_WHILE_DOZING: - handlePulseWhileDozing((PulseCallback) msg.obj, msg.arg1); - break; - case MSG_STOP_DOZING: - handleStopDozing(); - break; - } - } + @Override + public boolean isPowerSaveActive() { + return mBatteryController != null && mBatteryController.isPowerSave(); + } + + @Override + public boolean isPulsingBlocked() { + return mFingerprintUnlockController.getMode() + == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK; } + + @Override + public boolean isNotificationLightOn() { + return mNotificationLightOn; + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java new file mode 100644 index 000000000000..ba7c923efe71 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2016 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.systemui.doze; + +import static com.android.systemui.doze.DozeMachine.State.DOZE; +import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD; +import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE; +import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING; +import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE; +import static com.android.systemui.doze.DozeMachine.State.FINISH; +import static com.android.systemui.doze.DozeMachine.State.INITIALIZED; +import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.support.test.annotation.UiThreadTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.Display; + +import com.android.systemui.statusbar.phone.DozeParameters; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DozeMachineTest { + + DozeMachine mMachine; + + private DozeServiceFake mServiceFake; + private WakeLockFake mWakeLockFake; + private DozeParameters mParamsMock; + private DozeMachine.Part mPartMock; + + @Before + public void setUp() { + mServiceFake = new DozeServiceFake(); + mWakeLockFake = new WakeLockFake(); + mParamsMock = mock(DozeParameters.class); + mPartMock = mock(DozeMachine.Part.class); + + mMachine = new DozeMachine(mServiceFake, mParamsMock, mWakeLockFake); + + mMachine.setParts(new DozeMachine.Part[]{mPartMock}); + } + + @Test + @UiThreadTest + public void testInitialize_initializesParts() { + mMachine.requestState(INITIALIZED); + + verify(mPartMock).transitionTo(UNINITIALIZED, INITIALIZED); + } + + @Test + @UiThreadTest + public void testInitialize_goesToDoze() { + when(mParamsMock.getAlwaysOn()).thenReturn(false); + + mMachine.requestState(INITIALIZED); + + verify(mPartMock).transitionTo(INITIALIZED, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test + @UiThreadTest + public void testInitialize_goesToAod() { + when(mParamsMock.getAlwaysOn()).thenReturn(true); + + mMachine.requestState(INITIALIZED); + + verify(mPartMock).transitionTo(INITIALIZED, DOZE_AOD); + assertEquals(DOZE_AOD, mMachine.getState()); + } + + @Test + @UiThreadTest + public void testPulseDone_goesToDoze() { + when(mParamsMock.getAlwaysOn()).thenReturn(false); + mMachine.requestState(INITIALIZED); + mMachine.requestState(DOZE_REQUEST_PULSE); + mMachine.requestState(DOZE_PULSING); + + mMachine.requestState(DOZE_PULSE_DONE); + + verify(mPartMock).transitionTo(DOZE_PULSE_DONE, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test + @UiThreadTest + public void testPulseDone_goesToAoD() { + when(mParamsMock.getAlwaysOn()).thenReturn(true); + mMachine.requestState(INITIALIZED); + mMachine.requestState(DOZE_REQUEST_PULSE); + mMachine.requestState(DOZE_PULSING); + + mMachine.requestState(DOZE_PULSE_DONE); + + verify(mPartMock).transitionTo(DOZE_PULSE_DONE, DOZE_AOD); + assertEquals(DOZE_AOD, mMachine.getState()); + } + + @Test + @UiThreadTest + public void testFinished_staysFinished() { + mMachine.requestState(INITIALIZED); + mMachine.requestState(FINISH); + reset(mPartMock); + + mMachine.requestState(DOZE); + + verify(mPartMock, never()).transitionTo(any(), any()); + assertEquals(FINISH, mMachine.getState()); + } + + @Test + @UiThreadTest + public void testFinish_finishesService() { + mMachine.requestState(INITIALIZED); + + mMachine.requestState(FINISH); + + assertTrue(mServiceFake.finished); + } + + @Test + @UiThreadTest + public void testWakeLock_heldInTransition() { + doAnswer((inv) -> { + assertTrue(mWakeLockFake.isHeld()); + return null; + }).when(mPartMock).transitionTo(any(), any()); + + mMachine.requestState(INITIALIZED); + } + + @Test + @UiThreadTest + public void testWakeLock_heldInPulseStates() { + mMachine.requestState(INITIALIZED); + + mMachine.requestState(DOZE_REQUEST_PULSE); + assertTrue(mWakeLockFake.isHeld()); + + mMachine.requestState(DOZE_PULSING); + assertTrue(mWakeLockFake.isHeld()); + } + + @Test + @UiThreadTest + public void testWakeLock_notHeldInDozeStates() { + mMachine.requestState(INITIALIZED); + + mMachine.requestState(DOZE); + assertFalse(mWakeLockFake.isHeld()); + + mMachine.requestState(DOZE_AOD); + assertFalse(mWakeLockFake.isHeld()); + } + + @Test + @UiThreadTest + public void testScreen_offInDoze() { + mMachine.requestState(INITIALIZED); + + mMachine.requestState(DOZE); + + assertEquals(Display.STATE_OFF, mServiceFake.screenState); + } + + @Test + @UiThreadTest + public void testScreen_onInAod() { + mMachine.requestState(INITIALIZED); + + mMachine.requestState(DOZE_AOD); + + assertEquals(Display.STATE_DOZE, mServiceFake.screenState); + } + + @Test + @UiThreadTest + public void testScreen_onInPulse() { + mMachine.requestState(INITIALIZED); + + mMachine.requestState(DOZE_REQUEST_PULSE); + mMachine.requestState(DOZE_PULSING); + + assertEquals(Display.STATE_DOZE, mServiceFake.screenState); + } + + @Test + @UiThreadTest + public void testScreen_offInRequestPulseWithoutAoD() { + mMachine.requestState(INITIALIZED); + + mMachine.requestState(DOZE); + mMachine.requestState(DOZE_REQUEST_PULSE); + + assertEquals(Display.STATE_OFF, mServiceFake.screenState); + } + + @Test + @UiThreadTest + public void testScreen_onInRequestPulseWithoutAoD() { + mMachine.requestState(INITIALIZED); + + mMachine.requestState(DOZE_AOD); + mMachine.requestState(DOZE_REQUEST_PULSE); + + assertEquals(Display.STATE_DOZE, mServiceFake.screenState); + } + + @Test + @UiThreadTest + public void testTransitions_canRequestTransitions() { + mMachine.requestState(INITIALIZED); + mMachine.requestState(DOZE); + doAnswer(inv -> { + mMachine.requestState(DOZE_PULSING); + return null; + }).when(mPartMock).transitionTo(any(), eq(DOZE_REQUEST_PULSE)); + + mMachine.requestState(DOZE_REQUEST_PULSE); + + assertEquals(DOZE_PULSING, mMachine.getState()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeServiceFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeServiceFake.java new file mode 100644 index 000000000000..d12fc2cf33a0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeServiceFake.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 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.systemui.doze; + +import android.view.Display; + +public class DozeServiceFake implements DozeMachine.Service { + + public boolean finished; + public int screenState; + + public DozeServiceFake() { + reset(); + } + + @Override + public void finish() { + finished = true; + } + + @Override + public void setDozeScreenState(int state) { + screenState = state; + } + + public void reset() { + finished = false; + screenState = Display.STATE_UNKNOWN; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/WakeLockFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/WakeLockFake.java new file mode 100644 index 000000000000..7c04fe293213 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/WakeLockFake.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 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.systemui.doze; + +import com.android.internal.util.Preconditions; + +public class WakeLockFake extends DozeFactory.WakeLock { + + private int mAcquired = 0; + + public WakeLockFake() { + super(null); + } + + @Override + public void acquire() { + mAcquired++; + } + + @Override + public void release() { + Preconditions.checkState(mAcquired > 0); + mAcquired--; + } + + @Override + public Runnable wrap(Runnable runnable) { + acquire(); + return () -> { + try { + runnable.run(); + } finally { + release(); + } + }; + } + + public boolean isHeld() { + return mAcquired > 0; + } +} |