1 /*
2  * Copyright (C) 2021 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.server.power;
18 
19 import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
20 
21 import static com.android.server.power.FaceDownDetector.KEY_FEATURE_ENABLED;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import static org.mockito.Mockito.doReturn;
26 
27 import android.content.Intent;
28 import android.hardware.Sensor;
29 import android.hardware.SensorEvent;
30 import android.hardware.SensorManager;
31 import android.os.PowerManager;
32 import android.provider.DeviceConfig;
33 import android.testing.TestableContext;
34 
35 import androidx.test.platform.app.InstrumentationRegistry;
36 
37 import com.android.modules.utils.testing.TestableDeviceConfig;
38 
39 import org.junit.Before;
40 import org.junit.ClassRule;
41 import org.junit.Rule;
42 import org.junit.Test;
43 import org.mockito.Mock;
44 import org.mockito.MockitoAnnotations;
45 
46 import java.lang.reflect.Constructor;
47 import java.lang.reflect.Field;
48 import java.lang.reflect.Method;
49 import java.time.Duration;
50 import java.util.concurrent.CountDownLatch;
51 import java.util.concurrent.TimeUnit;
52 
53 public class FaceDownDetectorTest {
54     @ClassRule
55     public static final TestableContext sContext = new TestableContext(
56             InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
57     @Rule
58     public TestableDeviceConfig.TestableDeviceConfigRule
59             mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
60 
61     private final FaceDownDetector mFaceDownDetector = new FaceDownDetector(this::onFlip);
62 
63     @Mock private SensorManager mSensorManager;
64     @Mock private PowerManager mPowerManager;
65 
66     private Duration mCurrentTime;
67     private int mOnFaceDownCalls;
68     private int mOnFaceDownExitCalls;
69 
70     @Before
setup()71     public void setup() throws Exception {
72         MockitoAnnotations.initMocks(this);
73         sContext.addMockSystemService(SensorManager.class, mSensorManager);
74         sContext.addMockSystemService(PowerManager.class, mPowerManager);
75         doReturn(true).when(mPowerManager).isInteractive();
76         DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
77                 KEY_FEATURE_ENABLED, "true", false);
78         mCurrentTime = Duration.ZERO;
79         mOnFaceDownCalls = 0;
80         mOnFaceDownExitCalls = 0;
81     }
82 
83     @Test
faceDownFor2Seconds_triggersFaceDown()84     public void faceDownFor2Seconds_triggersFaceDown() throws Exception {
85         mFaceDownDetector.systemReady(sContext);
86 
87         triggerFaceDown();
88 
89         assertThat(mOnFaceDownCalls).isEqualTo(1);
90         assertThat(mOnFaceDownExitCalls).isEqualTo(0);
91     }
92 
93     @Test
faceDownFor2Seconds_withMotion_DoesNotTriggerFaceDown()94     public void faceDownFor2Seconds_withMotion_DoesNotTriggerFaceDown() throws Exception {
95         mFaceDownDetector.systemReady(sContext);
96 
97         // Face up
98         mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f));
99 
100         for (int i = 0; i < 100; i++) {
101             advanceTime(Duration.ofMillis(20));
102             //Move along x direction
103             mFaceDownDetector.onSensorChanged(createTestEvent(0.5f * i, 0.0f, -10.0f));
104         }
105 
106         assertThat(mOnFaceDownCalls).isEqualTo(0);
107         assertThat(mOnFaceDownExitCalls).isEqualTo(0);
108     }
109 
110     @Test
faceDownForHalfSecond_DoesNotTriggerFaceDown()111     public void faceDownForHalfSecond_DoesNotTriggerFaceDown() throws Exception {
112         mFaceDownDetector.systemReady(sContext);
113 
114         // Face up
115         mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f));
116 
117         for (int i = 0; i < 100; i++) {
118             advanceTime(Duration.ofMillis(5));
119             mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f));
120         }
121 
122         assertThat(mOnFaceDownCalls).isEqualTo(0);
123         assertThat(mOnFaceDownExitCalls).isEqualTo(0);
124     }
125 
126     @Test
faceDownFor2Seconds_followedByFaceUp_triggersFaceDownExit()127     public void faceDownFor2Seconds_followedByFaceUp_triggersFaceDownExit() throws Exception {
128         mFaceDownDetector.systemReady(sContext);
129 
130         triggerFaceDown();
131 
132         // Phone flips
133         triggerUnflip();
134 
135         assertThat(mOnFaceDownCalls).isEqualTo(1);
136         assertThat(mOnFaceDownExitCalls).isEqualTo(1);
137     }
138 
139     @Test
notInteractive_doesNotTriggerFaceDown()140     public void notInteractive_doesNotTriggerFaceDown() throws Exception {
141         doReturn(false).when(mPowerManager).isInteractive();
142         mFaceDownDetector.systemReady(sContext);
143 
144         triggerFaceDown();
145 
146         assertThat(mOnFaceDownCalls).isEqualTo(0);
147         assertThat(mOnFaceDownExitCalls).isEqualTo(0);
148     }
149 
150     @Test
afterDisablingFeature_doesNotTriggerFaceDown()151     public void afterDisablingFeature_doesNotTriggerFaceDown() throws Exception {
152         mFaceDownDetector.systemReady(sContext);
153         setEnabled(false);
154 
155         triggerFaceDown();
156 
157         assertThat(mOnFaceDownCalls).isEqualTo(0);
158     }
159 
160     @Test
afterReenablingWhileNonInteractive_doesNotTriggerFaceDown()161     public void afterReenablingWhileNonInteractive_doesNotTriggerFaceDown() throws Exception {
162         mFaceDownDetector.systemReady(sContext);
163         setEnabled(false);
164 
165         doReturn(false).when(mPowerManager).isInteractive();
166         setEnabled(true);
167 
168         triggerFaceDown();
169 
170         assertThat(mOnFaceDownCalls).isEqualTo(0);
171     }
172 
173     @Test
afterReenablingWhileInteractive_doesTriggerFaceDown()174     public void afterReenablingWhileInteractive_doesTriggerFaceDown() throws Exception {
175         mFaceDownDetector.systemReady(sContext);
176         setEnabled(false);
177 
178         setEnabled(true);
179 
180         triggerFaceDown();
181 
182         assertThat(mOnFaceDownCalls).isEqualTo(1);
183     }
184 
185     @Test
faceDownToScreenOff_followedByScreenOnAndUserInteraction_doesNotDisable()186     public void faceDownToScreenOff_followedByScreenOnAndUserInteraction_doesNotDisable()
187             throws Exception {
188         mFaceDownDetector.systemReady(sContext);
189         // Face down to screen off
190         triggerFaceDown();
191         mFaceDownDetector.mScreenReceiver.onReceive(sContext, new Intent(Intent.ACTION_SCREEN_OFF));
192 
193         // Screen on
194         mFaceDownDetector.mScreenReceiver.onReceive(sContext, new Intent(Intent.ACTION_SCREEN_ON));
195 
196         // User interaction
197         mFaceDownDetector.userActivity(PowerManager.USER_ACTIVITY_EVENT_TOUCH);
198         waitForListenerToHandle();
199 
200         // Attempt another face down to see if disabled
201         triggerFaceDown();
202 
203         assertThat(mOnFaceDownCalls).isEqualTo(2);
204     }
205 
206     @Test
faceDownUserInteraction_disablesDetector()207     public void faceDownUserInteraction_disablesDetector()  throws Exception {
208         mFaceDownDetector.systemReady(sContext);
209         triggerFaceDown();
210         mFaceDownDetector.userActivity(PowerManager.USER_ACTIVITY_EVENT_TOUCH);
211         waitForListenerToHandle();
212 
213         triggerUnflip();
214         triggerFaceDown();
215 
216         assertThat(mOnFaceDownCalls).isEqualTo(1);
217     }
218 
triggerUnflip()219     private void triggerUnflip() throws Exception {
220         for (int i = 0; i < 10; i++) {
221             advanceTime(Duration.ofMillis(5));
222             mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 1.0f, 0.0f));
223         }
224     }
225 
triggerFaceDown()226     private void triggerFaceDown() throws Exception {
227         // Face up
228         // Using 0.5 on x to simulate constant acceleration, such as a sloped surface.
229         mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f));
230 
231         for (int i = 0; i < 200; i++) {
232             advanceTime(Duration.ofMillis(20));
233             mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f));
234         }
235     }
236 
setEnabled(Boolean enabled)237     private void setEnabled(Boolean enabled) throws Exception {
238         DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
239                 KEY_FEATURE_ENABLED, enabled.toString(), false);
240         waitForListenerToHandle();
241     }
242 
advanceTime(Duration duration)243     private void advanceTime(Duration duration) {
244         mCurrentTime = mCurrentTime.plus(duration);
245     }
246 
247     /**
248      * Create a test event to replicate an accelerometer sensor event.
249      * @param x Acceleration along the x dimension.
250      * @param y Acceleration along the y dimension.
251      * @param gravity Acceleration along the Z dimension. Relates to
252      */
createTestEvent(float x, float y, float gravity)253     private SensorEvent createTestEvent(float x, float y, float gravity) throws Exception {
254         final Constructor<SensorEvent> constructor =
255                 SensorEvent.class.getDeclaredConstructor(int.class);
256         constructor.setAccessible(true);
257         final SensorEvent event = constructor.newInstance(3);
258         event.sensor = createSensor(Sensor.TYPE_ACCELEROMETER, Sensor.STRING_TYPE_ACCELEROMETER);
259         event.values[0] = x;
260         event.values[1] = y;
261         event.values[2] = gravity;
262         event.timestamp = mCurrentTime.toNanos();
263         return event;
264     }
265 
onFlip(boolean isFaceDown)266     private void onFlip(boolean isFaceDown) {
267         if (isFaceDown) {
268             mOnFaceDownCalls++;
269         } else {
270             mOnFaceDownExitCalls++;
271         }
272     }
273 
createSensor(int type, String strType)274     private Sensor createSensor(int type, String strType) throws Exception {
275         Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
276         constr.setAccessible(true);
277         Sensor sensor = constr.newInstance();
278         Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
279         setter.setAccessible(true);
280         setter.invoke(sensor, type);
281         if (strType != null) {
282             Field f = sensor.getClass().getDeclaredField("mStringType");
283             f.setAccessible(true);
284             f.set(sensor, strType);
285         }
286         return sensor;
287     }
288 
waitForListenerToHandle()289     private void waitForListenerToHandle() throws Exception {
290         final CountDownLatch latch = new CountDownLatch(1);
291         sContext.getMainExecutor().execute(latch::countDown);
292         assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
293     }
294 }
295