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