blob: aa50292edbf72f9aeda099680b60462a83edfdc8 [file] [log] [blame]
/*
* 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 final float mThresholdLatch;
private int mSensorDelay;
private SensorEventListener mSensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
boolean below = event.values[0] < mThreshold;
boolean above = event.values[0] >= mThresholdLatch;
logDebug("Sensor value: " + event.values[0]);
onSensorEvent(below, above, event.timestamp);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
private ThresholdSensorImpl(AsyncSensorManager sensorManager,
Sensor sensor, float threshold, float thresholdLatch, int sensorDelay) {
mSensorManager = sensorManager;
mSensor = sensor;
mThreshold = threshold;
mThresholdLatch = thresholdLatch;
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.
}
/**
* Call when the sensor reports a new value.
*
* Separate below-threshold and above-thresholds are specified. this allows latching behavior,
* where a different threshold can be specified for triggering the sensor depending on if it's
* going from above to below or below to above. To outside listeners of this class, the class
* still appears entirely binary.
*/
private void onSensorEvent(boolean belowThreshold, boolean aboveThreshold, long timestampNs) {
Assert.isMainThread();
if (!mRegistered) {
return;
}
if (mLastBelow != null) {
// If we last reported below and are not yet above, change nothing.
if (mLastBelow && !aboveThreshold) {
return;
}
// If we last reported above and are not yet below, change nothing.
if (!mLastBelow && !belowThreshold) {
return;
}
}
mLastBelow = belowThreshold;
logDebug("Alerting below: " + belowThreshold);
alertListenersInternal(belowThreshold, 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 float mThresholdLatchValue;
private Sensor mSensor;
private boolean mSensorSet;
private boolean mThresholdSet;
private boolean mThresholdLatchValueSet;
@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 setThresholdLatchResourceId(int thresholdLatchResourceId) {
try {
setThresholdLatchValue(mResources.getFloat(thresholdLatchResourceId));
} 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;
if (!mThresholdLatchValueSet) {
mThresholdLatchValue = mThresholdValue;
}
return this;
}
Builder setThresholdLatchValue(float thresholdLatchValue) {
mThresholdLatchValue = thresholdLatchValue;
mThresholdLatchValueSet = 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.");
}
if (mThresholdValue > mThresholdLatchValue) {
throw new IllegalStateException(
"Threshold must be less than or equal to Threshold Latch");
}
return new ThresholdSensorImpl(
mSensorManager, mSensor, mThresholdValue, mThresholdLatchValue, 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;
}
}
}