1 /*
2  * Copyright (C) 2022 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.settings.privacy;
18 
19 import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
20 
21 import static com.android.settings.utils.SensorPrivacyManagerHelper.SENSOR_CAMERA;
22 import static com.android.settings.utils.SensorPrivacyManagerHelper.SENSOR_MICROPHONE;
23 import static com.android.settings.utils.SensorPrivacyManagerHelper.TOGGLE_TYPE_HARDWARE;
24 import static com.android.settings.utils.SensorPrivacyManagerHelper.TOGGLE_TYPE_SOFTWARE;
25 
26 import static org.mockito.ArgumentMatchers.any;
27 import static org.mockito.ArgumentMatchers.anyBoolean;
28 import static org.mockito.ArgumentMatchers.anyInt;
29 import static org.mockito.ArgumentMatchers.eq;
30 import static org.mockito.Mockito.doReturn;
31 import static org.mockito.Mockito.mock;
32 import static org.mockito.Mockito.never;
33 import static org.mockito.Mockito.times;
34 import static org.mockito.Mockito.verify;
35 
36 import android.content.Context;
37 import android.hardware.SensorPrivacyManager;
38 import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener;
39 import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener.SensorPrivacyChangedParams;
40 
41 import com.android.settings.utils.SensorPrivacyManagerHelper;
42 
43 import org.junit.After;
44 import org.junit.Before;
45 import org.junit.Test;
46 import org.junit.runner.RunWith;
47 import org.mockito.ArgumentCaptor;
48 import org.mockito.Mock;
49 import org.mockito.Mockito;
50 import org.mockito.MockitoSession;
51 import org.mockito.quality.Strictness;
52 import org.robolectric.RobolectricTestRunner;
53 
54 import java.util.concurrent.Executor;
55 
56 @RunWith(RobolectricTestRunner.class)
57 public class SensorPrivacyManagerHelperTest {
58 
59     private MockitoSession mMockitoSession;
60 
61     /** Execute synchronously */
62     private Executor mExecutor = r -> r.run();
63 
64     @Mock
65     private Context mContext;
66     @Mock
67     private SensorPrivacyManager mSensorPrivacyManager;
68 
69     private SensorPrivacyManagerHelper mSensorPrivacyManagerHelper;
70 
71     @Before
setUp()72     public void setUp() {
73         mMockitoSession = Mockito.mockitoSession()
74                 .initMocks(this)
75                 .strictness(Strictness.WARN)
76                 .startMocking();
77 
78         doReturn(mExecutor).when(mContext)
79                 .getMainExecutor();
80         doReturn(mSensorPrivacyManager).when(mContext)
81                 .getSystemService(eq(SensorPrivacyManager.class));
82 
83         mSensorPrivacyManagerHelper = new SensorPrivacyManagerHelper(mContext);
84     }
85 
86     @After
tearDown()87     public void tearDown() {
88         mMockitoSession.finishMocking();
89     }
90 
91     /**
92      * Verify that a sensor privacy listener is added in constructor.
93      */
94     @Test
constructor_invokeAddSensorPrivacyListener()95     public void constructor_invokeAddSensorPrivacyListener() {
96         verify(mSensorPrivacyManager, times(1)).addSensorPrivacyListener(eq(mExecutor),
97                 any(OnSensorPrivacyChangedListener.class));
98     }
99 
100     /**
101      * Verify when SensorPrivacyManagerHelper#setSensorBlocked(microphone, true) called,
102      * SensorPrivacyManager#setSensorPrivacy(microphone, true) is invoked.
103      */
104     @Test
invokeSetMicrophoneBlocked_invokeSetMicrophonePrivacyTrue()105     public void invokeSetMicrophoneBlocked_invokeSetMicrophonePrivacyTrue() {
106         mSensorPrivacyManagerHelper.setSensorBlocked(SENSOR_MICROPHONE, true);
107         verify(mSensorPrivacyManager, times(1))
108                 .setSensorPrivacy(eq(SETTINGS), eq(SENSOR_MICROPHONE), eq(true));
109     }
110 
111     /**
112      * Verify when SensorPrivacyManagerHelper#setSensorBlocked(microphone, false) called,
113      * SensorPrivacyManager#setSensorPrivacy(microphone, false) is invoked.
114      */
115     @Test
invokeSetMicrophoneUnBlocked_invokeSetMicrophonePrivacyFalse()116     public void invokeSetMicrophoneUnBlocked_invokeSetMicrophonePrivacyFalse() {
117         mSensorPrivacyManagerHelper.setSensorBlocked(SENSOR_MICROPHONE, false);
118         verify(mSensorPrivacyManager, times(1))
119                 .setSensorPrivacy(eq(SETTINGS), eq(SENSOR_MICROPHONE), eq(false));
120     }
121 
122     /**
123      * Verify when a callback is added with no toggleType and no sensor filter, then the
124      * callback is invoked on changes to all states.
125      */
126     @Test
addCallbackNoFilter_invokeCallback()127     public void addCallbackNoFilter_invokeCallback() {
128         SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener();
129 
130         SensorPrivacyManagerHelper.Callback callback =
131                 mock(SensorPrivacyManagerHelper.Callback.class);
132         mSensorPrivacyManagerHelper.addSensorBlockedListener(mExecutor, callback);
133 
134         verifyAllCases(listener, (t, s, e, i) -> {
135             verify(callback, times(1)).onSensorPrivacyChanged(eq(t), eq(s), eq(e));
136             verify(callback, times(i + 1)).onSensorPrivacyChanged(anyInt(), anyInt(), anyBoolean());
137         });
138     }
139 
140     /**
141      * Verify when a callback is added with a filter to only dispatch microphone events, then the
142      * callback is only invoked on changes to microphone state.
143      */
144     @Test
addCallbackMicrophoneOnlyFilter_invokeCallbackMicrophoneOnly()145     public void addCallbackMicrophoneOnlyFilter_invokeCallbackMicrophoneOnly() {
146         SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener();
147 
148         SensorPrivacyManagerHelper.Callback callback =
149                 mock(SensorPrivacyManagerHelper.Callback.class);
150         mSensorPrivacyManagerHelper.addSensorBlockedListener(SENSOR_MICROPHONE, mExecutor,
151                 callback);
152         mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback);
153 
154         verifyAllCases(listener, (t, s, e, i) -> {
155             verify(callback, never()).onSensorPrivacyChanged(anyInt(), eq(SENSOR_MICROPHONE),
156                     anyBoolean());
157         });
158     }
159 
160     /**
161      * Verify when a callback is added with a filter to only dispatch camera events, then the
162      * callback is only invoked on changes to camera state.
163      */
164     @Test
addCallbackCameraOnlyFilter_invokeCallbackCameraOnly()165     public void addCallbackCameraOnlyFilter_invokeCallbackCameraOnly() {
166         SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener();
167 
168         SensorPrivacyManagerHelper.Callback callback =
169                 mock(SensorPrivacyManagerHelper.Callback.class);
170         mSensorPrivacyManagerHelper.addSensorBlockedListener(SENSOR_CAMERA, mExecutor,
171                 callback);
172         mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback);
173 
174         verifyAllCases(listener, (t, s, e, i) -> {
175             verify(callback, never()).onSensorPrivacyChanged(anyInt(), eq(SENSOR_CAMERA),
176                     anyBoolean());
177         });
178     }
179 
180     /**
181      * Verify when a callback is added with a filter to only dispatch software_toggle+microphone
182      * events, then the callback is only invoked on changes to microphone state.
183      */
184     @Test
addCallbackSoftwareMicrophoneOnlyFilter_invokeCallbackSoftwareMicrophoneOnly()185     public void addCallbackSoftwareMicrophoneOnlyFilter_invokeCallbackSoftwareMicrophoneOnly() {
186         SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener();
187 
188         SensorPrivacyManagerHelper.Callback callback =
189                 mock(SensorPrivacyManagerHelper.Callback.class);
190         mSensorPrivacyManagerHelper.addSensorBlockedListener(TOGGLE_TYPE_SOFTWARE,
191                 SENSOR_MICROPHONE, mExecutor, callback);
192         mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback);
193 
194         verifyAllCases(listener, (t, s, e, i) -> {
195             verify(callback, never()).onSensorPrivacyChanged(eq(TOGGLE_TYPE_SOFTWARE),
196                     eq(SENSOR_MICROPHONE), anyBoolean());
197         });
198     }
199 
200     /**
201      * Verify when a callback is added with a filter to only dispatch software_toggle+camera
202      * events, then the callback is only invoked on changes to camera state.
203      */
204     @Test
addCallbackSoftwareCameraOnlyFilter_invokeCallbackSoftwareCameraOnly()205     public void addCallbackSoftwareCameraOnlyFilter_invokeCallbackSoftwareCameraOnly() {
206         SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener();
207 
208         SensorPrivacyManagerHelper.Callback callback =
209                 mock(SensorPrivacyManagerHelper.Callback.class);
210         mSensorPrivacyManagerHelper.addSensorBlockedListener(TOGGLE_TYPE_SOFTWARE,
211                 SENSOR_CAMERA, mExecutor, callback);
212         mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback);
213 
214         verifyAllCases(listener, (t, s, e, i) -> {
215             verify(callback, never()).onSensorPrivacyChanged(eq(TOGGLE_TYPE_SOFTWARE),
216                     eq(SENSOR_CAMERA), anyBoolean());
217         });
218     }
219 
220     /**
221      * Verify when a callback is added with a filter to only dispatch hardware_toggle+microphone
222      * events, then the callback is only invoked on changes to microphone state.
223      */
224     @Test
addCallbackHardwareMicrophoneOnlyFilter_invokeCallbackHardwareMicrophoneOnly()225     public void addCallbackHardwareMicrophoneOnlyFilter_invokeCallbackHardwareMicrophoneOnly() {
226         SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener();
227 
228         SensorPrivacyManagerHelper.Callback callback =
229                 mock(SensorPrivacyManagerHelper.Callback.class);
230         mSensorPrivacyManagerHelper.addSensorBlockedListener(TOGGLE_TYPE_HARDWARE,
231                 SENSOR_MICROPHONE, mExecutor, callback);
232         mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback);
233 
234         verifyAllCases(listener, (t, s, e, i) -> {
235             verify(callback, never()).onSensorPrivacyChanged(eq(TOGGLE_TYPE_HARDWARE),
236                     eq(SENSOR_MICROPHONE), anyBoolean());
237         });
238     }
239 
240     /**
241      * Verify when a callback is added with a filter to only dispatch hardware_toggle+camera
242      * events, then the callback is only invoked on changes to camera state.
243      */
244     @Test
addCallbackHardwareCameraOnlyFilter_invokeCallbackHardwareCameraOnly()245     public void addCallbackHardwareCameraOnlyFilter_invokeCallbackHardwareCameraOnly() {
246         SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener();
247 
248         SensorPrivacyManagerHelper.Callback callback =
249                 mock(SensorPrivacyManagerHelper.Callback.class);
250         mSensorPrivacyManagerHelper.addSensorBlockedListener(TOGGLE_TYPE_HARDWARE,
251                 SENSOR_CAMERA, mExecutor, callback);
252         mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback);
253 
254         verifyAllCases(listener, (t, s, e, i) -> {
255             verify(callback, never()).onSensorPrivacyChanged(eq(TOGGLE_TYPE_HARDWARE),
256                     eq(SENSOR_CAMERA), anyBoolean());
257         });
258     }
259 
260     /**
261      * Verify when a callback is removed, then the callback is never invoked on changes to state.
262      */
263     @Test
removeCallback_noInvokeCallback()264     public void removeCallback_noInvokeCallback() {
265         SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener();
266 
267         SensorPrivacyManagerHelper.Callback callback =
268                 mock(SensorPrivacyManagerHelper.Callback.class);
269         mSensorPrivacyManagerHelper.addSensorBlockedListener(mExecutor, callback);
270         mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback);
271 
272         verifyAllCases(listener, (t, s, e, i) -> {
273             verify(callback, never()).onSensorPrivacyChanged(anyInt(), anyInt(), anyBoolean());
274         });
275     }
276 
277     private interface Verifier {
278 
279         /**
280          * This method should throw in the fail case.
281          */
verifyCallback(int toggleType, int sensor, boolean isEnabled, int iterationNumber)282         void verifyCallback(int toggleType, int sensor, boolean isEnabled, int iterationNumber);
283     }
284 
verifyAllCases(SensorPrivacyManager.OnSensorPrivacyChangedListener listener, Verifier verifier)285     private void verifyAllCases(SensorPrivacyManager.OnSensorPrivacyChangedListener listener,
286             Verifier verifier) {
287         int[] toggleTypes = {TOGGLE_TYPE_SOFTWARE, TOGGLE_TYPE_HARDWARE};
288         int[] sensors = {SENSOR_MICROPHONE, SENSOR_CAMERA};
289         boolean[] enabledValues = {false, true};
290 
291         int i = 0;
292         for (int t : toggleTypes) {
293             for (int s : sensors) {
294                 for (boolean e : enabledValues) {
295                     listener.onSensorPrivacyChanged(createParams(t, s, e));
296 
297                     verifier.verifyCallback(t, s, e, i++);
298                 }
299             }
300         }
301     }
302 
getServiceListener()303     private OnSensorPrivacyChangedListener getServiceListener() {
304         ArgumentCaptor<OnSensorPrivacyChangedListener> captor =
305                 ArgumentCaptor.forClass(OnSensorPrivacyChangedListener.class);
306         verify(mSensorPrivacyManager).addSensorPrivacyListener(eq(mExecutor),
307                 captor.capture());
308 
309         OnSensorPrivacyChangedListener listener = captor.getValue();
310         return listener;
311     }
312 
createParams(int toggleType, int sensor, boolean enabled)313     private SensorPrivacyChangedParams createParams(int toggleType, int sensor, boolean enabled) {
314         SensorPrivacyChangedParams params = mock(SensorPrivacyChangedParams.class);
315         doReturn(toggleType).when(params).getToggleType();
316         doReturn(sensor).when(params).getSensor();
317         doReturn(enabled).when(params).isEnabled();
318         return params;
319     }
320 }
321