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.sensorprivacy;
18 
19 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
20 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
21 import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE;
22 import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE;
23 
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
26 
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertFalse;
29 import static org.junit.Assert.assertNull;
30 import static org.junit.Assert.assertTrue;
31 import static org.mockito.ArgumentMatchers.any;
32 import static org.mockito.ArgumentMatchers.anyInt;
33 import static org.mockito.ArgumentMatchers.eq;
34 import static org.mockito.Mockito.mock;
35 import static org.mockito.Mockito.times;
36 
37 import android.content.Context;
38 import android.hardware.SensorPrivacyManager;
39 import android.os.Environment;
40 import android.os.Handler;
41 import android.testing.AndroidTestingRunner;
42 
43 import androidx.test.platform.app.InstrumentationRegistry;
44 
45 import com.android.dx.mockito.inline.extended.ExtendedMockito;
46 import com.android.server.LocalServices;
47 import com.android.server.pm.UserManagerInternal;
48 
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.mockito.ArgumentCaptor;
53 import org.mockito.MockitoSession;
54 import org.mockito.quality.Strictness;
55 
56 import java.io.File;
57 import java.io.IOException;
58 import java.nio.file.Files;
59 import java.nio.file.StandardCopyOption;
60 
61 @RunWith(AndroidTestingRunner.class)
62 public class SensorPrivacyServiceMockingTest {
63 
64     private static final String PERSISTENCE_FILE_PATHS_TEMPLATE =
65             "SensorPrivacyServiceMockingTest/persisted_file%d.xml";
66     public static final String PERSISTENCE_FILE1 =
67             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 1);
68     public static final String PERSISTENCE_FILE2 =
69             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 2);
70     public static final String PERSISTENCE_FILE3 =
71             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 3);
72     public static final String PERSISTENCE_FILE4 =
73             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 4);
74     public static final String PERSISTENCE_FILE5 =
75             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 5);
76     public static final String PERSISTENCE_FILE6 =
77             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 6);
78     public static final String PERSISTENCE_FILE7 =
79             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 7);
80     public static final String PERSISTENCE_FILE8 =
81             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 8);
82 
83     public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE =
84             "SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml";
85     public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE =
86             "SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml";
87     public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE =
88             "SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml";
89     public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE =
90             "SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml";
91 
92     Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
93     String mDataDir = mContext.getApplicationInfo().dataDir;
94 
95     @Before
setUp()96     public void setUp() {
97         new File(mDataDir, "sensor_privacy.xml").delete();
98         new File(mDataDir, "sensor_privacy_impl.xml").delete();
99     }
100 
101     @Test
testMigration1()102     public void testMigration1() throws IOException {
103         PersistedState ps = migrateFromFile(PERSISTENCE_FILE1);
104 
105         assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).isEnabled());
106         assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
107 
108         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
109         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
110 
111         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
112         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
113         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
114         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
115     }
116 
117     @Test
testMigration2()118     public void testMigration2() throws IOException {
119         PersistedState ps = migrateFromFile(PERSISTENCE_FILE2);
120 
121         assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).isEnabled());
122         assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
123 
124         assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE).isEnabled());
125         assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA).isEnabled());
126 
127         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 11, MICROPHONE));
128         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 11, CAMERA));
129 
130         assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 12, MICROPHONE).isEnabled());
131         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 12, CAMERA));
132 
133         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
134         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
135         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
136         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
137         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 11, MICROPHONE));
138         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 11, CAMERA));
139         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 12, MICROPHONE));
140         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 12, CAMERA));
141     }
142 
143     @Test
testMigration3()144     public void testMigration3() throws IOException {
145         PersistedState ps = migrateFromFile(PERSISTENCE_FILE3);
146 
147         assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).isEnabled());
148         assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
149 
150         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
151         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
152 
153         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
154         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
155         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
156         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
157     }
158 
159     @Test
testMigration4()160     public void testMigration4() throws IOException {
161         PersistedState ps = migrateFromFile(PERSISTENCE_FILE4);
162 
163         assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).isEnabled());
164         assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
165 
166         assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE).isEnabled());
167         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
168 
169         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
170         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
171         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
172         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
173     }
174 
175     @Test
testMigration5()176     public void testMigration5() throws IOException {
177         PersistedState ps = migrateFromFile(PERSISTENCE_FILE5);
178 
179         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE));
180         assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
181 
182         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
183         assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA).isEnabled());
184 
185         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
186         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
187         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
188         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
189     }
190 
191     @Test
testMigration6()192     public void testMigration6() throws IOException {
193         PersistedState ps = migrateFromFile(PERSISTENCE_FILE6);
194 
195         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE));
196         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA));
197 
198         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
199         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
200 
201         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
202         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
203         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
204         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
205     }
206 
migrateFromFile(String fileName)207     private PersistedState migrateFromFile(String fileName) throws IOException {
208         MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
209                 .initMocks(this)
210                 .strictness(Strictness.WARN)
211                 .spyStatic(LocalServices.class)
212                 .spyStatic(Environment.class)
213                 .startMocking();
214         try {
215             doReturn(new File(mDataDir)).when(() -> Environment.getDataSystemDirectory());
216 
217             UserManagerInternal umi = mock(UserManagerInternal.class);
218             doReturn(umi).when(() -> LocalServices.getService(UserManagerInternal.class));
219             doReturn(new int[] {0}).when(umi).getUserIds();
220 
221             Files.copy(
222                     mContext.getAssets().open(fileName),
223                     new File(mDataDir, "sensor_privacy.xml").toPath(),
224                     StandardCopyOption.REPLACE_EXISTING);
225 
226             return PersistedState.fromFile("sensor_privacy_impl.xml");
227         } finally {
228             mockitoSession.finishMocking();
229         }
230     }
231 
232     @Test
testPersistence1Version2()233     public void testPersistence1Version2() throws IOException {
234         PersistedState ps = getPersistedStateV2(PERSISTENCE_FILE7);
235 
236         assertEquals(1, ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).getState());
237         assertEquals(123L, ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).getLastChange());
238         assertEquals(2, ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).getState());
239         assertEquals(123L, ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).getLastChange());
240 
241         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
242         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
243         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
244         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
245     }
246 
247     @Test
testPersistence2Version2()248     public void testPersistence2Version2() throws IOException {
249         PersistedState ps = getPersistedStateV2(PERSISTENCE_FILE8);
250 
251         assertEquals(1, ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE).getState());
252         assertEquals(1234L, ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE).getLastChange());
253         assertEquals(2, ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA).getState());
254         assertEquals(1234L, ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA).getLastChange());
255 
256         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE));
257         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA));
258         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
259         assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
260         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
261         assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
262     }
263 
getPersistedStateV2(String version2FilePath)264     private PersistedState getPersistedStateV2(String version2FilePath) throws IOException {
265         MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
266                 .initMocks(this)
267                 .strictness(Strictness.WARN)
268                 .spyStatic(LocalServices.class)
269                 .spyStatic(Environment.class)
270                 .startMocking();
271         try {
272             doReturn(new File(mDataDir)).when(() -> Environment.getDataSystemDirectory());
273             Files.copy(
274                     mContext.getAssets().open(version2FilePath),
275                     new File(mDataDir, "sensor_privacy_impl.xml").toPath(),
276                     StandardCopyOption.REPLACE_EXISTING);
277 
278             return PersistedState.fromFile("sensor_privacy_impl.xml");
279         } finally {
280             mockitoSession.finishMocking();
281         }
282     }
283 
284     @Test
testGetDefaultState()285     public void testGetDefaultState() {
286         MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
287                 .initMocks(this)
288                 .strictness(Strictness.WARN)
289                 .spyStatic(PersistedState.class)
290                 .startMocking();
291         try {
292             PersistedState persistedState = mock(PersistedState.class);
293             doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
294             doReturn(null).when(persistedState).getState(anyInt(), anyInt(), anyInt());
295 
296             SensorPrivacyStateController sensorPrivacyStateController =
297                     getSensorPrivacyStateControllerImpl();
298 
299             SensorState micState = sensorPrivacyStateController.getState(TOGGLE_TYPE_SOFTWARE, 0,
300                     MICROPHONE);
301             SensorState camState = sensorPrivacyStateController.getState(TOGGLE_TYPE_SOFTWARE, 0,
302                     CAMERA);
303 
304             assertEquals(SensorPrivacyManager.StateTypes.DISABLED, micState.getState());
305             assertEquals(SensorPrivacyManager.StateTypes.DISABLED, camState.getState());
306             verify(persistedState, times(1)).getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE);
307             verify(persistedState, times(1)).getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA);
308         } finally {
309             mockitoSession.finishMocking();
310         }
311     }
312 
313     @Test
testGetSetState()314     public void testGetSetState() {
315         MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
316                 .initMocks(this)
317                 .strictness(Strictness.WARN)
318                 .spyStatic(PersistedState.class)
319                 .startMocking();
320         try {
321             PersistedState persistedState = mock(PersistedState.class);
322             SensorState sensorState = mock(SensorState.class);
323             doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
324             doReturn(sensorState).when(persistedState).getState(TOGGLE_TYPE_SOFTWARE, 0,
325                     MICROPHONE);
326             doReturn(SensorPrivacyManager.StateTypes.ENABLED).when(sensorState).getState();
327             doReturn(0L).when(sensorState).getLastChange();
328 
329             SensorPrivacyStateController sensorPrivacyStateController =
330                     getSensorPrivacyStateControllerImpl();
331 
332             SensorState micState = sensorPrivacyStateController.getState(TOGGLE_TYPE_SOFTWARE, 0,
333                     MICROPHONE);
334 
335             assertEquals(SensorPrivacyManager.StateTypes.ENABLED, micState.getState());
336             assertEquals(0L, micState.getLastChange());
337         } finally {
338             mockitoSession.finishMocking();
339         }
340     }
341 
342     @Test
testSetState()343     public void testSetState() {
344         MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
345                 .initMocks(this)
346                 .strictness(Strictness.WARN)
347                 .spyStatic(PersistedState.class)
348                 .startMocking();
349         try {
350             PersistedState persistedState = mock(PersistedState.class);
351             doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
352 
353             SensorPrivacyStateController sensorPrivacyStateController =
354                     getSensorPrivacyStateControllerImpl();
355 
356             sensorPrivacyStateController.setState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE, true,
357                     mock(Handler.class), changed -> {});
358 
359             ArgumentCaptor<SensorState> captor = ArgumentCaptor.forClass(SensorState.class);
360 
361             verify(persistedState, times(1)).setState(eq(TOGGLE_TYPE_SOFTWARE), eq(0),
362                     eq(MICROPHONE), captor.capture());
363             assertEquals(SensorPrivacyManager.StateTypes.ENABLED, captor.getValue().getState());
364         } finally {
365             mockitoSession.finishMocking();
366         }
367     }
368 
getSensorPrivacyStateControllerImpl()369     private SensorPrivacyStateController getSensorPrivacyStateControllerImpl() {
370         SensorPrivacyStateControllerImpl.getInstance().resetForTestingImpl();
371         return SensorPrivacyStateControllerImpl.getInstance();
372     }
373 }
374