diff options
7 files changed, 479 insertions, 2 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 90e1c0723945..7139d59c6eef 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -43,6 +43,8 @@ public class DozeLog { public static final int PULSE_REASON_SENSOR_PICKUP = 3; public static final int PULSE_REASON_SENSOR_DOUBLE_TAP = 4; + private static boolean sRegisterKeyguardCallback = true; + private static long[] sTimes; private static String[] sMessages; private static int sPosition; @@ -103,7 +105,9 @@ public class DozeLog { sProxStats[i][1] = new SummaryStats(); } log("init"); - KeyguardUpdateMonitor.getInstance(context).registerCallback(sKeyguardCallback); + if (sRegisterKeyguardCallback) { + KeyguardUpdateMonitor.getInstance(context).registerCallback(sKeyguardCallback); + } } } } @@ -218,6 +222,31 @@ public class DozeLog { if (DEBUG) Log.d(TAG, msg); } + public static void tracePulseDropped(Context context, boolean pulsePending, + DozeMachine.State state, boolean blocked) { + if (!ENABLED) return; + init(context); + log("pulseDropped pulsePending=" + pulsePending + " state=" + + state + " blocked=" + blocked); + } + + public static void tracePulseCanceledByProx(Context context) { + if (!ENABLED) return; + init(context); + log("pulseCanceledByProx"); + } + + public static void setRegisterKeyguardCallback(boolean registerKeyguardCallback) { + if (!ENABLED) return; + synchronized (DozeLog.class) { + if (sRegisterKeyguardCallback != registerKeyguardCallback && sMessages != null) { + throw new IllegalStateException("Cannot change setRegisterKeyguardCallback " + + "after init()"); + } + sRegisterKeyguardCallback = registerKeyguardCallback; + } + } + private static class SummaryStats { private int mCount; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index 1cc5fb956c99..38b32e950225 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -42,7 +42,7 @@ public class DozeMachine { static final String TAG = "DozeMachine"; static final boolean DEBUG = DozeService.DEBUG; - enum State { + public 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. */ diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 9b3593b9cbdd..ea55c5f8bd6e 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -119,6 +119,7 @@ public class DozeTriggers implements DozeMachine.Part { DozeMachine.State state = mMachine.getState(); if (near && state == DozeMachine.State.DOZE_PULSING) { if (DEBUG) Log.i(TAG, "Prox NEAR, ending pulse"); + DozeLog.tracePulseCanceledByProx(mContext); mMachine.requestState(DozeMachine.State.DOZE_PULSE_DONE); } if (far && state == DozeMachine.State.DOZE_AOD_PAUSED) { @@ -181,6 +182,10 @@ public class DozeTriggers implements DozeMachine.Part { Assert.isMainThread(); mDozeHost.extendPulse(); if (mPulsePending || !mAllowPulseTriggers || !canPulse()) { + if (mAllowPulseTriggers) { + DozeLog.tracePulseDropped(mContext, mPulsePending, mMachine.getState(), + mDozeHost.isPulsingBlocked()); + } return; } @@ -204,6 +209,7 @@ public class DozeTriggers implements DozeMachine.Part { } // avoid pulsing in pockets if (result == RESULT_NEAR) { + mPulsePending = false; return; } @@ -221,6 +227,8 @@ public class DozeTriggers implements DozeMachine.Part { private void continuePulseRequest(int reason) { mPulsePending = false; if (mDozeHost.isPulsingBlocked() || !canPulse()) { + DozeLog.tracePulseDropped(mContext, mPulsePending, mMachine.getState(), + mDozeHost.isPulsingBlocked()); return; } mMachine.requestState(DozeMachine.State.DOZE_REQUEST_PULSE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java new file mode 100644 index 000000000000..3aef2478ecae --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 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 org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import com.android.internal.hardware.AmbientDisplayConfiguration; +import com.android.systemui.statusbar.phone.DozeParameters; + +import org.mockito.Answers; +import org.mockito.MockSettings; + +public class DozeConfigurationUtil { + public static DozeParameters createMockParameters() { + boolean[] doneHolder = new boolean[1]; + DozeParameters params = mock(DozeParameters.class, noDefaultAnswer(doneHolder)); + + when(params.getPulseOnSigMotion()).thenReturn(false); + when(params.getSensorsWakeUpFully()).thenReturn(false); + when(params.getPickupVibrationThreshold()).thenReturn(0); + when(params.getProxCheckBeforePulse()).thenReturn(true); + when(params.getPickupSubtypePerformsProxCheck(anyInt())).thenReturn(true); + + doneHolder[0] = true; + return params; + } + + public static AmbientDisplayConfiguration createMockConfig() { + boolean[] doneHolder = new boolean[1]; + AmbientDisplayConfiguration config = mock(AmbientDisplayConfiguration.class, + noDefaultAnswer(doneHolder)); + when(config.pulseOnDoubleTapEnabled(anyInt())).thenReturn(false); + when(config.pulseOnPickupEnabled(anyInt())).thenReturn(false); + when(config.pulseOnNotificationEnabled(anyInt())).thenReturn(true); + + when(config.doubleTapSensorType()).thenReturn(null); + when(config.pulseOnPickupAvailable()).thenReturn(false); + + doneHolder[0] = true; + return config; + } + + private static MockSettings noDefaultAnswer(boolean[] setupDoneHolder) { + return withSettings().defaultAnswer((i) -> { + if (setupDoneHolder[0]) { + throw new IllegalArgumentException("not defined"); + } else { + return Answers.RETURNS_DEFAULTS.answer(i); + } + }); + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java new file mode 100644 index 000000000000..d2afa2aae28a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017 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.NonNull; +import android.app.PendingIntent; + +/** + * A rudimentary fake for DozeHost. + */ +class DozeHostFake implements DozeHost { + Callback callback; + private boolean pulseAborted; + private boolean pulseExtended; + + @Override + public void addCallback(@NonNull Callback callback) { + this.callback = callback; + } + + @Override + public void removeCallback(@NonNull Callback callback) { + this.callback = null; + } + + @Override + public void startDozing() { + throw new RuntimeException("not implemented"); + } + + @Override + public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) { + throw new RuntimeException("not implemented"); + } + + @Override + public void stopDozing() { + throw new RuntimeException("not implemented"); + } + + @Override + public void dozeTimeTick() { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean isPowerSaveActive() { + return false; + } + + @Override + public boolean isPulsingBlocked() { + return false; + } + + @Override + public void startPendingIntentDismissingKeyguard(PendingIntent intent) { + throw new RuntimeException("not implemented"); + } + + @Override + public void abortPulsing() { + pulseAborted = true; + } + + @Override + public void extendPulse() { + pulseExtended = true; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java new file mode 100644 index 000000000000..12e75a14ba2b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017 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 org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import android.app.Instrumentation; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.support.test.InstrumentationRegistry; + +import com.android.internal.hardware.AmbientDisplayConfiguration; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.util.wakelock.WakeLock; +import com.android.systemui.util.wakelock.WakeLockFake; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Answers; +import org.mockito.MockSettings; + +public class DozeTriggersTest { + private Context mContext; + private DozeTriggers mTriggers; + private DozeMachine mMachine; + private DozeHostFake mHost; + private AmbientDisplayConfiguration mConfig; + private DozeParameters mParameters; + private SensorManagerFake mSensors; + private Handler mHandler; + private WakeLock mWakeLock; + private Instrumentation mInstrumentation; + + @BeforeClass + public static void setupSuite() { + // We can't use KeyguardUpdateMonitor from tests. + DozeLog.setRegisterKeyguardCallback(false); + } + + @Before + public void setUp() throws Exception { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mContext = InstrumentationRegistry.getContext(); + mMachine = mock(DozeMachine.class); + mHost = new DozeHostFake(); + mConfig = DozeConfigurationUtil.createMockConfig(); + mParameters = DozeConfigurationUtil.createMockParameters(); + mSensors = new SensorManagerFake(mContext); + mHandler = new Handler(Looper.getMainLooper()); + mWakeLock = new WakeLockFake(); + + mInstrumentation.runOnMainSync(() -> { + mTriggers = new DozeTriggers(mContext, mMachine, mHost, + mConfig, mParameters, mSensors, mHandler, mWakeLock, true); + }); + } + + @Test + @Ignore("setup crashes on virtual devices") + public void testOnNotification_stillWorksAfterOneFailedProxCheck() throws Exception { + when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE); + + mInstrumentation.runOnMainSync(()->{ + mTriggers.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED); + mTriggers.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.DOZE); + + mHost.callback.onNotificationHeadsUp(); + }); + + mInstrumentation.runOnMainSync(() -> { + mSensors.PROXIMITY.sendProximityResult(false); /* Near */ + }); + + verify(mMachine, never()).requestState(any()); + + mInstrumentation.runOnMainSync(()->{ + mHost.callback.onNotificationHeadsUp(); + }); + + mInstrumentation.runOnMainSync(() -> { + mSensors.PROXIMITY.sendProximityResult(true); /* Far */ + }); + + verify(mMachine).requestState(DozeMachine.State.DOZE_REQUEST_PULSE); + } + +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/SensorManagerFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/SensorManagerFake.java new file mode 100644 index 000000000000..5b4b8917d8d1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/SensorManagerFake.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2017 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; +import android.hardware.HardwareBuffer; +import android.hardware.Sensor; +import android.hardware.SensorAdditionalInfo; +import android.hardware.SensorDirectChannel; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.TriggerEventListener; +import android.os.Handler; +import android.os.MemoryFile; +import android.os.SystemClock; +import android.util.ArraySet; + +import com.google.android.collect.Lists; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; + +/** + * Rudimentary fake for SensorManager + * + * Currently only supports the proximity sensor. + * + * Note that this class ignores the "Handler" argument, so the test is responsible for calling the + * listener on the right thread. + */ +public class SensorManagerFake extends SensorManager { + + public MockSensor PROXIMITY; + + public SensorManagerFake(Context context) { + PROXIMITY = new MockSensor(context.getSystemService(SensorManager.class) + .getDefaultSensor(Sensor.TYPE_PROXIMITY)); + } + + @Override + protected List<Sensor> getFullSensorList() { + return Lists.newArrayList(PROXIMITY.sensor); + } + + @Override + protected List<Sensor> getFullDynamicSensorList() { + return new ArrayList<>(); + } + + @Override + protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) { + if (sensor == PROXIMITY.sensor || sensor == null) { + PROXIMITY.listeners.remove(listener); + } + } + + @Override + protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor, + int delayUs, + Handler handler, int maxReportLatencyUs, int reservedFlags) { + if (sensor == PROXIMITY.sensor) { + PROXIMITY.listeners.add(listener); + return true; + } + return false; + } + + @Override + protected boolean flushImpl(SensorEventListener listener) { + return false; + } + + @Override + protected SensorDirectChannel createDirectChannelImpl(MemoryFile memoryFile, + HardwareBuffer hardwareBuffer) { + return null; + } + + @Override + protected void destroyDirectChannelImpl(SensorDirectChannel channel) { + + } + + @Override + protected int configureDirectChannelImpl(SensorDirectChannel channel, Sensor s, int rate) { + return 0; + } + + @Override + protected void registerDynamicSensorCallbackImpl(DynamicSensorCallback callback, + Handler handler) { + + } + + @Override + protected void unregisterDynamicSensorCallbackImpl( + DynamicSensorCallback callback) { + + } + + @Override + protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) { + return false; + } + + @Override + protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor, + boolean disable) { + return false; + } + + @Override + protected boolean initDataInjectionImpl(boolean enable) { + return false; + } + + @Override + protected boolean injectSensorDataImpl(Sensor sensor, float[] values, int accuracy, + long timestamp) { + return false; + } + + @Override + protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) { + return false; + } + + public class MockSensor { + final Sensor sensor; + final ArraySet<SensorEventListener> listeners = new ArraySet<>(); + + private MockSensor(Sensor sensor) { + this.sensor = sensor; + } + + public void sendProximityResult(boolean far) { + SensorEvent event = createSensorEvent(1); + event.values[0] = far ? sensor.getMaximumRange() : 0; + for (SensorEventListener listener : listeners) { + listener.onSensorChanged(event); + } + } + + private SensorEvent createSensorEvent(int valuesSize) { + SensorEvent event; + try { + Constructor<SensorEvent> constr = + SensorEvent.class.getDeclaredConstructor(Integer.TYPE); + constr.setAccessible(true); + event = constr.newInstance(valuesSize); + } catch (Exception e) { + throw new RuntimeException(e); + } + event.sensor = sensor; + event.timestamp = SystemClock.elapsedRealtimeNanos(); + + return event; + } + } +} |