Merge "RESTRICT AUTOMERGE ProximitySensor now supports dual-sensor approach." into rvc-d1-dev

This commit is contained in:
Dave Mankoff
2020-04-24 00:43:37 +00:00
committed by Android (Google) Code Review
25 changed files with 1411 additions and 254 deletions

View File

@@ -205,6 +205,14 @@
far break points. A sensor value less than this is considered "near". -->
<item name="proximity_sensor_threshold" translatable="false" format="float" type="dimen"></item>
<!-- Override value to use for proximity sensor as confirmation for proximity_sensor_type. -->
<string name="proximity_sensor_secondary_type" translatable="false"></string>
<!-- If using proximity_sensor_confirmation_type, specifies a threshold value to distinguish
near and far break points. A sensor value less than this is considered "near". -->
<item name="proximity_sensor_secondary_threshold" translatable="false" format="float"
type="dimen"></item>
<!-- Doze: pulse parameter - how long does it take to fade in? -->
<integer name="doze_pulse_duration_in">130</integer>

View File

@@ -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());

View File

@@ -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<FalsingClassifier> 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));

View File

@@ -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.

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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,

View File

@@ -81,7 +81,8 @@ public class DozeSensors {
public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager,
DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog) {
Callback callback, Consumer<Boolean> 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());
}
});
}

View File

@@ -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);

View File

@@ -60,8 +60,8 @@ public class AsyncSensorManager extends SensorManager
private final List<SensorManagerPlugin> 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

View File

@@ -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 {
}

View File

@@ -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<ProximitySensorListener> mListeners = new ArrayList<>();
private final ThresholdSensor mPrimaryThresholdSensor;
private final ThresholdSensor mSecondaryThresholdSensor;
private final DelayableExecutor mDelayableExecutor;
private final List<ThresholdSensor.Listener> 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<Sensor> 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<ThresholdSensor.Listener> 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<Boolean> 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);

View File

@@ -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 {
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<Listener> 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<Listener> 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<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
Sensor sensor = null;
for (Sensor s : sensorList) {
if (sensorType.equals(s.getStringType())) {
sensor = s;
break;
}
}
return sensor;
}
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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()

View File

@@ -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());
}

View File

@@ -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;
}

View File

@@ -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<Listener> 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();
}
}

View File

@@ -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++;
}
};
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}