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 com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
22 
23 import static org.mockito.ArgumentMatchers.any;
24 import static org.mockito.ArgumentMatchers.anyBoolean;
25 import static org.mockito.ArgumentMatchers.anyInt;
26 
27 import android.app.ActivityManager;
28 import android.app.ActivityTaskManager;
29 import android.app.AppOpsManager;
30 import android.app.AppOpsManagerInternal;
31 import android.content.Context;
32 import android.content.pm.UserInfo;
33 import android.os.Environment;
34 import android.telephony.TelephonyManager;
35 import android.testing.AndroidTestingRunner;
36 
37 import androidx.test.platform.app.InstrumentationRegistry;
38 
39 import com.android.dx.mockito.inline.extended.ExtendedMockito;
40 import com.android.server.LocalServices;
41 import com.android.server.SensorPrivacyService;
42 import com.android.server.SystemService;
43 import com.android.server.pm.UserManagerInternal;
44 
45 import org.junit.Assert;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 import org.mockito.Mock;
49 import org.mockito.MockitoSession;
50 import org.mockito.quality.Strictness;
51 
52 import java.io.File;
53 import java.io.IOException;
54 import java.nio.file.Files;
55 import java.util.concurrent.CompletableFuture;
56 
57 @RunWith(AndroidTestingRunner.class)
58 public class SensorPrivacyServiceMockingTest {
59 
60     private static final String PERSISTENCE_FILE_PATHS_TEMPLATE =
61             "SensorPrivacyServiceMockingTest/persisted_file%d.xml";
62     public static final String PERSISTENCE_FILE1 =
63             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 1);
64     public static final String PERSISTENCE_FILE2 =
65             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 2);
66     public static final String PERSISTENCE_FILE3 =
67             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 3);
68     public static final String PERSISTENCE_FILE4 =
69             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 4);
70     public static final String PERSISTENCE_FILE5 =
71             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 5);
72     public static final String PERSISTENCE_FILE6 =
73             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 6);
74 
75     public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE =
76             "SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml";
77     public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE =
78             "SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml";
79     public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE =
80             "SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml";
81     public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE =
82             "SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml";
83 
84     private Context mContext;
85     @Mock
86     private AppOpsManager mMockedAppOpsManager;
87     @Mock
88     private AppOpsManagerInternal mMockedAppOpsManagerInternal;
89     @Mock
90     private UserManagerInternal mMockedUserManagerInternal;
91     @Mock
92     private ActivityManager mMockedActivityManager;
93     @Mock
94     private ActivityTaskManager mMockedActivityTaskManager;
95     @Mock
96     private TelephonyManager mMockedTelephonyManager;
97 
98     @Test
testServiceInit()99     public void testServiceInit() throws IOException {
100         MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
101                 .initMocks(this)
102                 .strictness(Strictness.WARN)
103                 .spyStatic(LocalServices.class)
104                 .spyStatic(Environment.class)
105                 .startMocking();
106 
107         try {
108             mContext = InstrumentationRegistry.getInstrumentation().getContext();
109             spyOn(mContext);
110 
111             doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
112             doReturn(mMockedUserManagerInternal)
113                     .when(() -> LocalServices.getService(UserManagerInternal.class));
114             doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
115             doReturn(mMockedActivityTaskManager)
116                     .when(mContext).getSystemService(ActivityTaskManager.class);
117             doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
118                     TelephonyManager.class);
119 
120             String dataDir = mContext.getApplicationInfo().dataDir;
121             doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
122 
123             File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
124             onDeviceFile.delete();
125 
126             // Try all files with one known user
127             doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
128             doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
129                     .getUserInfo(0);
130             initServiceWithPersistenceFile(onDeviceFile, null);
131             initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE1);
132             initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE2);
133             initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE3);
134             initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE4);
135             initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE5);
136             initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE6);
137 
138             // Try all files with two known users
139             doReturn(new int[]{0, 10}).when(mMockedUserManagerInternal).getUserIds();
140             doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
141                     .getUserInfo(0);
142             doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
143                     .getUserInfo(10);
144             initServiceWithPersistenceFile(onDeviceFile, null);
145             initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE1);
146             initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE2);
147             initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE3);
148             initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE4);
149             initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE5);
150             initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE6);
151 
152         } finally {
153             mockitoSession.finishMocking();
154         }
155     }
156 
157     @Test
testServiceInit_AppOpsRestricted_micMute_camMute()158     public void testServiceInit_AppOpsRestricted_micMute_camMute() throws IOException {
159         testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE, true, true);
160     }
161 
162     @Test
testServiceInit_AppOpsRestricted_micMute_camUnmute()163     public void testServiceInit_AppOpsRestricted_micMute_camUnmute() throws IOException {
164         testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE, true, false);
165     }
166 
167     @Test
testServiceInit_AppOpsRestricted_micUnmute_camMute()168     public void testServiceInit_AppOpsRestricted_micUnmute_camMute() throws IOException {
169         testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE, false, true);
170     }
171 
172     @Test
testServiceInit_AppOpsRestricted_micUnmute_camUnmute()173     public void testServiceInit_AppOpsRestricted_micUnmute_camUnmute() throws IOException {
174         testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE, false, false);
175     }
176 
testServiceInit_AppOpsRestricted(String persistenceFileMicMuteCamMute, boolean expectedMicState, boolean expectedCamState)177     private void testServiceInit_AppOpsRestricted(String persistenceFileMicMuteCamMute,
178             boolean expectedMicState, boolean expectedCamState)
179             throws IOException {
180         MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
181                 .initMocks(this)
182                 .strictness(Strictness.WARN)
183                 .spyStatic(LocalServices.class)
184                 .spyStatic(Environment.class)
185                 .startMocking();
186 
187         try {
188             mContext = InstrumentationRegistry.getInstrumentation().getContext();
189             spyOn(mContext);
190 
191             doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
192             doReturn(mMockedAppOpsManagerInternal)
193                     .when(() -> LocalServices.getService(AppOpsManagerInternal.class));
194             doReturn(mMockedUserManagerInternal)
195                     .when(() -> LocalServices.getService(UserManagerInternal.class));
196             doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
197             doReturn(mMockedActivityTaskManager)
198                     .when(mContext).getSystemService(ActivityTaskManager.class);
199             doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
200                     TelephonyManager.class);
201 
202             String dataDir = mContext.getApplicationInfo().dataDir;
203             doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
204 
205             File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
206             onDeviceFile.delete();
207 
208             doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
209             doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
210                     .getUserInfo(0);
211 
212             CompletableFuture<Boolean> micState = new CompletableFuture<>();
213             CompletableFuture<Boolean> camState = new CompletableFuture<>();
214             doAnswer(invocation -> {
215                 int code = invocation.getArgument(0);
216                 boolean restricted = invocation.getArgument(1);
217                 if (code == AppOpsManager.OP_RECORD_AUDIO) {
218                     micState.complete(restricted);
219                 } else if (code == AppOpsManager.OP_CAMERA) {
220                     camState.complete(restricted);
221                 }
222                 return null;
223             }).when(mMockedAppOpsManagerInternal).setGlobalRestriction(anyInt(), anyBoolean(),
224                     any());
225 
226             initServiceWithPersistenceFile(onDeviceFile, persistenceFileMicMuteCamMute, 0);
227 
228             Assert.assertTrue(micState.join() == expectedMicState);
229             Assert.assertTrue(camState.join() == expectedCamState);
230 
231         } finally {
232             mockitoSession.finishMocking();
233         }
234     }
235 
initServiceWithPersistenceFile(File onDeviceFile, String persistenceFilePath)236     private void initServiceWithPersistenceFile(File onDeviceFile,
237             String persistenceFilePath) throws IOException {
238         initServiceWithPersistenceFile(onDeviceFile, persistenceFilePath, -1);
239     }
240 
initServiceWithPersistenceFile(File onDeviceFile, String persistenceFilePath, int startingUserId)241     private void initServiceWithPersistenceFile(File onDeviceFile,
242             String persistenceFilePath, int startingUserId) throws IOException {
243         if (persistenceFilePath != null) {
244             Files.copy(mContext.getAssets().open(persistenceFilePath),
245                     onDeviceFile.toPath());
246         }
247         SensorPrivacyService service = new SensorPrivacyService(mContext);
248         if (startingUserId != -1) {
249             SystemService.TargetUser mockedTargetUser =
250                     ExtendedMockito.mock(SystemService.TargetUser.class);
251             doReturn(startingUserId).when(mockedTargetUser).getUserIdentifier();
252             service.onUserStarting(mockedTargetUser);
253         }
254         onDeviceFile.delete();
255     }
256 }
257