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.os.Binder;
27 import android.os.Bundle;
28 import android.os.IBinder;
29 import android.testing.AndroidTestingRunner;
30 import android.testing.TestableLooper;
31 
32 import androidx.annotation.NonNull;
33 import androidx.test.filters.SmallTest;
34 import androidx.test.platform.app.InstrumentationRegistry;
35 
36 import com.android.wm.shell.ShellTestCase;
37 import com.android.wm.shell.TestShellExecutor;
38 import com.android.wm.shell.common.ExternalInterfaceBinder;
39 import com.android.wm.shell.common.ShellExecutor;
40 
41 import org.junit.After;
42 import org.junit.Before;
43 import org.junit.Test;
44 import org.junit.runner.RunWith;
45 import org.mockito.Mock;
46 import org.mockito.MockitoAnnotations;
47 
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.Locale;
51 
52 @SmallTest
53 @RunWith(AndroidTestingRunner.class)
54 @TestableLooper.RunWithLooper(setAsMainLooper = true)
55 public class ShellControllerTest extends ShellTestCase {
56 
57     private static final int TEST_USER_ID = 100;
58     private static final String EXTRA_TEST_BINDER = "test_binder";
59 
60     @Mock
61     private ShellInit mShellInit;
62     @Mock
63     private ShellCommandHandler mShellCommandHandler;
64     @Mock
65     private Context mTestUserContext;
66 
67     private TestShellExecutor mExecutor;
68     private ShellController mController;
69     private TestConfigurationChangeListener mConfigChangeListener;
70     private TestKeyguardChangeListener mKeyguardChangeListener;
71     private TestUserChangeListener mUserChangeListener;
72 
73 
74     @Before
setUp()75     public void setUp() {
76         MockitoAnnotations.initMocks(this);
77         mKeyguardChangeListener = new TestKeyguardChangeListener();
78         mConfigChangeListener = new TestConfigurationChangeListener();
79         mUserChangeListener = new TestUserChangeListener();
80         mExecutor = new TestShellExecutor();
81         mController = new ShellController(mContext, mShellInit, mShellCommandHandler, mExecutor);
82         mController.onConfigurationChanged(getConfigurationCopy());
83     }
84 
85     @After
tearDown()86     public void tearDown() {
87         // Do nothing
88     }
89 
90     @Test
testAddExternalInterface_ensureCallback()91     public void testAddExternalInterface_ensureCallback() {
92         Binder callback = new Binder();
93         ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
94             @Override
95             public void invalidate() {
96                 // Do nothing
97             }
98 
99             @Override
100             public IBinder asBinder() {
101                 return callback;
102             }
103         };
104         mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
105 
106         Bundle b = new Bundle();
107         mController.asShell().createExternalInterfaces(b);
108         mExecutor.flushAll();
109         assertTrue(b.getIBinder(EXTRA_TEST_BINDER) == callback);
110     }
111 
112     @Test
testAddExternalInterface_disallowDuplicateKeys()113     public void testAddExternalInterface_disallowDuplicateKeys() {
114         Binder callback = new Binder();
115         ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
116             @Override
117             public void invalidate() {
118                 // Do nothing
119             }
120 
121             @Override
122             public IBinder asBinder() {
123                 return callback;
124             }
125         };
126         mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
127         assertThrows(IllegalArgumentException.class, () -> {
128             mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
129         });
130     }
131 
132     @Test
testAddUserChangeListener_ensureCallback()133     public void testAddUserChangeListener_ensureCallback() {
134         mController.addUserChangeListener(mUserChangeListener);
135 
136         mController.onUserChanged(TEST_USER_ID, mTestUserContext);
137         assertTrue(mUserChangeListener.userChanged == 1);
138         assertTrue(mUserChangeListener.lastUserContext == mTestUserContext);
139     }
140 
141     @Test
testDoubleAddUserChangeListener_ensureSingleCallback()142     public void testDoubleAddUserChangeListener_ensureSingleCallback() {
143         mController.addUserChangeListener(mUserChangeListener);
144         mController.addUserChangeListener(mUserChangeListener);
145 
146         mController.onUserChanged(TEST_USER_ID, mTestUserContext);
147         assertTrue(mUserChangeListener.userChanged == 1);
148         assertTrue(mUserChangeListener.lastUserContext == mTestUserContext);
149     }
150 
151     @Test
testAddRemoveUserChangeListener_ensureNoCallback()152     public void testAddRemoveUserChangeListener_ensureNoCallback() {
153         mController.addUserChangeListener(mUserChangeListener);
154         mController.removeUserChangeListener(mUserChangeListener);
155 
156         mController.onUserChanged(TEST_USER_ID, mTestUserContext);
157         assertTrue(mUserChangeListener.userChanged == 0);
158         assertTrue(mUserChangeListener.lastUserContext == null);
159     }
160 
161     @Test
testUserProfilesChanged()162     public void testUserProfilesChanged() {
163         mController.addUserChangeListener(mUserChangeListener);
164 
165         ArrayList<UserInfo> profiles = new ArrayList<>();
166         profiles.add(mock(UserInfo.class));
167         profiles.add(mock(UserInfo.class));
168         mController.onUserProfilesChanged(profiles);
169         assertTrue(mUserChangeListener.lastUserProfiles.equals(profiles));
170     }
171 
172     @Test
testAddKeyguardChangeListener_ensureCallback()173     public void testAddKeyguardChangeListener_ensureCallback() {
174         mController.addKeyguardChangeListener(mKeyguardChangeListener);
175 
176         mController.onKeyguardVisibilityChanged(true, false, false);
177         assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
178         assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
179     }
180 
181     @Test
testDoubleAddKeyguardChangeListener_ensureSingleCallback()182     public void testDoubleAddKeyguardChangeListener_ensureSingleCallback() {
183         mController.addKeyguardChangeListener(mKeyguardChangeListener);
184         mController.addKeyguardChangeListener(mKeyguardChangeListener);
185 
186         mController.onKeyguardVisibilityChanged(true, false, false);
187         assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
188         assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
189     }
190 
191     @Test
testAddRemoveKeyguardChangeListener_ensureNoCallback()192     public void testAddRemoveKeyguardChangeListener_ensureNoCallback() {
193         mController.addKeyguardChangeListener(mKeyguardChangeListener);
194         mController.removeKeyguardChangeListener(mKeyguardChangeListener);
195 
196         mController.onKeyguardVisibilityChanged(true, false, false);
197         assertTrue(mKeyguardChangeListener.visibilityChanged == 0);
198         assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
199     }
200 
201     @Test
testKeyguardVisibilityChanged()202     public void testKeyguardVisibilityChanged() {
203         mController.addKeyguardChangeListener(mKeyguardChangeListener);
204 
205         mController.onKeyguardVisibilityChanged(true, true, true);
206         assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
207         assertTrue(mKeyguardChangeListener.lastAnimatingDismiss);
208         assertTrue(mKeyguardChangeListener.lastOccluded);
209         assertTrue(mKeyguardChangeListener.lastAnimatingDismiss);
210         assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
211     }
212 
213     @Test
testKeyguardDismissAnimationFinished()214     public void testKeyguardDismissAnimationFinished() {
215         mController.addKeyguardChangeListener(mKeyguardChangeListener);
216 
217         mController.onKeyguardDismissAnimationFinished();
218         assertTrue(mKeyguardChangeListener.visibilityChanged == 0);
219         assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 1);
220     }
221 
222     @Test
testAddConfigurationChangeListener_ensureCallback()223     public void testAddConfigurationChangeListener_ensureCallback() {
224         mController.addConfigurationChangeListener(mConfigChangeListener);
225 
226         Configuration newConfig = getConfigurationCopy();
227         newConfig.densityDpi = 200;
228         mController.onConfigurationChanged(newConfig);
229         assertTrue(mConfigChangeListener.configChanges == 1);
230     }
231 
232     @Test
testDoubleAddConfigurationChangeListener_ensureSingleCallback()233     public void testDoubleAddConfigurationChangeListener_ensureSingleCallback() {
234         mController.addConfigurationChangeListener(mConfigChangeListener);
235         mController.addConfigurationChangeListener(mConfigChangeListener);
236 
237         Configuration newConfig = getConfigurationCopy();
238         newConfig.densityDpi = 200;
239         mController.onConfigurationChanged(newConfig);
240         assertTrue(mConfigChangeListener.configChanges == 1);
241     }
242 
243     @Test
testAddRemoveConfigurationChangeListener_ensureNoCallback()244     public void testAddRemoveConfigurationChangeListener_ensureNoCallback() {
245         mController.addConfigurationChangeListener(mConfigChangeListener);
246         mController.removeConfigurationChangeListener(mConfigChangeListener);
247 
248         Configuration newConfig = getConfigurationCopy();
249         newConfig.densityDpi = 200;
250         mController.onConfigurationChanged(newConfig);
251         assertTrue(mConfigChangeListener.configChanges == 0);
252     }
253 
254     @Test
testMultipleConfigurationChangeListeners()255     public void testMultipleConfigurationChangeListeners() {
256         TestConfigurationChangeListener listener2 = new TestConfigurationChangeListener();
257         mController.addConfigurationChangeListener(mConfigChangeListener);
258         mController.addConfigurationChangeListener(listener2);
259 
260         Configuration newConfig = getConfigurationCopy();
261         newConfig.densityDpi = 200;
262         mController.onConfigurationChanged(newConfig);
263         assertTrue(mConfigChangeListener.configChanges == 1);
264         assertTrue(listener2.configChanges == 1);
265     }
266 
267     @Test
testRemoveListenerDuringCallback()268     public void testRemoveListenerDuringCallback() {
269         TestConfigurationChangeListener badListener = new TestConfigurationChangeListener() {
270             @Override
271             public void onConfigurationChanged(Configuration newConfiguration) {
272                 mController.removeConfigurationChangeListener(this);
273             }
274         };
275         mController.addConfigurationChangeListener(badListener);
276         mController.addConfigurationChangeListener(mConfigChangeListener);
277 
278         // Ensure we don't fail just because a listener was removed mid-callback
279         Configuration newConfig = getConfigurationCopy();
280         newConfig.densityDpi = 200;
281         mController.onConfigurationChanged(newConfig);
282     }
283 
284     @Test
testDensityChangeCallback()285     public void testDensityChangeCallback() {
286         mController.addConfigurationChangeListener(mConfigChangeListener);
287 
288         Configuration newConfig = getConfigurationCopy();
289         newConfig.densityDpi = 200;
290         mController.onConfigurationChanged(newConfig);
291         assertTrue(mConfigChangeListener.configChanges == 1);
292         assertTrue(mConfigChangeListener.densityChanges == 1);
293         assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
294         assertTrue(mConfigChangeListener.themeChanges == 0);
295         assertTrue(mConfigChangeListener.localeChanges == 0);
296     }
297 
298     @Test
testFontScaleChangeCallback()299     public void testFontScaleChangeCallback() {
300         mController.addConfigurationChangeListener(mConfigChangeListener);
301 
302         Configuration newConfig = getConfigurationCopy();
303         newConfig.fontScale = 2;
304         mController.onConfigurationChanged(newConfig);
305         assertTrue(mConfigChangeListener.configChanges == 1);
306         assertTrue(mConfigChangeListener.densityChanges == 1);
307         assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
308         assertTrue(mConfigChangeListener.themeChanges == 0);
309         assertTrue(mConfigChangeListener.localeChanges == 0);
310     }
311 
312     @Test
testSmallestWidthChangeCallback()313     public void testSmallestWidthChangeCallback() {
314         mController.addConfigurationChangeListener(mConfigChangeListener);
315 
316         Configuration newConfig = getConfigurationCopy();
317         newConfig.smallestScreenWidthDp = 100;
318         mController.onConfigurationChanged(newConfig);
319         assertTrue(mConfigChangeListener.configChanges == 1);
320         assertTrue(mConfigChangeListener.densityChanges == 0);
321         assertTrue(mConfigChangeListener.smallestWidthChanges == 1);
322         assertTrue(mConfigChangeListener.themeChanges == 0);
323         assertTrue(mConfigChangeListener.localeChanges == 0);
324     }
325 
326     @Test
testThemeChangeCallback()327     public void testThemeChangeCallback() {
328         mController.addConfigurationChangeListener(mConfigChangeListener);
329 
330         Configuration newConfig = getConfigurationCopy();
331         newConfig.assetsSeq++;
332         mController.onConfigurationChanged(newConfig);
333         assertTrue(mConfigChangeListener.configChanges == 1);
334         assertTrue(mConfigChangeListener.densityChanges == 0);
335         assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
336         assertTrue(mConfigChangeListener.themeChanges == 1);
337         assertTrue(mConfigChangeListener.localeChanges == 0);
338     }
339 
340     @Test
testNightModeChangeCallback()341     public void testNightModeChangeCallback() {
342         mController.addConfigurationChangeListener(mConfigChangeListener);
343 
344         Configuration newConfig = getConfigurationCopy();
345         newConfig.uiMode = Configuration.UI_MODE_NIGHT_YES;
346         mController.onConfigurationChanged(newConfig);
347         assertTrue(mConfigChangeListener.configChanges == 1);
348         assertTrue(mConfigChangeListener.densityChanges == 0);
349         assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
350         assertTrue(mConfigChangeListener.themeChanges == 1);
351         assertTrue(mConfigChangeListener.localeChanges == 0);
352     }
353 
354     @Test
testLocaleChangeCallback()355     public void testLocaleChangeCallback() {
356         mController.addConfigurationChangeListener(mConfigChangeListener);
357 
358         Configuration newConfig = getConfigurationCopy();
359         // Just change the locales to be different
360         if (newConfig.locale == Locale.CANADA) {
361             newConfig.locale = Locale.US;
362         } else {
363             newConfig.locale = Locale.CANADA;
364         }
365         mController.onConfigurationChanged(newConfig);
366         assertTrue(mConfigChangeListener.configChanges == 1);
367         assertTrue(mConfigChangeListener.densityChanges == 0);
368         assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
369         assertTrue(mConfigChangeListener.themeChanges == 0);
370         assertTrue(mConfigChangeListener.localeChanges == 1);
371     }
372 
getConfigurationCopy()373     private Configuration getConfigurationCopy() {
374         final Configuration c = new Configuration(InstrumentationRegistry.getInstrumentation()
375                 .getTargetContext().getResources().getConfiguration());
376         // In tests this might be undefined so make sure it's valid
377         c.assetsSeq = 1;
378         return c;
379     }
380 
381     private class TestConfigurationChangeListener implements ConfigurationChangeListener {
382         // Counts of number of times each of the callbacks are called
383         public int configChanges;
384         public int densityChanges;
385         public int smallestWidthChanges;
386         public int themeChanges;
387         public int localeChanges;
388 
389         @Override
onConfigurationChanged(Configuration newConfiguration)390         public void onConfigurationChanged(Configuration newConfiguration) {
391             configChanges++;
392         }
393 
394         @Override
onDensityOrFontScaleChanged()395         public void onDensityOrFontScaleChanged() {
396             densityChanges++;
397         }
398 
399         @Override
onSmallestScreenWidthChanged()400         public void onSmallestScreenWidthChanged() {
401             smallestWidthChanges++;
402         }
403 
404         @Override
onThemeChanged()405         public void onThemeChanged() {
406             themeChanges++;
407         }
408 
409         @Override
onLocaleOrLayoutDirectionChanged()410         public void onLocaleOrLayoutDirectionChanged() {
411             localeChanges++;
412         }
413     }
414 
415     private class TestKeyguardChangeListener implements KeyguardChangeListener {
416         // Counts of number of times each of the callbacks are called
417         public int visibilityChanged;
418         public boolean lastVisibility;
419         public boolean lastOccluded;
420         public boolean lastAnimatingDismiss;
421         public int dismissAnimationFinished;
422 
423         @Override
onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss)424         public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
425                 boolean animatingDismiss) {
426             lastVisibility = visible;
427             lastOccluded = occluded;
428             lastAnimatingDismiss = animatingDismiss;
429             visibilityChanged++;
430         }
431 
432         @Override
onKeyguardDismissAnimationFinished()433         public void onKeyguardDismissAnimationFinished() {
434             dismissAnimationFinished++;
435         }
436     }
437 
438     private class TestUserChangeListener implements UserChangeListener {
439         // Counts of number of times each of the callbacks are called
440         public int userChanged;
441         public int lastUserId;
442         public Context lastUserContext;
443         public int userProfilesChanged;
444         public List<? extends UserInfo> lastUserProfiles;
445 
446 
447         @Override
onUserChanged(int newUserId, @NonNull Context userContext)448         public void onUserChanged(int newUserId, @NonNull Context userContext) {
449             userChanged++;
450             lastUserId = newUserId;
451             lastUserContext = userContext;
452         }
453 
454         @Override
onUserProfilesChanged(@onNull List<UserInfo> profiles)455         public void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {
456             userProfilesChanged++;
457             lastUserProfiles = profiles;
458         }
459     }
460 }
461