1 /*
2  * Copyright (C) 2018 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.systemui.appops;
18 
19 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
20 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import static junit.framework.TestCase.assertFalse;
25 
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertNotNull;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assume.assumeFalse;
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.ArgumentMatchers.anyBoolean;
32 import static org.mockito.ArgumentMatchers.anyInt;
33 import static org.mockito.ArgumentMatchers.anyLong;
34 import static org.mockito.ArgumentMatchers.anyString;
35 import static org.mockito.ArgumentMatchers.eq;
36 import static org.mockito.Mockito.doAnswer;
37 import static org.mockito.Mockito.inOrder;
38 import static org.mockito.Mockito.mock;
39 import static org.mockito.Mockito.never;
40 import static org.mockito.Mockito.times;
41 import static org.mockito.Mockito.verify;
42 import static org.mockito.Mockito.when;
43 
44 import android.app.AppOpsManager;
45 import android.content.pm.PackageManager;
46 import android.media.AudioManager;
47 import android.media.AudioRecordingConfiguration;
48 import android.os.Looper;
49 import android.os.UserHandle;
50 import android.testing.AndroidTestingRunner;
51 import android.testing.TestableLooper;
52 
53 import androidx.test.filters.SmallTest;
54 
55 import com.android.internal.R;
56 import com.android.systemui.SysuiTestCase;
57 import com.android.systemui.broadcast.BroadcastDispatcher;
58 import com.android.systemui.dump.DumpManager;
59 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
60 import com.android.systemui.util.time.FakeSystemClock;
61 
62 import org.junit.Before;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 import org.mockito.InOrder;
66 import org.mockito.Mock;
67 import org.mockito.MockitoAnnotations;
68 
69 import java.util.Collections;
70 import java.util.List;
71 import java.util.Map;
72 
73 @SmallTest
74 @RunWith(AndroidTestingRunner.class)
75 @TestableLooper.RunWithLooper
76 public class AppOpsControllerTest extends SysuiTestCase {
77     private static final String TEST_PACKAGE_NAME = "test";
78     private static final String TEST_ATTRIBUTION_NAME = "attribution";
79     private static final String SYSTEM_PKG = "android";
80     private static final int TEST_UID = UserHandle.getUid(0, 0);
81     private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0);
82     private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0);
83     private static final int TEST_CHAIN_ID = 1;
84 
85     @Mock
86     private AppOpsManager mAppOpsManager;
87     @Mock
88     private AppOpsController.Callback mCallback;
89     @Mock
90     private AppOpsControllerImpl.H mMockHandler;
91     @Mock
92     private DumpManager mDumpManager;
93     @Mock
94     private PackageManager mPackageManager;
95     @Mock
96     private IndividualSensorPrivacyController mSensorPrivacyController;
97     @Mock(stubOnly = true)
98     private AudioManager mAudioManager;
99     @Mock()
100     private BroadcastDispatcher mDispatcher;
101     @Mock(stubOnly = true)
102     private AudioManager.AudioRecordingCallback mRecordingCallback;
103     @Mock(stubOnly = true)
104     private AudioRecordingConfiguration mPausedMockRecording;
105 
106     private AppOpsControllerImpl mController;
107     private TestableLooper mTestableLooper;
108 
109     private String mExemptedRolePkgName;
110 
111     @Before
setUp()112     public void setUp() {
113         MockitoAnnotations.initMocks(this);
114         mTestableLooper = TestableLooper.get(this);
115         mExemptedRolePkgName = getContext().getString(R.string.config_systemUiIntelligence);
116 
117         getContext().addMockSystemService(AppOpsManager.class, mAppOpsManager);
118 
119         // All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of
120         // TEST_UID_NON_USER_SENSITIVE are user sensitive.
121         getContext().setMockPackageManager(mPackageManager);
122 
123         doAnswer((invocation) -> mRecordingCallback = invocation.getArgument(0))
124                 .when(mAudioManager).registerAudioRecordingCallback(any(), any());
125         when(mPausedMockRecording.getClientUid()).thenReturn(TEST_UID);
126         when(mPausedMockRecording.isClientSilenced()).thenReturn(true);
127 
128         when(mAudioManager.getActiveRecordingConfigurations())
129                 .thenReturn(List.of(mPausedMockRecording));
130 
131         when(mSensorPrivacyController.isSensorBlocked(CAMERA))
132                 .thenReturn(false);
133         when(mSensorPrivacyController.isSensorBlocked(CAMERA))
134                 .thenReturn(false);
135 
136         mController = new AppOpsControllerImpl(
137                 mContext,
138                 mTestableLooper.getLooper(),
139                 mDumpManager,
140                 mAudioManager,
141                 mSensorPrivacyController,
142                 mDispatcher,
143                 new FakeSystemClock()
144         );
145     }
146 
147     @Test
testOnlyListenForFewOps()148     public void testOnlyListenForFewOps() {
149         mController.setListening(true);
150         verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController);
151         verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any());
152         verify(mSensorPrivacyController, times(1)).addCallback(mController);
153     }
154 
155     @Test
testStopListening()156     public void testStopListening() {
157         mController.setListening(false);
158         verify(mAppOpsManager, times(1)).stopWatchingActive(mController);
159         verify(mDispatcher, times(1)).unregisterReceiver(mController);
160         verify(mSensorPrivacyController, times(1)).removeCallback(mController);
161     }
162 
163     @Test
startListening_fetchesCurrentActive_none()164     public void startListening_fetchesCurrentActive_none() {
165         when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
166                 .thenReturn(List.of());
167 
168         mController.setListening(true);
169 
170         assertThat(mController.getActiveAppOps()).isEmpty();
171     }
172 
173     /** Regression test for b/294104969. */
174     @Test
startListening_fetchesCurrentActive_oneActive()175     public void startListening_fetchesCurrentActive_oneActive() {
176         AppOpsManager.PackageOps packageOps = createPackageOp(
177                 "package.test",
178                 /* packageUid= */ 2,
179                 AppOpsManager.OPSTR_FINE_LOCATION,
180                 /* isRunning= */ true);
181         when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
182                 .thenReturn(List.of(packageOps));
183 
184         // WHEN we start listening
185         mController.setListening(true);
186 
187         // THEN the active list has the op
188         List<AppOpItem> list = mController.getActiveAppOps();
189         assertEquals(1, list.size());
190         AppOpItem first = list.get(0);
191         assertThat(first.getPackageName()).isEqualTo("package.test");
192         assertThat(first.getUid()).isEqualTo(2);
193         assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
194     }
195 
196     @Test
startListening_fetchesCurrentActive_multiplePackages()197     public void startListening_fetchesCurrentActive_multiplePackages() {
198         AppOpsManager.PackageOps packageOps1 = createPackageOp(
199                 "package.one",
200                 /* packageUid= */ 1,
201                 AppOpsManager.OPSTR_FINE_LOCATION,
202                 /* isRunning= */ true);
203         AppOpsManager.PackageOps packageOps2 = createPackageOp(
204                 "package.two",
205                 /* packageUid= */ 2,
206                 AppOpsManager.OPSTR_FINE_LOCATION,
207                 /* isRunning= */ false);
208         AppOpsManager.PackageOps packageOps3 = createPackageOp(
209                 "package.three",
210                 /* packageUid= */ 3,
211                 AppOpsManager.OPSTR_FINE_LOCATION,
212                 /* isRunning= */ true);
213         when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
214                 .thenReturn(List.of(packageOps1, packageOps2, packageOps3));
215 
216         // WHEN we start listening
217         mController.setListening(true);
218 
219         // THEN the active list has the ops
220         List<AppOpItem> list = mController.getActiveAppOps();
221         assertEquals(2, list.size());
222 
223         AppOpItem item0 = list.get(0);
224         assertThat(item0.getPackageName()).isEqualTo("package.one");
225         assertThat(item0.getUid()).isEqualTo(1);
226         assertThat(item0.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
227 
228         AppOpItem item1 = list.get(1);
229         assertThat(item1.getPackageName()).isEqualTo("package.three");
230         assertThat(item1.getUid()).isEqualTo(3);
231         assertThat(item1.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
232     }
233 
234     @Test
startListening_fetchesCurrentActive_multipleEntries()235     public void startListening_fetchesCurrentActive_multipleEntries() {
236         AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
237         when(packageOps.getUid()).thenReturn(1);
238         when(packageOps.getPackageName()).thenReturn("package.one");
239 
240         // Entry 1
241         AppOpsManager.OpEntry entry1 = mock(AppOpsManager.OpEntry.class);
242         when(entry1.getOpStr()).thenReturn(AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE);
243         AppOpsManager.AttributedOpEntry attributed1 = mock(AppOpsManager.AttributedOpEntry.class);
244         when(attributed1.isRunning()).thenReturn(true);
245         when(entry1.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed1));
246         // Entry 2
247         AppOpsManager.OpEntry entry2 = mock(AppOpsManager.OpEntry.class);
248         when(entry2.getOpStr()).thenReturn(AppOpsManager.OPSTR_CAMERA);
249         AppOpsManager.AttributedOpEntry attributed2 = mock(AppOpsManager.AttributedOpEntry.class);
250         when(attributed2.isRunning()).thenReturn(true);
251         when(entry2.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed2));
252         // Entry 3
253         AppOpsManager.OpEntry entry3 = mock(AppOpsManager.OpEntry.class);
254         when(entry3.getOpStr()).thenReturn(AppOpsManager.OPSTR_FINE_LOCATION);
255         AppOpsManager.AttributedOpEntry attributed3 = mock(AppOpsManager.AttributedOpEntry.class);
256         when(attributed3.isRunning()).thenReturn(false);
257         when(entry3.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed3));
258 
259         when(packageOps.getOps()).thenReturn(List.of(entry1, entry2, entry3));
260         when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
261                 .thenReturn(List.of(packageOps));
262 
263         // WHEN we start listening
264         mController.setListening(true);
265 
266         // THEN the active list has the ops
267         List<AppOpItem> list = mController.getActiveAppOps();
268         assertEquals(2, list.size());
269 
270         AppOpItem first = list.get(0);
271         assertThat(first.getPackageName()).isEqualTo("package.one");
272         assertThat(first.getUid()).isEqualTo(1);
273         assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_PHONE_CALL_MICROPHONE);
274 
275         AppOpItem second = list.get(1);
276         assertThat(second.getPackageName()).isEqualTo("package.one");
277         assertThat(second.getUid()).isEqualTo(1);
278         assertThat(second.getCode()).isEqualTo(AppOpsManager.OP_CAMERA);
279     }
280 
281     @Test
startListening_fetchesCurrentActive_multipleAttributes()282     public void startListening_fetchesCurrentActive_multipleAttributes() {
283         AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
284         when(packageOps.getUid()).thenReturn(1);
285         when(packageOps.getPackageName()).thenReturn("package.one");
286         AppOpsManager.OpEntry entry = mock(AppOpsManager.OpEntry.class);
287         when(entry.getOpStr()).thenReturn(AppOpsManager.OPSTR_RECORD_AUDIO);
288 
289         AppOpsManager.AttributedOpEntry attributed1 = mock(AppOpsManager.AttributedOpEntry.class);
290         when(attributed1.isRunning()).thenReturn(false);
291         AppOpsManager.AttributedOpEntry attributed2 = mock(AppOpsManager.AttributedOpEntry.class);
292         when(attributed2.isRunning()).thenReturn(true);
293         AppOpsManager.AttributedOpEntry attributed3 = mock(AppOpsManager.AttributedOpEntry.class);
294         when(attributed3.isRunning()).thenReturn(true);
295         when(entry.getAttributedOpEntries()).thenReturn(
296                 Map.of("attr1", attributed1, "attr2", attributed2, "attr3", attributed3));
297 
298         when(packageOps.getOps()).thenReturn(List.of(entry));
299         when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
300                 .thenReturn(List.of(packageOps));
301 
302         // WHEN we start listening
303         mController.setListening(true);
304 
305         // THEN the active list has the ops
306         List<AppOpItem> list = mController.getActiveAppOps();
307         // Multiple attributes get merged into one entry in the active ops
308         assertEquals(1, list.size());
309 
310         AppOpItem first = list.get(0);
311         assertThat(first.getPackageName()).isEqualTo("package.one");
312         assertThat(first.getUid()).isEqualTo(1);
313         assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_RECORD_AUDIO);
314     }
315 
316     /** Regression test for b/294104969. */
317     @Test
addCallback_existingCallbacksNotifiedOfCurrentActive()318     public void addCallback_existingCallbacksNotifiedOfCurrentActive() {
319         AppOpsManager.PackageOps packageOps1 = createPackageOp(
320                 "package.one",
321                 /* packageUid= */ 1,
322                 AppOpsManager.OPSTR_FINE_LOCATION,
323                 /* isRunning= */ true);
324         AppOpsManager.PackageOps packageOps2 = createPackageOp(
325                 "package.two",
326                 /* packageUid= */ 2,
327                 AppOpsManager.OPSTR_RECORD_AUDIO,
328                 /* isRunning= */ true);
329         AppOpsManager.PackageOps packageOps3 = createPackageOp(
330                 "package.three",
331                 /* packageUid= */ 3,
332                 AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE,
333                 /* isRunning= */ true);
334         when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
335                 .thenReturn(List.of(packageOps1, packageOps2, packageOps3));
336 
337         // WHEN we start listening
338         mController.addCallback(
339                 new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION},
340                 mCallback);
341         mTestableLooper.processAllMessages();
342 
343         // THEN the callback is notified of the current active ops it cares about
344         verify(mCallback).onActiveStateChanged(
345                 AppOpsManager.OP_FINE_LOCATION,
346                 /* uid= */ 1,
347                 "package.one",
348                 true);
349         verify(mCallback).onActiveStateChanged(
350                 AppOpsManager.OP_RECORD_AUDIO,
351                 /* uid= */ 2,
352                 "package.two",
353                 true);
354         verify(mCallback, never()).onActiveStateChanged(
355                 AppOpsManager.OP_PHONE_CALL_MICROPHONE,
356                 /* uid= */ 3,
357                 "package.three",
358                 true);
359     }
360 
361     @Test
addCallback_includedCode()362     public void addCallback_includedCode() {
363         mController.addCallback(
364                 new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION},
365                 mCallback);
366         mController.onOpActiveChanged(
367                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
368         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
369                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
370         mTestableLooper.processAllMessages();
371         verify(mCallback).onActiveStateChanged(AppOpsManager.OP_RECORD_AUDIO,
372                 TEST_UID, TEST_PACKAGE_NAME, true);
373     }
374 
375     @Test
addCallback_notIncludedCode()376     public void addCallback_notIncludedCode() {
377         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
378         mController.onOpActiveChanged(
379                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
380         mTestableLooper.processAllMessages();
381         verify(mCallback, never()).onActiveStateChanged(
382                 anyInt(), anyInt(), anyString(), anyBoolean());
383     }
384 
385     @Test
removeCallback_sameCode()386     public void removeCallback_sameCode() {
387         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
388         mController.removeCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
389         mController.onOpActiveChanged(
390                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
391         mTestableLooper.processAllMessages();
392         verify(mCallback, never()).onActiveStateChanged(
393                 anyInt(), anyInt(), anyString(), anyBoolean());
394     }
395 
396     @Test
addCallback_notSameCode()397     public void addCallback_notSameCode() {
398         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
399         mController.removeCallback(new int[]{AppOpsManager.OP_CAMERA}, mCallback);
400         mController.onOpActiveChanged(
401                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
402         mTestableLooper.processAllMessages();
403         verify(mCallback).onActiveStateChanged(AppOpsManager.OP_RECORD_AUDIO,
404                 TEST_UID, TEST_PACKAGE_NAME, true);
405     }
406 
407     @Test
getActiveItems_sameDetails()408     public void getActiveItems_sameDetails() {
409         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO,
410                 TEST_UID, TEST_PACKAGE_NAME, true);
411         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO,
412                 TEST_UID, TEST_PACKAGE_NAME, true);
413         assertEquals(1, mController.getActiveAppOps().size());
414     }
415 
416     @Test
getActiveItems_differentDetails()417     public void getActiveItems_differentDetails() {
418         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO,
419                 TEST_UID, TEST_PACKAGE_NAME, true);
420         mController.onOpActiveChanged(AppOpsManager.OPSTR_CAMERA,
421                 TEST_UID, TEST_PACKAGE_NAME, true);
422         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION,
423                 TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME,
424                 AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
425         assertEquals(3, mController.getActiveAppOps().size());
426     }
427 
428     @Test
getActiveItemsForUser()429     public void getActiveItemsForUser() {
430         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO,
431                 TEST_UID, TEST_PACKAGE_NAME, true);
432         mController.onOpActiveChanged(AppOpsManager.OPSTR_CAMERA,
433                 TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
434         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION,
435                 TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME,
436                 AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
437         assertEquals(2,
438                 mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID), false).size());
439         assertEquals(1, mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER),
440                 false).size());
441     }
442 
443     @Test
systemAndExemptedRolesAreIgnored()444     public void systemAndExemptedRolesAreIgnored() {
445         assumeFalse(mExemptedRolePkgName == null || mExemptedRolePkgName.equals(""));
446 
447         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO,
448                 TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true);
449         assertEquals(0, mController.getActiveAppOpsForUser(
450                 UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size());
451         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO,
452                 TEST_UID_NON_USER_SENSITIVE, SYSTEM_PKG, true);
453         assertEquals(0, mController.getActiveAppOpsForUser(
454                 UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size());
455     }
456 
457     @Test
exemptedRoleNotNotified()458     public void exemptedRoleNotNotified() {
459         assumeFalse(mExemptedRolePkgName == null || mExemptedRolePkgName.equals(""));
460 
461         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
462         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO,
463                 TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true);
464 
465         mTestableLooper.processAllMessages();
466 
467         verify(mCallback, never())
468                 .onActiveStateChanged(anyInt(), anyInt(), anyString(), anyBoolean());
469     }
470 
471     @Test
opNotedScheduledForRemoval()472     public void opNotedScheduledForRemoval() {
473         mController.setBGHandler(mMockHandler);
474         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
475                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
476         verify(mMockHandler).scheduleRemoval(any(AppOpItem.class), anyLong());
477     }
478 
479     @Test
noItemsAfterStopListening()480     public void noItemsAfterStopListening() {
481         mController.setBGHandler(mMockHandler);
482 
483         mController.setListening(true);
484         mController.onOpActiveChanged(AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID,
485                 TEST_PACKAGE_NAME, true);
486         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
487                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
488         assertFalse(mController.getActiveAppOps().isEmpty());
489 
490         mController.setListening(false);
491 
492         verify(mMockHandler).removeCallbacksAndMessages(null);
493         assertTrue(mController.getActiveAppOps().isEmpty());
494     }
495 
496     @Test
noDoubleUpdateOnOpNoted()497     public void noDoubleUpdateOnOpNoted() {
498         mController.setBGHandler(mMockHandler);
499 
500         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
501                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
502         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
503                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
504 
505         // Only one post to notify subscribers
506         verify(mMockHandler, times(1)).post(any());
507 
508         List<AppOpItem> list = mController.getActiveAppOps();
509         assertEquals(1, list.size());
510     }
511 
512     @Test
onDoubleOPNoted_scheduleTwiceForRemoval()513     public void onDoubleOPNoted_scheduleTwiceForRemoval() {
514         mController.setBGHandler(mMockHandler);
515 
516         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
517                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
518         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
519                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
520 
521         // Only one post to notify subscribers
522         verify(mMockHandler, times(2)).scheduleRemoval(any(), anyLong());
523     }
524 
525     @Test
testUntrustedChainUsagesDiscarded()526     public void testUntrustedChainUsagesDiscarded() {
527         assertTrue(mController.getActiveAppOps().isEmpty());
528         mController.setBGHandler(mMockHandler);
529 
530         //untrusted receiver access
531         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID,
532                 TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true,
533                 AppOpsManager.ATTRIBUTION_FLAG_RECEIVER, TEST_CHAIN_ID);
534         //untrusted intermediary access
535         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID,
536                 TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true,
537                 AppOpsManager.ATTRIBUTION_FLAG_INTERMEDIARY, TEST_CHAIN_ID);
538         assertTrue(mController.getActiveAppOps().isEmpty());
539     }
540 
541     @Test
testTrustedChainUsagesKept()542     public void testTrustedChainUsagesKept() {
543         //untrusted accessor access
544         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID,
545                 TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true,
546                 AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR, TEST_CHAIN_ID);
547         //trusted access
548         mController.onOpActiveChanged(AppOpsManager.OPSTR_CAMERA, TEST_UID,
549                 TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true,
550                 AppOpsManager.ATTRIBUTION_FLAG_RECEIVER | AppOpsManager.ATTRIBUTION_FLAG_TRUSTED,
551                 TEST_CHAIN_ID);
552         assertEquals(2, mController.getActiveAppOps().size());
553     }
554 
555     @Test
testActiveOpNotRemovedAfterNoted()556     public void testActiveOpNotRemovedAfterNoted() throws InterruptedException {
557         // Replaces the timeout delay with 5 ms
558         TestHandler testHandler = new TestHandler(mTestableLooper.getLooper());
559 
560         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
561         mController.setBGHandler(testHandler);
562 
563         mController.onOpActiveChanged(
564                 AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
565 
566         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
567                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
568 
569         // Check that we "scheduled" the removal. Don't actually schedule until we are ready to
570         // process messages at a later time.
571         assertNotNull(testHandler.mDelayScheduled);
572 
573         mTestableLooper.processAllMessages();
574         List<AppOpItem> list = mController.getActiveAppOps();
575         verify(mCallback).onActiveStateChanged(
576                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
577 
578         // Duplicates are not removed between active and noted
579         assertEquals(2, list.size());
580 
581         // Now is later, so we can schedule delayed messages.
582         testHandler.scheduleDelayed();
583         mTestableLooper.processAllMessages();
584 
585         verify(mCallback, never()).onActiveStateChanged(
586                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
587         list = mController.getActiveAppOps();
588         assertEquals(1, list.size());
589     }
590 
591     @Test
testNotedNotRemovedAfterActive()592     public void testNotedNotRemovedAfterActive() {
593         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
594 
595         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
596                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
597 
598         mController.onOpActiveChanged(
599                 AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
600 
601         mTestableLooper.processAllMessages();
602         List<AppOpItem> list = mController.getActiveAppOps();
603         verify(mCallback).onActiveStateChanged(
604                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
605 
606         // Duplicates are not removed between active and noted
607         assertEquals(2, list.size());
608 
609         mController.onOpActiveChanged(
610                 AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
611 
612         mTestableLooper.processAllMessages();
613 
614         verify(mCallback, never()).onActiveStateChanged(
615                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
616         list = mController.getActiveAppOps();
617         assertEquals(1, list.size());
618     }
619 
620     @Test
testNotedAndActiveOnlyOneCall()621     public void testNotedAndActiveOnlyOneCall() {
622         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
623 
624         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
625                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
626 
627         mController.onOpActiveChanged(
628                 AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
629 
630         mTestableLooper.processAllMessages();
631         verify(mCallback).onActiveStateChanged(
632                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
633     }
634 
635     @Test
testActiveAndNotedOnlyOneCall()636     public void testActiveAndNotedOnlyOneCall() {
637         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
638 
639         mController.onOpActiveChanged(
640                 AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
641 
642         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
643                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
644 
645         mTestableLooper.processAllMessages();
646         verify(mCallback).onActiveStateChanged(
647                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
648     }
649 
650     @Test
testPausedRecordingIsRetrievedOnCreation()651     public void testPausedRecordingIsRetrievedOnCreation() {
652         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
653         mTestableLooper.processAllMessages();
654 
655         mController.onOpActiveChanged(
656                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
657         mTestableLooper.processAllMessages();
658 
659         verify(mCallback, never())
660                 .onActiveStateChanged(anyInt(), anyInt(), anyString(), anyBoolean());
661     }
662 
663     @Test
testPausedRecordingFilteredOut()664     public void testPausedRecordingFilteredOut() {
665         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
666         mTestableLooper.processAllMessages();
667 
668         mController.onOpActiveChanged(
669                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
670         mTestableLooper.processAllMessages();
671 
672         assertTrue(mController.getActiveAppOps().isEmpty());
673     }
674 
675     @Test
testPausedPhoneCallMicrophoneFilteredOut()676     public void testPausedPhoneCallMicrophoneFilteredOut() {
677         mController.addCallback(new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE}, mCallback);
678         mTestableLooper.processAllMessages();
679 
680         mController.onOpActiveChanged(
681                 AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, true);
682         mTestableLooper.processAllMessages();
683 
684         assertTrue(mController.getActiveAppOps().isEmpty());
685     }
686 
687     @Test
testOnlyRecordAudioPhoneCallMicrophonePaused()688     public void testOnlyRecordAudioPhoneCallMicrophonePaused() {
689         mController.addCallback(new int[]{
690                 AppOpsManager.OP_RECORD_AUDIO,
691                 AppOpsManager.OP_CAMERA
692         }, mCallback);
693         mTestableLooper.processAllMessages();
694 
695         mController.onOpActiveChanged(
696                 AppOpsManager.OPSTR_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true);
697         mTestableLooper.processAllMessages();
698 
699         verify(mCallback).onActiveStateChanged(
700                 AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true);
701         List<AppOpItem> list = mController.getActiveAppOps();
702 
703         assertEquals(1, list.size());
704         assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
705     }
706 
707     @Test
testUnpausedRecordingSentActive()708     public void testUnpausedRecordingSentActive() {
709         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
710         mTestableLooper.processAllMessages();
711         mController.onOpActiveChanged(
712                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
713 
714         mTestableLooper.processAllMessages();
715         mRecordingCallback.onRecordingConfigChanged(Collections.emptyList());
716 
717         mTestableLooper.processAllMessages();
718 
719         verify(mCallback).onActiveStateChanged(
720                 AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
721     }
722 
723     @Test
testAudioPausedSentInactive()724     public void testAudioPausedSentInactive() {
725         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
726         mTestableLooper.processAllMessages();
727         mController.onOpActiveChanged(
728                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
729         mTestableLooper.processAllMessages();
730 
731         AudioRecordingConfiguration mockARC = mock(AudioRecordingConfiguration.class);
732         when(mockARC.getClientUid()).thenReturn(TEST_UID_OTHER);
733         when(mockARC.isClientSilenced()).thenReturn(true);
734 
735         mRecordingCallback.onRecordingConfigChanged(List.of(mockARC));
736         mTestableLooper.processAllMessages();
737 
738         InOrder inOrder = inOrder(mCallback);
739         inOrder.verify(mCallback).onActiveStateChanged(
740                 AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
741         inOrder.verify(mCallback).onActiveStateChanged(
742                 AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
743     }
744 
745     @Test
testAudioFilteredWhenMicDisabled()746     public void testAudioFilteredWhenMicDisabled() {
747         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
748                 mCallback);
749         mTestableLooper.processAllMessages();
750         mController.onOpActiveChanged(
751                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
752         mTestableLooper.processAllMessages();
753         List<AppOpItem> list = mController.getActiveAppOps();
754         assertEquals(1, list.size());
755         assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());
756         assertFalse(list.get(0).isDisabled());
757 
758         // Add a camera op, and disable the microphone. The camera op should be the only op returned
759         mController.onSensorBlockedChanged(MICROPHONE, true);
760         mController.onOpActiveChanged(
761                 AppOpsManager.OPSTR_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
762         mTestableLooper.processAllMessages();
763         list = mController.getActiveAppOps();
764         assertEquals(1, list.size());
765         assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
766 
767 
768         // Re enable the microphone, and verify the op returns
769         mController.onSensorBlockedChanged(MICROPHONE, false);
770         mTestableLooper.processAllMessages();
771 
772         list = mController.getActiveAppOps();
773         assertEquals(2, list.size());
774         int micIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 1 : 0;
775         assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(micIdx).getCode());
776     }
777 
778     @Test
testPhoneCallMicrophoneFilteredWhenMicDisabled()779     public void testPhoneCallMicrophoneFilteredWhenMicDisabled() {
780         mController.addCallback(
781                 new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE, AppOpsManager.OP_CAMERA},
782                 mCallback);
783         mTestableLooper.processAllMessages();
784         mController.onOpActiveChanged(
785                 AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
786         mTestableLooper.processAllMessages();
787         List<AppOpItem> list = mController.getActiveAppOps();
788         assertEquals(1, list.size());
789         assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(0).getCode());
790         assertFalse(list.get(0).isDisabled());
791 
792         // Add a camera op, and disable the microphone. The camera op should be the only op returned
793         mController.onSensorBlockedChanged(MICROPHONE, true);
794         mController.onOpActiveChanged(
795                 AppOpsManager.OPSTR_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
796         mTestableLooper.processAllMessages();
797         list = mController.getActiveAppOps();
798         assertEquals(1, list.size());
799         assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
800 
801 
802         // Re enable the microphone, and verify the op returns
803         mController.onSensorBlockedChanged(MICROPHONE, false);
804         mTestableLooper.processAllMessages();
805 
806         list = mController.getActiveAppOps();
807         assertEquals(2, list.size());
808         int micIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 1 : 0;
809         assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(micIdx).getCode());
810     }
811 
812     @Test
testCameraFilteredWhenCameraDisabled()813     public void testCameraFilteredWhenCameraDisabled() {
814         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
815                 mCallback);
816         mTestableLooper.processAllMessages();
817         mController.onOpActiveChanged(
818                 AppOpsManager.OPSTR_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
819         mTestableLooper.processAllMessages();
820         List<AppOpItem> list = mController.getActiveAppOps();
821         assertEquals(1, list.size());
822         assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
823         assertFalse(list.get(0).isDisabled());
824 
825         // Add an audio op, and disable the camera. The audio op should be the only op returned
826         mController.onSensorBlockedChanged(CAMERA, true);
827         mController.onOpActiveChanged(
828                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
829         mTestableLooper.processAllMessages();
830         list = mController.getActiveAppOps();
831         assertEquals(1, list.size());
832         assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());
833 
834         // Re enable the camera, and verify the op returns
835         mController.onSensorBlockedChanged(CAMERA, false);
836         mTestableLooper.processAllMessages();
837 
838         list = mController.getActiveAppOps();
839         assertEquals(2, list.size());
840         int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 0 : 1;
841         assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode());
842     }
843 
844     @Test
testPhoneCallCameraFilteredWhenCameraDisabled()845     public void testPhoneCallCameraFilteredWhenCameraDisabled() {
846         mController.addCallback(
847                 new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_PHONE_CALL_CAMERA},
848                 mCallback);
849         mTestableLooper.processAllMessages();
850         mController.onOpActiveChanged(
851                 AppOpsManager.OPSTR_PHONE_CALL_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
852         mTestableLooper.processAllMessages();
853         List<AppOpItem> list = mController.getActiveAppOps();
854         assertEquals(1, list.size());
855         assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(0).getCode());
856         assertFalse(list.get(0).isDisabled());
857 
858         // Add an audio op, and disable the camera. The audio op should be the only op returned
859         mController.onSensorBlockedChanged(CAMERA, true);
860         mController.onOpActiveChanged(
861                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
862         mTestableLooper.processAllMessages();
863         list = mController.getActiveAppOps();
864         assertEquals(1, list.size());
865         assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());
866 
867         // Re enable the camera, and verify the op returns
868         mController.onSensorBlockedChanged(CAMERA, false);
869         mTestableLooper.processAllMessages();
870 
871         list = mController.getActiveAppOps();
872         assertEquals(2, list.size());
873         int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_PHONE_CALL_CAMERA ? 0 : 1;
874         assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(cameraIdx).getCode());
875     }
876 
createPackageOp( String packageName, int packageUid, String opStr, boolean isRunning)877     private AppOpsManager.PackageOps createPackageOp(
878             String packageName, int packageUid, String opStr, boolean isRunning) {
879         AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
880         when(packageOps.getPackageName()).thenReturn(packageName);
881         when(packageOps.getUid()).thenReturn(packageUid);
882         AppOpsManager.OpEntry entry = mock(AppOpsManager.OpEntry.class);
883         when(entry.getOpStr()).thenReturn(opStr);
884         AppOpsManager.AttributedOpEntry attributed = mock(AppOpsManager.AttributedOpEntry.class);
885         when(attributed.isRunning()).thenReturn(isRunning);
886 
887         when(packageOps.getOps()).thenReturn(Collections.singletonList(entry));
888         when(entry.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed));
889 
890         return packageOps;
891     }
892 
893     private class TestHandler extends AppOpsControllerImpl.H {
TestHandler(Looper looper)894         TestHandler(Looper looper) {
895             mController.super(looper);
896         }
897 
898         Runnable mDelayScheduled;
899 
scheduleDelayed()900         void scheduleDelayed() {
901             if (mDelayScheduled != null) {
902                 mDelayScheduled.run();
903                 mDelayScheduled = null;
904             }
905         }
906 
907         @Override
scheduleRemoval(AppOpItem item, long timeToRemoval)908         public void scheduleRemoval(AppOpItem item, long timeToRemoval) {
909             mDelayScheduled = () -> super.scheduleRemoval(item, 0L);
910         }
911     }
912 }
913