From 63a1282b037d1df9ac85c92107f44baaaff7b2ca Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Mon, 16 Sep 2019 14:38:06 -0400 Subject: [PATCH] Make DozeSensors, DozeTriggers use ProximitySensor This CL takes the features used in DozeSensors and applies them to the publicly available util.sensors.ProximitySensor class. It introduces a RateLimiter class for temprorarily suspending the ProximitySensor as requested. It introduces a ProximityCheck class for a quick, one time test of the proximity sensor. There is some reshuffling of Sensor related code to its own package as well. Bug: 141122220 Test: atest SystemUITests Change-Id: If126be9a56c6966c1678ee70fa6f4623e51361a7 --- .../systemui/plugins/SensorManagerPlugin.java | 2 +- .../src/com/android/systemui/Dependency.java | 5 +- .../android/systemui/DependencyProvider.java | 8 + .../com/android/systemui/SystemUIModule.java | 2 +- .../classifier/FalsingManagerImpl.java | 2 +- .../classifier/FalsingManagerProxy.java | 4 +- .../brightline/BrightLineFalsingManager.java | 2 +- .../brightline/FalsingClassifier.java | 2 +- .../brightline/ProximityClassifier.java | 2 +- .../android/systemui/doze/DozeFactory.java | 6 +- .../android/systemui/doze/DozeSensors.java | 144 ++------ .../android/systemui/doze/DozeTriggers.java | 151 ++------ .../statusbar/phone/KeyguardLiftController.kt | 2 +- .../systemui/util/ProximitySensor.java | 186 ---------- .../{ => sensors}/AsyncSensorManager.java | 26 +- .../util/sensors/LimitableSensor.java | 32 ++ .../util/sensors/ProximitySensor.java | 332 ++++++++++++++++++ .../util/sensors/SensorRateLimiter.java | 77 ++++ .../classifier/FalsingManagerProxyTest.java | 2 +- .../brightline/ProximityClassifierTest.java | 2 +- .../systemui/doze/DozeConfigurationUtil.java | 2 +- .../doze/DozeScreenBrightnessTest.java | 2 +- .../systemui/doze/DozeSensorsTest.java | 2 +- .../systemui/doze/DozeTriggersTest.java | 35 +- .../{ => sensors}/AsyncSensorManagerTest.java | 40 +-- .../sensors}/FakeSensorManager.java | 16 +- .../util/sensors/ProximitySensorTest.java | 261 ++++++++++++++ .../util/sensors/RateLimiterTest.java | 119 +++++++ 28 files changed, 976 insertions(+), 490 deletions(-) delete mode 100644 packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java rename packages/SystemUI/src/com/android/systemui/util/{ => sensors}/AsyncSensorManager.java (91%) create mode 100644 packages/SystemUI/src/com/android/systemui/util/sensors/LimitableSensor.java create mode 100644 packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java create mode 100644 packages/SystemUI/src/com/android/systemui/util/sensors/SensorRateLimiter.java rename packages/SystemUI/tests/src/com/android/systemui/util/{ => sensors}/AsyncSensorManagerTest.java (71%) rename packages/SystemUI/tests/src/com/android/systemui/{utils/hardware => util/sensors}/FakeSensorManager.java (95%) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/util/sensors/RateLimiterTest.java diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java index 60435d0dec356..d62c1d411cff1 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java @@ -22,7 +22,7 @@ import com.android.systemui.plugins.annotations.ProvidesInterface; /** * Allows for additional sensors to be retrieved from - * {@link com.android.systemui.util.AsyncSensorManager}. + * {@link com.android.systemui.util.sensors.AsyncSensorManager}. */ @ProvidesInterface(action = SensorManagerPlugin.ACTION, version = SensorManagerPlugin.VERSION) public interface SensorManagerPlugin extends Plugin { diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index bd5b9c724eb36..ca6803d480c0c 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -15,6 +15,7 @@ package com.android.systemui; import android.annotation.Nullable; +import android.app.AlarmManager; import android.app.INotificationManager; import android.content.res.Configuration; import android.hardware.SensorPrivacyManager; @@ -110,10 +111,10 @@ import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunablePadding.TunablePaddingService; import com.android.systemui.tuner.TunerService; -import com.android.systemui.util.AsyncSensorManager; import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.util.leak.LeakReporter; +import com.android.systemui.util.sensors.AsyncSensorManager; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -314,6 +315,7 @@ public class Dependency { @Inject Lazy mINotificationManager; @Inject Lazy mFalsingManager; @Inject Lazy mSysUiStateFlagsContainer; + @Inject Lazy mAlarmManager; @Inject public Dependency() { @@ -508,6 +510,7 @@ public class Dependency { mProviders.put(INotificationManager.class, mINotificationManager::get); mProviders.put(FalsingManager.class, mFalsingManager::get); mProviders.put(SysUiState.class, mSysUiStateFlagsContainer::get); + mProviders.put(AlarmManager.class, mAlarmManager::get); // TODO(b/118592525): to support multi-display , we start to add something which is // per-display, while others may be global. I think it's time to add diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java index d46a86cc728ff..239cbfe389753 100644 --- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java @@ -23,6 +23,7 @@ import static com.android.systemui.Dependency.MAIN_LOOPER_NAME; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import android.annotation.Nullable; +import android.app.AlarmManager; import android.app.INotificationManager; import android.content.Context; import android.hardware.SensorPrivacyManager; @@ -230,4 +231,11 @@ public class DependencyProvider { @Named(MAIN_HANDLER_NAME) Handler mainHandler) { return new DeviceProvisionedControllerImpl(context, mainHandler); } + + /** */ + @Singleton + @Provides + public AlarmManager provideAlarmManager(Context context) { + return context.getSystemService(AlarmManager.class); + } } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/SystemUIModule.java index ff4eb83bd7963..38f2f5e8a3a1c 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIModule.java @@ -24,7 +24,7 @@ import com.android.systemui.assist.AssistModule; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.KeyguardLiftController; -import com.android.systemui.util.AsyncSensorManager; +import com.android.systemui.util.sensors.AsyncSensorManager; import javax.inject.Singleton; diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java index c2b0fe4ab2d9e..20742d6d23586 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java @@ -43,7 +43,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.util.AsyncSensorManager; +import com.android.systemui.util.sensors.AsyncSensorManager; import java.io.PrintWriter; diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java index 85bc22bab36f5..914258f48b463 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java @@ -20,6 +20,7 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHT import static com.android.systemui.Dependency.MAIN_HANDLER_NAME; import android.content.Context; +import android.hardware.SensorManager; import android.net.Uri; import android.os.Handler; import android.provider.DeviceConfig; @@ -35,7 +36,7 @@ import com.android.systemui.plugins.FalsingPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.util.DeviceConfigProxy; -import com.android.systemui.util.ProximitySensor; +import com.android.systemui.util.sensors.ProximitySensor; import java.io.PrintWriter; @@ -66,6 +67,7 @@ public class FalsingManagerProxy implements FalsingManager { DeviceConfigProxy deviceConfig) { mProximitySensor = proximitySensor; mProximitySensor.setTag(PROXIMITY_SENSOR_TAG); + mProximitySensor.setSensorDelay(SensorManager.SENSOR_DELAY_GAME); mDeviceConfig = deviceConfig; mDeviceConfigListener = properties -> onDeviceConfigPropertiesChanged(context, properties.getNamespace()); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java index 3f5cae678d016..0aa66af1701c5 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java @@ -30,7 +30,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.classifier.Classifier; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.util.DeviceConfigProxy; -import com.android.systemui.util.ProximitySensor; +import com.android.systemui.util.sensors.ProximitySensor; import java.io.PrintWriter; import java.util.ArrayList; diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java index bf397518de461..53ca78355b127 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java @@ -19,7 +19,7 @@ package com.android.systemui.classifier.brightline; import android.view.MotionEvent; import com.android.systemui.classifier.Classifier; -import com.android.systemui.util.ProximitySensor; +import com.android.systemui.util.sensors.ProximitySensor; import java.util.List; diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java index eeca409866a89..f0feb4ee791d1 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java @@ -23,7 +23,7 @@ import android.provider.DeviceConfig; import android.view.MotionEvent; import com.android.systemui.util.DeviceConfigProxy; -import com.android.systemui.util.ProximitySensor; +import com.android.systemui.util.sensors.ProximitySensor; /** diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index c3672868ba320..bb8c7f1dabf84 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -33,7 +33,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.util.AsyncSensorManager; +import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.wakelock.DelayedWakeLock; import com.android.systemui.util.wakelock.WakeLock; @@ -45,7 +45,7 @@ public class DozeFactory { /** Creates a DozeMachine with its parts for {@code dozeService}. */ public DozeMachine assembleMachine(DozeService dozeService, FalsingManager falsingManager) { Context context = dozeService; - SensorManager sensorManager = Dependency.get(AsyncSensorManager.class); + AsyncSensorManager sensorManager = Dependency.get(AsyncSensorManager.class); AlarmManager alarmManager = context.getSystemService(AlarmManager.class); DockManager dockManager = Dependency.get(DockManager.class); WakefulnessLifecycle wakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class); @@ -91,7 +91,7 @@ public class DozeFactory { params.getPolicy()); } - private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager, + private DozeTriggers createDozeTriggers(Context context, AsyncSensorManager sensorManager, DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config, DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine, DockManager dockManager) { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 026a62528c8d8..7c1c04048c21b 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -26,7 +26,6 @@ import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.hardware.Sensor; -import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.TriggerEvent; import android.hardware.TriggerEventListener; @@ -43,11 +42,13 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; -import com.android.systemui.R; +import com.android.systemui.Dependency; import com.android.systemui.plugins.SensorManagerPlugin; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.util.AlarmTimeout; -import com.android.systemui.util.AsyncSensorManager; +import com.android.systemui.util.sensors.AsyncSensorManager; +import com.android.systemui.util.sensors.ProximitySensor; +import com.android.systemui.util.sensors.SensorRateLimiter; import com.android.systemui.util.wakelock.WakeLock; import java.io.PrintWriter; @@ -62,7 +63,7 @@ public class DozeSensors { private final Context mContext; private final AlarmManager mAlarmManager; - private final SensorManager mSensorManager; + private final AsyncSensorManager mSensorManager; private final ContentResolver mResolver; private final TriggerSensor mPickupSensor; private final DozeParameters mDozeParameters; @@ -74,13 +75,13 @@ public class DozeSensors { protected TriggerSensor[] mSensors; private final Handler mHandler = new Handler(); - private final ProxSensor mProxSensor; + private final ProximitySensor mProximitySensor; private long mDebounceFrom; private boolean mSettingRegistered; private boolean mListening; private boolean mPaused; - public DozeSensors(Context context, AlarmManager alarmManager, SensorManager sensorManager, + public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager, DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback, Consumer proxCallback, AlwaysOnDisplayPolicy policy) { mContext = context; @@ -91,6 +92,7 @@ public class DozeSensors { mWakeLock = wakeLock; mProxCallback = proxCallback; mResolver = mContext.getContentResolver(); + mCallback = callback; boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT); mSensors = new TriggerSensor[] { @@ -146,8 +148,13 @@ public class DozeSensors { false /* touchscreen */, mConfig.getWakeLockScreenDebounce()), }; - mProxSensor = new ProxSensor(policy); - mCallback = callback; + mProximitySensor = new ProximitySensor( + context, sensorManager, Dependency.get(PluginManager.class)); + new SensorRateLimiter(mProximitySensor, mAlarmManager, policy.proxCooldownTriggerMs, + policy.proxCooldownPeriodMs, "prox_sensor"); + + mProximitySensor.register( + proximityEvent -> mProxCallback.accept(!proximityEvent.getNear())); } /** @@ -236,7 +243,15 @@ public class DozeSensors { } public void setProxListening(boolean listen) { - mProxSensor.setRequested(listen); + if (mProximitySensor.isRegistered() && listen) { + mProximitySensor.alertListeners(); + } else { + if (listen) { + mProximitySensor.resume(); + } else { + mProximitySensor.pause(); + } + } } private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { @@ -267,115 +282,16 @@ public class DozeSensors { /** Dump current state */ public void dump(PrintWriter pw) { for (TriggerSensor s : mSensors) { - pw.print(" Sensor: "); pw.println(s.toString()); + pw.println(" Sensor: " + s.toString()); } - pw.print(" ProxSensor: "); pw.println(mProxSensor.toString()); + pw.println(" ProxSensor: " + mProximitySensor.toString()); } /** - * @return true if prox is currently far, false if near or null if unknown. + * @return true if prox is currently near, false if far or null if unknown. */ - public Boolean isProximityCurrentlyFar() { - return mProxSensor.mCurrentlyFar; - } - - private class ProxSensor implements SensorEventListener { - - boolean mRequested; - boolean mRegistered; - Boolean mCurrentlyFar; - long mLastNear; - final AlarmTimeout mCooldownTimer; - final AlwaysOnDisplayPolicy mPolicy; - final Sensor mSensor; - final boolean mUsingBrightnessSensor; - - public ProxSensor(AlwaysOnDisplayPolicy policy) { - mPolicy = policy; - mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered, - "prox_cooldown", mHandler); - - // The default prox sensor can be noisy, so let's use a prox gated brightness sensor - // if available. - Sensor sensor = DozeSensors.findSensorWithType(mSensorManager, - mContext.getString(R.string.doze_brightness_sensor_type)); - mUsingBrightnessSensor = sensor != null; - if (sensor == null) { - sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); - } - mSensor = sensor; - } - - void setRequested(boolean requested) { - if (mRequested == requested) { - // Send an update even if we don't re-register. - mHandler.post(() -> { - if (mCurrentlyFar != null) { - mProxCallback.accept(mCurrentlyFar); - } - }); - return; - } - mRequested = requested; - updateRegistered(); - } - - private void updateRegistered() { - setRegistered(mRequested && !mCooldownTimer.isScheduled()); - } - - private void setRegistered(boolean register) { - if (mRegistered == register) { - return; - } - if (register) { - mRegistered = mSensorManager.registerListener(this, mSensor, - SensorManager.SENSOR_DELAY_NORMAL, mHandler); - } else { - mSensorManager.unregisterListener(this); - mRegistered = false; - mCurrentlyFar = null; - } - } - - @Override - public void onSensorChanged(android.hardware.SensorEvent event) { - if (DEBUG) Log.d(TAG, "onSensorChanged " + event); - - if (mUsingBrightnessSensor) { - // The custom brightness sensor is gated by the proximity sensor and will return 0 - // whenever prox is covered. - mCurrentlyFar = event.values[0] > 0; - } else { - mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange(); - } - mProxCallback.accept(mCurrentlyFar); - - long now = SystemClock.elapsedRealtime(); - if (mCurrentlyFar == null) { - // Sensor has been unregistered by the proxCallback. Do nothing. - } else if (!mCurrentlyFar) { - mLastNear = now; - } else if (mCurrentlyFar && now - mLastNear < mPolicy.proxCooldownTriggerMs) { - // If the last near was very recent, we might be using more power for prox - // wakeups than we're saving from turning of the screen. Instead, turn it off - // for a while. - mCooldownTimer.schedule(mPolicy.proxCooldownPeriodMs, - AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); - updateRegistered(); - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - } - - @Override - public String toString() { - return String.format("{registered=%s, requested=%s, coolingDown=%s, currentlyFar=%s," - + " sensor=%s}", mRegistered, mRequested, mCooldownTimer.isScheduled(), - mCurrentlyFar, mSensor); - } + public Boolean isProximityCurrentlyNear() { + return mProximitySensor.isNear(); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index bab64db4519c1..80d4b631314a8 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -24,10 +24,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; import android.hardware.display.AmbientDisplayConfiguration; import android.metrics.LogMaker; import android.os.Handler; @@ -39,16 +35,16 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.util.Preconditions; import com.android.systemui.Dependency; -import com.android.systemui.R; import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.Assert; +import com.android.systemui.util.sensors.AsyncSensorManager; +import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.wakelock.WakeLock; import java.io.PrintWriter; -import java.util.function.IntConsumer; +import java.util.function.Consumer; /** * Handles triggers for ambient state changes. @@ -67,13 +63,15 @@ public class DozeTriggers implements DozeMachine.Part { */ private static boolean sWakeDisplaySensorState = true; + private static final int PROXIMITY_TIMEOUT_DELAY_MS = 500; + private final Context mContext; private final DozeMachine mMachine; private final DozeSensors mDozeSensors; private final DozeHost mDozeHost; private final AmbientDisplayConfiguration mConfig; private final DozeParameters mDozeParameters; - private final SensorManager mSensorManager; + private final AsyncSensorManager mSensorManager; private final Handler mHandler; private final WakeLock mWakeLock; private final boolean mAllowPulseTriggers; @@ -81,6 +79,7 @@ public class DozeTriggers implements DozeMachine.Part { private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver(); private final DockEventListener mDockEventListener = new DockEventListener(); private final DockManager mDockManager; + private final ProximitySensor.ProximityCheck mProxCheck; private long mNotificationPulseTime; private boolean mPulsePending; @@ -89,7 +88,7 @@ public class DozeTriggers implements DozeMachine.Part { public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost, AlarmManager alarmManager, AmbientDisplayConfiguration config, - DozeParameters dozeParameters, SensorManager sensorManager, Handler handler, + DozeParameters dozeParameters, AsyncSensorManager sensorManager, Handler handler, WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager) { mContext = context; mMachine = machine; @@ -105,6 +104,9 @@ public class DozeTriggers implements DozeMachine.Part { dozeParameters.getPolicy()); mUiModeManager = mContext.getSystemService(UiModeManager.class); mDockManager = dockManager; + mProxCheck = new ProximitySensor.ProximityCheck( + new ProximitySensor(mContext, mSensorManager, null), + mHandler); } private void onNotification(Runnable onPulseSuppressedListener) { @@ -134,25 +136,27 @@ public class DozeTriggers implements DozeMachine.Part { } } - private void proximityCheckThenCall(IntConsumer callback, + private void proximityCheckThenCall(Consumer callback, boolean alreadyPerformedProxCheck, int reason) { - Boolean cachedProxFar = mDozeSensors.isProximityCurrentlyFar(); + Boolean cachedProxNear = mDozeSensors.isProximityCurrentlyNear(); if (alreadyPerformedProxCheck) { - callback.accept(ProximityCheck.RESULT_NOT_CHECKED); - } else if (cachedProxFar != null) { - callback.accept(cachedProxFar ? ProximityCheck.RESULT_FAR : ProximityCheck.RESULT_NEAR); + callback.accept(null); + } else if (cachedProxNear != null) { + callback.accept(cachedProxNear); } else { final long start = SystemClock.uptimeMillis(); - new ProximityCheck() { - @Override - public void onProximityResult(int result) { - final long end = SystemClock.uptimeMillis(); - DozeLog.traceProximityResult(mContext, result == RESULT_NEAR, - end - start, reason); - callback.accept(result); - } - }.check(); + mProxCheck.check(PROXIMITY_TIMEOUT_DELAY_MS, near -> { + final long end = SystemClock.uptimeMillis(); + DozeLog.traceProximityResult( + mContext, + near == null ? false : near, + end - start, + reason); + callback.accept(near); + mWakeLock.release(TAG); + }); + mWakeLock.acquire(TAG); } } @@ -178,7 +182,7 @@ public class DozeTriggers implements DozeMachine.Part { } } else { proximityCheckThenCall((result) -> { - if (result == ProximityCheck.RESULT_NEAR) { + if (result) { // In pocket, drop event. return; } @@ -267,7 +271,7 @@ public class DozeTriggers implements DozeMachine.Part { if (wake) { proximityCheckThenCall((result) -> { - if (result == ProximityCheck.RESULT_NEAR) { + if (result) { // In pocket, drop event. return; } @@ -376,7 +380,7 @@ public class DozeTriggers implements DozeMachine.Part { mPulsePending = true; proximityCheckThenCall((result) -> { - if (result == ProximityCheck.RESULT_NEAR) { + if (result) { // in pocket, abort pulse DozeLog.tracePulseDropped(mContext, "inPocket"); mPulsePending = false; @@ -412,104 +416,11 @@ public class DozeTriggers implements DozeMachine.Part { pw.print(" notificationPulseTime="); pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime)); - pw.print(" pulsePending="); pw.println(mPulsePending); + pw.println(" pulsePending=" + mPulsePending); pw.println("DozeSensors:"); mDozeSensors.dump(pw); } - /** - * @see DozeSensors.ProxSensor - */ - private abstract class ProximityCheck implements SensorEventListener, Runnable { - private static final int TIMEOUT_DELAY_MS = 500; - - protected static final int RESULT_UNKNOWN = 0; - protected static final int RESULT_NEAR = 1; - protected static final int RESULT_FAR = 2; - protected static final int RESULT_NOT_CHECKED = 3; - - private boolean mRegistered; - private boolean mFinished; - private float mMaxRange; - private boolean mUsingBrightnessSensor; - - protected abstract void onProximityResult(int result); - - public void check() { - Preconditions.checkState(!mFinished && !mRegistered); - Sensor sensor = DozeSensors.findSensorWithType(mSensorManager, - mContext.getString(R.string.doze_brightness_sensor_type)); - mUsingBrightnessSensor = sensor != null; - if (sensor == null) { - sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); - } - if (sensor == null) { - if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No sensor found"); - finishWithResult(RESULT_UNKNOWN); - return; - } - mDozeSensors.setDisableSensorsInterferingWithProximity(true); - - mMaxRange = sensor.getMaximumRange(); - mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0, - mHandler); - mHandler.postDelayed(this, TIMEOUT_DELAY_MS); - mWakeLock.acquire(TAG); - mRegistered = true; - } - - /** - * @see DozeSensors.ProxSensor#onSensorChanged(SensorEvent) - */ - @Override - public void onSensorChanged(SensorEvent event) { - if (event.values.length == 0) { - if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: Event has no values!"); - finishWithResult(RESULT_UNKNOWN); - } else { - if (DozeMachine.DEBUG) { - Log.d(TAG, "ProxCheck: Event: value=" + event.values[0] + " max=" + mMaxRange); - } - final boolean isNear; - if (mUsingBrightnessSensor) { - // The custom brightness sensor is gated by the proximity sensor and will - // return 0 whenever prox is covered. - isNear = event.values[0] == 0; - } else { - isNear = event.values[0] < mMaxRange; - } - finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR); - } - } - - @Override - public void run() { - if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No event received before timeout"); - finishWithResult(RESULT_UNKNOWN); - } - - private void finishWithResult(int result) { - if (mFinished) return; - boolean wasRegistered = mRegistered; - if (mRegistered) { - mHandler.removeCallbacks(this); - mSensorManager.unregisterListener(this); - mDozeSensors.setDisableSensorsInterferingWithProximity(false); - mRegistered = false; - } - onProximityResult(result); - if (wasRegistered) { - mWakeLock.release(TAG); - } - mFinished = true; - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // noop - } - } - private class TriggerReceiver extends BroadcastReceiver { private boolean mRegistered; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt index f7bb97b38dd52..00b764bfbe9bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt @@ -24,7 +24,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.Dependency import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.util.Assert -import com.android.systemui.util.AsyncSensorManager +import com.android.systemui.util.sensors.AsyncSensorManager class KeyguardLiftController constructor( private val statusBarStateController: StatusBarStateController, diff --git a/packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java deleted file mode 100644 index a905eba1f0ed7..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * 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; - -import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.util.Log; - -import com.android.systemui.R; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -/** - * Simple wrapper around SensorManager customized for the Proximity sensor. - */ -public class ProximitySensor { - private static final String TAG = "ProxSensor"; - private static final boolean DEBUG = false; - - private final Sensor mSensor; - private final AsyncSensorManager mSensorManager; - private final boolean mUsingBrightnessSensor; - private final float mMaxRange; - - private SensorEventListener mSensorEventListener = new SensorEventListener() { - @Override - public synchronized void onSensorChanged(SensorEvent event) { - onSensorEvent(event); - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - } - }; - private boolean mNear; - private List mListeners = new ArrayList<>(); - private String mTag = null; - - @Inject - public ProximitySensor(Context context, AsyncSensorManager sensorManager) { - mSensorManager = sensorManager; - Sensor sensor = findBrightnessSensor(context, sensorManager); - - if (sensor == null) { - mUsingBrightnessSensor = false; - sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); - } else { - mUsingBrightnessSensor = true; - } - mSensor = sensor; - if (mSensor != null) { - mMaxRange = mSensor.getMaximumRange(); - } else { - mMaxRange = 0; - } - } - - public void setTag(String tag) { - mTag = tag; - } - - private Sensor findBrightnessSensor(Context context, SensorManager sensorManager) { - String sensorType = context.getString(R.string.doze_brightness_sensor_type); - List sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL); - Sensor sensor = null; - for (Sensor s : sensorList) { - if (sensorType.equals(s.getStringType())) { - sensor = s; - break; - } - } - - return sensor; - } - - /** - * Returns {@code false} if a Proximity sensor is not available. - */ - public boolean getSensorAvailable() { - return mSensor != null; - } - - /** - * Add a listener. - * - * Registers itself with the {@link SensorManager} if this is the first listener - * added. - */ - public boolean register(ProximitySensorListener listener) { - if (!getSensorAvailable()) { - return false; - } - - logDebug("using brightness sensor? " + mUsingBrightnessSensor); - mListeners.add(listener); - if (mListeners.size() == 1) { - logDebug("registering sensor listener"); - mSensorManager.registerListener( - mSensorEventListener, mSensor, SensorManager.SENSOR_DELAY_GAME); - } - - return true; - } - - /** - * Remove a listener. - * - * If all listeners are removed from an instance of this class, - * it will unregister itself with the SensorManager. - */ - public void unregister(ProximitySensorListener listener) { - mListeners.remove(listener); - if (mListeners.size() == 0) { - logDebug("unregistering sensor listener"); - mSensorManager.unregisterListener(mSensorEventListener); - } - } - - public boolean isNear() { - return getSensorAvailable() && mNear; - } - - private void onSensorEvent(SensorEvent event) { - boolean near = event.values[0] < mMaxRange; - if (mUsingBrightnessSensor) { - near = event.values[0] == 0; - } - mNear = near; - mListeners.forEach(proximitySensorListener -> - proximitySensorListener.onProximitySensorEvent( - new ProximityEvent(mNear, event.timestamp))); - } - - /** Implement to be notified of ProximityEvents. */ - public interface ProximitySensorListener { - /** Called when the ProximitySensor changes. */ - void onProximitySensorEvent(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; - } - } - - private void logDebug(String msg) { - if (DEBUG) { - Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java similarity index 91% rename from packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java rename to packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java index b9c5ee5a7a7e7..dcd0c58a5310c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.util; +package com.android.systemui.util.sensors; import android.content.Context; import android.hardware.HardwareBuffer; @@ -56,23 +56,31 @@ public class AsyncSensorManager extends SensorManager private final SensorManager mInner; private final List mSensorCache; - private final HandlerThread mHandlerThread = new HandlerThread("async_sensor"); - @VisibleForTesting final Handler mHandler; + private final Handler mHandler; private final List mPlugins; @Inject public AsyncSensorManager(Context context, PluginManager pluginManager) { - this(context.getSystemService(SensorManager.class), pluginManager); + this(context.getSystemService(SensorManager.class), pluginManager, null); } @VisibleForTesting - AsyncSensorManager(SensorManager sensorManager, PluginManager pluginManager) { + public AsyncSensorManager( + SensorManager sensorManager, PluginManager pluginManager, Handler handler) { mInner = sensorManager; - mHandlerThread.start(); - mHandler = new Handler(mHandlerThread.getLooper()); + if (handler == null) { + HandlerThread handlerThread = new HandlerThread("async_sensor"); + handlerThread.start(); + mHandler = new Handler(handlerThread.getLooper()); + } else { + mHandler = handler; + } mSensorCache = mInner.getSensorList(Sensor.TYPE_ALL); mPlugins = new ArrayList<>(); - pluginManager.addPluginListener(this, SensorManagerPlugin.class, true /* allowMultiple */); + if (pluginManager != null) { + pluginManager.addPluginListener(this, SensorManagerPlugin.class, + true /* allowMultiple */); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/LimitableSensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/LimitableSensor.java new file mode 100644 index 0000000000000..2dfd187e1c1b2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/LimitableSensor.java @@ -0,0 +1,32 @@ +/* + * 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; + +/** + * Represents a sensor that can be throttled by a {@link SensorRateLimiter}. + */ +public interface LimitableSensor { + /** + * Allows a sensor to be temporarily throttled or disabled. + */ + void setRateLimited(boolean limited); + + /** + * Registers a {@link SensorRateLimiter} with the sensor. + */ + void setRateLimiter(SensorRateLimiter rateLimiter); +} diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java new file mode 100644 index 0000000000000..1eed566500738 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java @@ -0,0 +1,332 @@ +/* + * 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.content.Context; +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.systemui.R; +import com.android.systemui.shared.plugins.PluginManager; + +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. + */ +public class ProximitySensor implements LimitableSensor { + private static final String TAG = "ProxSensor"; + private static final boolean DEBUG = false; + + private final Sensor mSensor; + private final AsyncSensorManager mSensorManager; + private final boolean mUsingBrightnessSensor; + private final float mMaxRange; + private List mListeners = new ArrayList<>(); + private String mTag = null; + private ProximityEvent mLastEvent; + private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL; + private boolean mPaused; + private boolean mRegistered; + private SensorRateLimiter mRateLimiter; + private boolean mRateLimited = false; + + private SensorEventListener mSensorEventListener = new SensorEventListener() { + @Override + public synchronized void onSensorChanged(SensorEvent event) { + onSensorEvent(event); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + }; + + @Inject + public ProximitySensor( + Context context, AsyncSensorManager sensorManager, PluginManager pluginManager) { + mSensorManager = sensorManager; + Sensor sensor = findBrightnessSensor(context); + + if (sensor == null) { + mUsingBrightnessSensor = false; + sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); + } else { + mUsingBrightnessSensor = true; + } + mSensor = sensor; + if (mSensor != null) { + mMaxRange = mSensor.getMaximumRange(); + } else { + mMaxRange = 0; + } + } + + public void setTag(String tag) { + mTag = tag; + } + + public void setSensorDelay(int sensorDelay) { + mSensorDelay = sensorDelay; + } + + /** + * Unregister with the {@link SensorManager} without unsetting listeners on this object. + */ + public void pause() { + mPaused = true; + unregisterInternal(); + } + + /** + * Register with the {@link SensorManager}. No-op if no listeners are registered on this object. + */ + public void resume() { + mPaused = false; + registerInternal(); + } + + /** + * Supply a {@link SensorRateLimiter} to help limit power usage. + */ + @Override + public void setRateLimiter(SensorRateLimiter rateLimiter) { + mRateLimiter = rateLimiter; + } + + private Sensor findBrightnessSensor(Context context) { + String sensorType = context.getString(R.string.doze_brightness_sensor_type); + List sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL); + Sensor sensor = null; + for (Sensor s : sensorList) { + if (sensorType.equals(s.getStringType())) { + sensor = s; + break; + } + } + + return sensor; + } + + /** + * Registers/unregisters the proximity sensor with the SensorManager. + */ + @Override + public void setRateLimited(boolean limited) { + mRateLimited = limited; + if (mRateLimited) { + unregisterInternal(); + } else { + registerInternal(); + } + } + + /** + * Returns true if we are registered with the SensorManager. + */ + public boolean isRegistered() { + return mRegistered; + } + + /** + * Returns {@code false} if a Proximity sensor is not available. + */ + public boolean getSensorAvailable() { + return mSensor != null; + } + + /** + * 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. + */ + public boolean register(ProximitySensorListener listener) { + if (!getSensorAvailable()) { + return false; + } + + logDebug("Using brightness sensor? " + mUsingBrightnessSensor); + mListeners.add(listener); + registerInternal(); + + return true; + } + + private void registerInternal() { + if (mRegistered || mRateLimited || mPaused || mListeners.isEmpty()) { + return; + } + logDebug("Registering sensor listener"); + mRegistered = true; + mSensorManager.registerListener(mSensorEventListener, mSensor, mSensorDelay); + } + + /** + * Remove a listener. + * + * If all listeners are removed from an instance of this class, + * it will unregister itself with the SensorManager. + */ + public void unregister(ProximitySensorListener listener) { + mListeners.remove(listener); + if (mListeners.size() == 0) { + unregisterInternal(); + } + } + + private void unregisterInternal() { + if (!mRegistered) { + return; + } + logDebug("unregistering sensor listener"); + mSensorManager.unregisterListener(mSensorEventListener); + mRegistered = false; + } + + public Boolean isNear() { + return getSensorAvailable() && mLastEvent != null ? mLastEvent.getNear() : null; + } + + /** Update all listeners with the last value this class received from the sensor. */ + public void alertListeners() { + mListeners.forEach(proximitySensorListener -> + proximitySensorListener.onSensorEvent(mLastEvent)); + } + + private void onSensorEvent(SensorEvent event) { + boolean near = event.values[0] < mMaxRange; + if (mUsingBrightnessSensor) { + near = event.values[0] == 0; + } + mLastEvent = new ProximityEvent(near, event.timestamp); + alertListeners(); + if (mRateLimiter != null) { + mRateLimiter.onSensorEvent(mLastEvent.getTimestampMs()); + } + } + + @Override + public String toString() { + return String.format("{registered=%s, paused=%s, near=%s, rateLimiter=%s, sensor=%s}", + isRegistered(), mPaused, isNear(), mRateLimiter, mSensor); + } + + /** + * Convenience class allowing for briefly checking the proximity sensor. + */ + public static class ProximityCheck implements Runnable { + + private final ProximitySensor mSensor; + private final Handler mHandler; + private List> mCallbacks = new ArrayList<>(); + + @Inject + public ProximityCheck(ProximitySensor sensor, Handler handler) { + mSensor = sensor; + mSensor.setTag("prox_check"); + mHandler = handler; + mSensor.pause(); + ProximitySensorListener listener = proximityEvent -> { + mCallbacks.forEach( + booleanConsumer -> + booleanConsumer.accept( + proximityEvent == null ? null : proximityEvent.getNear())); + mCallbacks.clear(); + mSensor.pause(); + }; + mSensor.register(listener); + } + + /** Set a descriptive tag for the sensors registration. */ + public void setTag(String tag) { + mSensor.setTag(tag); + } + + @Override + public void run() { + mSensor.pause(); + mSensor.alertListeners(); + } + + /** + * Query the proximity sensor, timing out if no result. + */ + public void check(long timeoutMs, Consumer callback) { + if (!mSensor.getSensorAvailable()) { + callback.accept(null); + } + mCallbacks.add(callback); + if (!mSensor.isRegistered()) { + mSensor.resume(); + mHandler.postDelayed(this, timeoutMs); + } + } + } + + /** Implement to be notified of ProximityEvents. */ + public interface ProximitySensorListener { + /** Called when the ProximitySensor changes. */ + void onSensorEvent(ProximityEvent proximityEvent); + } + + /** + * Returned when the near/far state of a {@link ProximitySensor} changes. + */ + public static class ProximityEvent { + private final boolean mNear; + private final long mTimestampNs; + + public ProximityEvent(boolean near, long timestampNs) { + mNear = near; + mTimestampNs = timestampNs; + } + + public boolean getNear() { + return mNear; + } + + public long getTimestampNs() { + return mTimestampNs; + } + + public long getTimestampMs() { + return mTimestampNs / 1000000; + } + + @Override + public String toString() { + return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mNear, mTimestampNs); + } + + } + + private void logDebug(String msg) { + if (DEBUG) { + Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorRateLimiter.java b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorRateLimiter.java new file mode 100644 index 0000000000000..00f5bb04465bc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorRateLimiter.java @@ -0,0 +1,77 @@ +/* + * 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.app.AlarmManager; +import android.os.Handler; + +import com.android.systemui.util.AlarmTimeout; + +/** + * Monitors a sensor for over-activity and can throttle it down as needed. + * + * Currently, this class is designed with the {@link ProximitySensor} in mind. + */ +public class SensorRateLimiter { + private final LimitableSensor mSensor; + private final AlarmTimeout mCooldownTimer; + private final long mCoolDownTriggerMs; + private final long mCoolDownPeriodMs; + private long mLastTimestampMs; + + public SensorRateLimiter( + LimitableSensor sensor, + AlarmManager alarmManager, + long coolDownTriggerMs, + long coolDownPeriodMs, + String alarmTag) { + + mSensor = sensor; + mCoolDownTriggerMs = coolDownTriggerMs; + mCoolDownPeriodMs = coolDownPeriodMs; + mLastTimestampMs = -1; + + Handler handler = new Handler(); + mCooldownTimer = new AlarmTimeout(alarmManager, this::coolDownComplete, alarmTag, handler); + + mSensor.setRateLimiter(this); + } + + + void onSensorEvent(long timestampMs) { + if (mLastTimestampMs >= 0 && mCoolDownTriggerMs > 0 && mCoolDownPeriodMs > 0 + && timestampMs - mLastTimestampMs < mCoolDownTriggerMs) { + scheduleCoolDown(); + } + + mLastTimestampMs = timestampMs; + } + + private void scheduleCoolDown() { + mSensor.setRateLimited(true); + mCooldownTimer.schedule(mCoolDownPeriodMs, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); + } + + private void coolDownComplete() { + mSensor.setRateLimited(false); + } + + @Override + public String toString() { + return String.format("{coolingDown=%s}", mCooldownTimer.isScheduled()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java index b9793562d8d81..3561e34658981 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java @@ -34,7 +34,7 @@ import com.android.systemui.classifier.brightline.BrightLineFalsingManager; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxyFake; -import com.android.systemui.util.ProximitySensor; +import com.android.systemui.util.sensors.ProximitySensor; import org.junit.After; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java index 3fc5d7202d0ab..35d59c1c97261 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java @@ -30,7 +30,7 @@ import android.view.MotionEvent; import androidx.test.filters.SmallTest; import com.android.systemui.util.DeviceConfigProxyFake; -import com.android.systemui.util.ProximitySensor; +import com.android.systemui.util.sensors.ProximitySensor; import org.junit.After; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java index 0c124fff53a37..752e145d79bdb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java @@ -24,7 +24,7 @@ import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.utils.hardware.FakeSensorManager; +import com.android.systemui.util.sensors.FakeSensorManager; import org.mockito.Answers; import org.mockito.MockSettings; diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java index 392c677b98278..aa62e9aca4fe1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -40,7 +40,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; -import com.android.systemui.utils.hardware.FakeSensorManager; +import com.android.systemui.util.sensors.FakeSensorManager; import org.junit.Before; import org.junit.Test; diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java index cd6d1e0695664..ddd1685bf8bc1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java @@ -44,7 +44,7 @@ import com.android.systemui.SysuiTestCase; 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.AsyncSensorManager; +import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.wakelock.WakeLock; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index e190f9923da8d..f7cd69643fc6b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -32,6 +32,7 @@ import android.hardware.display.AmbientDisplayConfiguration; import android.os.Handler; import android.os.Looper; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; @@ -39,9 +40,10 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.util.sensors.AsyncSensorManager; +import com.android.systemui.util.sensors.FakeSensorManager; import com.android.systemui.util.wakelock.WakeLock; import com.android.systemui.util.wakelock.WakeLockFake; -import com.android.systemui.utils.hardware.FakeSensorManager; import org.junit.Before; import org.junit.BeforeClass; @@ -55,12 +57,8 @@ public class DozeTriggersTest extends SysuiTestCase { private DozeTriggers mTriggers; private DozeMachine mMachine; private DozeHostFake mHost; - private AmbientDisplayConfiguration mConfig; - private DozeParameters mParameters; private FakeSensorManager mSensors; private Sensor mTapSensor; - private WakeLock mWakeLock; - private AlarmManager mAlarmManager; private DockManager mDockManagerFake; @BeforeClass @@ -72,18 +70,21 @@ public class DozeTriggersTest extends SysuiTestCase { @Before public void setUp() throws Exception { mMachine = mock(DozeMachine.class); - mAlarmManager = mock(AlarmManager.class); + AlarmManager alarmManager = mock(AlarmManager.class); mHost = spy(new DozeHostFake()); - mConfig = DozeConfigurationUtil.createMockConfig(); - mParameters = DozeConfigurationUtil.createMockParameters(); + AmbientDisplayConfiguration config = DozeConfigurationUtil.createMockConfig(); + DozeParameters parameters = DozeConfigurationUtil.createMockParameters(); mSensors = spy(new FakeSensorManager(mContext)); mTapSensor = mSensors.getFakeTapSensor().getSensor(); - mWakeLock = new WakeLockFake(); + WakeLock wakeLock = new WakeLockFake(); mDockManagerFake = mock(DockManager.class); + AsyncSensorManager asyncSensorManager = + new AsyncSensorManager(mSensors, null, new Handler()); - mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, mConfig, mParameters, - mSensors, Handler.createAsync(Looper.myLooper()), mWakeLock, true, + mTriggers = new DozeTriggers(mContext, mMachine, mHost, alarmManager, config, parameters, + asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true, mDockManagerFake); + waitForSensorManager(); } @Test @@ -95,13 +96,14 @@ public class DozeTriggersTest extends SysuiTestCase { clearInvocations(mMachine); mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */); - mSensors.getMockProximitySensor().sendProximityResult(false); /* Near */ + mSensors.getFakeProximitySensor().sendProximityResult(false); /* Near */ verify(mMachine, never()).requestState(any()); verify(mMachine, never()).requestPulse(anyInt()); mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */); - mSensors.getMockProximitySensor().sendProximityResult(true); /* Far */ + waitForSensorManager(); + mSensors.getFakeProximitySensor().sendProximityResult(true); /* Far */ verify(mMachine).requestPulse(anyInt()); } @@ -111,6 +113,7 @@ public class DozeTriggersTest extends SysuiTestCase { when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE); mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE); + waitForSensorManager(); verify(mSensors).requestTriggerSensor(any(), eq(mTapSensor)); clearInvocations(mSensors); @@ -118,10 +121,12 @@ public class DozeTriggersTest extends SysuiTestCase { DozeMachine.State.DOZE_REQUEST_PULSE); mTriggers.transitionTo(DozeMachine.State.DOZE_REQUEST_PULSE, DozeMachine.State.DOZE_PULSING); + waitForSensorManager(); verify(mSensors).cancelTriggerSensor(any(), eq(mTapSensor)); clearInvocations(mSensors); mTriggers.transitionTo(DozeMachine.State.DOZE_PULSING, DozeMachine.State.DOZE_PULSE_DONE); + waitForSensorManager(); verify(mSensors).requestTriggerSensor(any(), eq(mTapSensor)); } @@ -133,4 +138,8 @@ public class DozeTriggersTest extends SysuiTestCase { mTriggers.transitionTo(DozeMachine.State.DOZE, DozeMachine.State.FINISH); verify(mDockManagerFake).removeListener(any()); } + + private void waitForSensorManager() { + TestableLooper.get(this).processAllMessages(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java similarity index 71% rename from packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java rename to packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java index 4a9b1b3c40069..9149599f2c7c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.android.systemui.util; +package com.android.systemui.util.sensors; -import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -24,15 +23,15 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.hardware.SensorEventListener; -import android.hardware.SensorManager; +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.plugins.SensorManagerPlugin; import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.utils.hardware.FakeSensorManager; import org.junit.Before; import org.junit.Test; @@ -40,20 +39,21 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper public class AsyncSensorManagerTest extends SysuiTestCase { - private TestableAsyncSensorManager mAsyncSensorManager; - private FakeSensorManager mFakeSensorManager; + private AsyncSensorManager mAsyncSensorManager; private SensorEventListener mListener; - private FakeSensorManager.MockProximitySensor mSensor; + private FakeSensorManager.FakeProximitySensor mSensor; private PluginManager mPluginManager; @Before public void setUp() throws Exception { mPluginManager = mock(PluginManager.class); - mFakeSensorManager = new FakeSensorManager(mContext); - mAsyncSensorManager = new TestableAsyncSensorManager(mFakeSensorManager); - mSensor = mFakeSensorManager.getMockProximitySensor(); + FakeSensorManager fakeSensorManager = new FakeSensorManager(mContext); + mAsyncSensorManager = new AsyncSensorManager( + fakeSensorManager, mPluginManager, new Handler()); + mSensor = fakeSensorManager.getFakeProximitySensor(); mListener = mock(SensorEventListener.class); } @@ -61,7 +61,7 @@ public class AsyncSensorManagerTest extends SysuiTestCase { public void registerListenerImpl() throws Exception { mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100); - mAsyncSensorManager.waitUntilRequestsCompleted(); + waitUntilRequestsCompleted(); // Verify listener was registered. mSensor.sendProximityResult(true); @@ -73,7 +73,7 @@ public class AsyncSensorManagerTest extends SysuiTestCase { mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100); mAsyncSensorManager.unregisterListener(mListener); - mAsyncSensorManager.waitUntilRequestsCompleted(); + waitUntilRequestsCompleted(); // Verify listener was unregistered. mSensor.sendProximityResult(true); @@ -85,7 +85,7 @@ public class AsyncSensorManagerTest extends SysuiTestCase { mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100); mAsyncSensorManager.unregisterListener(mListener, mSensor.getSensor()); - mAsyncSensorManager.waitUntilRequestsCompleted(); + waitUntilRequestsCompleted(); // Verify listener was unregistered. mSensor.sendProximityResult(true); @@ -98,13 +98,7 @@ public class AsyncSensorManagerTest extends SysuiTestCase { eq(SensorManagerPlugin.class), eq(true) /* allowMultiple */); } - private class TestableAsyncSensorManager extends AsyncSensorManager { - public TestableAsyncSensorManager(SensorManager sensorManager) { - super(sensorManager, mPluginManager); - } - - public void waitUntilRequestsCompleted() { - assertTrue(mHandler.runWithScissors(() -> {}, 0)); - } + public void waitUntilRequestsCompleted() { + TestableLooper.get(this).processAllMessages(); } -} \ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java similarity index 95% rename from packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java rename to packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java index 29b8ab600caf1..1deb495b52501 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java @@ -11,10 +11,10 @@ * 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 + * limitations under the License. */ -package com.android.systemui.utils.hardware; +package com.android.systemui.util.sensors; import android.content.Context; import android.hardware.HardwareBuffer; @@ -54,7 +54,7 @@ public class FakeSensorManager extends SensorManager { public static final String TAP_SENSOR_TYPE = "tapSensorType"; - private final MockProximitySensor mMockProximitySensor; + private final FakeProximitySensor mFakeProximitySensor; private final FakeGenericSensor mFakeLightSensor; private final FakeGenericSensor mFakeTapSensor; private final FakeGenericSensor[] mSensors; @@ -68,14 +68,14 @@ public class FakeSensorManager extends SensorManager { } mSensors = new FakeGenericSensor[]{ - mMockProximitySensor = new MockProximitySensor(proxSensor), + mFakeProximitySensor = new FakeProximitySensor(proxSensor), mFakeLightSensor = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null)), mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE)) }; } - public MockProximitySensor getMockProximitySensor() { - return mMockProximitySensor; + public FakeProximitySensor getFakeProximitySensor() { + return mFakeProximitySensor; } public FakeGenericSensor getFakeLightSensor() { @@ -231,9 +231,9 @@ public class FakeSensorManager extends SensorManager { setter.invoke(sensor, type); } - public class MockProximitySensor extends FakeGenericSensor { + public class FakeProximitySensor extends FakeGenericSensor { - private MockProximitySensor(Sensor sensor) { + private FakeProximitySensor(Sensor sensor) { super(sensor); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java new file mode 100644 index 0000000000000..9c316c54d2f37 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java @@ -0,0 +1,261 @@ +/* + * 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 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.os.Handler; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +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 ProximitySensorTest extends SysuiTestCase { + + private ProximitySensor mProximitySensor; + private FakeSensorManager.FakeProximitySensor mFakeProximitySensor; + + @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(), asyncSensorManager, null); + } + + @Test + public void testSingleListener() { + TestableListener listener = new TestableListener(); + + 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); + + mProximitySensor.unregister(listener); + waitForSensorManager(); + } + + @Test + public void testMultiListener() { + TestableListener listenerA = new TestableListener(); + TestableListener listenerB = new TestableListener(); + + 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); + + mProximitySensor.unregister(listenerA); + mProximitySensor.unregister(listenerB); + waitForSensorManager(); + } + + @Test + public void testUnregister() { + TestableListener listener = new TestableListener(); + + assertFalse(mProximitySensor.isRegistered()); + mProximitySensor.register(listener); + waitForSensorManager(); + assertTrue(mProximitySensor.isRegistered()); + assertNull(listener.mLastEvent); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mLastEvent.getNear()); + assertEquals(listener.mCallCount, 1); + + mProximitySensor.unregister(listener); + waitForSensorManager(); + assertFalse(mProximitySensor.isRegistered()); + } + + @Test + public void testPauseAndResume() { + TestableListener listener = new TestableListener(); + + assertFalse(mProximitySensor.isRegistered()); + mProximitySensor.register(listener); + waitForSensorManager(); + assertTrue(mProximitySensor.isRegistered()); + assertNull(listener.mLastEvent); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mLastEvent.getNear()); + assertEquals(listener.mCallCount, 1); + + 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); + + mProximitySensor.resume(); + waitForSensorManager(); + assertTrue(mProximitySensor.isRegistered()); + // Still matches our previous call + assertFalse(listener.mLastEvent.getNear()); + assertEquals(listener.mCallCount, 1); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mLastEvent.getNear()); + assertEquals(listener.mCallCount, 2); + + mProximitySensor.unregister(listener); + waitForSensorManager(); + assertFalse(mProximitySensor.isRegistered()); + } + + + @Test + public void testRateLimit() { + TestableListener listener = new TestableListener(); + + assertFalse(mProximitySensor.isRegistered()); + mProximitySensor.register(listener); + waitForSensorManager(); + assertTrue(mProximitySensor.isRegistered()); + assertNull(listener.mLastEvent); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mLastEvent.getNear()); + assertEquals(listener.mCallCount, 1); + + mProximitySensor.setRateLimited(true); + waitForSensorManager(); + assertFalse(mProximitySensor.isRegistered()); + + // More events do nothing when rate limited. + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mLastEvent.getNear()); + assertEquals(listener.mCallCount, 1); + mFakeProximitySensor.sendProximityResult(false); + assertFalse(listener.mLastEvent.getNear()); + assertEquals(listener.mCallCount, 1); + + mProximitySensor.setRateLimited(false); + waitForSensorManager(); + assertTrue(mProximitySensor.isRegistered()); + // Still matches our previous call + assertFalse(listener.mLastEvent.getNear()); + assertEquals(listener.mCallCount, 1); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mLastEvent.getNear()); + assertEquals(listener.mCallCount, 2); + + mProximitySensor.unregister(listener); + waitForSensorManager(); + assertFalse(mProximitySensor.isRegistered()); + } + + @Test + public void testAlertListeners() { + TestableListener listenerA = new TestableListener(); + TestableListener listenerB = new TestableListener(); + + assertFalse(mProximitySensor.isRegistered()); + + 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); + assertNull(listenerB.mLastEvent); + assertEquals(listenerB.mCallCount, 1); + + mFakeProximitySensor.sendProximityResult(false); + assertTrue(listenerA.mLastEvent.getNear()); + assertEquals(listenerA.mCallCount, 2); + assertTrue(listenerB.mLastEvent.getNear()); + assertEquals(listenerB.mCallCount, 2); + + mProximitySensor.unregister(listenerA); + mProximitySensor.unregister(listenerB); + waitForSensorManager(); + } + + class TestableListener implements ProximitySensor.ProximitySensorListener { + ProximitySensor.ProximityEvent mLastEvent; + int mCallCount = 0; + + @Override + public void onSensorEvent(ProximitySensor.ProximityEvent proximityEvent) { + mLastEvent = proximityEvent; + mCallCount++; + } + + void reset() { + mLastEvent = null; + mCallCount = 0; + } + }; + + private void waitForSensorManager() { + TestableLooper.get(this).processAllMessages(); + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/RateLimiterTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/RateLimiterTest.java new file mode 100644 index 0000000000000..cd43cf7ee78a2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/RateLimiterTest.java @@ -0,0 +1,119 @@ +/* + * 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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.AlarmManager; +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.AlarmTimeout; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class RateLimiterTest extends SysuiTestCase { + + private static final long COOL_DOWN_TRIGGER_MS = 100; + private static final long COOL_DOWN_PERIOD_MS = 200; + private static final String ALARM_TAG = "rate_limiter_test"; + + @Mock + LimitableSensor mSensor; + @Mock + private AlarmManager mAlarmManager; + + private SensorRateLimiter mSensorRateLimiter; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mSensorRateLimiter = new SensorRateLimiter(mSensor, mAlarmManager, COOL_DOWN_TRIGGER_MS, + COOL_DOWN_PERIOD_MS, ALARM_TAG); + } + + @Test + public void testInfrequentEvents() { + mSensorRateLimiter.onSensorEvent(0); + mSensorRateLimiter.onSensorEvent(COOL_DOWN_TRIGGER_MS); + mSensorRateLimiter.onSensorEvent(COOL_DOWN_TRIGGER_MS * 2); + mSensorRateLimiter.onSensorEvent(COOL_DOWN_TRIGGER_MS * 4); + mSensorRateLimiter.onSensorEvent(COOL_DOWN_TRIGGER_MS * 8); + + verify(mSensor, never()).setRateLimited(true); + } + + @Test + public void testRateLimit() { + mSensorRateLimiter.onSensorEvent(0); + mSensorRateLimiter.onSensorEvent(COOL_DOWN_TRIGGER_MS - 1); + + verify(mSensor).setRateLimited(true); + verify(mAlarmManager).setExact( + anyInt(), anyLong(), eq(ALARM_TAG), any(AlarmTimeout.class), any(Handler.class)); + } + + @Test + public void testSlowToOverTrigger() { + mSensorRateLimiter.onSensorEvent(0); + mSensorRateLimiter.onSensorEvent(COOL_DOWN_TRIGGER_MS); + mSensorRateLimiter.onSensorEvent(COOL_DOWN_TRIGGER_MS * 2); + mSensorRateLimiter.onSensorEvent(COOL_DOWN_TRIGGER_MS * 4); + + verify(mSensor, never()).setRateLimited(true); + + mSensorRateLimiter.onSensorEvent(COOL_DOWN_TRIGGER_MS * 5 - 1); + + verify(mSensor).setRateLimited(true); + verify(mAlarmManager).setExact( + anyInt(), anyLong(), eq(ALARM_TAG), any(AlarmTimeout.class), any(Handler.class)); + } + + @Test + public void testCoolDownComplete() { + mSensorRateLimiter.onSensorEvent(0); + mSensorRateLimiter.onSensorEvent(COOL_DOWN_TRIGGER_MS - 1); + + ArgumentCaptor listenerArgumentCaptor = + ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class); + + verify(mSensor).setRateLimited(true); + verify(mAlarmManager).setExact( + anyInt(), anyLong(), eq(ALARM_TAG), listenerArgumentCaptor.capture(), + any(Handler.class)); + + listenerArgumentCaptor.getValue().onAlarm(); + verify(mSensor).setRateLimited(false); + } +}