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