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