1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.doze; 18 19 import static com.android.systemui.doze.DozeLog.REASON_SENSOR_TAP; 20 import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS; 21 import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN; 22 23 import static org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertTrue; 26 import static org.mockito.ArgumentMatchers.any; 27 import static org.mockito.ArgumentMatchers.anyFloat; 28 import static org.mockito.ArgumentMatchers.anyInt; 29 import static org.mockito.ArgumentMatchers.eq; 30 import static org.mockito.Mockito.doAnswer; 31 import static org.mockito.Mockito.mock; 32 import static org.mockito.Mockito.never; 33 import static org.mockito.Mockito.reset; 34 import static org.mockito.Mockito.times; 35 import static org.mockito.Mockito.verify; 36 import static org.mockito.Mockito.when; 37 38 import android.database.ContentObserver; 39 import android.hardware.Sensor; 40 import android.hardware.display.AmbientDisplayConfiguration; 41 import android.os.UserHandle; 42 import android.testing.AndroidTestingRunner; 43 import android.testing.TestableLooper; 44 import android.testing.TestableLooper.RunWithLooper; 45 46 import androidx.test.filters.SmallTest; 47 48 import com.android.systemui.SysuiTestCase; 49 import com.android.systemui.biometrics.AuthController; 50 import com.android.systemui.doze.DozeSensors.TriggerSensor; 51 import com.android.systemui.plugins.SensorManagerPlugin; 52 import com.android.systemui.statusbar.phone.DozeParameters; 53 import com.android.systemui.statusbar.policy.DevicePostureController; 54 import com.android.systemui.util.sensors.AsyncSensorManager; 55 import com.android.systemui.util.sensors.ProximitySensor; 56 import com.android.systemui.util.settings.FakeSettings; 57 import com.android.systemui.util.wakelock.WakeLock; 58 59 import org.junit.Before; 60 import org.junit.Test; 61 import org.junit.runner.RunWith; 62 import org.mockito.ArgumentCaptor; 63 import org.mockito.Captor; 64 import org.mockito.Mock; 65 import org.mockito.MockitoAnnotations; 66 67 import java.lang.reflect.Constructor; 68 import java.lang.reflect.Field; 69 import java.lang.reflect.Method; 70 import java.util.ArrayList; 71 import java.util.List; 72 import java.util.function.Consumer; 73 74 @RunWith(AndroidTestingRunner.class) 75 @RunWithLooper 76 @SmallTest 77 public class DozeSensorsTest extends SysuiTestCase { 78 79 @Mock 80 private AsyncSensorManager mSensorManager; 81 @Mock 82 private DozeParameters mDozeParameters; 83 @Mock 84 private AmbientDisplayConfiguration mAmbientDisplayConfiguration; 85 @Mock 86 private WakeLock mWakeLock; 87 @Mock 88 private DozeSensors.Callback mCallback; 89 @Mock 90 private Consumer<Boolean> mProxCallback; 91 @Mock 92 private TriggerSensor mTriggerSensor; 93 @Mock 94 private DozeLog mDozeLog; 95 @Mock 96 private AuthController mAuthController; 97 @Mock 98 private DevicePostureController mDevicePostureController; 99 @Mock 100 private ProximitySensor mProximitySensor; 101 102 // Capture listeners so that they can be used to send events 103 @Captor 104 private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor = 105 ArgumentCaptor.forClass(AuthController.Callback.class); 106 private AuthController.Callback mAuthControllerCallback; 107 108 private FakeSettings mFakeSettings = new FakeSettings(); 109 private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener; 110 private TestableLooper mTestableLooper; 111 private TestableDozeSensors mDozeSensors; 112 private TriggerSensor mSensorTap; 113 114 @Before setUp()115 public void setUp() { 116 MockitoAnnotations.initMocks(this); 117 mTestableLooper = TestableLooper.get(this); 118 when(mAmbientDisplayConfiguration.tapSensorTypeMapping()) 119 .thenReturn(new String[]{"tapSensor"}); 120 when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L); 121 when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); 122 when(mAmbientDisplayConfiguration.enabled(UserHandle.USER_CURRENT)).thenReturn(true); 123 doAnswer(invocation -> { 124 ((Runnable) invocation.getArgument(0)).run(); 125 return null; 126 }).when(mWakeLock).wrap(any(Runnable.class)); 127 mDozeSensors = new TestableDozeSensors(); 128 129 verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture()); 130 mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue(); 131 } 132 133 @Test testRegisterProx()134 public void testRegisterProx() { 135 assertFalse(mProximitySensor.isRegistered()); 136 mDozeSensors.setProxListening(true); 137 verify(mProximitySensor).resume(); 138 } 139 140 @Test testSensorDebounce()141 public void testSensorDebounce() { 142 mDozeSensors.setListening(true, true); 143 144 mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class)); 145 mTestableLooper.processAllMessages(); 146 verify(mCallback).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH), 147 anyFloat(), anyFloat(), eq(null)); 148 149 mDozeSensors.requestTemporaryDisable(); 150 reset(mCallback); 151 mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class)); 152 mTestableLooper.processAllMessages(); 153 verify(mCallback, never()).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH), 154 anyFloat(), anyFloat(), eq(null)); 155 } 156 157 @Test testSetListening_firstTrue_registerSettingsObserver()158 public void testSetListening_firstTrue_registerSettingsObserver() { 159 verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt()); 160 mDozeSensors.setListening(true, true); 161 162 verify(mTriggerSensor).registerSettingsObserver(any(ContentObserver.class)); 163 } 164 165 @Test testSetListening_twiceTrue_onlyRegisterSettingsObserverOnce()166 public void testSetListening_twiceTrue_onlyRegisterSettingsObserverOnce() { 167 verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt()); 168 mDozeSensors.setListening(true, true); 169 mDozeSensors.setListening(true, true); 170 171 verify(mTriggerSensor, times(1)).registerSettingsObserver(any(ContentObserver.class)); 172 } 173 174 @Test testDestroy()175 public void testDestroy() { 176 mDozeSensors.destroy(); 177 178 verify(mTriggerSensor).setListening(false); 179 } 180 181 @Test testRegisterSensorsUsingProx()182 public void testRegisterSensorsUsingProx() { 183 // GIVEN we only should register sensors using prox when not in low-powered mode / off 184 // and the single tap sensor uses the proximity sensor 185 when(mDozeParameters.getSelectivelyRegisterSensorsUsingProx()).thenReturn(true); 186 when(mDozeParameters.singleTapUsesProx(anyInt())).thenReturn(true); 187 TestableDozeSensors dozeSensors = new TestableDozeSensors(); 188 189 // THEN on initialization, the tap sensor isn't requested 190 assertFalse(mSensorTap.mRequested); 191 192 // WHEN we're now in a low powered state 193 dozeSensors.setListening(true, true, true); 194 195 // THEN the tap sensor is registered 196 assertTrue(mSensorTap.mRequested); 197 } 198 199 @Test testDozeSensorSetListening()200 public void testDozeSensorSetListening() { 201 // GIVEN doze sensors enabled 202 when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true); 203 204 // GIVEN a trigger sensor 205 Sensor mockSensor = mock(Sensor.class); 206 TriggerSensor triggerSensor = mDozeSensors.createDozeSensor( 207 mockSensor, 208 /* settingEnabled */ true, 209 /* requiresTouchScreen */ true); 210 when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor))) 211 .thenReturn(true); 212 213 // WHEN we want to listen for the trigger sensor 214 triggerSensor.setListening(true); 215 216 // THEN the sensor is registered 217 assertTrue(triggerSensor.mRegistered); 218 } 219 220 @Test testDozeSensorSettingDisabled()221 public void testDozeSensorSettingDisabled() { 222 // GIVEN doze sensors enabled 223 when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true); 224 225 // GIVEN a trigger sensor 226 Sensor mockSensor = mock(Sensor.class); 227 TriggerSensor triggerSensor = mDozeSensors.createDozeSensor( 228 mockSensor, 229 /* settingEnabled*/ false, 230 /* requiresTouchScreen */ true); 231 when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor))) 232 .thenReturn(true); 233 234 // WHEN setListening is called 235 triggerSensor.setListening(true); 236 237 // THEN the sensor is not registered 238 assertFalse(triggerSensor.mRegistered); 239 } 240 241 @Test testDozeSensorIgnoreSetting()242 public void testDozeSensorIgnoreSetting() { 243 // GIVEN doze sensors enabled 244 when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true); 245 246 // GIVEN a trigger sensor that's 247 Sensor mockSensor = mock(Sensor.class); 248 TriggerSensor triggerSensor = mDozeSensors.createDozeSensor( 249 mockSensor, 250 /* settingEnabled*/ false, 251 /* requiresTouchScreen */ true); 252 when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor))) 253 .thenReturn(true); 254 255 // GIVEN sensor is listening 256 triggerSensor.setListening(true); 257 258 // WHEN ignoreSetting is called 259 triggerSensor.ignoreSetting(true); 260 261 // THEN the sensor is registered 262 assertTrue(triggerSensor.mRegistered); 263 } 264 265 @Test testUpdateListeningAfterAlreadyRegistered()266 public void testUpdateListeningAfterAlreadyRegistered() { 267 // GIVEN doze sensors enabled 268 when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true); 269 270 // GIVEN a trigger sensor 271 Sensor mockSensor = mock(Sensor.class); 272 TriggerSensor triggerSensor = mDozeSensors.createDozeSensor( 273 mockSensor, 274 /* settingEnabled*/ true, 275 /* requiresTouchScreen */ true); 276 when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor))) 277 .thenReturn(true); 278 279 // WHEN setListening is called AND updateListening is called 280 triggerSensor.setListening(true); 281 triggerSensor.updateListening(); 282 283 // THEN the sensor is still registered 284 assertTrue(triggerSensor.mRegistered); 285 } 286 287 @Test testPostureStartStateClosed_registersCorrectSensor()288 public void testPostureStartStateClosed_registersCorrectSensor() throws Exception { 289 // GIVEN doze sensor that supports postures 290 Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT); 291 Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT); 292 TriggerSensor triggerSensor = mDozeSensors.createDozeSensor( 293 new Sensor[] { 294 null /* unknown */, 295 closedSensor, 296 null /* half-opened */, 297 openedSensor}, 298 DevicePostureController.DEVICE_POSTURE_CLOSED); 299 300 // WHEN trigger sensor requests listening 301 triggerSensor.setListening(true); 302 303 // THEN the correct sensor is registered 304 verify(mSensorManager).requestTriggerSensor(eq(triggerSensor), eq(closedSensor)); 305 verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), eq(openedSensor)); 306 } 307 308 @Test testPostureChange_registersCorrectSensor()309 public void testPostureChange_registersCorrectSensor() throws Exception { 310 // GIVEN doze sensor that supports postures 311 Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT); 312 Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT); 313 TriggerSensor triggerSensor = mDozeSensors.createDozeSensor( 314 new Sensor[] { 315 null /* unknown */, 316 closedSensor, 317 null /* half-opened */, 318 openedSensor}, 319 DevicePostureController.DEVICE_POSTURE_CLOSED); 320 321 // GIVEN sensor is listening 322 when(mSensorManager.requestTriggerSensor(any(), any())).thenReturn(true); 323 triggerSensor.setListening(true); 324 reset(mSensorManager); 325 assertTrue(triggerSensor.mRegistered); 326 327 // WHEN posture changes 328 boolean sensorChanged = 329 triggerSensor.setPosture(DevicePostureController.DEVICE_POSTURE_OPENED); 330 331 // THEN the correct sensor is registered 332 assertTrue(sensorChanged); 333 verify(mSensorManager).requestTriggerSensor(eq(triggerSensor), eq(openedSensor)); 334 verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), eq(closedSensor)); 335 } 336 337 @Test testPostureChange_noSensorChange()338 public void testPostureChange_noSensorChange() throws Exception { 339 // GIVEN doze sensor that supports postures 340 Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT); 341 Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT); 342 TriggerSensor triggerSensor = mDozeSensors.createDozeSensor( 343 new Sensor[] { 344 null /* unknown */, 345 closedSensor, 346 openedSensor /* half-opened uses the same sensor as opened*/, 347 openedSensor}, 348 DevicePostureController.DEVICE_POSTURE_HALF_OPENED); 349 350 // GIVEN sensor is listening 351 when(mSensorManager.requestTriggerSensor(any(), any())).thenReturn(true); 352 triggerSensor.setListening(true); 353 reset(mSensorManager); 354 355 // WHEN posture changes 356 boolean sensorChanged = 357 triggerSensor.setPosture(DevicePostureController.DEVICE_POSTURE_OPENED); 358 359 // THEN no change in sensor 360 assertFalse(sensorChanged); 361 verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), any()); 362 } 363 364 @Test testFindSensor()365 public void testFindSensor() throws Exception { 366 // GIVEN a prox sensor 367 List<Sensor> sensors = new ArrayList<>(); 368 Sensor proxSensor = 369 createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY); 370 sensors.add(proxSensor); 371 372 when(mSensorManager.getSensorList(anyInt())).thenReturn(sensors); 373 374 // WHEN we try to find the prox sensor with the same type and name 375 // THEN we find the added sensor 376 assertEquals( 377 proxSensor, 378 DozeSensors.findSensor( 379 mSensorManager, 380 Sensor.STRING_TYPE_PROXIMITY, 381 proxSensor.getName())); 382 383 // WHEN we try to find a prox sensor with a different name 384 // THEN no sensor is found 385 assertEquals( 386 null, 387 DozeSensors.findSensor( 388 mSensorManager, 389 Sensor.STRING_TYPE_PROXIMITY, 390 "some other name")); 391 } 392 393 @Test testUdfpsEnrollmentChanged()394 public void testUdfpsEnrollmentChanged() throws Exception { 395 // GIVEN a UDFPS_LONG_PRESS trigger sensor that's not configured 396 Sensor mockSensor = mock(Sensor.class); 397 TriggerSensor triggerSensor = mDozeSensors.createDozeSensor( 398 mockSensor, 399 REASON_SENSOR_UDFPS_LONG_PRESS, 400 /* configured */ false); 401 mDozeSensors.addSensor(triggerSensor); 402 when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor))) 403 .thenReturn(true); 404 405 // WHEN listening state is set to TRUE 406 mDozeSensors.setListening(true, true); 407 408 // THEN mRegistered is still false b/c !mConfigured 409 assertFalse(triggerSensor.mConfigured); 410 assertFalse(triggerSensor.mRegistered); 411 412 // WHEN enrollment changes to TRUE 413 when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true); 414 mAuthControllerCallback.onEnrollmentsChanged(); 415 416 // THEN mConfigured = TRUE 417 assertTrue(triggerSensor.mConfigured); 418 419 // THEN mRegistered = TRUE 420 assertTrue(triggerSensor.mRegistered); 421 } 422 423 @Test testGesturesAllInitiallyRespectSettings()424 public void testGesturesAllInitiallyRespectSettings() { 425 DozeSensors dozeSensors = new DozeSensors(getContext(), mSensorManager, mDozeParameters, 426 mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog, 427 mProximitySensor, mFakeSettings, mAuthController, 428 mDevicePostureController); 429 430 for (TriggerSensor sensor : dozeSensors.mTriggerSensors) { 431 assertFalse(sensor.mIgnoresSetting); 432 } 433 } 434 435 private class TestableDozeSensors extends DozeSensors { TestableDozeSensors()436 TestableDozeSensors() { 437 super(getContext(), mSensorManager, mDozeParameters, 438 mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog, 439 mProximitySensor, mFakeSettings, mAuthController, 440 mDevicePostureController); 441 for (TriggerSensor sensor : mTriggerSensors) { 442 if (sensor instanceof PluginSensor 443 && ((PluginSensor) sensor).mPluginSensor.getType() 444 == TYPE_WAKE_LOCK_SCREEN) { 445 mWakeLockScreenListener = (PluginSensor) sensor; 446 } else if (sensor.mPulseReason == REASON_SENSOR_TAP) { 447 mSensorTap = sensor; 448 } 449 } 450 mTriggerSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap}; 451 } 452 createDozeSensor(Sensor sensor, boolean settingEnabled, boolean requiresTouchScreen)453 public TriggerSensor createDozeSensor(Sensor sensor, boolean settingEnabled, 454 boolean requiresTouchScreen) { 455 return new TriggerSensor(/* sensor */ sensor, 456 /* setting name */ "test_setting", 457 /* settingDefault */ settingEnabled, 458 /* configured */ true, 459 /* pulseReason*/ 0, 460 /* reportsTouchCoordinate*/ false, 461 /* requiresTouchscreen */ false, 462 /* ignoresSetting */ false, 463 requiresTouchScreen); 464 } 465 createDozeSensor( Sensor sensor, int pulseReason, boolean configured )466 public TriggerSensor createDozeSensor( 467 Sensor sensor, 468 int pulseReason, 469 boolean configured 470 ) { 471 return new TriggerSensor(/* sensor */ sensor, 472 /* setting name */ "test_setting", 473 /* settingDefault */ true, 474 /* configured */ configured, 475 /* pulseReason*/ pulseReason, 476 /* reportsTouchCoordinate*/ false, 477 /* requiresTouchscreen */ false, 478 /* ignoresSetting */ false, 479 /* requiresTouchScreen */false); 480 } 481 482 /** 483 * create a doze sensor that supports postures and is enabled 484 */ createDozeSensor(Sensor[] sensors, int posture)485 public TriggerSensor createDozeSensor(Sensor[] sensors, int posture) { 486 return new TriggerSensor(/* sensor */ sensors, 487 /* setting name */ "test_setting", 488 /* settingDefault */ true, 489 /* configured */ true, 490 /* pulseReason*/ 0, 491 /* reportsTouchCoordinate*/ false, 492 /* requiresTouchscreen */ false, 493 /* ignoresSetting */ true, 494 /* requiresProx */false, 495 posture); 496 } 497 addSensor(TriggerSensor sensor)498 public void addSensor(TriggerSensor sensor) { 499 TriggerSensor[] newArray = new TriggerSensor[mTriggerSensors.length + 1]; 500 for (int i = 0; i < mTriggerSensors.length; i++) { 501 newArray[i] = mTriggerSensors[i]; 502 } 503 newArray[mTriggerSensors.length] = sensor; 504 mTriggerSensors = newArray; 505 } 506 } 507 setSensorType(Sensor sensor, int type, String strType)508 public static void setSensorType(Sensor sensor, int type, String strType) throws Exception { 509 Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE); 510 setter.setAccessible(true); 511 setter.invoke(sensor, type); 512 if (strType != null) { 513 Field f = sensor.getClass().getDeclaredField("mStringType"); 514 f.setAccessible(true); 515 f.set(sensor, strType); 516 } 517 } 518 createSensor(int type, String strType)519 public static Sensor createSensor(int type, String strType) throws Exception { 520 Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor(); 521 constr.setAccessible(true); 522 Sensor sensor = constr.newInstance(); 523 setSensorType(sensor, type, strType); 524 return sensor; 525 } 526 } 527