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