1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.sysui;
18 
19 import static org.junit.Assert.assertThrows;
20 import static org.junit.Assert.assertTrue;
21 import static org.mockito.Mockito.mock;
22 
23 import android.content.Context;
24 import android.content.pm.UserInfo;
25 import android.content.res.Configuration;
26 import android.graphics.Rect;
27 import android.os.Binder;
28 import android.os.Bundle;
29 import android.os.IBinder;
30 import android.testing.AndroidTestingRunner;
31 import android.testing.TestableLooper;
32 
33 import androidx.annotation.NonNull;
34 import androidx.test.filters.SmallTest;
35 import androidx.test.platform.app.InstrumentationRegistry;
36 
37 import com.android.wm.shell.ShellTestCase;
38 import com.android.wm.shell.TestShellExecutor;
39 import com.android.wm.shell.common.DisplayInsetsController;
40 import com.android.wm.shell.common.ExternalInterfaceBinder;
41 
42 import org.junit.After;
43 import org.junit.Before;
44 import org.junit.Test;
45 import org.junit.runner.RunWith;
46 import org.mockito.Mock;
47 import org.mockito.MockitoAnnotations;
48 
49 import java.util.ArrayList;
50 import java.util.List;
51 import java.util.Locale;
52 
53 @SmallTest
54 @RunWith(AndroidTestingRunner.class)
55 @TestableLooper.RunWithLooper(setAsMainLooper = true)
56 public class ShellControllerTest extends ShellTestCase {
57 
58     private static final int TEST_USER_ID = 100;
59     private static final String EXTRA_TEST_BINDER = "test_binder";
60 
61     @Mock
62     private ShellInit mShellInit;
63     @Mock
64     private ShellCommandHandler mShellCommandHandler;
65     @Mock
66     private Context mTestUserContext;
67     @Mock
68     private DisplayInsetsController mDisplayInsetsController;
69 
70     private TestShellExecutor mExecutor;
71     private ShellController mController;
72     private TestConfigurationChangeListener mConfigChangeListener;
73     private TestKeyguardChangeListener mKeyguardChangeListener;
74     private TestUserChangeListener mUserChangeListener;
75     private TestDisplayImeChangeListener mDisplayImeChangeListener;
76 
77 
78     @Before
setUp()79     public void setUp() {
80         MockitoAnnotations.initMocks(this);
81         mKeyguardChangeListener = new TestKeyguardChangeListener();
82         mConfigChangeListener = new TestConfigurationChangeListener();
83         mUserChangeListener = new TestUserChangeListener();
84         mDisplayImeChangeListener = new TestDisplayImeChangeListener();
85         mExecutor = new TestShellExecutor();
86         mController = new ShellController(mContext, mShellInit, mShellCommandHandler,
87                 mDisplayInsetsController, mExecutor);
88         mController.onConfigurationChanged(getConfigurationCopy());
89     }
90 
91     @After
tearDown()92     public void tearDown() {
93         // Do nothing
94     }
95 
96     @Test
testAddExternalInterface_ensureCallback()97     public void testAddExternalInterface_ensureCallback() {
98         Binder callback = new Binder();
99         ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
100             @Override
101             public void invalidate() {
102                 // Do nothing
103             }
104 
105             @Override
106             public IBinder asBinder() {
107                 return callback;
108             }
109         };
110         mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
111 
112         Bundle b = new Bundle();
113         mController.asShell().createExternalInterfaces(b);
114         mExecutor.flushAll();
115         assertTrue(b.getIBinder(EXTRA_TEST_BINDER) == callback);
116     }
117 
118     @Test
testAddExternalInterface_disallowDuplicateKeys()119     public void testAddExternalInterface_disallowDuplicateKeys() {
120         Binder callback = new Binder();
121         ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
122             @Override
123             public void invalidate() {
124                 // Do nothing
125             }
126 
127             @Override
128             public IBinder asBinder() {
129                 return callback;
130             }
131         };
132         mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
133         assertThrows(IllegalArgumentException.class, () -> {
134             mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
135         });
136     }
137 
138     @Test
testAddDisplayImeChangeListener_ensureCallback()139     public void testAddDisplayImeChangeListener_ensureCallback() {
140         mController.asShell().addDisplayImeChangeListener(
141                 mDisplayImeChangeListener, mExecutor);
142 
143         final Rect bounds = new Rect(10, 20, 30, 40);
144         mController.onImeBoundsChanged(bounds);
145         mController.onImeVisibilityChanged(true);
146         mExecutor.flushAll();
147 
148         assertTrue(mDisplayImeChangeListener.boundsChanged == 1);
149         assertTrue(bounds.equals(mDisplayImeChangeListener.lastBounds));
150         assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
151         assertTrue(mDisplayImeChangeListener.lastVisibility);
152     }
153 
154     @Test
testDoubleAddDisplayImeChangeListener_ensureSingleCallback()155     public void testDoubleAddDisplayImeChangeListener_ensureSingleCallback() {
156         mController.asShell().addDisplayImeChangeListener(
157                 mDisplayImeChangeListener, mExecutor);
158         mController.asShell().addDisplayImeChangeListener(
159                 mDisplayImeChangeListener, mExecutor);
160 
161         mController.onImeVisibilityChanged(true);
162         mExecutor.flushAll();
163         assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
164     }
165 
166     @Test
testAddRemoveDisplayImeChangeListener_ensureNoCallback()167     public void testAddRemoveDisplayImeChangeListener_ensureNoCallback() {
168         mController.asShell().addDisplayImeChangeListener(
169                 mDisplayImeChangeListener, mExecutor);
170         mController.asShell().removeDisplayImeChangeListener(mDisplayImeChangeListener);
171 
172         mController.onImeVisibilityChanged(true);
173         mExecutor.flushAll();
174         assertTrue(mDisplayImeChangeListener.visibilityChanged == 0);
175     }
176 
177     @Test
testAddUserChangeListener_ensureCallback()178     public void testAddUserChangeListener_ensureCallback() {
179         mController.addUserChangeListener(mUserChangeListener);
180 
181         mController.onUserChanged(TEST_USER_ID, mTestUserContext);
182         assertTrue(mUserChangeListener.userChanged == 1);
183         assertTrue(mUserChangeListener.lastUserContext == mTestUserContext);
184     }
185 
186     @Test
testDoubleAddUserChangeListener_ensureSingleCallback()187     public void testDoubleAddUserChangeListener_ensureSingleCallback() {
188         mController.addUserChangeListener(mUserChangeListener);
189         mController.addUserChangeListener(mUserChangeListener);
190 
191         mController.onUserChanged(TEST_USER_ID, mTestUserContext);
192         assertTrue(mUserChangeListener.userChanged == 1);
193         assertTrue(mUserChangeListener.lastUserContext == mTestUserContext);
194     }
195 
196     @Test
testAddRemoveUserChangeListener_ensureNoCallback()197     public void testAddRemoveUserChangeListener_ensureNoCallback() {
198         mController.addUserChangeListener(mUserChangeListener);
199         mController.removeUserChangeListener(mUserChangeListener);
200 
201         mController.onUserChanged(TEST_USER_ID, mTestUserContext);
202         assertTrue(mUserChangeListener.userChanged == 0);
203         assertTrue(mUserChangeListener.lastUserContext == null);
204     }
205 
206     @Test
testUserProfilesChanged()207     public void testUserProfilesChanged() {
208         mController.addUserChangeListener(mUserChangeListener);
209 
210         ArrayList<UserInfo> profiles = new ArrayList<>();
211         profiles.add(mock(UserInfo.class));
212         profiles.add(mock(UserInfo.class));
213         mController.onUserProfilesChanged(profiles);
214         assertTrue(mUserChangeListener.lastUserProfiles.equals(profiles));
215     }
216 
217     @Test
testAddKeyguardChangeListener_ensureCallback()218     public void testAddKeyguardChangeListener_ensureCallback() {
219         mController.addKeyguardChangeListener(mKeyguardChangeListener);
220 
221         mController.onKeyguardVisibilityChanged(true, false, false);
222         assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
223         assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
224     }
225 
226     @Test
testDoubleAddKeyguardChangeListener_ensureSingleCallback()227     public void testDoubleAddKeyguardChangeListener_ensureSingleCallback() {
228         mController.addKeyguardChangeListener(mKeyguardChangeListener);
229         mController.addKeyguardChangeListener(mKeyguardChangeListener);
230 
231         mController.onKeyguardVisibilityChanged(true, false, false);
232         assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
233         assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
234     }
235 
236     @Test
testAddRemoveKeyguardChangeListener_ensureNoCallback()237     public void testAddRemoveKeyguardChangeListener_ensureNoCallback() {
238         mController.addKeyguardChangeListener(mKeyguardChangeListener);
239         mController.removeKeyguardChangeListener(mKeyguardChangeListener);
240 
241         mController.onKeyguardVisibilityChanged(true, false, false);
242         assertTrue(mKeyguardChangeListener.visibilityChanged == 0);
243         assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
244     }
245 
246     @Test
testKeyguardVisibilityChanged()247     public void testKeyguardVisibilityChanged() {
248         mController.addKeyguardChangeListener(mKeyguardChangeListener);
249 
250         mController.onKeyguardVisibilityChanged(true, true, true);
251         assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
252         assertTrue(mKeyguardChangeListener.lastAnimatingDismiss);
253         assertTrue(mKeyguardChangeListener.lastOccluded);
254         assertTrue(mKeyguardChangeListener.lastAnimatingDismiss);
255         assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
256     }
257 
258     @Test
testKeyguardDismissAnimationFinished()259     public void testKeyguardDismissAnimationFinished() {
260         mController.addKeyguardChangeListener(mKeyguardChangeListener);
261 
262         mController.onKeyguardDismissAnimationFinished();
263         assertTrue(mKeyguardChangeListener.visibilityChanged == 0);
264         assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 1);
265     }
266 
267     @Test
testAddConfigurationChangeListener_ensureCallback()268     public void testAddConfigurationChangeListener_ensureCallback() {
269         mController.addConfigurationChangeListener(mConfigChangeListener);
270 
271         Configuration newConfig = getConfigurationCopy();
272         newConfig.densityDpi = 200;
273         mController.onConfigurationChanged(newConfig);
274         assertTrue(mConfigChangeListener.configChanges == 1);
275     }
276 
277     @Test
testDoubleAddConfigurationChangeListener_ensureSingleCallback()278     public void testDoubleAddConfigurationChangeListener_ensureSingleCallback() {
279         mController.addConfigurationChangeListener(mConfigChangeListener);
280         mController.addConfigurationChangeListener(mConfigChangeListener);
281 
282         Configuration newConfig = getConfigurationCopy();
283         newConfig.densityDpi = 200;
284         mController.onConfigurationChanged(newConfig);
285         assertTrue(mConfigChangeListener.configChanges == 1);
286     }
287 
288     @Test
testAddRemoveConfigurationChangeListener_ensureNoCallback()289     public void testAddRemoveConfigurationChangeListener_ensureNoCallback() {
290         mController.addConfigurationChangeListener(mConfigChangeListener);
291         mController.removeConfigurationChangeListener(mConfigChangeListener);
292 
293         Configuration newConfig = getConfigurationCopy();
294         newConfig.densityDpi = 200;
295         mController.onConfigurationChanged(newConfig);
296         assertTrue(mConfigChangeListener.configChanges == 0);
297     }
298 
299     @Test
testMultipleConfigurationChangeListeners()300     public void testMultipleConfigurationChangeListeners() {
301         TestConfigurationChangeListener listener2 = new TestConfigurationChangeListener();
302         mController.addConfigurationChangeListener(mConfigChangeListener);
303         mController.addConfigurationChangeListener(listener2);
304 
305         Configuration newConfig = getConfigurationCopy();
306         newConfig.densityDpi = 200;
307         mController.onConfigurationChanged(newConfig);
308         assertTrue(mConfigChangeListener.configChanges == 1);
309         assertTrue(listener2.configChanges == 1);
310     }
311 
312     @Test
testRemoveListenerDuringCallback()313     public void testRemoveListenerDuringCallback() {
314         TestConfigurationChangeListener badListener = new TestConfigurationChangeListener() {
315             @Override
316             public void onConfigurationChanged(Configuration newConfiguration) {
317                 mController.removeConfigurationChangeListener(this);
318             }
319         };
320         mController.addConfigurationChangeListener(badListener);
321         mController.addConfigurationChangeListener(mConfigChangeListener);
322 
323         // Ensure we don't fail just because a listener was removed mid-callback
324         Configuration newConfig = getConfigurationCopy();
325         newConfig.densityDpi = 200;
326         mController.onConfigurationChanged(newConfig);
327     }
328 
329     @Test
testDensityChangeCallback()330     public void testDensityChangeCallback() {
331         mController.addConfigurationChangeListener(mConfigChangeListener);
332 
333         Configuration newConfig = getConfigurationCopy();
334         newConfig.densityDpi = 200;
335         mController.onConfigurationChanged(newConfig);
336         assertTrue(mConfigChangeListener.configChanges == 1);
337         assertTrue(mConfigChangeListener.densityChanges == 1);
338         assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
339         assertTrue(mConfigChangeListener.themeChanges == 0);
340         assertTrue(mConfigChangeListener.localeChanges == 0);
341     }
342 
343     @Test
testFontScaleChangeCallback()344     public void testFontScaleChangeCallback() {
345         mController.addConfigurationChangeListener(mConfigChangeListener);
346 
347         Configuration newConfig = getConfigurationCopy();
348         newConfig.fontScale = 2;
349         mController.onConfigurationChanged(newConfig);
350         assertTrue(mConfigChangeListener.configChanges == 1);
351         assertTrue(mConfigChangeListener.densityChanges == 1);
352         assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
353         assertTrue(mConfigChangeListener.themeChanges == 0);
354         assertTrue(mConfigChangeListener.localeChanges == 0);
355     }
356 
357     @Test
testSmallestWidthChangeCallback()358     public void testSmallestWidthChangeCallback() {
359         mController.addConfigurationChangeListener(mConfigChangeListener);
360 
361         Configuration newConfig = getConfigurationCopy();
362         newConfig.smallestScreenWidthDp = 100;
363         mController.onConfigurationChanged(newConfig);
364         assertTrue(mConfigChangeListener.configChanges == 1);
365         assertTrue(mConfigChangeListener.densityChanges == 0);
366         assertTrue(mConfigChangeListener.smallestWidthChanges == 1);
367         assertTrue(mConfigChangeListener.themeChanges == 0);
368         assertTrue(mConfigChangeListener.localeChanges == 0);
369     }
370 
371     @Test
testThemeChangeCallback()372     public void testThemeChangeCallback() {
373         mController.addConfigurationChangeListener(mConfigChangeListener);
374 
375         Configuration newConfig = getConfigurationCopy();
376         newConfig.assetsSeq++;
377         mController.onConfigurationChanged(newConfig);
378         assertTrue(mConfigChangeListener.configChanges == 1);
379         assertTrue(mConfigChangeListener.densityChanges == 0);
380         assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
381         assertTrue(mConfigChangeListener.themeChanges == 1);
382         assertTrue(mConfigChangeListener.localeChanges == 0);
383     }
384 
385     @Test
testNightModeChangeCallback()386     public void testNightModeChangeCallback() {
387         mController.addConfigurationChangeListener(mConfigChangeListener);
388 
389         Configuration newConfig = getConfigurationCopy();
390         newConfig.uiMode = Configuration.UI_MODE_NIGHT_YES;
391         mController.onConfigurationChanged(newConfig);
392         assertTrue(mConfigChangeListener.configChanges == 1);
393         assertTrue(mConfigChangeListener.densityChanges == 0);
394         assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
395         assertTrue(mConfigChangeListener.themeChanges == 1);
396         assertTrue(mConfigChangeListener.localeChanges == 0);
397     }
398 
399     @Test
testLocaleChangeCallback()400     public void testLocaleChangeCallback() {
401         mController.addConfigurationChangeListener(mConfigChangeListener);
402 
403         Configuration newConfig = getConfigurationCopy();
404         // Just change the locales to be different
405         if (newConfig.locale == Locale.CANADA) {
406             newConfig.locale = Locale.US;
407         } else {
408             newConfig.locale = Locale.CANADA;
409         }
410         mController.onConfigurationChanged(newConfig);
411         assertTrue(mConfigChangeListener.configChanges == 1);
412         assertTrue(mConfigChangeListener.densityChanges == 0);
413         assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
414         assertTrue(mConfigChangeListener.themeChanges == 0);
415         assertTrue(mConfigChangeListener.localeChanges == 1);
416     }
417 
getConfigurationCopy()418     private Configuration getConfigurationCopy() {
419         final Configuration c = new Configuration(InstrumentationRegistry.getInstrumentation()
420                 .getTargetContext().getResources().getConfiguration());
421         // In tests this might be undefined so make sure it's valid
422         c.assetsSeq = 1;
423         return c;
424     }
425 
426     private class TestConfigurationChangeListener implements ConfigurationChangeListener {
427         // Counts of number of times each of the callbacks are called
428         public int configChanges;
429         public int densityChanges;
430         public int smallestWidthChanges;
431         public int themeChanges;
432         public int localeChanges;
433 
434         @Override
onConfigurationChanged(Configuration newConfiguration)435         public void onConfigurationChanged(Configuration newConfiguration) {
436             configChanges++;
437         }
438 
439         @Override
onDensityOrFontScaleChanged()440         public void onDensityOrFontScaleChanged() {
441             densityChanges++;
442         }
443 
444         @Override
onSmallestScreenWidthChanged()445         public void onSmallestScreenWidthChanged() {
446             smallestWidthChanges++;
447         }
448 
449         @Override
onThemeChanged()450         public void onThemeChanged() {
451             themeChanges++;
452         }
453 
454         @Override
onLocaleOrLayoutDirectionChanged()455         public void onLocaleOrLayoutDirectionChanged() {
456             localeChanges++;
457         }
458     }
459 
460     private class TestKeyguardChangeListener implements KeyguardChangeListener {
461         // Counts of number of times each of the callbacks are called
462         public int visibilityChanged;
463         public boolean lastVisibility;
464         public boolean lastOccluded;
465         public boolean lastAnimatingDismiss;
466         public int dismissAnimationFinished;
467 
468         @Override
onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss)469         public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
470                 boolean animatingDismiss) {
471             lastVisibility = visible;
472             lastOccluded = occluded;
473             lastAnimatingDismiss = animatingDismiss;
474             visibilityChanged++;
475         }
476 
477         @Override
onKeyguardDismissAnimationFinished()478         public void onKeyguardDismissAnimationFinished() {
479             dismissAnimationFinished++;
480         }
481     }
482 
483     private class TestUserChangeListener implements UserChangeListener {
484         // Counts of number of times each of the callbacks are called
485         public int userChanged;
486         public int lastUserId;
487         public Context lastUserContext;
488         public int userProfilesChanged;
489         public List<? extends UserInfo> lastUserProfiles;
490 
491 
492         @Override
onUserChanged(int newUserId, @NonNull Context userContext)493         public void onUserChanged(int newUserId, @NonNull Context userContext) {
494             userChanged++;
495             lastUserId = newUserId;
496             lastUserContext = userContext;
497         }
498 
499         @Override
onUserProfilesChanged(@onNull List<UserInfo> profiles)500         public void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {
501             userProfilesChanged++;
502             lastUserProfiles = profiles;
503         }
504     }
505 
506     private static class TestDisplayImeChangeListener implements DisplayImeChangeListener {
507         public int boundsChanged = 0;
508         public Rect lastBounds;
509         public int visibilityChanged = 0;
510         public boolean lastVisibility = false;
511 
512         @Override
onImeBoundsChanged(int displayId, Rect bounds)513         public void onImeBoundsChanged(int displayId, Rect bounds) {
514             boundsChanged++;
515             lastBounds = bounds;
516         }
517 
518         @Override
onImeVisibilityChanged(int displayId, boolean isShowing)519         public void onImeVisibilityChanged(int displayId, boolean isShowing) {
520             visibilityChanged++;
521             lastVisibility = isShowing;
522         }
523     }
524 }
525