1 /* 2 * Copyright (C) 2015 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.telecom.tests; 18 19 import static junit.framework.Assert.fail; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertTrue; 24 import static org.mockito.ArgumentMatchers.anyInt; 25 import static org.mockito.ArgumentMatchers.eq; 26 import static org.mockito.ArgumentMatchers.isNull; 27 import static org.mockito.ArgumentMatchers.any; 28 import static org.mockito.Mockito.atLeastOnce; 29 import static org.mockito.Mockito.doAnswer; 30 import static org.mockito.Mockito.doReturn; 31 import static org.mockito.Mockito.mock; 32 import static org.mockito.Mockito.times; 33 import static org.mockito.Mockito.verify; 34 import static org.mockito.Mockito.when; 35 36 import android.app.UiModeManager; 37 import android.content.BroadcastReceiver; 38 import android.content.Context; 39 import android.content.Intent; 40 import android.content.IntentFilter; 41 import android.content.res.Configuration; 42 import android.hardware.Sensor; 43 import android.hardware.SensorEvent; 44 import android.hardware.SensorEventListener; 45 import android.hardware.SensorManager; 46 import android.net.Uri; 47 48 import androidx.test.filters.SmallTest; 49 50 import com.android.server.telecom.SystemStateHelper; 51 import com.android.server.telecom.SystemStateHelper.SystemStateListener; 52 import com.android.server.telecom.TelecomSystem; 53 54 import org.junit.After; 55 import org.junit.Before; 56 import org.junit.Test; 57 import org.junit.runner.RunWith; 58 import org.junit.runners.JUnit4; 59 import org.mockito.ArgumentCaptor; 60 import org.mockito.Mock; 61 import org.mockito.MockitoAnnotations; 62 import org.mockito.internal.util.reflection.FieldSetter; 63 64 import java.util.List; 65 import java.util.Set; 66 import java.util.function.Predicate; 67 import java.util.stream.Collectors; 68 69 /** 70 * Unit tests for SystemStateHelper 71 */ 72 @RunWith(JUnit4.class) 73 public class SystemStateHelperTest extends TelecomTestCase { 74 75 Context mContext; 76 @Mock SystemStateListener mSystemStateListener; 77 @Mock Sensor mGravitySensor; 78 @Mock Sensor mProxSensor; 79 @Mock UiModeManager mUiModeManager; 80 @Mock SensorManager mSensorManager; 81 @Mock Intent mIntentEnter; 82 @Mock Intent mIntentExit; 83 TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { }; 84 85 @Override 86 @Before setUp()87 public void setUp() throws Exception { 88 super.setUp(); 89 MockitoAnnotations.initMocks(this); 90 mContext = mComponentContextFixture.getTestDouble().getApplicationContext(); 91 doReturn(mSensorManager).when(mContext).getSystemService(SensorManager.class); 92 when(mGravitySensor.getType()).thenReturn(Sensor.TYPE_GRAVITY); 93 when(mProxSensor.getType()).thenReturn(Sensor.TYPE_PROXIMITY); 94 when(mProxSensor.getMaximumRange()).thenReturn(5.0f); 95 when(mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)).thenReturn(mGravitySensor); 96 when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(mProxSensor); 97 98 doReturn(mUiModeManager).when(mContext).getSystemService(UiModeManager.class); 99 100 mComponentContextFixture.putFloatResource( 101 R.dimen.device_on_ear_xy_gravity_threshold, 5.5f); 102 mComponentContextFixture.putFloatResource( 103 R.dimen.device_on_ear_y_gravity_negative_threshold, -1f); 104 } 105 106 @Override 107 @After tearDown()108 public void tearDown() throws Exception { 109 super.tearDown(); 110 } 111 112 @SmallTest 113 @Test testListeners()114 public void testListeners() throws Exception { 115 SystemStateHelper systemStateHelper = new SystemStateHelper(mContext, mLock); 116 117 assertFalse(systemStateHelper.removeListener(mSystemStateListener)); 118 systemStateHelper.addListener(mSystemStateListener); 119 assertTrue(systemStateHelper.removeListener(mSystemStateListener)); 120 assertFalse(systemStateHelper.removeListener(mSystemStateListener)); 121 } 122 123 @SmallTest 124 @Test testQuerySystemForCarMode_True()125 public void testQuerySystemForCarMode_True() { 126 when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); 127 assertTrue(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); 128 } 129 130 @SmallTest 131 @Test testQuerySystemForCarMode_False()132 public void testQuerySystemForCarMode_False() { 133 when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_NORMAL); 134 assertFalse(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); 135 } 136 137 @SmallTest 138 @Test testQuerySystemForAutomotiveProjection_True()139 public void testQuerySystemForAutomotiveProjection_True() { 140 when(mUiModeManager.getActiveProjectionTypes()) 141 .thenReturn(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE); 142 assertTrue(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); 143 144 when(mUiModeManager.getActiveProjectionTypes()) 145 .thenReturn(UiModeManager.PROJECTION_TYPE_ALL); 146 assertTrue(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); 147 } 148 149 @SmallTest 150 @Test testQuerySystemForAutomotiveProjection_False()151 public void testQuerySystemForAutomotiveProjection_False() { 152 when(mUiModeManager.getActiveProjectionTypes()) 153 .thenReturn(UiModeManager.PROJECTION_TYPE_NONE); 154 assertFalse(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); 155 } 156 157 @SmallTest 158 @Test testQuerySystemForAutomotiveProjectionAndCarMode_True()159 public void testQuerySystemForAutomotiveProjectionAndCarMode_True() { 160 when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); 161 when(mUiModeManager.getActiveProjectionTypes()) 162 .thenReturn(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE); 163 assertTrue(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); 164 } 165 166 @SmallTest 167 @Test testQuerySystemForAutomotiveProjectionOrCarMode_nullService()168 public void testQuerySystemForAutomotiveProjectionOrCarMode_nullService() { 169 when(mContext.getSystemService(UiModeManager.class)) 170 .thenReturn(mUiModeManager) // Without this, class construction will throw NPE. 171 .thenReturn(null); 172 assertFalse(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); 173 } 174 175 @SmallTest 176 @Test testPackageRemoved()177 public void testPackageRemoved() { 178 ArgumentCaptor<BroadcastReceiver> receiver = 179 ArgumentCaptor.forClass(BroadcastReceiver.class); 180 new SystemStateHelper(mContext, mLock).addListener(mSystemStateListener); 181 verify(mContext, atLeastOnce()) 182 .registerReceiver(receiver.capture(), any(IntentFilter.class)); 183 Intent packageRemovedIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED); 184 packageRemovedIntent.setData(Uri.fromParts("package", "com.android.test", null)); 185 receiver.getValue().onReceive(mContext, packageRemovedIntent); 186 verify(mSystemStateListener).onPackageUninstalled("com.android.test"); 187 } 188 189 @SmallTest 190 @Test testReceiverAndIntentFilter()191 public void testReceiverAndIntentFilter() { 192 ArgumentCaptor<IntentFilter> intentFilterCaptor = 193 ArgumentCaptor.forClass(IntentFilter.class); 194 new SystemStateHelper(mContext, mLock); 195 verify(mContext, times(2)).registerReceiver( 196 any(BroadcastReceiver.class), intentFilterCaptor.capture()); 197 198 Predicate<IntentFilter> carModeFilterTest = (intentFilter) -> 199 2 == intentFilter.countActions() 200 && intentFilter.hasAction(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED) 201 && intentFilter.hasAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED); 202 203 Predicate<IntentFilter> packageRemovedFilterTest = (intentFilter) -> 204 1 == intentFilter.countActions() 205 && intentFilter.hasAction(Intent.ACTION_PACKAGE_REMOVED) 206 && intentFilter.hasDataScheme("package"); 207 208 List<IntentFilter> capturedFilters = intentFilterCaptor.getAllValues(); 209 assertEquals(2, capturedFilters.size()); 210 for (IntentFilter filter : capturedFilters) { 211 if (carModeFilterTest.test(filter)) { 212 carModeFilterTest = (i) -> false; 213 continue; 214 } 215 if (packageRemovedFilterTest.test(filter)) { 216 packageRemovedFilterTest = (i) -> false; 217 continue; 218 } 219 String failString = String.format("Registered intent filters not correct. Got %s", 220 capturedFilters.stream().map(IntentFilter::toString) 221 .collect(Collectors.joining("\n"))); 222 fail(failString); 223 } 224 } 225 226 @SmallTest 227 @Test testOnEnterExitCarMode()228 public void testOnEnterExitCarMode() { 229 ArgumentCaptor<BroadcastReceiver> receiver = 230 ArgumentCaptor.forClass(BroadcastReceiver.class); 231 new SystemStateHelper(mContext, mLock).addListener(mSystemStateListener); 232 233 verify(mContext, atLeastOnce()) 234 .registerReceiver(receiver.capture(), any(IntentFilter.class)); 235 236 when(mIntentEnter.getAction()).thenReturn(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED); 237 receiver.getValue().onReceive(mContext, mIntentEnter); 238 verify(mSystemStateListener).onCarModeChanged(anyInt(), isNull(), eq(true)); 239 240 when(mIntentExit.getAction()).thenReturn(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED); 241 receiver.getValue().onReceive(mContext, mIntentExit); 242 verify(mSystemStateListener).onCarModeChanged(anyInt(), isNull(), eq(false)); 243 244 receiver.getValue().onReceive(mContext, new Intent("invalid action")); 245 } 246 247 @SmallTest 248 @Test testOnSetReleaseAutomotiveProjection()249 public void testOnSetReleaseAutomotiveProjection() { 250 SystemStateHelper systemStateHelper = new SystemStateHelper(mContext, mLock); 251 // We don't care what listener is registered, that's an implementation detail, but we need 252 // to call methods on whatever it is. 253 ArgumentCaptor<UiModeManager.OnProjectionStateChangedListener> listenerCaptor = 254 ArgumentCaptor.forClass(UiModeManager.OnProjectionStateChangedListener.class); 255 verify(mUiModeManager).addOnProjectionStateChangedListener( 256 eq(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE), any(), listenerCaptor.capture()); 257 systemStateHelper.addListener(mSystemStateListener); 258 259 String packageName1 = "Sufjan Stevens"; 260 String packageName2 = "The Ascension"; 261 262 // Should pay attention to automotive projection, though. 263 listenerCaptor.getValue().onProjectionStateChanged( 264 UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, Set.of(packageName2)); 265 verify(mSystemStateListener).onAutomotiveProjectionStateSet(packageName2); 266 267 // Without any automotive projection, it should see it as released. 268 listenerCaptor.getValue().onProjectionStateChanged( 269 UiModeManager.PROJECTION_TYPE_NONE, Set.of()); 270 verify(mSystemStateListener).onAutomotiveProjectionStateReleased(); 271 272 // Try the whole thing again, with different values. 273 listenerCaptor.getValue().onProjectionStateChanged( 274 UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, Set.of(packageName1)); 275 verify(mSystemStateListener).onAutomotiveProjectionStateSet(packageName1); 276 listenerCaptor.getValue().onProjectionStateChanged( 277 UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, Set.of()); 278 verify(mSystemStateListener, times(2)).onAutomotiveProjectionStateReleased(); 279 } 280 281 @SmallTest 282 @Test testDeviceOnEarCorrectlyDetected()283 public void testDeviceOnEarCorrectlyDetected() { 284 doAnswer(invocation -> { 285 SensorEventListener listener = invocation.getArgument(0); 286 Sensor sensor = invocation.getArgument(1); 287 if (sensor.getType() == Sensor.TYPE_GRAVITY) { 288 listener.onSensorChanged(makeSensorEvent( 289 new float[]{1.0f, 9.0f, 1.0f}, Sensor.TYPE_GRAVITY)); 290 } else { 291 listener.onSensorChanged(makeSensorEvent( 292 new float[]{0.0f}, Sensor.TYPE_PROXIMITY)); 293 } 294 return true; 295 }).when(mSensorManager) 296 .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt()); 297 298 assertTrue(SystemStateHelper.isDeviceAtEar(mContext)); 299 verify(mSensorManager).unregisterListener(any(SensorEventListener.class)); 300 } 301 302 @SmallTest 303 @Test testDeviceIsNotOnEarWithProxNotSensed()304 public void testDeviceIsNotOnEarWithProxNotSensed() { 305 doAnswer(invocation -> { 306 SensorEventListener listener = invocation.getArgument(0); 307 Sensor sensor = invocation.getArgument(1); 308 if (sensor.getType() == Sensor.TYPE_GRAVITY) { 309 listener.onSensorChanged(makeSensorEvent( 310 new float[]{1.0f, 9.0f, 1.0f}, Sensor.TYPE_GRAVITY)); 311 } else { 312 // do nothing to simulate proximity sensor not reporting 313 } 314 return true; 315 }).when(mSensorManager) 316 .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt()); 317 318 assertFalse(SystemStateHelper.isDeviceAtEar(mContext)); 319 verify(mSensorManager).unregisterListener(any(SensorEventListener.class)); 320 } 321 322 @SmallTest 323 @Test testDeviceIsNotOnEarWithWrongOrientation()324 public void testDeviceIsNotOnEarWithWrongOrientation() { 325 doAnswer(invocation -> { 326 SensorEventListener listener = invocation.getArgument(0); 327 Sensor sensor = invocation.getArgument(1); 328 if (sensor.getType() == Sensor.TYPE_GRAVITY) { 329 listener.onSensorChanged(makeSensorEvent( 330 new float[]{1.0f, 1.0f, 9.0f}, Sensor.TYPE_GRAVITY)); 331 } else { 332 listener.onSensorChanged(makeSensorEvent( 333 new float[]{0.0f}, Sensor.TYPE_PROXIMITY)); 334 } 335 return true; 336 }).when(mSensorManager) 337 .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt()); 338 339 assertFalse(SystemStateHelper.isDeviceAtEar(mContext)); 340 verify(mSensorManager).unregisterListener(any(SensorEventListener.class)); 341 } 342 343 @SmallTest 344 @Test testDeviceIsNotOnEarWithMissingSensor()345 public void testDeviceIsNotOnEarWithMissingSensor() { 346 when(mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)).thenReturn(null); 347 doAnswer(invocation -> { 348 SensorEventListener listener = invocation.getArgument(0); 349 Sensor sensor = invocation.getArgument(1); 350 if (sensor.getType() == Sensor.TYPE_GRAVITY) { 351 listener.onSensorChanged(makeSensorEvent( 352 new float[]{1.0f, 9.0f, 1.0f}, Sensor.TYPE_GRAVITY)); 353 } else { 354 listener.onSensorChanged(makeSensorEvent( 355 new float[]{0.0f}, Sensor.TYPE_PROXIMITY)); 356 } 357 return true; 358 }).when(mSensorManager) 359 .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt()); 360 361 assertFalse(SystemStateHelper.isDeviceAtEar(mContext)); 362 } 363 364 @SmallTest 365 @Test testDeviceIsNotOnEarWithTimeout()366 public void testDeviceIsNotOnEarWithTimeout() { 367 doAnswer(invocation -> { 368 SensorEventListener listener = invocation.getArgument(0); 369 Sensor sensor = invocation.getArgument(1); 370 if (sensor.getType() == Sensor.TYPE_GRAVITY) { 371 // do nothing 372 } else { 373 listener.onSensorChanged(makeSensorEvent( 374 new float[]{0.0f}, Sensor.TYPE_PROXIMITY)); 375 } 376 return true; 377 }).when(mSensorManager) 378 .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt()); 379 380 assertFalse(SystemStateHelper.isDeviceAtEar(mContext)); 381 } 382 383 @SmallTest 384 @Test testDeviceIsOnEarWithMultiSensorInputs()385 public void testDeviceIsOnEarWithMultiSensorInputs() { 386 doAnswer(invocation -> { 387 SensorEventListener listener = invocation.getArgument(0); 388 Sensor sensor = invocation.getArgument(1); 389 if (sensor.getType() == Sensor.TYPE_GRAVITY) { 390 listener.onSensorChanged(makeSensorEvent( 391 new float[]{1.0f, 9.0f, 1.0f}, Sensor.TYPE_GRAVITY)); 392 listener.onSensorChanged(makeSensorEvent( 393 new float[]{1.0f, -9.0f, 1.0f}, Sensor.TYPE_GRAVITY)); 394 listener.onSensorChanged(makeSensorEvent( 395 new float[]{1.0f, 0.0f, 8.0f}, Sensor.TYPE_GRAVITY)); 396 } else { 397 listener.onSensorChanged(makeSensorEvent( 398 new float[]{0.0f}, Sensor.TYPE_PROXIMITY)); 399 } 400 return true; 401 }).when(mSensorManager) 402 .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt()); 403 404 assertTrue(SystemStateHelper.isDeviceAtEar(mContext)); 405 verify(mSensorManager).unregisterListener(any(SensorEventListener.class)); 406 } 407 makeSensorEvent(float[] values, int sensorType)408 private SensorEvent makeSensorEvent(float[] values, int sensorType) throws Exception { 409 SensorEvent event = mock(SensorEvent.class); 410 Sensor mockSensor = mock(Sensor.class); 411 when(mockSensor.getType()).thenReturn(sensorType); 412 FieldSetter.setField(event, SensorEvent.class.getDeclaredField("sensor"), mockSensor); 413 FieldSetter.setField(event, SensorEvent.class.getDeclaredField("values"), values); 414 return event; 415 } 416 } 417