From 311fb193c3cfde3d4667d192572398ef41c8fbec Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Thu, 23 Apr 2020 14:01:46 -0400 Subject: [PATCH] RESTRICT AUTOMERGE ProximitySensor now supports dual-sensor approach. The ProximitySensor now supports the concept of a primary and a secondary hardware sensor. The primary sensor is used for a first pass check if the phone covered. When triggered, it then checks the secondary sensor for confirmation (if there is one). It does not send a proximity event until the secondary sensor confirms (or rejects) the reading. The secondary sensor is, in fact, the source of truth. This is necessary as sometimes keeping the secondary sensor on for extends periods is undesirable. It may, however, result in increased latency for proximity readings. Phones should configure this via a config.xml overlay. If no proximity sensor is set (primary or secondary) we fall back to the default Sensor.TYPE_PROXIMITY. If proximity_sensor_type is set in config.xml, that will be used as the primary sensor. If proximity_sensor_secondary_type is set, that will function as the secondary sensor. If no secondary is set, only the primary will be used. This CL also introduces the ThresholdSensor interface. This is a simple sensor wrapper for sensors that need to report a binary above/below value. ProximitySensor now implements this interface and also takes in ThresholdSensor as its primary and secondary inputs. Bug: 147026387 Test: atest SystemUITests && manual Change-Id: I1e808e591f1db05c664df6190670cb92315f63f3 Merged-In: I1e808e591f1db05c664df6190670cb92315f63f3 --- packages/SystemUI/res/values/config.xml | 8 + .../classifier/FalsingManagerProxy.java | 2 +- .../brightline/BrightLineFalsingManager.java | 5 +- .../brightline/FalsingClassifier.java | 2 +- .../brightline/ProximityClassifier.java | 4 +- .../systemui/dagger/SystemServicesModule.java | 7 + .../systemui/dagger/SystemUIModule.java | 4 +- .../android/systemui/doze/DozeSensors.java | 7 +- .../android/systemui/doze/DozeTriggers.java | 7 +- .../util/sensors/AsyncSensorManager.java | 4 +- .../util/sensors/PrimaryProxSensor.java | 30 ++ .../util/sensors/ProximitySensor.java | 283 +++++++++--------- .../util/sensors/SecondaryProxSensor.java | 30 ++ .../systemui/util/sensors/SensorModule.java | 64 ++++ .../util/sensors/ThresholdSensor.java | 118 ++++++++ .../util/sensors/ThresholdSensorImpl.java | 278 +++++++++++++++++ .../BrightLineFalsingManagerTest.java | 15 +- .../brightline/ProximityClassifierTest.java | 5 +- .../systemui/doze/DozeSensorsTest.java | 11 +- .../systemui/doze/DozeTriggersTest.java | 18 +- .../util/sensors/FakeProximitySensor.java | 12 +- .../util/sensors/FakeThresholdSensor.java | 81 +++++ .../util/sensors/ProximitySensorDualTest.java | 276 +++++++++++++++++ ...st.java => ProximitySensorSingleTest.java} | 148 ++++----- .../util/sensors/ThresholdSensorImplTest.java | 246 +++++++++++++++ 25 files changed, 1411 insertions(+), 254 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/util/sensors/PrimaryProxSensor.java create mode 100644 packages/SystemUI/src/com/android/systemui/util/sensors/SecondaryProxSensor.java create mode 100644 packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java create mode 100644 packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java create mode 100644 packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java rename packages/SystemUI/tests/src/com/android/systemui/util/sensors/{ProximitySensorTest.java => ProximitySensorSingleTest.java} (54%) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 62335abd4329e..3395c71bd5a31 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -205,6 +205,14 @@ far break points. A sensor value less than this is considered "near". --> + + + + + + 130 diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java index 79b691bb3e37c..cab805848e7bc 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java @@ -87,7 +87,7 @@ public class FalsingManagerProxy implements FalsingManager, Dumpable { mUiBgExecutor = uiBgExecutor; mStatusBarStateController = statusBarStateController; mProximitySensor.setTag(PROXIMITY_SENSOR_TAG); - mProximitySensor.setSensorDelay(SensorManager.SENSOR_DELAY_GAME); + mProximitySensor.setDelay(SensorManager.SENSOR_DELAY_GAME); mDeviceConfig = deviceConfig; mDeviceConfigListener = properties -> onDeviceConfigPropertiesChanged(context, properties.getNamespace()); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java index caab18712b0b3..fad6aaf06ab3a 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.sensors.ProximitySensor; +import com.android.systemui.util.sensors.ThresholdSensor; import java.io.PrintWriter; import java.util.ArrayDeque; @@ -76,7 +77,7 @@ public class BrightLineFalsingManager implements FalsingManager { private final List mClassifiers; - private ProximitySensor.ProximitySensorListener mSensorEventListener = this::onProximityEvent; + private ThresholdSensor.Listener mSensorEventListener = this::onProximityEvent; private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback = new KeyguardUpdateMonitorCallback() { @@ -240,7 +241,7 @@ public class BrightLineFalsingManager implements FalsingManager { mClassifiers.forEach((classifier) -> classifier.onTouchEvent(motionEvent)); } - private void onProximityEvent(ProximitySensor.ProximityEvent proximityEvent) { + private void onProximityEvent(ThresholdSensor.ThresholdSensorEvent proximityEvent) { // TODO: some of these classifiers might allow us to abort early, meaning we don't have to // make these calls. mClassifiers.forEach((classifier) -> classifier.onProximityEvent(proximityEvent)); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java index cf088213644e9..85e95a66bfe3c 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java @@ -96,7 +96,7 @@ abstract class FalsingClassifier { /** * Called when a ProximityEvent occurs (change in near/far). */ - void onProximityEvent(ProximitySensor.ProximityEvent proximityEvent) {}; + void onProximityEvent(ProximitySensor.ThresholdSensorEvent proximityEvent) {}; /** * The phone screen has turned on and we need to begin falsing detection. diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java index 749914e1b6251..b128678af5db2 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java @@ -101,8 +101,8 @@ class ProximityClassifier extends FalsingClassifier { @Override public void onProximityEvent( - ProximitySensor.ProximityEvent proximityEvent) { - boolean near = proximityEvent.getNear(); + ProximitySensor.ThresholdSensorEvent proximityEvent) { + boolean near = proximityEvent.getBelow(); long timestampNs = proximityEvent.getTimestampNs(); logDebug("Sensor is: " + near + " at time " + timestampNs); update(near, timestampNs); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java index 8f3dc224384bc..250fb2b678bc0 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java @@ -36,6 +36,7 @@ import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; import android.content.res.Resources; +import android.hardware.SensorManager; import android.hardware.SensorPrivacyManager; import android.media.AudioManager; import android.net.ConnectivityManager; @@ -240,6 +241,12 @@ public class SystemServicesModule { return context.getResources(); } + @Provides + @Singleton + static SensorManager providesSensorManager(Context context) { + return context.getSystemService(SensorManager.class); + } + @Singleton @Provides static SensorPrivacyManager provideSensorPrivacyManager(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 90cd13fd1330f..cb45926d3f240 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.concurrency.ConcurrencyModule; import com.android.systemui.util.sensors.AsyncSensorManager; +import com.android.systemui.util.sensors.SensorModule; import com.android.systemui.util.time.SystemClock; import com.android.systemui.util.time.SystemClockImpl; @@ -62,7 +63,8 @@ import dagger.Provides; ConcurrencyModule.class, LogModule.class, PeopleHubModule.class, - SettingsModule.class + SensorModule.class, + SettingsModule.class, }, subcomponents = {StatusBarComponent.class, NotificationRowComponent.class, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 700a8611c8bd7..8a8f0450550d0 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -81,7 +81,8 @@ public class DozeSensors { public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager, DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock, - Callback callback, Consumer proxCallback, DozeLog dozeLog) { + Callback callback, Consumer proxCallback, DozeLog dozeLog, + ProximitySensor proximitySensor) { mContext = context; mAlarmManager = alarmManager; mSensorManager = sensorManager; @@ -153,12 +154,12 @@ public class DozeSensors { dozeLog), }; - mProximitySensor = new ProximitySensor(context.getResources(), sensorManager); + mProximitySensor = proximitySensor; setProxListening(false); // Don't immediately start listening when we register. mProximitySensor.register( proximityEvent -> { if (proximityEvent != null) { - mProxCallback.accept(!proximityEvent.getNear()); + mProxCallback.accept(!proximityEvent.getBelow()); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index b3299916356cc..4d40986003ba7 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -92,8 +92,8 @@ public class DozeTriggers implements DozeMachine.Part { AlarmManager alarmManager, AmbientDisplayConfiguration config, DozeParameters dozeParameters, AsyncSensorManager sensorManager, Handler handler, WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager, - ProximitySensor proximitySensor, - DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher) { + ProximitySensor proximitySensor, DozeLog dozeLog, + BroadcastDispatcher broadcastDispatcher) { mContext = context; mMachine = machine; mDozeHost = dozeHost; @@ -103,7 +103,8 @@ public class DozeTriggers implements DozeMachine.Part { mWakeLock = wakeLock; mAllowPulseTriggers = allowPulseTriggers; mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters, - config, wakeLock, this::onSensor, this::onProximityFar, dozeLog); + config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, + proximitySensor); mUiModeManager = mContext.getSystemService(UiModeManager.class); mDockManager = dockManager; mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, handler); diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java index 2224c9cce40ae..450336a6b73bb 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java @@ -60,8 +60,8 @@ public class AsyncSensorManager extends SensorManager private final List mPlugins; @Inject - public AsyncSensorManager(Context context, PluginManager pluginManager) { - this(context.getSystemService(SensorManager.class), pluginManager, null); + public AsyncSensorManager(SensorManager sensorManager, PluginManager pluginManager) { + this(sensorManager, pluginManager, null); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/PrimaryProxSensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/PrimaryProxSensor.java new file mode 100644 index 0000000000000..96c76c1a15d0a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/PrimaryProxSensor.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 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.util.sensors; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +@interface PrimaryProxSensor { +} diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java index 378dde284747a..9be42f3fc5f48 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java @@ -16,92 +16,119 @@ package com.android.systemui.util.sensors; -import android.content.res.Resources; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Handler; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.Assert; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.function.Consumer; import javax.inject.Inject; /** - * Simple wrapper around SensorManager customized for the Proximity sensor. + * Wrapper around SensorManager customized for the Proximity sensor. + * + * The ProximitySensor supports the concept of a primary and a + * secondary hardware sensor. The primary sensor is used for a first + * pass check if the phone covered. When triggered, it then checks + * the secondary sensor for confirmation (if there is one). It does + * not send a proximity event until the secondary sensor confirms (or + * rejects) the reading. The secondary sensor is, in fact, the source + * of truth. + * + * This is necessary as sometimes keeping the secondary sensor on for + * extends periods is undesirable. It may, however, result in increased + * latency for proximity readings. + * + * Phones should configure this via a config.xml overlay. If no + * proximity sensor is set (primary or secondary) we fall back to the + * default Sensor.TYPE_PROXIMITY. If proximity_sensor_type is set in + * config.xml, that will be used as the primary sensor. If + * proximity_sensor_secondary_type is set, that will function as the + * secondary sensor. If no secondary is set, only the primary will be + * used. */ -public class ProximitySensor { +public class ProximitySensor implements ThresholdSensor { private static final String TAG = "ProxSensor"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final long SECONDARY_PING_INTERVAL_MS = 5000; - private final Sensor mSensor; - private final AsyncSensorManager mSensorManager; - private final float mThreshold; - private List mListeners = new ArrayList<>(); + private final ThresholdSensor mPrimaryThresholdSensor; + private final ThresholdSensor mSecondaryThresholdSensor; + private final DelayableExecutor mDelayableExecutor; + private final List mListeners = new ArrayList<>(); private String mTag = null; - @VisibleForTesting ProximityEvent mLastEvent; - private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL; + private ThresholdSensorEvent mLastPrimaryEvent; + @VisibleForTesting + ThresholdSensorEvent mLastEvent; private boolean mPaused; private boolean mRegistered; + private Runnable mCancelSecondaryRunnable; + private boolean mInitializedListeners = false; - private SensorEventListener mSensorEventListener = new SensorEventListener() { + private ThresholdSensor.Listener mPrimaryEventListener = new ThresholdSensor.Listener() { @Override - public synchronized void onSensorChanged(SensorEvent event) { - onSensorEvent(event); + public void onThresholdCrossed(ThresholdSensorEvent event) { + onPrimarySensorEvent(event); } + }; + private ThresholdSensor.Listener mSecondaryEventListener = new ThresholdSensor.Listener() { @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { + public void onThresholdCrossed(ThresholdSensorEvent event) { + // This sensor should only be used briefly. Turn it off as soon as we get a reading. + mSecondaryThresholdSensor.pause(); + + // Only check the secondary as long as the primary thinks we're near. + if (!mLastPrimaryEvent.getBelow()) { + mCancelSecondaryRunnable = null; + return; + } + logDebug("Secondary sensor event: " + event.getBelow() + "."); + + onSensorEvent(event); + + // Check this sensor again in a moment. + mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed( + mSecondaryThresholdSensor::resume, SECONDARY_PING_INTERVAL_MS); } }; @Inject - public ProximitySensor(@Main Resources resources, - AsyncSensorManager sensorManager) { - mSensorManager = sensorManager; - - Sensor sensor = findCustomProxSensor(resources); - float threshold = 0; - if (sensor != null) { - try { - threshold = getCustomProxThreshold(resources); - } catch (IllegalStateException e) { - Log.e(TAG, "Can not load custom proximity sensor.", e); - sensor = null; - } - } - if (sensor == null) { - sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); - if (sensor != null) { - threshold = sensor.getMaximumRange(); - } - } - - mThreshold = threshold; - - mSensor = sensor; + public ProximitySensor(@PrimaryProxSensor ThresholdSensor primary, + @SecondaryProxSensor ThresholdSensor secondary, + @Main DelayableExecutor delayableExecutor) { + mPrimaryThresholdSensor = primary; + mSecondaryThresholdSensor = secondary; + mDelayableExecutor = delayableExecutor; } + @Override public void setTag(String tag) { mTag = tag; + mPrimaryThresholdSensor.setTag(tag + ":primary"); + mSecondaryThresholdSensor.setTag(tag + ":secondary"); } - public void setSensorDelay(int sensorDelay) { - mSensorDelay = sensorDelay; + @Override + public void setDelay(int delay) { + Assert.isMainThread(); + mPrimaryThresholdSensor.setDelay(delay); + mSecondaryThresholdSensor.setDelay(delay); } /** * Unregister with the {@link SensorManager} without unsetting listeners on this object. */ + @Override public void pause() { + Assert.isMainThread(); mPaused = true; unregisterInternal(); } @@ -109,41 +136,13 @@ public class ProximitySensor { /** * Register with the {@link SensorManager}. No-op if no listeners are registered on this object. */ + @Override public void resume() { + Assert.isMainThread(); mPaused = false; registerInternal(); } - /** - * Returns a brightness sensor that can be used for proximity purposes. - */ - private Sensor findCustomProxSensor(Resources resources) { - String sensorType = resources.getString(R.string.proximity_sensor_type); - if (sensorType.isEmpty()) { - return null; - } - List sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL); - Sensor sensor = null; - for (Sensor s : sensorList) { - if (sensorType.equals(s.getStringType())) { - sensor = s; - break; - } - } - - return sensor; - } - - /** - * Returns a threshold value that can be used along with {@link #findCustomProxSensor} - */ - private float getCustomProxThreshold(Resources resources) { - try { - return resources.getFloat(R.dimen.proximity_sensor_threshold); - } catch (Resources.NotFoundException e) { - throw new IllegalStateException("R.dimen.proximity_sensor_threshold must be set."); - } - } /** * Returns true if we are registered with the SensorManager. @@ -155,38 +154,46 @@ public class ProximitySensor { /** * Returns {@code false} if a Proximity sensor is not available. */ - public boolean getSensorAvailable() { - return mSensor != null; + @Override + public boolean isLoaded() { + return mPrimaryThresholdSensor.isLoaded(); } /** * Add a listener. * * Registers itself with the {@link SensorManager} if this is the first listener - * added. If a cool down is currently running, the sensor will be registered when it is over. + * added. If the ProximitySensor is paused, it will be registered when resumed. */ - public boolean register(ProximitySensorListener listener) { - if (!getSensorAvailable()) { - return false; + @Override + public void register(ThresholdSensor.Listener listener) { + Assert.isMainThread(); + if (!isLoaded()) { + return; } if (mListeners.contains(listener)) { - Log.d(TAG, "ProxListener registered multiple times: " + listener); + logDebug("ProxListener registered multiple times: " + listener); } else { mListeners.add(listener); } registerInternal(); - - return true; } protected void registerInternal() { + Assert.isMainThread(); if (mRegistered || mPaused || mListeners.isEmpty()) { return; } + if (!mInitializedListeners) { + mPrimaryThresholdSensor.register(mPrimaryEventListener); + mSecondaryThresholdSensor.pause(); + mSecondaryThresholdSensor.register(mSecondaryEventListener); + mInitializedListeners = true; + } logDebug("Registering sensor listener"); + mPrimaryThresholdSensor.resume(); mRegistered = true; - mSensorManager.registerListener(mSensorEventListener, mSensor, mSensorDelay); } /** @@ -195,7 +202,9 @@ public class ProximitySensor { * If all listeners are removed from an instance of this class, * it will unregister itself with the SensorManager. */ - public void unregister(ProximitySensorListener listener) { + @Override + public void unregister(ThresholdSensor.Listener listener) { + Assert.isMainThread(); mListeners.remove(listener); if (mListeners.size() == 0) { unregisterInternal(); @@ -203,34 +212,77 @@ public class ProximitySensor { } protected void unregisterInternal() { + Assert.isMainThread(); if (!mRegistered) { return; } logDebug("unregistering sensor listener"); - mSensorManager.unregisterListener(mSensorEventListener); + mPrimaryThresholdSensor.pause(); + mSecondaryThresholdSensor.pause(); + if (mCancelSecondaryRunnable != null) { + mCancelSecondaryRunnable.run(); + mCancelSecondaryRunnable = null; + } + mLastPrimaryEvent = null; // Forget what we know. + mLastEvent = null; mRegistered = false; } public Boolean isNear() { - return getSensorAvailable() && mLastEvent != null ? mLastEvent.getNear() : null; + return isLoaded() && mLastEvent != null ? mLastEvent.getBelow() : null; } /** Update all listeners with the last value this class received from the sensor. */ public void alertListeners() { - mListeners.forEach(proximitySensorListener -> - proximitySensorListener.onSensorEvent(mLastEvent)); + Assert.isMainThread(); + if (mLastEvent == null) { + return; + } + + List listeners = new ArrayList<>(mListeners); + listeners.forEach(proximitySensorListener -> + proximitySensorListener.onThresholdCrossed(mLastEvent)); } - private void onSensorEvent(SensorEvent event) { - boolean near = event.values[0] < mThreshold; - mLastEvent = new ProximityEvent(near, event.timestamp); + private void onPrimarySensorEvent(ThresholdSensorEvent event) { + Assert.isMainThread(); + if (mLastPrimaryEvent != null && event.getBelow() == mLastPrimaryEvent.getBelow()) { + return; + } + + mLastPrimaryEvent = event; + + if (event.getBelow() && mSecondaryThresholdSensor.isLoaded()) { + logDebug("Primary sensor is near. Checking secondary."); + if (mCancelSecondaryRunnable == null) { + mSecondaryThresholdSensor.resume(); + } + } else { + if (!mSecondaryThresholdSensor.isLoaded()) { + logDebug("Primary sensor event: " + event.getBelow() + ". No secondary."); + } else { + logDebug("Primary sensor event: " + event.getBelow() + "."); + } + onSensorEvent(event); + } + } + + private void onSensorEvent(ThresholdSensorEvent event) { + Assert.isMainThread(); + if (mLastEvent != null && event.getBelow() == mLastEvent.getBelow()) { + return; + } + + mLastEvent = event; alertListeners(); } @Override public String toString() { - return String.format("{registered=%s, paused=%s, near=%s, sensor=%s}", - isRegistered(), mPaused, isNear(), mSensor); + return String.format("{registered=%s, paused=%s, near=%s, primarySensor=%s, " + + "secondarySensor=%s}", + isRegistered(), mPaused, isNear(), mPrimaryThresholdSensor, + mSecondaryThresholdSensor); } /** @@ -248,11 +300,11 @@ public class ProximitySensor { mSensor.setTag("prox_check"); mHandler = handler; mSensor.pause(); - ProximitySensorListener listener = proximityEvent -> { + ThresholdSensor.Listener listener = proximityEvent -> { mCallbacks.forEach( booleanConsumer -> booleanConsumer.accept( - proximityEvent == null ? null : proximityEvent.getNear())); + proximityEvent == null ? null : proximityEvent.getBelow())); mCallbacks.clear(); mSensor.pause(); }; @@ -274,7 +326,7 @@ public class ProximitySensor { * Query the proximity sensor, timing out if no result. */ public void check(long timeoutMs, Consumer callback) { - if (!mSensor.getSensorAvailable()) { + if (!mSensor.isLoaded()) { callback.accept(null); } mCallbacks.add(callback); @@ -285,43 +337,6 @@ public class ProximitySensor { } } - /** Implement to be notified of ProximityEvents. */ - public interface ProximitySensorListener { - /** Called when the ProximitySensor changes. */ - void onSensorEvent(ProximityEvent proximityEvent); - } - - /** - * Returned when the near/far state of a {@link ProximitySensor} changes. - */ - public static class ProximityEvent { - private final boolean mNear; - private final long mTimestampNs; - - public ProximityEvent(boolean near, long timestampNs) { - mNear = near; - mTimestampNs = timestampNs; - } - - public boolean getNear() { - return mNear; - } - - public long getTimestampNs() { - return mTimestampNs; - } - - public long getTimestampMs() { - return mTimestampNs / 1000000; - } - - @Override - public String toString() { - return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mNear, mTimestampNs); - } - - } - private void logDebug(String msg) { if (DEBUG) { Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg); diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/SecondaryProxSensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/SecondaryProxSensor.java new file mode 100644 index 0000000000000..89fc0eabf607f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/SecondaryProxSensor.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 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.util.sensors; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +@interface SecondaryProxSensor { +} diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java new file mode 100644 index 0000000000000..5a93e6c9f77a9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 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.util.sensors; + +import android.hardware.Sensor; +import android.hardware.SensorManager; + +import com.android.systemui.R; + +import dagger.Module; +import dagger.Provides; + +/** + * Dagger module for Sensor related classes. + */ +@Module +public class SensorModule { + @Provides + @PrimaryProxSensor + static ThresholdSensor providePrimaryProxSensor(SensorManager sensorManager, + ThresholdSensorImpl.Builder thresholdSensorBuilder) { + try { + return thresholdSensorBuilder + .setSensorDelay(SensorManager.SENSOR_DELAY_NORMAL) + .setSensorResourceId(R.string.proximity_sensor_type) + .setThresholdResourceId(R.dimen.proximity_sensor_threshold) + .build(); + } catch (IllegalStateException e) { + Sensor defaultSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); + return thresholdSensorBuilder + .setSensor(defaultSensor) + .setThresholdValue(defaultSensor.getMaximumRange()) + .build(); + } + } + + @Provides + @SecondaryProxSensor + static ThresholdSensor provideSecondaryProxSensor( + ThresholdSensorImpl.Builder thresholdSensorBuilder) { + try { + return thresholdSensorBuilder + .setSensorResourceId(R.string.proximity_sensor_secondary_type) + .setThresholdResourceId(R.dimen.proximity_sensor_secondary_threshold) + .build(); + } catch (IllegalStateException e) { + return thresholdSensorBuilder.setSensor(null).setThresholdValue(0).build(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java new file mode 100644 index 0000000000000..363a734a6ae51 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2020 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.util.sensors; + +import java.util.Locale; + +/** + * A wrapper class for sensors that have a boolean state - above/below. + */ +public interface ThresholdSensor { + /** + * Optional label to use for logging. + * + * This should be set to something meaningful by owner of the instance. + */ + void setTag(String tag); + + /** + * Change the delay used when registering the sensor. + * + * If the sensor is already registered, this should cause it to re-register with the new + * delay. + */ + void setDelay(int delay); + + /** + * True if this sensor successfully loads and can be listened to. + */ + boolean isLoaded(); + + /** + * Registers with the sensor and calls the supplied callback on value change. + * + * If this instance is paused, the listener will be recorded, but no registration with + * the underlying physical sensor will occur until {@link #resume()} is called. + * + * @see #unregister(Listener) + */ + void register(Listener listener); + + /** + * Unregisters from the physical sensor without removing any supplied listeners. + * + * No events will be sent to listeners as long as this sensor is paused. + * + * @see #resume() + * @see #unregister(Listener) + */ + void pause(); + + /** + * Resumes listening to the physical sensor after previously pausing. + * + * @see #pause() + */ + void resume(); + + /** + * Unregister a listener with the sensor. + * + * @see #register(Listener) + */ + void unregister(Listener listener); + + /** + * Interface for listening to events on {@link ThresholdSensor} + */ + interface Listener { + /** + * Called whenever the threshold for the registered sensor is crossed. + */ + void onThresholdCrossed(ThresholdSensorEvent event); + } + + /** + * Returned when the below/above state of a {@link ThresholdSensor} changes. + */ + class ThresholdSensorEvent { + private final boolean mBelow; + private final long mTimestampNs; + + public ThresholdSensorEvent(boolean below, long timestampNs) { + mBelow = below; + mTimestampNs = timestampNs; + } + + public boolean getBelow() { + return mBelow; + } + + public long getTimestampNs() { + return mTimestampNs; + } + + public long getTimestampMs() { + return mTimestampNs / 1000000; + } + + @Override + public String toString() { + return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mBelow, mTimestampNs); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java new file mode 100644 index 0000000000000..546333b3a073b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2020 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.util.sensors; + +import android.content.res.Resources; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.Assert; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +class ThresholdSensorImpl implements ThresholdSensor { + private static final String TAG = "ThresholdSensor"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final AsyncSensorManager mSensorManager; + private final Sensor mSensor; + private final float mThreshold; + private boolean mRegistered; + private boolean mPaused; + private List mListeners = new ArrayList<>(); + private Boolean mLastBelow; + private String mTag; + private int mSensorDelay; + + private SensorEventListener mSensorEventListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + boolean below = event.values[0] < mThreshold; + logDebug("Sensor value: " + event.values[0]); + onSensorEvent(below, event.timestamp); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + }; + + private ThresholdSensorImpl(AsyncSensorManager sensorManager, + Sensor sensor, float threshold, int sensorDelay) { + mSensorManager = sensorManager; + mSensor = sensor; + mThreshold = threshold; + mSensorDelay = sensorDelay; + } + + @Override + public void setTag(String tag) { + mTag = tag; + } + + @Override + public void setDelay(int delay) { + if (delay == mSensorDelay) { + return; + } + + mSensorDelay = delay; + if (isLoaded()) { + unregisterInternal(); + registerInternal(); + } + } + + @Override + public boolean isLoaded() { + return mSensor != null; + } + + @VisibleForTesting + boolean isRegistered() { + return mRegistered; + } + + /** + * Registers the listener with the sensor. + * + * Multiple listeners are not supported at this time. + * + * Returns true if the listener was successfully registered. False otherwise. + */ + @Override + public void register(Listener listener) { + Assert.isMainThread(); + if (!mListeners.contains(listener)) { + mListeners.add(listener); + } + registerInternal(); + } + + @Override + public void unregister(Listener listener) { + Assert.isMainThread(); + mListeners.remove(listener); + unregisterInternal(); + } + + /** + * Unregister with the {@link SensorManager} without unsetting listeners on this object. + */ + @Override + public void pause() { + Assert.isMainThread(); + mPaused = true; + unregisterInternal(); + } + + /** + * Register with the {@link SensorManager}. No-op if no listeners are registered on this object. + */ + @Override + public void resume() { + Assert.isMainThread(); + mPaused = false; + registerInternal(); + } + + private void alertListenersInternal(boolean below, long timestampNs) { + List listeners = new ArrayList<>(mListeners); + listeners.forEach(listener -> + listener.onThresholdCrossed(new ThresholdSensorEvent(below, timestampNs))); + } + + private void registerInternal() { + Assert.isMainThread(); + if (mRegistered || mPaused || mListeners.isEmpty()) { + return; + } + logDebug("Registering sensor listener"); + mSensorManager.registerListener(mSensorEventListener, mSensor, mSensorDelay); + mRegistered = true; + } + + private void unregisterInternal() { + Assert.isMainThread(); + if (!mRegistered) { + return; + } + logDebug("Unregister sensor listener"); + mSensorManager.unregisterListener(mSensorEventListener); + mRegistered = false; + mLastBelow = null; // Forget what we know. + } + + private void onSensorEvent(boolean below, long timestampNs) { + Assert.isMainThread(); + if (mLastBelow != null && mLastBelow == below) { + return; + } + mLastBelow = below; + alertListenersInternal(below, timestampNs); + } + + + @Override + public String toString() { + return String.format("{registered=%s, paused=%s, threshold=%s, sensor=%s}", + isLoaded(), mRegistered, mPaused, mThreshold, mSensor); + } + + private void logDebug(String msg) { + if (DEBUG) { + Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg); + } + } + + static class Builder { + private final Resources mResources; + private final AsyncSensorManager mSensorManager; + private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;; + private float mThresholdValue; + private Sensor mSensor; + private boolean mSensorSet; + private boolean mThresholdSet; + + @Inject + Builder(@Main Resources resources, AsyncSensorManager sensorManager) { + mResources = resources; + mSensorManager = sensorManager; + } + + + Builder setSensorDelay(int sensorDelay) { + mSensorDelay = sensorDelay; + return this; + } + + Builder setSensorResourceId(int sensorResourceId) { + setSensorType(mResources.getString(sensorResourceId)); + return this; + } + + Builder setThresholdResourceId(int thresholdResourceId) { + try { + setThresholdValue(mResources.getFloat(thresholdResourceId)); + } catch (Resources.NotFoundException e) { + // no-op + } + return this; + } + + Builder setSensorType(String sensorType) { + Sensor sensor = findSensorByType(sensorType); + if (sensor != null) { + setSensor(sensor); + } + return this; + } + + Builder setThresholdValue(float thresholdValue) { + mThresholdValue = thresholdValue; + mThresholdSet = true; + return this; + } + + Builder setSensor(Sensor sensor) { + mSensor = sensor; + mSensorSet = true; + return this; + } + + /** + * Creates a {@link ThresholdSensor} backed by a {@link ThresholdSensorImpl}. + */ + public ThresholdSensor build() { + if (!mSensorSet) { + throw new IllegalStateException("A sensor was not successfully set."); + } + + if (!mThresholdSet) { + throw new IllegalStateException("A threshold was not successfully set."); + } + + return new ThresholdSensorImpl( + mSensorManager, mSensor, mThresholdValue, mSensorDelay); + } + + private Sensor findSensorByType(String sensorType) { + if (sensorType.isEmpty()) { + return null; + } + + List sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL); + Sensor sensor = null; + for (Sensor s : sensorList) { + if (sensorType.equals(s.getStringType())) { + sensor = s; + break; + } + } + + return sensor; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java index 8b5cc9abb777d..ffe062598102d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.sensors.ProximitySensor; +import com.android.systemui.util.sensors.ThresholdSensor; import org.junit.Before; import org.junit.Test; @@ -77,7 +78,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { @Test public void testRegisterSensor() { mFalsingManager.onScreenTurningOn(); - verify(mProximitySensor).register(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).register(any(ThresholdSensor.Listener.class)); } @Test @@ -85,7 +86,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mFalsingManager.onScreenTurningOn(); reset(mProximitySensor); mFalsingManager.onScreenOff(); - verify(mProximitySensor).unregister(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class)); } @Test @@ -93,9 +94,9 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mFalsingManager.onScreenTurningOn(); reset(mProximitySensor); mFalsingManager.setQsExpanded(true); - verify(mProximitySensor).unregister(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class)); mFalsingManager.setQsExpanded(false); - verify(mProximitySensor).register(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).register(any(ThresholdSensor.Listener.class)); } @Test @@ -103,9 +104,9 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mFalsingManager.onScreenTurningOn(); reset(mProximitySensor); mFalsingManager.onBouncerShown(); - verify(mProximitySensor).unregister(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class)); mFalsingManager.onBouncerHidden(); - verify(mProximitySensor).register(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).register(any(ThresholdSensor.Listener.class)); } @Test @@ -113,6 +114,6 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mFalsingManager.onScreenTurningOn(); reset(mProximitySensor); mStatusBarStateController.setState(StatusBarState.SHADE); - verify(mProximitySensor).unregister(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java index 5b32a39403cd3..3cebf0d6af577 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java @@ -136,7 +136,8 @@ public class ProximityClassifierTest extends ClassifierTest { motionEvent.recycle(); } - private ProximitySensor.ProximityEvent createSensorEvent(boolean covered, long timestampMs) { - return new ProximitySensor.ProximityEvent(covered, timestampMs * NS_PER_MS); + private ProximitySensor.ThresholdSensorEvent createSensorEvent( + boolean covered, long timestampMs) { + return new ProximitySensor.ThresholdSensorEvent(covered, timestampMs * NS_PER_MS); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java index ff03fbae3f3a3..55fc239338367 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java @@ -46,6 +46,7 @@ import com.android.systemui.doze.DozeSensors.TriggerSensor; import com.android.systemui.plugins.SensorManagerPlugin; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.sensors.AsyncSensorManager; +import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.wakelock.WakeLock; import org.junit.Before; @@ -83,6 +84,9 @@ public class DozeSensorsTest extends SysuiTestCase { private DozeLog mDozeLog; @Mock private Sensor mProximitySensor; + @Mock + private ProximitySensor mMockProxSensor; + private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener; private TestableLooper mTestableLooper; private DozeSensors mDozeSensors; @@ -104,9 +108,9 @@ public class DozeSensorsTest extends SysuiTestCase { @Test public void testRegisterProx() { // We should not register with the sensor manager initially. - verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt()); + verify(mMockProxSensor).pause(); mDozeSensors.setProxListening(true); - verify(mSensorManager).registerListener(any(), any(Sensor.class), anyInt()); + verify(mMockProxSensor).resume(); } @Test @@ -162,7 +166,8 @@ public class DozeSensorsTest extends SysuiTestCase { TestableDozeSensors() { super(getContext(), mAlarmManager, mSensorManager, mDozeParameters, - mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog); + mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog, + mMockProxSensor); for (TriggerSensor sensor : mSensors) { if (sensor instanceof PluginSensor && ((PluginSensor) sensor).mPluginSensor.getType() diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index debc9d6430e07..1d2021721634d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -42,10 +42,13 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.FakeProximitySensor; import com.android.systemui.util.sensors.FakeSensorManager; +import com.android.systemui.util.sensors.FakeThresholdSensor; import com.android.systemui.util.sensors.ProximitySensor; +import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.wakelock.WakeLock; import com.android.systemui.util.wakelock.WakeLockFake; @@ -71,10 +74,12 @@ public class DozeTriggersTest extends SysuiTestCase { private BroadcastDispatcher mBroadcastDispatcher; @Mock private DockManager mDockManager; + private DozeTriggers mTriggers; private FakeSensorManager mSensors; private Sensor mTapSensor; private FakeProximitySensor mProximitySensor; + private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); @Before public void setUp() throws Exception { @@ -86,7 +91,10 @@ public class DozeTriggersTest extends SysuiTestCase { WakeLock wakeLock = new WakeLockFake(); AsyncSensorManager asyncSensorManager = new AsyncSensorManager(mSensors, null, new Handler()); - mProximitySensor = new FakeProximitySensor(getContext().getResources(), asyncSensorManager); + + FakeThresholdSensor thresholdSensor = new FakeThresholdSensor(); + thresholdSensor.setLoaded(true); + mProximitySensor = new FakeProximitySensor(thresholdSensor, null, mExecutor); mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, config, parameters, asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true, @@ -104,17 +112,17 @@ public class DozeTriggersTest extends SysuiTestCase { mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE); clearInvocations(mMachine); - mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(true, 1)); + mProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 1)); captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */); mProximitySensor.alertListeners(); verify(mMachine, never()).requestState(any()); verify(mMachine, never()).requestPulse(anyInt()); - captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */); - waitForSensorManager(); - mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(false, 2)); + mProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(false, 2)); mProximitySensor.alertListeners(); + waitForSensorManager(); + captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */); verify(mMachine).requestPulse(anyInt()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java index 31d884c38f582..16edc9d9cf82b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java @@ -16,13 +16,15 @@ package com.android.systemui.util.sensors; -import android.content.res.Resources; +import com.android.systemui.util.concurrency.DelayableExecutor; public class FakeProximitySensor extends ProximitySensor { private boolean mAvailable; - public FakeProximitySensor(Resources resources, AsyncSensorManager sensorManager) { - super(resources, sensorManager); + public FakeProximitySensor(ThresholdSensor primary, ThresholdSensor secondary, + DelayableExecutor delayableExecutor) { + super(primary, secondary == null ? new FakeThresholdSensor() : secondary, + delayableExecutor); mAvailable = true; } @@ -30,12 +32,12 @@ public class FakeProximitySensor extends ProximitySensor { mAvailable = available; } - public void setLastEvent(ProximityEvent event) { + public void setLastEvent(ThresholdSensorEvent event) { mLastEvent = event; } @Override - public boolean getSensorAvailable() { + public boolean isLoaded() { return mAvailable; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java new file mode 100644 index 0000000000000..d9f978944cdef --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 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.util.sensors; + +import java.util.ArrayList; +import java.util.List; + +public class FakeThresholdSensor implements ThresholdSensor { + private boolean mIsLoaded; + private boolean mPaused; + private List mListeners = new ArrayList<>(); + + public FakeThresholdSensor() { + } + + public void setTag(String tag) { + } + + @Override + public void setDelay(int delay) { + } + + @Override + public boolean isLoaded() { + return mIsLoaded; + } + + @Override + public void pause() { + mPaused = true; + } + + @Override + public void resume() { + mPaused = false; + } + + @Override + public void register(ThresholdSensor.Listener listener) { + mListeners.add(listener); + } + + @Override + public void unregister(ThresholdSensor.Listener listener) { + mListeners.remove(listener); + } + + public void setLoaded(boolean loaded) { + mIsLoaded = loaded; + } + + void triggerEvent(boolean below, long timestampNs) { + if (!mPaused) { + for (Listener listener : mListeners) { + listener.onThresholdCrossed(new ThresholdSensorEvent(below, timestampNs)); + } + } + } + + boolean isPaused() { + return mPaused; + } + + int getNumListeners() { + return mListeners.size(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java new file mode 100644 index 0000000000000..a39c201695183 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2020 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.util.sensors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class ProximitySensorDualTest extends SysuiTestCase { + private ProximitySensor mProximitySensor; + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + private FakeThresholdSensor mThresholdSensorPrimary; + private FakeThresholdSensor mThresholdSensorSecondary; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + allowTestableLooperAsMainThread(); + mThresholdSensorPrimary = new FakeThresholdSensor(); + mThresholdSensorPrimary.setLoaded(true); + mThresholdSensorSecondary = new FakeThresholdSensor(); + mThresholdSensorSecondary.setLoaded(true); + + mProximitySensor = new ProximitySensor( + mThresholdSensorPrimary, mThresholdSensorSecondary, mFakeExecutor); + } + + @Test + public void testSingleListener() { + TestableListener listener = new TestableListener(); + + assertFalse(mProximitySensor.isRegistered()); + mProximitySensor.register(listener); + assertTrue(mProximitySensor.isRegistered()); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + // Trigger second sensor. Nothing should happen yet. + mThresholdSensorSecondary.triggerEvent(true, 0); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + // Trigger first sensor. Our second sensor is now registered. + mThresholdSensorPrimary.triggerEvent(true, 0); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertFalse(mThresholdSensorSecondary.isPaused()); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + // Trigger second sensor. + mThresholdSensorSecondary.triggerEvent(true, 0); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + assertTrue(mThresholdSensorSecondary.isPaused()); + + mProximitySensor.unregister(listener); + } + + @Test + public void testSecondaryPausing() { + TestableListener listener = new TestableListener(); + + assertFalse(mProximitySensor.isRegistered()); + mProximitySensor.register(listener); + assertTrue(mProximitySensor.isRegistered()); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + // Trigger first sensor. Our second sensor is now registered. + mThresholdSensorPrimary.triggerEvent(true, 0); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + // Trigger second sensor. + mThresholdSensorSecondary.triggerEvent(true, 0); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + assertTrue(mThresholdSensorSecondary.isPaused()); + + // Advance time. Second sensor should resume. + mFakeExecutor.advanceClockToNext(); + mFakeExecutor.runNextReady(); + assertFalse(mThresholdSensorSecondary.isPaused()); + + // Triggering should pause again. + mThresholdSensorSecondary.triggerEvent(false, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); + assertTrue(mThresholdSensorSecondary.isPaused()); + + mProximitySensor.unregister(listener); + } + + @Test + public void testUnregister() { + TestableListener listener = new TestableListener(); + + assertFalse(mProximitySensor.isRegistered()); + mProximitySensor.register(listener); + assertTrue(mProximitySensor.isRegistered()); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertNull(listener.mLastEvent); + + mThresholdSensorPrimary.triggerEvent(true, 0); + mThresholdSensorSecondary.triggerEvent(true, 0); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + + mProximitySensor.unregister(listener); + assertTrue(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertFalse(mProximitySensor.isRegistered()); + } + + @Test + public void testPauseAndResume() { + TestableListener listener = new TestableListener(); + + assertFalse(mProximitySensor.isRegistered()); + mProximitySensor.register(listener); + assertTrue(mProximitySensor.isRegistered()); + assertNull(listener.mLastEvent); + + mThresholdSensorPrimary.triggerEvent(true, 0); + mThresholdSensorSecondary.triggerEvent(true, 0); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + + mProximitySensor.pause(); + assertFalse(mProximitySensor.isRegistered()); + assertTrue(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + + // More events do nothing when paused. + mThresholdSensorSecondary.triggerEvent(false, 1); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + + mProximitySensor.resume(); + assertTrue(mProximitySensor.isRegistered()); + // Still matches our previous call + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + + // Need to trigger the primary sensor before the secondary re-registers itself. + mThresholdSensorPrimary.triggerEvent(true, 3); + mThresholdSensorSecondary.triggerEvent(false, 3); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); + + mProximitySensor.unregister(listener); + assertFalse(mProximitySensor.isRegistered()); + } + + @Test + public void testPrimarySecondaryDisagreement() { + TestableListener listener = new TestableListener(); + + mProximitySensor.register(listener); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + // Trigger our sensors with different values. Secondary overrides primary. + mThresholdSensorPrimary.triggerEvent(true, 0); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + mThresholdSensorSecondary.triggerEvent(false, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + + mThresholdSensorSecondary.resume(); + mThresholdSensorSecondary.triggerEvent(true, 0); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); + + mThresholdSensorSecondary.resume(); + mThresholdSensorSecondary.triggerEvent(false, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(3, listener.mCallCount); + + mProximitySensor.unregister(listener); + } + + @Test + public void testPrimaryCancelsSecondary() { + TestableListener listener = new TestableListener(); + + mProximitySensor.register(listener); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + mThresholdSensorPrimary.triggerEvent(true, 0); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + mThresholdSensorSecondary.triggerEvent(true, 0); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + + // When the primary reports false, the secondary is no longer needed. We get an immediate + // report. + mThresholdSensorPrimary.triggerEvent(false, 1); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); + + // The secondary is now ignored. No more work is scheduled. + mFakeExecutor.advanceClockToNext(); + mFakeExecutor.runNextReady(); + mThresholdSensorSecondary.triggerEvent(true, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); + assertEquals(0, mFakeExecutor.numPending()); + + mProximitySensor.unregister(listener); + } + + private static class TestableListener implements ThresholdSensor.Listener { + ThresholdSensor.ThresholdSensorEvent mLastEvent; + int mCallCount = 0; + + @Override + public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent proximityEvent) { + mLastEvent = proximityEvent; + mCallCount++; + } + }; + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java similarity index 54% rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java rename to packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java index 526fba726e9d9..f1cee5061695f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java @@ -21,33 +21,40 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +/** + * Tests for ProximitySensor that rely on a single hardware sensor. + */ @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper -public class ProximitySensorTest extends SysuiTestCase { - +public class ProximitySensorSingleTest extends SysuiTestCase { private ProximitySensor mProximitySensor; - private FakeSensorManager.FakeProximitySensor mFakeProximitySensor; + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + private FakeThresholdSensor mThresholdSensor; @Before public void setUp() throws Exception { - FakeSensorManager sensorManager = new FakeSensorManager(getContext()); - AsyncSensorManager asyncSensorManager = new AsyncSensorManager( - sensorManager, null, new Handler()); - mFakeProximitySensor = sensorManager.getFakeProximitySensor(); - mProximitySensor = new ProximitySensor(getContext().getResources(), asyncSensorManager); + MockitoAnnotations.initMocks(this); + allowTestableLooperAsMainThread(); + mThresholdSensor = new FakeThresholdSensor(); + mThresholdSensor.setLoaded(true); + + mProximitySensor = new ProximitySensor( + mThresholdSensor, new FakeThresholdSensor(), mFakeExecutor); } @Test @@ -56,19 +63,17 @@ public class ProximitySensorTest extends SysuiTestCase { assertFalse(mProximitySensor.isRegistered()); mProximitySensor.register(listener); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); assertNull(listener.mLastEvent); - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 1); - mFakeProximitySensor.sendProximityResult(false); - assertTrue(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 2); + mThresholdSensor.triggerEvent(false, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + mThresholdSensor.triggerEvent(true, 0); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); mProximitySensor.unregister(listener); - waitForSensorManager(); } @Test @@ -79,28 +84,25 @@ public class ProximitySensorTest extends SysuiTestCase { assertFalse(mProximitySensor.isRegistered()); mProximitySensor.register(listenerA); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); mProximitySensor.register(listenerB); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); assertNull(listenerA.mLastEvent); assertNull(listenerB.mLastEvent); - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listenerA.mLastEvent.getNear()); - assertFalse(listenerB.mLastEvent.getNear()); - assertEquals(listenerA.mCallCount, 1); - assertEquals(listenerB.mCallCount, 1); - mFakeProximitySensor.sendProximityResult(false); - assertTrue(listenerA.mLastEvent.getNear()); - assertTrue(listenerB.mLastEvent.getNear()); - assertEquals(listenerA.mCallCount, 2); - assertEquals(listenerB.mCallCount, 2); + mThresholdSensor.triggerEvent(false, 0); + assertFalse(listenerA.mLastEvent.getBelow()); + assertFalse(listenerB.mLastEvent.getBelow()); + assertEquals(1, listenerA.mCallCount); + assertEquals(1, listenerB.mCallCount); + mThresholdSensor.triggerEvent(true, 1); + assertTrue(listenerA.mLastEvent.getBelow()); + assertTrue(listenerB.mLastEvent.getBelow()); + assertEquals(2, listenerA.mCallCount); + assertEquals(2, listenerB.mCallCount); mProximitySensor.unregister(listenerA); mProximitySensor.unregister(listenerB); - waitForSensorManager(); } @Test @@ -110,22 +112,19 @@ public class ProximitySensorTest extends SysuiTestCase { assertFalse(mProximitySensor.isRegistered()); mProximitySensor.register(listenerA); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); mProximitySensor.register(listenerA); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); assertNull(listenerA.mLastEvent); - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listenerA.mLastEvent.getNear()); - assertEquals(listenerA.mCallCount, 1); - mFakeProximitySensor.sendProximityResult(false); - assertTrue(listenerA.mLastEvent.getNear()); - assertEquals(listenerA.mCallCount, 2); + mThresholdSensor.triggerEvent(false, 0); + assertFalse(listenerA.mLastEvent.getBelow()); + assertEquals(1, listenerA.mCallCount); + mThresholdSensor.triggerEvent(true, 1); + assertTrue(listenerA.mLastEvent.getBelow()); + assertEquals(2, listenerA.mCallCount); mProximitySensor.unregister(listenerA); - waitForSensorManager(); } @Test public void testUnregister() { @@ -133,16 +132,14 @@ public class ProximitySensorTest extends SysuiTestCase { assertFalse(mProximitySensor.isRegistered()); mProximitySensor.register(listener); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); assertNull(listener.mLastEvent); - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 1); + mThresholdSensor.triggerEvent(false, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); mProximitySensor.unregister(listener); - waitForSensorManager(); assertFalse(mProximitySensor.isRegistered()); } @@ -152,39 +149,35 @@ public class ProximitySensorTest extends SysuiTestCase { assertFalse(mProximitySensor.isRegistered()); mProximitySensor.register(listener); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); assertNull(listener.mLastEvent); - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 1); + mThresholdSensor.triggerEvent(false, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); mProximitySensor.pause(); - waitForSensorManager(); assertFalse(mProximitySensor.isRegistered()); // More events do nothing when paused. - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 1); - mFakeProximitySensor.sendProximityResult(false); - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 1); + mThresholdSensor.triggerEvent(false, 1); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + mThresholdSensor.triggerEvent(true, 2); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); mProximitySensor.resume(); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); // Still matches our previous call - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 1); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 2); + mThresholdSensor.triggerEvent(true, 3); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); mProximitySensor.unregister(listener); - waitForSensorManager(); assertFalse(mProximitySensor.isRegistered()); } @@ -197,46 +190,35 @@ public class ProximitySensorTest extends SysuiTestCase { mProximitySensor.register(listenerA); mProximitySensor.register(listenerB); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); assertNull(listenerA.mLastEvent); assertNull(listenerB.mLastEvent); mProximitySensor.alertListeners(); assertNull(listenerA.mLastEvent); - assertEquals(listenerA.mCallCount, 1); + assertEquals(0, listenerA.mCallCount); assertNull(listenerB.mLastEvent); - assertEquals(listenerB.mCallCount, 1); + assertEquals(0, listenerB.mCallCount); - mFakeProximitySensor.sendProximityResult(false); - assertTrue(listenerA.mLastEvent.getNear()); - assertEquals(listenerA.mCallCount, 2); - assertTrue(listenerB.mLastEvent.getNear()); - assertEquals(listenerB.mCallCount, 2); + mThresholdSensor.triggerEvent(true, 0); + assertTrue(listenerA.mLastEvent.getBelow()); + assertEquals(1, listenerA.mCallCount); + assertTrue(listenerB.mLastEvent.getBelow()); + assertEquals(1, listenerB.mCallCount); mProximitySensor.unregister(listenerA); mProximitySensor.unregister(listenerB); - waitForSensorManager(); } - class TestableListener implements ProximitySensor.ProximitySensorListener { - ProximitySensor.ProximityEvent mLastEvent; + private static class TestableListener implements ThresholdSensor.Listener { + ThresholdSensor.ThresholdSensorEvent mLastEvent; int mCallCount = 0; @Override - public void onSensorEvent(ProximitySensor.ProximityEvent proximityEvent) { + public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent proximityEvent) { mLastEvent = proximityEvent; mCallCount++; } - - void reset() { - mLastEvent = null; - mCallCount = 0; - } }; - private void waitForSensorManager() { - TestableLooper.get(this).processAllMessages(); - } - } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java new file mode 100644 index 0000000000000..09ec8e55efd62 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2020 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.util.sensors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Handler; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class ThresholdSensorImplTest extends SysuiTestCase { + + private ThresholdSensorImpl mThresholdSensor; + private FakeSensorManager.FakeProximitySensor mFakeProximitySensor; + + @Before + public void setUp() throws Exception { + allowTestableLooperAsMainThread(); + FakeSensorManager sensorManager = new FakeSensorManager(getContext()); + + AsyncSensorManager asyncSensorManager = new AsyncSensorManager( + sensorManager, null, new Handler()); + + mFakeProximitySensor = sensorManager.getFakeProximitySensor(); + ThresholdSensorImpl.Builder thresholdSensorBuilder = new ThresholdSensorImpl.Builder( + null, asyncSensorManager); + mThresholdSensor = (ThresholdSensorImpl) thresholdSensorBuilder + .setSensor(mFakeProximitySensor.getSensor()) + .setThresholdValue(mFakeProximitySensor.getSensor().getMaximumRange()) + .build(); + } + + @Test + public void testSingleListener() { + TestableListener listener = new TestableListener(); + + assertFalse(mThresholdSensor.isRegistered()); + mThresholdSensor.register(listener); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + assertEquals(0, listener.mCallCount); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mBelow); + assertEquals(1, listener.mCallCount); + mFakeProximitySensor.sendProximityResult(false); + assertTrue(listener.mBelow); + assertEquals(2, listener.mCallCount); + + mThresholdSensor.unregister(listener); + waitForSensorManager(); + } + + @Test + public void testMultiListener() { + TestableListener listenerA = new TestableListener(); + TestableListener listenerB = new TestableListener(); + + assertFalse(mThresholdSensor.isRegistered()); + + mThresholdSensor.register(listenerA); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + mThresholdSensor.register(listenerB); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + assertEquals(0, listenerA.mCallCount); + assertEquals(0, listenerB.mCallCount); + + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listenerA.mBelow); + assertFalse(listenerB.mBelow); + assertEquals(1, listenerA.mCallCount); + assertEquals(1, listenerB.mCallCount); + mFakeProximitySensor.sendProximityResult(false); + assertTrue(listenerA.mBelow); + assertTrue(listenerB.mBelow); + assertEquals(2, listenerA.mCallCount); + assertEquals(2, listenerB.mCallCount); + + mThresholdSensor.unregister(listenerA); + mThresholdSensor.unregister(listenerB); + waitForSensorManager(); + } + + @Test + public void testDuplicateListener() { + TestableListener listenerA = new TestableListener(); + + assertFalse(mThresholdSensor.isRegistered()); + + mThresholdSensor.register(listenerA); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + mThresholdSensor.register(listenerA); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + assertEquals(0, listenerA.mCallCount); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listenerA.mBelow); + assertEquals(1, listenerA.mCallCount); + mFakeProximitySensor.sendProximityResult(false); + assertTrue(listenerA.mBelow); + assertEquals(2, listenerA.mCallCount); + + mThresholdSensor.unregister(listenerA); + waitForSensorManager(); + } + @Test + public void testUnregister() { + TestableListener listener = new TestableListener(); + + assertFalse(mThresholdSensor.isRegistered()); + mThresholdSensor.register(listener); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + assertEquals(0, listener.mCallCount); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mBelow); + assertEquals(1, listener.mCallCount); + + mThresholdSensor.unregister(listener); + waitForSensorManager(); + assertFalse(mThresholdSensor.isRegistered()); + } + + @Test + public void testPauseAndResume() { + TestableListener listener = new TestableListener(); + + assertFalse(mThresholdSensor.isRegistered()); + mThresholdSensor.register(listener); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + assertEquals(0, listener.mCallCount); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mBelow); + assertEquals(1, listener.mCallCount); + + mThresholdSensor.pause(); + waitForSensorManager(); + assertFalse(mThresholdSensor.isRegistered()); + + // More events do nothing when paused. + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mBelow); + assertEquals(1, listener.mCallCount); + mFakeProximitySensor.sendProximityResult(false); + assertFalse(listener.mBelow); + assertEquals(1, listener.mCallCount); + + mThresholdSensor.resume(); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + // Still matches our previous call + assertFalse(listener.mBelow); + assertEquals(1, listener.mCallCount); + + mFakeProximitySensor.sendProximityResult(false); + assertTrue(listener.mBelow); + assertEquals(2, listener.mCallCount); + + mThresholdSensor.unregister(listener); + waitForSensorManager(); + assertFalse(mThresholdSensor.isRegistered()); + } + + @Test + public void testAlertListeners() { + TestableListener listenerA = new TestableListener(); + TestableListener listenerB = new TestableListener(); + + assertFalse(mThresholdSensor.isRegistered()); + + mThresholdSensor.register(listenerA); + mThresholdSensor.register(listenerB); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + assertEquals(0, listenerA.mCallCount); + assertEquals(0, listenerB.mCallCount); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listenerA.mBelow); + assertEquals(1, listenerA.mCallCount); + assertFalse(listenerB.mBelow); + assertEquals(1, listenerB.mCallCount); + + mFakeProximitySensor.sendProximityResult(false); + assertTrue(listenerA.mBelow); + assertEquals(2, listenerA.mCallCount); + assertTrue(listenerB.mBelow); + assertEquals(2, listenerB.mCallCount); + + mThresholdSensor.unregister(listenerA); + mThresholdSensor.unregister(listenerB); + waitForSensorManager(); + } + + static class TestableListener implements ThresholdSensor.Listener { + boolean mBelow; + long mTimestampNs; + int mCallCount; + + @Override + public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent event) { + mBelow = event.getBelow(); + mTimestampNs = event.getTimestampNs(); + mCallCount++; + } + } + + private void waitForSensorManager() { + TestableLooper.get(this).processAllMessages(); + } + +}