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.statusbar;
18 
19 import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
20 import static android.app.ActivityManager.PROCESS_STATE_TOP;
21 
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertNotEquals;
24 import static org.junit.Assert.assertThrows;
25 import static org.junit.Assert.fail;
26 import static org.mockito.ArgumentMatchers.anyBoolean;
27 import static org.mockito.ArgumentMatchers.nullable;
28 import static org.mockito.Mockito.any;
29 import static org.mockito.Mockito.anyInt;
30 import static org.mockito.Mockito.anyLong;
31 import static org.mockito.Mockito.anyString;
32 import static org.mockito.Mockito.argThat;
33 import static org.mockito.Mockito.eq;
34 import static org.mockito.Mockito.never;
35 import static org.mockito.Mockito.times;
36 import static org.mockito.Mockito.verify;
37 import static org.mockito.Mockito.when;
38 
39 import android.Manifest;
40 import android.app.ActivityManagerInternal;
41 import android.app.StatusBarManager;
42 import android.compat.testing.PlatformCompatChangeRule;
43 import android.content.ComponentName;
44 import android.content.Intent;
45 import android.content.om.IOverlayManager;
46 import android.content.pm.ApplicationInfo;
47 import android.content.pm.PackageInfo;
48 import android.content.pm.PackageManager;
49 import android.content.pm.PackageManagerInternal;
50 import android.content.pm.ResolveInfo;
51 import android.content.pm.ServiceInfo;
52 import android.graphics.drawable.Icon;
53 import android.hardware.display.DisplayManager;
54 import android.os.Binder;
55 import android.os.Looper;
56 import android.os.RemoteException;
57 import android.os.UserHandle;
58 import android.service.quicksettings.TileService;
59 import android.testing.TestableContext;
60 
61 import androidx.test.InstrumentationRegistry;
62 
63 import com.android.internal.statusbar.IAddTileResultCallback;
64 import com.android.internal.statusbar.IStatusBar;
65 import com.android.server.LocalServices;
66 import com.android.server.policy.GlobalActionsProvider;
67 import com.android.server.wm.ActivityTaskManagerInternal;
68 
69 import libcore.junit.util.compat.CoreCompatChangeRule;
70 
71 import org.junit.Before;
72 import org.junit.BeforeClass;
73 import org.junit.Rule;
74 import org.junit.Test;
75 import org.junit.rules.TestRule;
76 import org.junit.runner.RunWith;
77 import org.junit.runners.JUnit4;
78 import org.mockito.ArgumentCaptor;
79 import org.mockito.ArgumentMatcher;
80 import org.mockito.Captor;
81 import org.mockito.Mock;
82 import org.mockito.MockitoAnnotations;
83 import org.mockito.stubbing.Answer;
84 
85 @RunWith(JUnit4.class)
86 public class StatusBarManagerServiceTest {
87 
88     private static final String TEST_PACKAGE = "test_pkg";
89     private static final String TEST_SERVICE = "test_svc";
90     private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE,
91             TEST_SERVICE);
92     private static final CharSequence APP_NAME = "AppName";
93     private static final CharSequence TILE_LABEL = "Tile label";
94 
95     @Rule
96     public final TestableContext mContext =
97             new NoBroadcastContextWrapper(InstrumentationRegistry.getContext());
98 
99     @Rule
100     public TestRule mCompatChangeRule = new PlatformCompatChangeRule();
101 
102     @Mock
103     private ActivityTaskManagerInternal mActivityTaskManagerInternal;
104     @Mock
105     private PackageManagerInternal mPackageManagerInternal;
106     @Mock
107     private ActivityManagerInternal mActivityManagerInternal;
108     @Mock
109     private ApplicationInfo mApplicationInfo;
110     @Mock
111     private IStatusBar.Stub mMockStatusBar;
112     @Mock
113     private IOverlayManager mOverlayManager;
114     @Mock
115     private PackageManager mPackageManager;
116     @Captor
117     private ArgumentCaptor<IAddTileResultCallback> mAddTileResultCallbackCaptor;
118 
119     private Icon mIcon;
120     private StatusBarManagerService mStatusBarManagerService;
121 
122     @BeforeClass
oneTimeInitialization()123     public static void oneTimeInitialization() {
124         if (Looper.myLooper() == null) {
125             Looper.prepare();
126         }
127     }
128 
129     @Before
setUp()130     public void setUp() {
131         MockitoAnnotations.initMocks(this);
132         LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
133         LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal);
134         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
135         LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInternal);
136         LocalServices.removeServiceForTest(PackageManagerInternal.class);
137         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
138 
139         when(mMockStatusBar.asBinder()).thenReturn(mMockStatusBar);
140         when(mApplicationInfo.loadLabel(any())).thenReturn(APP_NAME);
141         mockHandleIncomingUser();
142 
143         mStatusBarManagerService = new StatusBarManagerService(mContext);
144         LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
145         LocalServices.removeServiceForTest(GlobalActionsProvider.class);
146 
147         mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(
148                 mStatusBarManagerService);
149 
150         mStatusBarManagerService.registerStatusBar(mMockStatusBar);
151         mStatusBarManagerService.registerOverlayManager(mOverlayManager);
152 
153         mIcon = Icon.createWithResource(mContext, android.R.drawable.btn_plus);
154     }
155 
156     @Test
157     @CoreCompatChangeRule.EnableCompatChanges(
158             {StatusBarManagerService.REQUEST_LISTENING_MUST_MATCH_PACKAGE})
testRequestActive_changeEnabled_OKCall()159     public void testRequestActive_changeEnabled_OKCall() throws RemoteException {
160         int user = 0;
161         mockEverything(user);
162         mStatusBarManagerService.requestTileServiceListeningState(TEST_COMPONENT, user);
163 
164         verify(mMockStatusBar).requestTileServiceListeningState(TEST_COMPONENT);
165     }
166 
167     @Test
168     @CoreCompatChangeRule.EnableCompatChanges(
169             {StatusBarManagerService.REQUEST_LISTENING_MUST_MATCH_PACKAGE})
testRequestActive_changeEnabled_differentPackage_fail()170     public void testRequestActive_changeEnabled_differentPackage_fail() throws RemoteException {
171         when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0L, mContext.getUserId()))
172                 .thenReturn(Binder.getCallingUid() + 1);
173         try {
174             mStatusBarManagerService.requestTileServiceListeningState(TEST_COMPONENT, 0);
175             fail("Should cause security exception");
176         } catch (SecurityException e) { }
177         verify(mMockStatusBar, never()).requestTileServiceListeningState(TEST_COMPONENT);
178     }
179 
180     @Test
181     @CoreCompatChangeRule.EnableCompatChanges(
182             {StatusBarManagerService.REQUEST_LISTENING_MUST_MATCH_PACKAGE})
testRequestActive_changeEnabled_notCurrentUser_fail()183     public void testRequestActive_changeEnabled_notCurrentUser_fail() throws RemoteException {
184         mockUidCheck();
185         int user = 0;
186         mockCurrentUserCheck(user);
187         try {
188             mStatusBarManagerService.requestTileServiceListeningState(TEST_COMPONENT, user + 1);
189             fail("Should cause illegal argument exception");
190         } catch (IllegalArgumentException e) { }
191 
192         // Do not call into SystemUI
193         verify(mMockStatusBar, never()).requestTileServiceListeningState(TEST_COMPONENT);
194     }
195 
196     @Test
197     @CoreCompatChangeRule.DisableCompatChanges(
198             {StatusBarManagerService.REQUEST_LISTENING_MUST_MATCH_PACKAGE})
testRequestActive_changeDisabled_pass()199     public void testRequestActive_changeDisabled_pass() throws RemoteException {
200         int user = 0;
201         mockEverything(user);
202         mStatusBarManagerService.requestTileServiceListeningState(TEST_COMPONENT, user);
203 
204         verify(mMockStatusBar).requestTileServiceListeningState(TEST_COMPONENT);
205     }
206 
207     @Test
208     @CoreCompatChangeRule.DisableCompatChanges(
209             {StatusBarManagerService.REQUEST_LISTENING_MUST_MATCH_PACKAGE})
testRequestActive_changeDisabled_differentPackage_pass()210     public void testRequestActive_changeDisabled_differentPackage_pass() throws RemoteException {
211         when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0L, mContext.getUserId()))
212                 .thenReturn(Binder.getCallingUid() + 1);
213         mStatusBarManagerService.requestTileServiceListeningState(TEST_COMPONENT, 0);
214 
215         verify(mMockStatusBar).requestTileServiceListeningState(TEST_COMPONENT);
216     }
217 
218     @Test
219     @CoreCompatChangeRule.DisableCompatChanges(
220             {StatusBarManagerService.REQUEST_LISTENING_MUST_MATCH_PACKAGE})
testRequestActive_changeDisabled_notCurrentUser_pass()221     public void testRequestActive_changeDisabled_notCurrentUser_pass() throws RemoteException {
222         mockUidCheck();
223         int user = 0;
224         mockCurrentUserCheck(user);
225         mStatusBarManagerService.requestTileServiceListeningState(TEST_COMPONENT, user + 1);
226 
227         verify(mMockStatusBar).requestTileServiceListeningState(TEST_COMPONENT);
228     }
229 
230     @Test
testHandleIncomingUserCalled()231     public void testHandleIncomingUserCalled() {
232         int fakeUser = 17;
233         try {
234             mStatusBarManagerService.requestAddTile(
235                     TEST_COMPONENT,
236                     TILE_LABEL,
237                     mIcon,
238                     fakeUser,
239                     new Callback()
240             );
241             fail("Should have SecurityException from uid check");
242         } catch (SecurityException e) {
243             verify(mActivityManagerInternal).handleIncomingUser(
244                     eq(Binder.getCallingPid()),
245                     eq(Binder.getCallingUid()),
246                     eq(fakeUser),
247                     eq(false),
248                     eq(ActivityManagerInternal.ALLOW_NON_FULL),
249                     anyString(),
250                     eq(TEST_PACKAGE)
251             );
252         }
253     }
254 
255     @Test
testCheckUid_pass()256     public void testCheckUid_pass() {
257         when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0, mContext.getUserId()))
258                 .thenReturn(Binder.getCallingUid());
259         try {
260             mStatusBarManagerService.requestAddTile(
261                     TEST_COMPONENT,
262                     TILE_LABEL,
263                     mIcon,
264                     mContext.getUserId(),
265                     new Callback()
266             );
267         } catch (SecurityException e) {
268             fail("No SecurityException should be thrown");
269         }
270     }
271 
272     @Test
testCheckUid_pass_differentUser()273     public void testCheckUid_pass_differentUser() {
274         int otherUserUid = UserHandle.getUid(17, UserHandle.getAppId(Binder.getCallingUid()));
275         when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0, mContext.getUserId()))
276                 .thenReturn(otherUserUid);
277         try {
278             mStatusBarManagerService.requestAddTile(
279                     TEST_COMPONENT,
280                     TILE_LABEL,
281                     mIcon,
282                     mContext.getUserId(),
283                     new Callback()
284             );
285         } catch (SecurityException e) {
286             fail("No SecurityException should be thrown");
287         }
288     }
289 
290     @Test
testCheckUid_fail()291     public void testCheckUid_fail() {
292         when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0, mContext.getUserId()))
293                 .thenReturn(Binder.getCallingUid() + 1);
294         try {
295             mStatusBarManagerService.requestAddTile(
296                     TEST_COMPONENT,
297                     TILE_LABEL,
298                     mIcon,
299                     mContext.getUserId(),
300                     new Callback()
301             );
302             fail("Should throw SecurityException");
303         } catch (SecurityException e) {
304             // pass
305         }
306     }
307 
308     @Test
testCurrentUser_fail()309     public void testCurrentUser_fail() {
310         mockUidCheck();
311         int user = 0;
312         when(mActivityManagerInternal.getCurrentUserId()).thenReturn(user + 1);
313 
314         Callback callback = new Callback();
315         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
316 
317         assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_NOT_CURRENT_USER,
318                 callback.mUserResponse);
319     }
320 
321     @Test
testCurrentUser_pass()322     public void testCurrentUser_pass() {
323         mockUidCheck();
324         int user = 0;
325         when(mActivityManagerInternal.getCurrentUserId()).thenReturn(user);
326 
327         Callback callback = new Callback();
328         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
329 
330         assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_NOT_CURRENT_USER,
331                 callback.mUserResponse);
332     }
333 
334     @Test
testValidComponent_fail_noComponentFound()335     public void testValidComponent_fail_noComponentFound() {
336         int user = 10;
337         mockUidCheck();
338         mockCurrentUserCheck(user);
339         IntentMatcher im = new IntentMatcher(
340                 new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
341         when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0L),
342                 eq(user), anyInt())).thenReturn(null);
343 
344         Callback callback = new Callback();
345         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
346 
347         assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
348     }
349 
350     @Test
testValidComponent_fail_notEnabled()351     public void testValidComponent_fail_notEnabled() {
352         int user = 10;
353         mockUidCheck();
354         mockCurrentUserCheck(user);
355 
356         ResolveInfo r = makeResolveInfo();
357         r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
358 
359         IntentMatcher im = new IntentMatcher(
360                 new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
361         when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0L),
362                 eq(user), anyInt())).thenReturn(r);
363         when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
364                 Binder.getCallingUid(), user)).thenReturn(
365                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
366 
367         Callback callback = new Callback();
368         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
369 
370         assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
371     }
372 
373     @Test
testValidComponent_fail_noPermission()374     public void testValidComponent_fail_noPermission() {
375         int user = 10;
376         mockUidCheck();
377         mockCurrentUserCheck(user);
378 
379         ResolveInfo r = makeResolveInfo();
380 
381         IntentMatcher im = new IntentMatcher(
382                 new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
383         when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0L),
384                 eq(user), anyInt())).thenReturn(r);
385         when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
386                 Binder.getCallingUid(), user)).thenReturn(
387                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
388 
389         Callback callback = new Callback();
390         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
391 
392         assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
393     }
394 
395     @Test
testValidComponent_fail_notExported()396     public void testValidComponent_fail_notExported() {
397         int user = 10;
398         mockUidCheck();
399         mockCurrentUserCheck(user);
400 
401         ResolveInfo r = makeResolveInfo();
402         r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
403         r.serviceInfo.exported = false;
404 
405         IntentMatcher im = new IntentMatcher(
406                 new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
407         when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0L),
408                 eq(user), anyInt())).thenReturn(r);
409         when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
410                 Binder.getCallingUid(), user)).thenReturn(
411                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
412 
413         Callback callback = new Callback();
414         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
415 
416         assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
417     }
418 
419     @Test
testValidComponent_pass()420     public void testValidComponent_pass() {
421         int user = 10;
422         mockUidCheck();
423         mockCurrentUserCheck(user);
424 
425         ResolveInfo r = makeResolveInfo();
426         r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
427         r.serviceInfo.exported = true;
428 
429         IntentMatcher im = new IntentMatcher(
430                 new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
431         when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0L),
432                 eq(user), anyInt())).thenReturn(r);
433         when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
434                 Binder.getCallingUid(), user)).thenReturn(
435                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
436 
437         Callback callback = new Callback();
438         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
439 
440         assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT,
441                 callback.mUserResponse);
442     }
443 
444     @Test
testAppInForeground_fail()445     public void testAppInForeground_fail() {
446         int user = 10;
447         mockUidCheck();
448         mockCurrentUserCheck(user);
449         mockComponentInfo(user);
450 
451         when(mActivityManagerInternal.getUidProcessState(Binder.getCallingUid())).thenReturn(
452                 PROCESS_STATE_FOREGROUND_SERVICE);
453 
454         Callback callback = new Callback();
455         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
456 
457         assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND,
458                 callback.mUserResponse);
459     }
460 
461     @Test
testAppInForeground_pass()462     public void testAppInForeground_pass() {
463         int user = 10;
464         mockUidCheck();
465         mockCurrentUserCheck(user);
466         mockComponentInfo(user);
467 
468         when(mActivityManagerInternal.getUidProcessState(Binder.getCallingUid())).thenReturn(
469                 PROCESS_STATE_TOP);
470 
471         Callback callback = new Callback();
472         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
473 
474         assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND,
475                 callback.mUserResponse);
476     }
477 
478     @Test
testRequestToStatusBar()479     public void testRequestToStatusBar() throws RemoteException {
480         int user = 10;
481         mockEverything(user);
482 
483         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
484                 new Callback());
485 
486         verify(mMockStatusBar).requestAddTile(
487                 eq(TEST_COMPONENT),
488                 eq(APP_NAME),
489                 eq(TILE_LABEL),
490                 eq(mIcon),
491                 any()
492         );
493     }
494 
495     @Test
testRequestInProgress_samePackage()496     public void testRequestInProgress_samePackage() throws RemoteException {
497         int user = 10;
498         mockEverything(user);
499 
500         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
501                 new Callback());
502 
503         Callback callback = new Callback();
504         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
505 
506         assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS,
507                 callback.mUserResponse);
508     }
509 
510     @Test
testRequestInProgress_differentPackage()511     public void testRequestInProgress_differentPackage() throws RemoteException {
512         int user = 10;
513         mockEverything(user);
514         ComponentName otherComponent = new ComponentName("a", "b");
515         mockUidCheck(otherComponent.getPackageName());
516         mockComponentInfo(user, otherComponent);
517 
518         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
519                 new Callback());
520 
521         Callback callback = new Callback();
522         mStatusBarManagerService.requestAddTile(otherComponent, TILE_LABEL, mIcon, user, callback);
523 
524         assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS,
525                 callback.mUserResponse);
526     }
527 
528     @Test
testResponseForwardedToCallback_tileAdded()529     public void testResponseForwardedToCallback_tileAdded() throws RemoteException {
530         int user = 10;
531         mockEverything(user);
532 
533         Callback callback = new Callback();
534         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
535 
536         verify(mMockStatusBar).requestAddTile(
537                 eq(TEST_COMPONENT),
538                 eq(APP_NAME),
539                 eq(TILE_LABEL),
540                 eq(mIcon),
541                 mAddTileResultCallbackCaptor.capture()
542         );
543 
544         mAddTileResultCallbackCaptor.getValue().onTileRequest(
545                 StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED);
546         assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED, callback.mUserResponse);
547     }
548 
549     @Test
testResponseForwardedToCallback_tileNotAdded()550     public void testResponseForwardedToCallback_tileNotAdded() throws RemoteException {
551         int user = 10;
552         mockEverything(user);
553 
554         Callback callback = new Callback();
555         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
556 
557         verify(mMockStatusBar).requestAddTile(
558                 eq(TEST_COMPONENT),
559                 eq(APP_NAME),
560                 eq(TILE_LABEL),
561                 eq(mIcon),
562                 mAddTileResultCallbackCaptor.capture()
563         );
564 
565         mAddTileResultCallbackCaptor.getValue().onTileRequest(
566                 StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED);
567         assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
568                 callback.mUserResponse);
569     }
570 
571     @Test
testResponseForwardedToCallback_tileAlreadyAdded()572     public void testResponseForwardedToCallback_tileAlreadyAdded() throws RemoteException {
573         int user = 10;
574         mockEverything(user);
575 
576         Callback callback = new Callback();
577         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
578 
579         verify(mMockStatusBar).requestAddTile(
580                 eq(TEST_COMPONENT),
581                 eq(APP_NAME),
582                 eq(TILE_LABEL),
583                 eq(mIcon),
584                 mAddTileResultCallbackCaptor.capture()
585         );
586 
587         mAddTileResultCallbackCaptor.getValue().onTileRequest(
588                 StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED);
589         assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED,
590                 callback.mUserResponse);
591     }
592 
593     @Test
testResponseForwardedToCallback_dialogDismissed()594     public void testResponseForwardedToCallback_dialogDismissed() throws RemoteException {
595         int user = 10;
596         mockEverything(user);
597 
598         Callback callback = new Callback();
599         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
600 
601         verify(mMockStatusBar).requestAddTile(
602                 eq(TEST_COMPONENT),
603                 eq(APP_NAME),
604                 eq(TILE_LABEL),
605                 eq(mIcon),
606                 mAddTileResultCallbackCaptor.capture()
607         );
608 
609         mAddTileResultCallbackCaptor.getValue().onTileRequest(
610                 StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED);
611         // This gets translated to TILE_NOT_ADDED
612         assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
613                 callback.mUserResponse);
614     }
615 
616     @Test
testInstaDenialAfterManyDenials()617     public void testInstaDenialAfterManyDenials() throws RemoteException {
618         int user = 10;
619         mockEverything(user);
620 
621         for (int i = 0; i < TileRequestTracker.MAX_NUM_DENIALS; i++) {
622             mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
623                     new Callback());
624 
625             verify(mMockStatusBar, times(i + 1)).requestAddTile(
626                     eq(TEST_COMPONENT),
627                     eq(APP_NAME),
628                     eq(TILE_LABEL),
629                     eq(mIcon),
630                     mAddTileResultCallbackCaptor.capture()
631             );
632             mAddTileResultCallbackCaptor.getValue().onTileRequest(
633                     StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED);
634         }
635 
636         Callback callback = new Callback();
637         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
638 
639         // Only called MAX_NUM_DENIALS times
640         verify(mMockStatusBar, times(TileRequestTracker.MAX_NUM_DENIALS)).requestAddTile(
641                 any(),
642                 any(),
643                 any(),
644                 any(),
645                 mAddTileResultCallbackCaptor.capture()
646         );
647         assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
648                 callback.mUserResponse);
649     }
650 
651     @Test
testDialogDismissalNotCountingAgainstDenials()652     public void testDialogDismissalNotCountingAgainstDenials() throws RemoteException {
653         int user = 10;
654         mockEverything(user);
655 
656         for (int i = 0; i < TileRequestTracker.MAX_NUM_DENIALS * 2; i++) {
657             mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
658                     new Callback());
659 
660             verify(mMockStatusBar, times(i + 1)).requestAddTile(
661                     eq(TEST_COMPONENT),
662                     eq(APP_NAME),
663                     eq(TILE_LABEL),
664                     eq(mIcon),
665                     mAddTileResultCallbackCaptor.capture()
666             );
667             mAddTileResultCallbackCaptor.getValue().onTileRequest(
668                     StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED);
669         }
670     }
671 
672     @Test
testSetNavBarMode_setsModeKids()673     public void testSetNavBarMode_setsModeKids() throws Exception {
674         mContext.setMockPackageManager(mPackageManager);
675         when(mPackageManager.getPackageInfo(anyString(),
676                 any(PackageManager.PackageInfoFlags.class))).thenReturn(new PackageInfo());
677         int navBarModeKids = StatusBarManager.NAV_BAR_MODE_KIDS;
678 
679         mStatusBarManagerService.setNavBarMode(navBarModeKids);
680 
681         assertEquals(navBarModeKids, mStatusBarManagerService.getNavBarMode());
682         verify(mOverlayManager).setEnabledExclusiveInCategory(anyString(), anyInt());
683     }
684 
685     @Test
testSetNavBarMode_setsModeNone()686     public void testSetNavBarMode_setsModeNone() throws RemoteException {
687         int navBarModeNone = StatusBarManager.NAV_BAR_MODE_DEFAULT;
688 
689         mStatusBarManagerService.setNavBarMode(navBarModeNone);
690 
691         assertEquals(navBarModeNone, mStatusBarManagerService.getNavBarMode());
692         verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt());
693     }
694 
695     @Test
testSetNavBarMode_invalidInputThrowsError()696     public void testSetNavBarMode_invalidInputThrowsError() throws RemoteException {
697         int navBarModeInvalid = -1;
698 
699         assertThrows(IllegalArgumentException.class,
700                 () -> mStatusBarManagerService.setNavBarMode(navBarModeInvalid));
701         verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt());
702     }
703 
704     @Test
testSetNavBarMode_noPackageDoesNotEnable()705     public void testSetNavBarMode_noPackageDoesNotEnable() throws Exception {
706         mContext.setMockPackageManager(mPackageManager);
707         when(mPackageManager.getPackageInfo(anyString(),
708                 any(PackageManager.PackageInfoFlags.class))).thenReturn(null);
709         int navBarModeKids = StatusBarManager.NAV_BAR_MODE_KIDS;
710 
711         mStatusBarManagerService.setNavBarMode(navBarModeKids);
712 
713         assertEquals(navBarModeKids, mStatusBarManagerService.getNavBarMode());
714         verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt());
715     }
716 
mockUidCheck()717     private void mockUidCheck() {
718         mockUidCheck(TEST_PACKAGE);
719     }
720 
mockUidCheck(String packageName)721     private void mockUidCheck(String packageName) {
722         when(mPackageManagerInternal.getPackageUid(eq(packageName), anyLong(), anyInt()))
723                 .thenReturn(Binder.getCallingUid());
724     }
725 
mockCurrentUserCheck(int user)726     private void mockCurrentUserCheck(int user) {
727         when(mActivityManagerInternal.getCurrentUserId()).thenReturn(user);
728     }
729 
mockComponentInfo(int user)730     private void mockComponentInfo(int user) {
731         mockComponentInfo(user, TEST_COMPONENT);
732     }
733 
makeResolveInfo()734     private ResolveInfo makeResolveInfo() {
735         ResolveInfo r = new ResolveInfo();
736         r.serviceInfo = new ServiceInfo();
737         r.serviceInfo.applicationInfo = mApplicationInfo;
738         return r;
739     }
740 
mockComponentInfo(int user, ComponentName componentName)741     private void mockComponentInfo(int user, ComponentName componentName) {
742         ResolveInfo r = makeResolveInfo();
743         r.serviceInfo.exported = true;
744         r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
745 
746         IntentMatcher im = new IntentMatcher(
747                 new Intent(TileService.ACTION_QS_TILE).setComponent(componentName));
748         when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0L),
749                 eq(user), anyInt())).thenReturn(r);
750         when(mPackageManagerInternal.getComponentEnabledSetting(componentName,
751                 Binder.getCallingUid(), user)).thenReturn(
752                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
753     }
754 
mockProcessState()755     private void mockProcessState() {
756         when(mActivityManagerInternal.getUidProcessState(Binder.getCallingUid())).thenReturn(
757                 PROCESS_STATE_TOP);
758     }
759 
mockHandleIncomingUser()760     private void mockHandleIncomingUser() {
761         when(mActivityManagerInternal.handleIncomingUser(anyInt(), anyInt(), anyInt(), anyBoolean(),
762                 anyInt(), anyString(), anyString())).thenAnswer(
763                     (Answer<Integer>) invocation -> {
764                         return invocation.getArgument(2); // same user
765                     }
766         );
767     }
768 
mockEverything(int user)769     private void mockEverything(int user) {
770         mockUidCheck();
771         mockCurrentUserCheck(user);
772         mockComponentInfo(user);
773         mockProcessState();
774     }
775 
776     private static class Callback extends IAddTileResultCallback.Stub {
777         int mUserResponse = -1;
778 
779         @Override
onTileRequest(int userResponse)780         public void onTileRequest(int userResponse) throws RemoteException {
781             if (mUserResponse != -1) {
782                 throw new IllegalStateException(
783                         "Setting response to " + userResponse + " but it already has "
784                                 + mUserResponse);
785             }
786             mUserResponse = userResponse;
787         }
788     }
789 
790     private static class IntentMatcher implements ArgumentMatcher<Intent> {
791         private final Intent mIntent;
792 
IntentMatcher(Intent intent)793         IntentMatcher(Intent intent) {
794             mIntent = intent;
795         }
796 
797         @Override
matches(Intent argument)798         public boolean matches(Intent argument) {
799             return argument != null && argument.filterEquals(mIntent);
800         }
801 
802         @Override
toString()803         public String toString() {
804             return "Expected: " + mIntent;
805         }
806     }
807 }
808