Merge "Add posture support for prox sensor" into sc-v2-dev

This commit is contained in:
Beverly Tai
2021-10-05 18:31:54 +00:00
committed by Android (Google) Code Review
21 changed files with 1037 additions and 457 deletions

View File

@@ -24,6 +24,8 @@ import android.provider.Settings;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import java.util.Arrays;
import com.android.internal.R; import com.android.internal.R;
/** /**
@@ -258,10 +260,11 @@ public class AmbientDisplayConfiguration {
String defaultValue, String defaultValue,
int posture) { int posture) {
String sensorType = defaultValue; String sensorType = defaultValue;
if (posture < postureMapping.length) { if (postureMapping != null && posture < postureMapping.length) {
sensorType = postureMapping[posture]; sensorType = postureMapping[posture];
} else { } else {
Log.e(TAG, "Unsupported doze posture " + posture); Log.e(TAG, "Unsupported doze posture " + posture
+ " postureMapping=" + Arrays.toString(postureMapping));
} }
return TextUtils.isEmpty(sensorType) ? defaultValue : sensorType; return TextUtils.isEmpty(sensorType) ? defaultValue : sensorType;

View File

@@ -224,15 +224,23 @@
display brightness, suitable to listen to while the device is asleep (e.g. during display brightness, suitable to listen to while the device is asleep (e.g. during
always-on display) --> always-on display) -->
<string-array name="doze_brightness_sensor_name_posture_mapping" translatable="false"> <string-array name="doze_brightness_sensor_name_posture_mapping" translatable="false">
<item></item> <!-- UNKNOWN --> <!-- UNKNOWN -->
<item></item> <!-- CLOSED --> <!-- CLOSED -->
<item></item> <!-- HALF_OPENED --> <!-- HALF_OPENED -->
<item></item> <!-- OPENED --> <!-- OPENED -->
</string-array> </string-array>
<!-- Override value to use for proximity sensor. --> <!-- Override value to use for proximity sensor. -->
<string name="proximity_sensor_type" translatable="false"></string> <string name="proximity_sensor_type" translatable="false"></string>
<!-- Sensor type per posture state to use for proximity sensor -->
<string-array name="proximity_sensor_posture_mapping" translatable="false">
<!-- UNKNOWN -->
<!-- CLOSED -->
<!-- HALF_OPENED -->
<!-- OPENED -->
</string-array>
<!-- If using proximity_sensor_type, specifies a threshold value to distinguish near and <!-- If using proximity_sensor_type, specifies a threshold value to distinguish near and
far break points. A sensor value less than this is considered "near". --> far break points. A sensor value less than this is considered "near". -->
<item name="proximity_sensor_threshold" translatable="false" format="float" type="dimen"></item> <item name="proximity_sensor_threshold" translatable="false" format="float" type="dimen"></item>
@@ -246,6 +254,15 @@
<!-- Override value to use for proximity sensor as confirmation for proximity_sensor_type. --> <!-- Override value to use for proximity sensor as confirmation for proximity_sensor_type. -->
<string name="proximity_sensor_secondary_type" translatable="false"></string> <string name="proximity_sensor_secondary_type" translatable="false"></string>
<!-- Sensor type per posture state to use for proximity sensor as a confirmation for
proximity_sensor_type. -->
<string-array name="proximity_sensor_secondary_posture_mapping" translatable="false">
<!-- UNKNOWN -->
<!-- CLOSED -->
<!-- HALF_OPENED -->
<!-- OPENED -->
</string-array>
<!-- If using proximity_sensor_secondary_type, specifies a threshold value to distinguish <!-- If using proximity_sensor_secondary_type, specifies a threshold value to distinguish
near and far break points. A sensor value less than this is considered "near". --> near and far break points. A sensor value less than this is considered "near". -->
<item name="proximity_sensor_secondary_threshold" translatable="false" format="float" <item name="proximity_sensor_secondary_threshold" translatable="false" format="float"

View File

@@ -37,6 +37,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor; import com.android.systemui.util.sensors.ThresholdSensor;
import com.android.systemui.util.sensors.ThresholdSensorEvent;
import com.android.systemui.util.time.SystemClock; import com.android.systemui.util.time.SystemClock;
import java.util.Collections; import java.util.Collections;
@@ -405,7 +406,7 @@ class FalsingCollectorImpl implements FalsingCollector {
mProximitySensor.unregister(mSensorEventListener); mProximitySensor.unregister(mSensorEventListener);
} }
private void onProximityEvent(ThresholdSensor.ThresholdSensorEvent proximityEvent) { private void onProximityEvent(ThresholdSensorEvent proximityEvent) {
// TODO: some of these classifiers might allow us to abort early, meaning we don't have to // TODO: some of these classifiers might allow us to abort early, meaning we don't have to
// make these calls. // make these calls.
mFalsingManager.onProximityEvent(new ProximityEventImpl(proximityEvent)); mFalsingManager.onProximityEvent(new ProximityEventImpl(proximityEvent));
@@ -423,9 +424,9 @@ class FalsingCollectorImpl implements FalsingCollector {
} }
private static class ProximityEventImpl implements FalsingManager.ProximityEvent { private static class ProximityEventImpl implements FalsingManager.ProximityEvent {
private ThresholdSensor.ThresholdSensorEvent mThresholdSensorEvent; private ThresholdSensorEvent mThresholdSensorEvent;
ProximityEventImpl(ThresholdSensor.ThresholdSensorEvent thresholdSensorEvent) { ProximityEventImpl(ThresholdSensorEvent thresholdSensorEvent) {
mThresholdSensorEvent = thresholdSensorEvent; mThresholdSensorEvent = thresholdSensorEvent;
} }
@Override @Override

View File

@@ -46,6 +46,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert; import com.android.systemui.util.Assert;
import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.ProximityCheck;
import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.wakelock.WakeLock; import com.android.systemui.util.wakelock.WakeLock;
@@ -90,7 +91,7 @@ public class DozeTriggers implements DozeMachine.Part {
private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver(); private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
private final DockEventListener mDockEventListener = new DockEventListener(); private final DockEventListener mDockEventListener = new DockEventListener();
private final DockManager mDockManager; private final DockManager mDockManager;
private final ProximitySensor.ProximityCheck mProxCheck; private final ProximityCheck mProxCheck;
private final BroadcastDispatcher mBroadcastDispatcher; private final BroadcastDispatcher mBroadcastDispatcher;
private final AuthController mAuthController; private final AuthController mAuthController;
private final DelayableExecutor mMainExecutor; private final DelayableExecutor mMainExecutor;
@@ -181,7 +182,8 @@ public class DozeTriggers implements DozeMachine.Part {
AmbientDisplayConfiguration config, AmbientDisplayConfiguration config,
DozeParameters dozeParameters, AsyncSensorManager sensorManager, DozeParameters dozeParameters, AsyncSensorManager sensorManager,
WakeLock wakeLock, DockManager dockManager, WakeLock wakeLock, DockManager dockManager,
ProximitySensor proximitySensor, ProximitySensor.ProximityCheck proxCheck, ProximitySensor proximitySensor,
ProximityCheck proxCheck,
DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher, DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher,
SecureSettings secureSettings, AuthController authController, SecureSettings secureSettings, AuthController authController,
@Main DelayableExecutor mainExecutor, @Main DelayableExecutor mainExecutor,

View File

@@ -46,6 +46,7 @@ public interface DevicePostureController extends CallbackController<Callback> {
int DEVICE_POSTURE_HALF_OPENED = 2; int DEVICE_POSTURE_HALF_OPENED = 2;
int DEVICE_POSTURE_OPENED = 3; int DEVICE_POSTURE_OPENED = 3;
int DEVICE_POSTURE_FLIPPED = 4; int DEVICE_POSTURE_FLIPPED = 4;
int SUPPORTED_POSTURES_SIZE = DEVICE_POSTURE_FLIPPED + 1;
/** Return the current device posture. */ /** Return the current device posture. */
@DevicePostureInt int getDevicePosture(); @DevicePostureInt int getDevicePosture();

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2021 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.util.Log;
import androidx.annotation.NonNull;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
import javax.inject.Inject;
/**
* Proximity sensor that changes proximity sensor usage based on the current posture.
* Posture -> prox sensor mapping can be found in SystemUI config overlays at:
* - proximity_sensor_posture_mapping
* - proximity_sensor_secondary_posture_mapping.
* where the array indices correspond to the following postures:
* [UNKNOWN, CLOSED, HALF_OPENED, OPENED]
*/
class PostureDependentProximitySensor extends ProximitySensorImpl {
private final ThresholdSensor[] mPostureToPrimaryProxSensorMap;
private final ThresholdSensor[] mPostureToSecondaryProxSensorMap;
@Inject
PostureDependentProximitySensor(
@PrimaryProxSensor ThresholdSensor[] postureToPrimaryProxSensorMap,
@SecondaryProxSensor ThresholdSensor[] postureToSecondaryProxSensorMap,
@NonNull DelayableExecutor delayableExecutor,
@NonNull Execution execution,
@NonNull DevicePostureController devicePostureController
) {
super(
postureToPrimaryProxSensorMap[0],
postureToSecondaryProxSensorMap[0],
delayableExecutor,
execution
);
mPostureToPrimaryProxSensorMap = postureToPrimaryProxSensorMap;
mPostureToSecondaryProxSensorMap = postureToSecondaryProxSensorMap;
mDevicePosture = devicePostureController.getDevicePosture();
devicePostureController.addCallback(mDevicePostureCallback);
chooseSensors();
}
private void chooseSensors() {
if (mDevicePosture >= mPostureToPrimaryProxSensorMap.length
|| mDevicePosture >= mPostureToSecondaryProxSensorMap.length) {
Log.e("PostureDependentProxSensor",
"unsupported devicePosture=" + mDevicePosture);
return;
}
ThresholdSensor newPrimaryProx = mPostureToPrimaryProxSensorMap[mDevicePosture];
ThresholdSensor newSecondaryProx = mPostureToSecondaryProxSensorMap[mDevicePosture];
if (newPrimaryProx != mPrimaryThresholdSensor
|| newSecondaryProx != mSecondaryThresholdSensor) {
logDebug("Register new proximity sensors newPosture="
+ DevicePostureController.devicePostureToString(mDevicePosture));
unregisterInternal();
if (mPrimaryThresholdSensor != null) {
mPrimaryThresholdSensor.unregister(mPrimaryEventListener);
}
if (mSecondaryThresholdSensor != null) {
mSecondaryThresholdSensor.unregister(mSecondaryEventListener);
}
mPrimaryThresholdSensor = newPrimaryProx;
mSecondaryThresholdSensor = newSecondaryProx;
mInitializedListeners = false;
registerInternal();
}
}
private final DevicePostureController.Callback mDevicePostureCallback =
posture -> {
if (mDevicePosture == posture) {
return;
}
mDevicePosture = posture;
chooseSensors();
};
@Override
public String toString() {
return String.format("{posture=%s, proximitySensor=%s}",
DevicePostureController.devicePostureToString(mDevicePosture), super.toString());
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2021 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 com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.inject.Inject;
/**
* Convenience class allowing for briefly checking the proximity sensor.
*/
public class ProximityCheck implements Runnable {
private final ProximitySensor mSensor;
private final DelayableExecutor mDelayableExecutor;
private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
private final ThresholdSensor.Listener mListener;
private final AtomicBoolean mRegistered = new AtomicBoolean();
@Inject
public ProximityCheck(
ProximitySensor sensor,
@Main DelayableExecutor delayableExecutor) {
mSensor = sensor;
mSensor.setTag("prox_check");
mDelayableExecutor = delayableExecutor;
mListener = this::onProximityEvent;
}
/** Set a descriptive tag for the sensors registration. */
public void setTag(String tag) {
mSensor.setTag(tag);
}
@Override
public void run() {
unregister();
onProximityEvent(null);
}
/**
* Query the proximity sensor, timing out if no result.
*/
public void check(long timeoutMs, Consumer<Boolean> callback) {
if (!mSensor.isLoaded()) {
callback.accept(null);
return;
}
mCallbacks.add(callback);
if (!mRegistered.getAndSet(true)) {
mSensor.register(mListener);
mDelayableExecutor.executeDelayed(this, timeoutMs);
}
}
private void unregister() {
mSensor.unregister(mListener);
mRegistered.set(false);
}
private void onProximityEvent(ThresholdSensorEvent proximityEvent) {
mCallbacks.forEach(
booleanConsumer ->
booleanConsumer.accept(
proximityEvent == null ? null : proximityEvent.getBelow()));
mCallbacks.clear();
unregister();
mRegistered.set(false);
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019 The Android Open Source Project * Copyright (C) 2021 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,149 +16,24 @@
package com.android.systemui.util.sensors; package com.android.systemui.util.sensors;
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.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.inject.Inject;
/** /**
* Wrapper around SensorManager customized for the Proximity sensor. * Wrapper class for a dual ProximitySensor which supports a secondary sensor gated upon the
* * primary).
* 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 implements ThresholdSensor { public interface ProximitySensor extends ThresholdSensor {
private static final String TAG = "ProxSensor"; /**
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); * Returns true if we are registered with the SensorManager.
private static final long SECONDARY_PING_INTERVAL_MS = 5000; */
boolean isRegistered();
private final ThresholdSensor mPrimaryThresholdSensor;
private final ThresholdSensor mSecondaryThresholdSensor;
private final DelayableExecutor mDelayableExecutor;
private final Execution mExecution;
private final List<ThresholdSensor.Listener> mListeners = new ArrayList<>();
private String mTag = null;
@VisibleForTesting protected boolean mPaused;
private ThresholdSensorEvent mLastPrimaryEvent;
@VisibleForTesting
ThresholdSensorEvent mLastEvent;
private boolean mRegistered;
private final AtomicBoolean mAlerting = new AtomicBoolean();
private Runnable mCancelSecondaryRunnable;
private boolean mInitializedListeners = false;
private boolean mSecondarySafe = false;
private final ThresholdSensor.Listener mPrimaryEventListener = this::onPrimarySensorEvent;
private final ThresholdSensor.Listener mSecondaryEventListener =
new ThresholdSensor.Listener() {
@Override
public void onThresholdCrossed(ThresholdSensorEvent event) {
// If we no longer have a "below" signal and the secondary sensor is not
// considered "safe", then we need to turn it off.
if (!mSecondarySafe
&& (mLastPrimaryEvent == null
|| !mLastPrimaryEvent.getBelow()
|| !event.getBelow())) {
chooseSensor();
if (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow()) {
// Only check the secondary as long as the primary thinks we're near.
if (mCancelSecondaryRunnable != null) {
mCancelSecondaryRunnable.run();
mCancelSecondaryRunnable = null;
}
return;
} else {
// Check this sensor again in a moment.
mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(() -> {
// This is safe because we know that mSecondaryThresholdSensor
// is loaded, otherwise we wouldn't be here.
mPrimaryThresholdSensor.pause();
mSecondaryThresholdSensor.resume();
},
SECONDARY_PING_INTERVAL_MS);
}
}
logDebug("Secondary sensor event: " + event.getBelow() + ".");
if (!mPaused) {
onSensorEvent(event);
}
}
};
@Inject
public ProximitySensor(
@PrimaryProxSensor ThresholdSensor primary,
@SecondaryProxSensor ThresholdSensor secondary,
@Main DelayableExecutor delayableExecutor,
Execution execution) {
mPrimaryThresholdSensor = primary;
mSecondaryThresholdSensor = secondary;
mDelayableExecutor = delayableExecutor;
mExecution = execution;
}
@Override
public void setTag(String tag) {
mTag = tag;
mPrimaryThresholdSensor.setTag(tag + ":primary");
mSecondaryThresholdSensor.setTag(tag + ":secondary");
}
@Override
public void setDelay(int delay) {
mExecution.assertIsMainThread();
mPrimaryThresholdSensor.setDelay(delay);
mSecondaryThresholdSensor.setDelay(delay);
}
/** /**
* Unregister with the {@link SensorManager} without unsetting listeners on this object. * Whether the proximity sensor reports near. Can return null if no information has been
* received yet.
*/ */
@Override Boolean isNear();
public void pause() {
mExecution.assertIsMainThread();
mPaused = true;
unregisterInternal();
}
/** /** Update all listeners with the last value this class received from the sensor. */
* Register with the {@link SensorManager}. No-op if no listeners are registered on this object. void alertListeners();
*/
@Override
public void resume() {
mExecution.assertIsMainThread();
mPaused = false;
registerInternal();
}
/** /**
* Sets that it is safe to leave the secondary sensor on indefinitely. * Sets that it is safe to leave the secondary sensor on indefinitely.
@@ -166,249 +41,5 @@ public class ProximitySensor implements ThresholdSensor {
* The secondary sensor will be turned on if there are any registered listeners, regardless * The secondary sensor will be turned on if there are any registered listeners, regardless
* of what is reported by the primary sensor. * of what is reported by the primary sensor.
*/ */
public void setSecondarySafe(boolean safe) { void setSecondarySafe(boolean safe);
mSecondarySafe = mSecondaryThresholdSensor.isLoaded() && safe;
chooseSensor();
}
/**
* Returns true if we are registered with the SensorManager.
*/
public boolean isRegistered() {
return mRegistered;
}
/**
* Returns {@code false} if a Proximity sensor is not available.
*/
@Override
public boolean isLoaded() {
return mPrimaryThresholdSensor.isLoaded();
}
/**
* Add a listener.
*
* Registers itself with the {@link SensorManager} if this is the first listener
* added. If the ProximitySensor is paused, it will be registered when resumed.
*/
@Override
public void register(ThresholdSensor.Listener listener) {
mExecution.assertIsMainThread();
if (!isLoaded()) {
return;
}
if (mListeners.contains(listener)) {
logDebug("ProxListener registered multiple times: " + listener);
} else {
mListeners.add(listener);
}
registerInternal();
}
protected void registerInternal() {
mExecution.assertIsMainThread();
if (mRegistered || mPaused || mListeners.isEmpty()) {
return;
}
if (!mInitializedListeners) {
mPrimaryThresholdSensor.pause();
mSecondaryThresholdSensor.pause();
mPrimaryThresholdSensor.register(mPrimaryEventListener);
mSecondaryThresholdSensor.register(mSecondaryEventListener);
mInitializedListeners = true;
}
logDebug("Registering sensor listener");
mRegistered = true;
chooseSensor();
}
private void chooseSensor() {
mExecution.assertIsMainThread();
if (!mRegistered || mPaused || mListeners.isEmpty()) {
return;
}
if (mSecondarySafe) {
mSecondaryThresholdSensor.resume();
mPrimaryThresholdSensor.pause();
} else {
mPrimaryThresholdSensor.resume();
mSecondaryThresholdSensor.pause();
}
}
/**
* Remove a listener.
*
* If all listeners are removed from an instance of this class,
* it will unregister itself with the SensorManager.
*/
@Override
public void unregister(ThresholdSensor.Listener listener) {
mExecution.assertIsMainThread();
mListeners.remove(listener);
if (mListeners.size() == 0) {
unregisterInternal();
}
}
protected void unregisterInternal() {
mExecution.assertIsMainThread();
if (!mRegistered) {
return;
}
logDebug("unregistering sensor listener");
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 isLoaded() && mLastEvent != null ? mLastEvent.getBelow() : null;
}
/** Update all listeners with the last value this class received from the sensor. */
public void alertListeners() {
mExecution.assertIsMainThread();
if (mAlerting.getAndSet(true)) {
return;
}
if (mLastEvent != null) {
ThresholdSensorEvent lastEvent = mLastEvent; // Listeners can null out mLastEvent.
List<ThresholdSensor.Listener> listeners = new ArrayList<>(mListeners);
listeners.forEach(proximitySensorListener ->
proximitySensorListener.onThresholdCrossed(lastEvent));
}
mAlerting.set(false);
}
private void onPrimarySensorEvent(ThresholdSensorEvent event) {
mExecution.assertIsMainThread();
if (mLastPrimaryEvent != null && event.getBelow() == mLastPrimaryEvent.getBelow()) {
return;
}
mLastPrimaryEvent = event;
if (mSecondarySafe && mSecondaryThresholdSensor.isLoaded()) {
logDebug("Primary sensor reported " + (event.getBelow() ? "near" : "far")
+ ". Checking secondary.");
if (mCancelSecondaryRunnable == null) {
mSecondaryThresholdSensor.resume();
}
return;
}
if (!mSecondaryThresholdSensor.isLoaded()) { // No secondary
logDebug("Primary sensor event: " + event.getBelow() + ". No secondary.");
onSensorEvent(event);
} else if (event.getBelow()) { // Covered? Check secondary.
logDebug("Primary sensor event: " + event.getBelow() + ". Checking secondary.");
if (mCancelSecondaryRunnable != null) {
mCancelSecondaryRunnable.run();
}
mSecondaryThresholdSensor.resume();
} else { // Uncovered. Report immediately.
onSensorEvent(event);
}
}
private void onSensorEvent(ThresholdSensorEvent event) {
mExecution.assertIsMainThread();
if (mLastEvent != null && event.getBelow() == mLastEvent.getBelow()) {
return;
}
if (!mSecondarySafe && !event.getBelow()) {
chooseSensor();
}
mLastEvent = event;
alertListeners();
}
@Override
public String toString() {
return String.format("{registered=%s, paused=%s, near=%s, primarySensor=%s, "
+ "secondarySensor=%s secondarySafe=%s}",
isRegistered(), mPaused, isNear(), mPrimaryThresholdSensor,
mSecondaryThresholdSensor, mSecondarySafe);
}
/**
* Convenience class allowing for briefly checking the proximity sensor.
*/
public static class ProximityCheck implements Runnable {
private final ProximitySensor mSensor;
private final DelayableExecutor mDelayableExecutor;
private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
private final ThresholdSensor.Listener mListener;
private final AtomicBoolean mRegistered = new AtomicBoolean();
@Inject
public ProximityCheck(ProximitySensor sensor, @Main DelayableExecutor delayableExecutor) {
mSensor = sensor;
mSensor.setTag("prox_check");
mDelayableExecutor = delayableExecutor;
mListener = this::onProximityEvent;
}
/** Set a descriptive tag for the sensors registration. */
public void setTag(String tag) {
mSensor.setTag(tag);
}
@Override
public void run() {
unregister();
onProximityEvent(null);
}
/**
* Query the proximity sensor, timing out if no result.
*/
public void check(long timeoutMs, Consumer<Boolean> callback) {
if (!mSensor.isLoaded()) {
callback.accept(null);
return;
}
mCallbacks.add(callback);
if (!mRegistered.getAndSet(true)) {
mSensor.register(mListener);
mDelayableExecutor.executeDelayed(this, timeoutMs);
}
}
private void unregister() {
mSensor.unregister(mListener);
mRegistered.set(false);
}
private void onProximityEvent(ThresholdSensorEvent proximityEvent) {
mCallbacks.forEach(
booleanConsumer ->
booleanConsumer.accept(
proximityEvent == null ? null : proximityEvent.getBelow()));
mCallbacks.clear();
unregister();
mRegistered.set(false);
}
}
private void logDebug(String msg) {
if (DEBUG) {
Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
}
}
} }

View File

@@ -0,0 +1,360 @@
/*
* Copyright (C) 2019 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.SensorManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
/**
* 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.
*/
class ProximitySensorImpl implements ProximitySensor {
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;
ThresholdSensor mPrimaryThresholdSensor;
ThresholdSensor mSecondaryThresholdSensor;
private final DelayableExecutor mDelayableExecutor;
private final Execution mExecution;
private final List<ThresholdSensor.Listener> mListeners = new ArrayList<>();
private String mTag = null;
@VisibleForTesting protected boolean mPaused;
private ThresholdSensorEvent mLastPrimaryEvent;
@VisibleForTesting
ThresholdSensorEvent mLastEvent;
private boolean mRegistered;
private final AtomicBoolean mAlerting = new AtomicBoolean();
private Runnable mCancelSecondaryRunnable;
boolean mInitializedListeners = false;
private boolean mSecondarySafe = false; // safe to skip primary sensor check and use secondary
protected @DevicePostureController.DevicePostureInt int mDevicePosture;
final ThresholdSensor.Listener mPrimaryEventListener = this::onPrimarySensorEvent;
final ThresholdSensor.Listener mSecondaryEventListener =
new ThresholdSensor.Listener() {
@Override
public void onThresholdCrossed(ThresholdSensorEvent event) {
// If we no longer have a "below" signal and the secondary sensor is not
// considered "safe", then we need to turn it off.
if (!mSecondarySafe
&& (mLastPrimaryEvent == null
|| !mLastPrimaryEvent.getBelow()
|| !event.getBelow())) {
chooseSensor();
if (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow()) {
// Only check the secondary as long as the primary thinks we're near.
if (mCancelSecondaryRunnable != null) {
mCancelSecondaryRunnable.run();
mCancelSecondaryRunnable = null;
}
return;
} else {
// Check this sensor again in a moment.
mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(() -> {
// This is safe because we know that mSecondaryThresholdSensor
// is loaded, otherwise we wouldn't be here.
mPrimaryThresholdSensor.pause();
mSecondaryThresholdSensor.resume();
},
SECONDARY_PING_INTERVAL_MS);
}
}
logDebug("Secondary sensor event: " + event.getBelow() + ".");
if (!mPaused) {
onSensorEvent(event);
}
}
};
@Inject
ProximitySensorImpl(
@PrimaryProxSensor ThresholdSensor primary,
@SecondaryProxSensor ThresholdSensor secondary,
@Main DelayableExecutor delayableExecutor,
Execution execution) {
mPrimaryThresholdSensor = primary;
mSecondaryThresholdSensor = secondary;
mDelayableExecutor = delayableExecutor;
mExecution = execution;
}
@Override
public void setTag(String tag) {
mTag = tag;
mPrimaryThresholdSensor.setTag(tag + ":primary");
mSecondaryThresholdSensor.setTag(tag + ":secondary");
}
@Override
public void setDelay(int delay) {
mExecution.assertIsMainThread();
mPrimaryThresholdSensor.setDelay(delay);
mSecondaryThresholdSensor.setDelay(delay);
}
/**
* Unregister with the {@link SensorManager} without unsetting listeners on this object.
*/
@Override
public void pause() {
mExecution.assertIsMainThread();
mPaused = true;
unregisterInternal();
}
/**
* Register with the {@link SensorManager}. No-op if no listeners are registered on this object.
*/
@Override
public void resume() {
mExecution.assertIsMainThread();
mPaused = false;
registerInternal();
}
@Override
public void setSecondarySafe(boolean safe) {
mSecondarySafe = mSecondaryThresholdSensor.isLoaded() && safe;
chooseSensor();
}
/**
* Returns true if we are registered with the SensorManager.
*/
@Override
public boolean isRegistered() {
return mRegistered;
}
/**
* Returns {@code false} if a Proximity sensor is not available.
*/
@Override
public boolean isLoaded() {
return mPrimaryThresholdSensor.isLoaded();
}
/**
* Add a listener.
*
* Registers itself with the {@link SensorManager} if this is the first listener
* added. If the ProximitySensor is paused, it will be registered when resumed.
*/
@Override
public void register(ThresholdSensor.Listener listener) {
mExecution.assertIsMainThread();
if (!isLoaded()) {
return;
}
if (mListeners.contains(listener)) {
logDebug("ProxListener registered multiple times: " + listener);
} else {
mListeners.add(listener);
}
registerInternal();
}
protected void registerInternal() {
mExecution.assertIsMainThread();
if (mRegistered || mPaused || mListeners.isEmpty()) {
return;
}
if (!mInitializedListeners) {
mPrimaryThresholdSensor.pause();
mSecondaryThresholdSensor.pause();
mPrimaryThresholdSensor.register(mPrimaryEventListener);
mSecondaryThresholdSensor.register(mSecondaryEventListener);
mInitializedListeners = true;
}
mRegistered = true;
chooseSensor();
}
private void chooseSensor() {
mExecution.assertIsMainThread();
if (!mRegistered || mPaused || mListeners.isEmpty()) {
return;
}
if (mSecondarySafe) {
mSecondaryThresholdSensor.resume();
mPrimaryThresholdSensor.pause();
} else {
mPrimaryThresholdSensor.resume();
mSecondaryThresholdSensor.pause();
}
}
/**
* Remove a listener.
*
* If all listeners are removed from an instance of this class,
* it will unregister itself with the SensorManager.
*/
@Override
public void unregister(ThresholdSensor.Listener listener) {
mExecution.assertIsMainThread();
mListeners.remove(listener);
if (mListeners.size() == 0) {
unregisterInternal();
}
}
@Override
public String getName() {
return mPrimaryThresholdSensor.getName();
}
@Override
public String getType() {
return mPrimaryThresholdSensor.getType();
}
protected void unregisterInternal() {
mExecution.assertIsMainThread();
if (!mRegistered) {
return;
}
logDebug("unregistering sensor listener");
mPrimaryThresholdSensor.pause();
mSecondaryThresholdSensor.pause();
if (mCancelSecondaryRunnable != null) {
mCancelSecondaryRunnable.run();
mCancelSecondaryRunnable = null;
}
mLastPrimaryEvent = null; // Forget what we know.
mLastEvent = null;
mRegistered = false;
}
@Override
public Boolean isNear() {
return isLoaded() && mLastEvent != null ? mLastEvent.getBelow() : null;
}
@Override
public void alertListeners() {
mExecution.assertIsMainThread();
if (mAlerting.getAndSet(true)) {
return;
}
if (mLastEvent != null) {
ThresholdSensorEvent lastEvent = mLastEvent; // Listeners can null out mLastEvent.
List<ThresholdSensor.Listener> listeners = new ArrayList<>(mListeners);
listeners.forEach(proximitySensorListener ->
proximitySensorListener.onThresholdCrossed(lastEvent));
}
mAlerting.set(false);
}
private void onPrimarySensorEvent(ThresholdSensorEvent event) {
mExecution.assertIsMainThread();
if (mLastPrimaryEvent != null && event.getBelow() == mLastPrimaryEvent.getBelow()) {
return;
}
mLastPrimaryEvent = event;
if (mSecondarySafe && mSecondaryThresholdSensor.isLoaded()) {
logDebug("Primary sensor reported " + (event.getBelow() ? "near" : "far")
+ ". Checking secondary.");
if (mCancelSecondaryRunnable == null) {
mSecondaryThresholdSensor.resume();
}
return;
}
if (!mSecondaryThresholdSensor.isLoaded()) { // No secondary
logDebug("Primary sensor event: " + event.getBelow() + ". No secondary.");
onSensorEvent(event);
} else if (event.getBelow()) { // Covered? Check secondary.
logDebug("Primary sensor event: " + event.getBelow() + ". Checking secondary.");
if (mCancelSecondaryRunnable != null) {
mCancelSecondaryRunnable.run();
}
mSecondaryThresholdSensor.resume();
} else { // Uncovered. Report immediately.
onSensorEvent(event);
}
}
private void onSensorEvent(ThresholdSensorEvent event) {
mExecution.assertIsMainThread();
if (mLastEvent != null && event.getBelow() == mLastEvent.getBelow()) {
return;
}
if (!mSecondarySafe && !event.getBelow()) {
chooseSensor();
}
mLastEvent = event;
alertListeners();
}
@Override
public String toString() {
return String.format("{registered=%s, paused=%s, near=%s, posture=%s, primarySensor=%s, "
+ "secondarySensor=%s secondarySafe=%s}",
isRegistered(), mPaused, isNear(), mDevicePosture, mPrimaryThresholdSensor,
mSecondaryThresholdSensor, mSecondarySafe);
}
void logDebug(String msg) {
if (DEBUG) {
Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
}
}
}

View File

@@ -16,11 +16,23 @@
package com.android.systemui.util.sensors; package com.android.systemui.util.sensors;
import android.content.res.Resources;
import android.hardware.Sensor; import android.hardware.Sensor;
import android.hardware.SensorManager; import android.hardware.SensorManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.HashMap;
import java.util.Map;
import dagger.Lazy;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -31,8 +43,10 @@ import dagger.Provides;
public class SensorModule { public class SensorModule {
@Provides @Provides
@PrimaryProxSensor @PrimaryProxSensor
static ThresholdSensor providePrimaryProxSensor(SensorManager sensorManager, static ThresholdSensor providePrimaryProximitySensor(
ThresholdSensorImpl.Builder thresholdSensorBuilder) { SensorManager sensorManager,
ThresholdSensorImpl.Builder thresholdSensorBuilder
) {
try { try {
return thresholdSensorBuilder return thresholdSensorBuilder
.setSensorDelay(SensorManager.SENSOR_DELAY_NORMAL) .setSensorDelay(SensorManager.SENSOR_DELAY_NORMAL)
@@ -52,8 +66,9 @@ public class SensorModule {
@Provides @Provides
@SecondaryProxSensor @SecondaryProxSensor
static ThresholdSensor provideSecondaryProxSensor( static ThresholdSensor provideSecondaryProximitySensor(
ThresholdSensorImpl.Builder thresholdSensorBuilder) { ThresholdSensorImpl.Builder thresholdSensorBuilder
) {
try { try {
return thresholdSensorBuilder return thresholdSensorBuilder
.setSensorResourceId(R.string.proximity_sensor_secondary_type, true) .setSensorResourceId(R.string.proximity_sensor_secondary_type, true)
@@ -64,4 +79,153 @@ public class SensorModule {
return thresholdSensorBuilder.setSensor(null).setThresholdValue(0).build(); return thresholdSensorBuilder.setSensor(null).setThresholdValue(0).build();
} }
} }
/**
* If postures are supported on the device, returns a posture dependent proximity sensor
* which switches proximity sensors based on the current posture.
*
* If postures are not supported the regular {@link ProximitySensorImpl} will be returned.
*/
@Provides
static ProximitySensor provideProximitySensor(
@Main Resources resources,
Lazy<PostureDependentProximitySensor> postureDependentProximitySensorProvider,
Lazy<ProximitySensorImpl> proximitySensorProvider
) {
if (hasPostureSupport(
resources.getStringArray(R.array.proximity_sensor_posture_mapping))) {
return postureDependentProximitySensorProvider.get();
} else {
return proximitySensorProvider.get();
}
}
@Provides
static ProximityCheck provideProximityCheck(
ProximitySensor proximitySensor,
@Main DelayableExecutor delayableExecutor
) {
return new ProximityCheck(
proximitySensor,
delayableExecutor
);
}
@Provides
@PrimaryProxSensor
@NonNull
static ThresholdSensor[] providePostureToProximitySensorMapping(
ThresholdSensorImpl.BuilderFactory thresholdSensorImplBuilderFactory,
@Main Resources resources
) {
return createPostureToSensorMapping(
thresholdSensorImplBuilderFactory,
resources.getStringArray(R.array.proximity_sensor_posture_mapping),
R.dimen.proximity_sensor_threshold,
R.dimen.proximity_sensor_threshold_latch
);
}
@Provides
@SecondaryProxSensor
@NonNull
static ThresholdSensor[] providePostureToSecondaryProximitySensorMapping(
ThresholdSensorImpl.BuilderFactory thresholdSensorImplBuilderFactory,
@Main Resources resources
) {
return createPostureToSensorMapping(
thresholdSensorImplBuilderFactory,
resources.getStringArray(R.array.proximity_sensor_secondary_posture_mapping),
R.dimen.proximity_sensor_secondary_threshold,
R.dimen.proximity_sensor_secondary_threshold_latch
);
}
/**
* Builds sensors to use per posture.
*
* @param sensorTypes an array where the index represents
* {@link DevicePostureController.DevicePostureInt} and the value
* at the given index is the sensorType. Empty values represent
* no sensor desired.
* @param proximitySensorThresholdResourceId resource id for the threshold for all sensor
* postures. This currently only supports one value.
* This needs to be updated in the future if postures
* use different sensors with differing thresholds.
* @param proximitySensorThresholdLatchResourceId resource id for the latch for all sensor
* postures. This currently only supports one
* value. This needs to be updated in the future
* if postures use different sensors with
* differing latches.
* @return an array where the index represents the device posture
* {@link DevicePostureController.DevicePostureInt} and the value at the index is the sensor to
* use when the device is in that posture.
*/
@NonNull
private static ThresholdSensor[] createPostureToSensorMapping(
ThresholdSensorImpl.BuilderFactory thresholdSensorImplBuilderFactory,
String[] sensorTypes,
int proximitySensorThresholdResourceId,
int proximitySensorThresholdLatchResourceId
) {
ThresholdSensor noProxSensor = thresholdSensorImplBuilderFactory
.createBuilder()
.setSensor(null).setThresholdValue(0).build();
// length and index of sensorMap correspond to DevicePostureController.DevicePostureInt:
final ThresholdSensor[] sensorMap =
new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE];
for (int i = 0; i < DevicePostureController.SUPPORTED_POSTURES_SIZE; i++) {
sensorMap[i] = noProxSensor;
}
if (!hasPostureSupport(sensorTypes)) {
Log.e("SensorModule", "config doesn't support postures,"
+ " but attempting to retrieve proxSensorMapping");
return sensorMap;
}
// Map of sensorType => Sensor, so we reuse the same sensor if it's the same between
// postures
Map<String, ThresholdSensor> typeToSensorMap = new HashMap<>();
for (int i = 0; i < sensorTypes.length; i++) {
try {
final String sensorType = sensorTypes[i];
if (typeToSensorMap.containsKey(sensorType)) {
sensorMap[i] = typeToSensorMap.get(sensorType);
} else {
sensorMap[i] = thresholdSensorImplBuilderFactory
.createBuilder()
.setSensorType(sensorTypes[i], true)
.setThresholdResourceId(proximitySensorThresholdResourceId)
.setThresholdLatchResourceId(proximitySensorThresholdLatchResourceId)
.build();
typeToSensorMap.put(sensorType, sensorMap[i]);
}
} catch (IllegalStateException e) {
// do nothing, sensor at this posture is already set to noProxSensor
}
}
return sensorMap;
}
/**
* Returns true if there's at least one non-empty sensor type in the given array.
*/
private static boolean hasPostureSupport(String[] postureToSensorTypeMapping) {
if (postureToSensorTypeMapping == null || postureToSensorTypeMapping.length == 0) {
return false;
}
for (String sensorType : postureToSensorTypeMapping) {
if (!TextUtils.isEmpty(sensorType)) {
return true;
}
}
return false;
}
} }

View File

@@ -16,8 +16,6 @@
package com.android.systemui.util.sensors; package com.android.systemui.util.sensors;
import java.util.Locale;
/** /**
* A wrapper class for sensors that have a boolean state - above/below. * A wrapper class for sensors that have a boolean state - above/below.
*/ */
@@ -76,6 +74,16 @@ public interface ThresholdSensor {
*/ */
void unregister(Listener listener); void unregister(Listener listener);
/**
* Name of the sensor.
*/
String getName();
/**
* Type of the sensor.
*/
String getType();
/** /**
* Interface for listening to events on {@link ThresholdSensor} * Interface for listening to events on {@link ThresholdSensor}
*/ */
@@ -85,34 +93,4 @@ public interface ThresholdSensor {
*/ */
void onThresholdCrossed(ThresholdSensorEvent event); 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,49 @@
/*
* Copyright (C) 2021 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;
/**
* Returned when the below/above state of a {@link ThresholdSensor} changes.
*/
public 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

@@ -21,6 +21,7 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent; import android.hardware.SensorEvent;
import android.hardware.SensorEventListener; import android.hardware.SensorEventListener;
import android.hardware.SensorManager; import android.hardware.SensorManager;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
@@ -32,7 +33,10 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
class ThresholdSensorImpl implements ThresholdSensor { /**
* Sensor that will only trigger beyond some lower and upper threshold.
*/
public class ThresholdSensorImpl implements ThresholdSensor {
private static final String TAG = "ThresholdSensor"; private static final String TAG = "ThresholdSensor";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -198,6 +202,15 @@ class ThresholdSensorImpl implements ThresholdSensor {
alertListenersInternal(belowThreshold, timestampNs); alertListenersInternal(belowThreshold, timestampNs);
} }
@Override
public String getName() {
return mSensor != null ? mSensor.getName() : null;
}
@Override
public String getType() {
return mSensor != null ? mSensor.getStringType() : null;
}
@Override @Override
public String toString() { public String toString() {
@@ -211,7 +224,12 @@ class ThresholdSensorImpl implements ThresholdSensor {
} }
} }
static class Builder { /**
* Use to build a ThresholdSensor. Should only be used once per sensor built, since
* parameters are not reset after calls to build(). For ease of retrievingnew Builders, use
* {@link BuilderFactory}.
*/
public static class Builder {
private final Resources mResources; private final Resources mResources;
private final AsyncSensorManager mSensorManager; private final AsyncSensorManager mSensorManager;
private final Execution mExecution; private final Execution mExecution;
@@ -318,7 +336,7 @@ class ThresholdSensorImpl implements ThresholdSensor {
@VisibleForTesting @VisibleForTesting
Sensor findSensorByType(String sensorType, boolean requireWakeUp) { Sensor findSensorByType(String sensorType, boolean requireWakeUp) {
if (sensorType.isEmpty()) { if (TextUtils.isEmpty(sensorType)) {
return null; return null;
} }
@@ -336,4 +354,29 @@ class ThresholdSensorImpl implements ThresholdSensor {
return sensor; return sensor;
} }
} }
/**
* Factory that creates a new ThresholdSensorImpl.Builder. In general, Builders should not be
* reused after creating a ThresholdSensor or else there may be default threshold and sensor
* values set from the previous built sensor.
*/
public static class BuilderFactory {
private final Resources mResources;
private final AsyncSensorManager mSensorManager;
private final Execution mExecution;
@Inject
BuilderFactory(
@Main Resources resources,
AsyncSensorManager sensorManager,
Execution execution) {
mResources = resources;
mSensorManager = sensorManager;
mExecution = execution;
}
ThresholdSensorImpl.Builder createBuilder() {
return new Builder(mResources, mSensorManager, mExecution);
}
}
} }

View File

@@ -53,7 +53,8 @@ import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.FakeProximitySensor; import com.android.systemui.util.sensors.FakeProximitySensor;
import com.android.systemui.util.sensors.FakeSensorManager; import com.android.systemui.util.sensors.FakeSensorManager;
import com.android.systemui.util.sensors.FakeThresholdSensor; import com.android.systemui.util.sensors.FakeThresholdSensor;
import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.sensors.ProximityCheck;
import com.android.systemui.util.sensors.ThresholdSensorEvent;
import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.wakelock.WakeLock; import com.android.systemui.util.wakelock.WakeLock;
@@ -80,7 +81,7 @@ public class DozeTriggersTest extends SysuiTestCase {
@Mock @Mock
private DockManager mDockManager; private DockManager mDockManager;
@Mock @Mock
private ProximitySensor.ProximityCheck mProximityCheck; private ProximityCheck mProximityCheck;
@Mock @Mock
private AuthController mAuthController; private AuthController mAuthController;
@Mock @Mock
@@ -136,14 +137,14 @@ public class DozeTriggersTest extends SysuiTestCase {
mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE); mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
clearInvocations(mMachine); clearInvocations(mMachine);
mProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 1)); mProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 1));
captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */); captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
mProximitySensor.alertListeners(); mProximitySensor.alertListeners();
verify(mMachine, never()).requestState(any()); verify(mMachine, never()).requestState(any());
verify(mMachine, never()).requestPulse(anyInt()); verify(mMachine, never()).requestPulse(anyInt());
mProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(false, 2)); mProximitySensor.setLastEvent(new ThresholdSensorEvent(false, 2));
mProximitySensor.alertListeners(); mProximitySensor.alertListeners();
waitForSensorManager(); waitForSensorManager();
captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */); captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);

View File

@@ -19,14 +19,21 @@ package com.android.systemui.util.sensors;
import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.FakeExecution; import com.android.systemui.util.concurrency.FakeExecution;
public class FakeProximitySensor extends ProximitySensor { public class FakeProximitySensor extends ProximitySensorImpl {
private boolean mAvailable; private boolean mAvailable;
private boolean mRegistered; private boolean mRegistered;
public FakeProximitySensor(ThresholdSensor primary, ThresholdSensor secondary, public FakeProximitySensor(
DelayableExecutor delayableExecutor) { ThresholdSensor primary,
super(primary, secondary == null ? new FakeThresholdSensor() : secondary, ThresholdSensor secondary,
delayableExecutor, new FakeExecution()); DelayableExecutor delayableExecutor
) {
super(
primary,
secondary == null ? new FakeThresholdSensor() : secondary,
delayableExecutor,
new FakeExecution()
);
mAvailable = true; mAvailable = true;
} }

View File

@@ -59,6 +59,16 @@ public class FakeThresholdSensor implements ThresholdSensor {
mListeners.remove(listener); mListeners.remove(listener);
} }
@Override
public String getName() {
return "FakeThresholdSensorName";
}
@Override
public String getType() {
return "FakeThresholdSensorType";
}
public void setLoaded(boolean loaded) { public void setLoaded(boolean loaded) {
mIsLoaded = loaded; mIsLoaded = loaded;
} }

View File

@@ -0,0 +1,114 @@
/*
* Copyright (C) 2021 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.mockito.Mockito.verify;
import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.concurrency.FakeExecution;
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.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class PostureDependentProximitySensorTest extends SysuiTestCase {
@Mock private Resources mResources;
@Mock private DevicePostureController mDevicePostureController;
@Mock private AsyncSensorManager mSensorManager;
@Captor private ArgumentCaptor<DevicePostureController.Callback> mPostureListenerCaptor =
ArgumentCaptor.forClass(DevicePostureController.Callback.class);
private DevicePostureController.Callback mPostureListener;
private PostureDependentProximitySensor mProximitySensor;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
allowTestableLooperAsMainThread();
mProximitySensor = new PostureDependentProximitySensor(
new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE],
new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE],
mFakeExecutor,
new FakeExecution(),
mDevicePostureController
);
}
@Test
public void testPostureChangeListenerAdded() {
capturePostureListener();
}
@Test
public void testPostureChangeListenerUpdatesPosture() {
// GIVEN posture listener is registered
capturePostureListener();
// WHEN the posture changes to DEVICE_POSTURE_OPENED
mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED);
// THEN device posture is updated to DEVICE_POSTURE_OPENED
assertEquals(DevicePostureController.DEVICE_POSTURE_OPENED,
mProximitySensor.mDevicePosture);
// WHEN the posture changes to DEVICE_POSTURE_CLOSED
mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_CLOSED);
// THEN device posture is updated to DEVICE_POSTURE_CLOSED
assertEquals(DevicePostureController.DEVICE_POSTURE_CLOSED,
mProximitySensor.mDevicePosture);
// WHEN the posture changes to DEVICE_POSTURE_FLIPPED
mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_FLIPPED);
// THEN device posture is updated to DEVICE_POSTURE_FLIPPED
assertEquals(DevicePostureController.DEVICE_POSTURE_FLIPPED,
mProximitySensor.mDevicePosture);
// WHEN the posture changes to DEVICE_POSTURE_HALF_OPENED
mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
// THEN device posture is updated to DEVICE_POSTURE_HALF_OPENED
assertEquals(DevicePostureController.DEVICE_POSTURE_HALF_OPENED,
mProximitySensor.mDevicePosture);
}
private void capturePostureListener() {
verify(mDevicePostureController).addCallback(mPostureListenerCaptor.capture());
mPostureListener = mPostureListenerCaptor.getValue();
}
}

View File

@@ -49,7 +49,7 @@ public class ProximityCheckTest extends SysuiTestCase {
private TestableCallback mTestableCallback = new TestableCallback(); private TestableCallback mTestableCallback = new TestableCallback();
private ProximitySensor.ProximityCheck mProximityCheck; private ProximityCheck mProximityCheck;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@@ -58,7 +58,7 @@ public class ProximityCheckTest extends SysuiTestCase {
thresholdSensor.setLoaded(true); thresholdSensor.setLoaded(true);
mFakeProximitySensor = new FakeProximitySensor(thresholdSensor, null, mFakeExecutor); mFakeProximitySensor = new FakeProximitySensor(thresholdSensor, null, mFakeExecutor);
mProximityCheck = new ProximitySensor.ProximityCheck(mFakeProximitySensor, mFakeExecutor); mProximityCheck = new ProximityCheck(mFakeProximitySensor, mFakeExecutor);
} }
@Test @Test
@@ -67,7 +67,7 @@ public class ProximityCheckTest extends SysuiTestCase {
assertNull(mTestableCallback.mLastResult); assertNull(mTestableCallback.mLastResult);
mFakeProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 0)); mFakeProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 0));
mFakeProximitySensor.alertListeners(); mFakeProximitySensor.alertListeners();
assertTrue(mTestableCallback.mLastResult); assertTrue(mTestableCallback.mLastResult);
@@ -103,7 +103,7 @@ public class ProximityCheckTest extends SysuiTestCase {
mProximityCheck.check(100, mTestableCallback); mProximityCheck.check(100, mTestableCallback);
mFakeProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 0)); mFakeProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 0));
mFakeProximitySensor.alertListeners(); mFakeProximitySensor.alertListeners();
assertThat(mTestableCallback.mLastResult).isNotNull(); assertThat(mTestableCallback.mLastResult).isNotNull();
@@ -123,7 +123,7 @@ public class ProximityCheckTest extends SysuiTestCase {
assertNull(mTestableCallback.mLastResult); assertNull(mTestableCallback.mLastResult);
mFakeProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 0)); mFakeProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 0));
mFakeProximitySensor.alertListeners(); mFakeProximitySensor.alertListeners();
assertTrue(mTestableCallback.mLastResult); assertTrue(mTestableCallback.mLastResult);

View File

@@ -42,7 +42,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest @SmallTest
@RunWith(AndroidTestingRunner.class) @RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper @TestableLooper.RunWithLooper
public class ProximitySensorDualTest extends SysuiTestCase { public class ProximitySensorImplDualTest extends SysuiTestCase {
private ProximitySensor mProximitySensor; private ProximitySensor mProximitySensor;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private FakeThresholdSensor mThresholdSensorPrimary; private FakeThresholdSensor mThresholdSensorPrimary;
@@ -57,7 +57,7 @@ public class ProximitySensorDualTest extends SysuiTestCase {
mThresholdSensorSecondary = new FakeThresholdSensor(); mThresholdSensorSecondary = new FakeThresholdSensor();
mThresholdSensorSecondary.setLoaded(true); mThresholdSensorSecondary.setLoaded(true);
mProximitySensor = new ProximitySensor( mProximitySensor = new ProximitySensorImpl(
mThresholdSensorPrimary, mThresholdSensorSecondary, mFakeExecutor, mThresholdSensorPrimary, mThresholdSensorSecondary, mFakeExecutor,
new FakeExecution()); new FakeExecution());
} }
@@ -430,11 +430,11 @@ public class ProximitySensorDualTest extends SysuiTestCase {
} }
private static class TestableListener implements ThresholdSensor.Listener { private static class TestableListener implements ThresholdSensor.Listener {
ThresholdSensor.ThresholdSensorEvent mLastEvent; ThresholdSensorEvent mLastEvent;
int mCallCount = 0; int mCallCount = 0;
@Override @Override
public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent proximityEvent) { public void onThresholdCrossed(ThresholdSensorEvent proximityEvent) {
mLastEvent = proximityEvent; mLastEvent = proximityEvent;
mCallCount++; mCallCount++;
} }

View File

@@ -42,7 +42,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest @SmallTest
@RunWith(AndroidTestingRunner.class) @RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper @TestableLooper.RunWithLooper
public class ProximitySensorSingleTest extends SysuiTestCase { public class ProximitySensorImplSingleTest extends SysuiTestCase {
private ProximitySensor mProximitySensor; private ProximitySensor mProximitySensor;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private FakeThresholdSensor mThresholdSensor; private FakeThresholdSensor mThresholdSensor;
@@ -54,7 +54,7 @@ public class ProximitySensorSingleTest extends SysuiTestCase {
mThresholdSensor = new FakeThresholdSensor(); mThresholdSensor = new FakeThresholdSensor();
mThresholdSensor.setLoaded(true); mThresholdSensor.setLoaded(true);
mProximitySensor = new ProximitySensor( mProximitySensor = new ProximitySensorImpl(
mThresholdSensor, new FakeThresholdSensor(), mFakeExecutor, new FakeExecution()); mThresholdSensor, new FakeThresholdSensor(), mFakeExecutor, new FakeExecution());
} }
@@ -215,7 +215,7 @@ public class ProximitySensorSingleTest extends SysuiTestCase {
public void testPreventRecursiveAlert() { public void testPreventRecursiveAlert() {
TestableListener listenerA = new TestableListener() { TestableListener listenerA = new TestableListener() {
@Override @Override
public void onThresholdCrossed(ProximitySensor.ThresholdSensorEvent proximityEvent) { public void onThresholdCrossed(ThresholdSensorEvent proximityEvent) {
super.onThresholdCrossed(proximityEvent); super.onThresholdCrossed(proximityEvent);
if (mCallCount < 2) { if (mCallCount < 2) {
mProximitySensor.alertListeners(); mProximitySensor.alertListeners();
@@ -231,11 +231,11 @@ public class ProximitySensorSingleTest extends SysuiTestCase {
} }
private static class TestableListener implements ThresholdSensor.Listener { private static class TestableListener implements ThresholdSensor.Listener {
ThresholdSensor.ThresholdSensorEvent mLastEvent; ThresholdSensorEvent mLastEvent;
int mCallCount = 0; int mCallCount = 0;
@Override @Override
public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent proximityEvent) { public void onThresholdCrossed(ThresholdSensorEvent proximityEvent) {
mLastEvent = proximityEvent; mLastEvent = proximityEvent;
mCallCount++; mCallCount++;
} }

View File

@@ -380,7 +380,7 @@ public class ThresholdSensorImplTest extends SysuiTestCase {
int mCallCount; int mCallCount;
@Override @Override
public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent event) { public void onThresholdCrossed(ThresholdSensorEvent event) {
mBelow = event.getBelow(); mBelow = event.getBelow();
mTimestampNs = event.getTimestampNs(); mTimestampNs = event.getTimestampNs();
mCallCount++; mCallCount++;