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.locales;
18 
19 import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static junit.framework.Assert.assertEquals;
24 import static junit.framework.Assert.assertNull;
25 import static junit.framework.Assert.fail;
26 
27 import static org.mockito.ArgumentMatchers.any;
28 import static org.mockito.ArgumentMatchers.anyBoolean;
29 import static org.mockito.ArgumentMatchers.anyInt;
30 import static org.mockito.ArgumentMatchers.anyString;
31 import static org.mockito.ArgumentMatchers.eq;
32 import static org.mockito.Mockito.doNothing;
33 import static org.mockito.Mockito.doReturn;
34 import static org.mockito.Mockito.doThrow;
35 import static org.mockito.Mockito.mock;
36 import static org.mockito.Mockito.never;
37 import static org.mockito.Mockito.times;
38 import static org.mockito.Mockito.verify;
39 
40 import android.Manifest;
41 import android.app.ActivityManagerInternal;
42 import android.content.ComponentName;
43 import android.content.Context;
44 import android.content.pm.InstallSourceInfo;
45 import android.content.pm.PackageInstaller;
46 import android.content.pm.PackageManager;
47 import android.os.Binder;
48 import android.os.LocaleList;
49 import android.provider.Settings;
50 
51 import androidx.test.InstrumentationRegistry;
52 import androidx.test.ext.junit.runners.AndroidJUnit4;
53 
54 import com.android.internal.content.PackageMonitor;
55 import com.android.internal.util.FrameworkStatsLog;
56 import com.android.server.wm.ActivityTaskManagerInternal;
57 import com.android.server.wm.ActivityTaskManagerInternal.PackageConfig;
58 
59 import org.junit.Before;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 import org.mockito.Mock;
63 
64 /**
65  * Unit tests for the {@link LocaleManagerService}.
66  */
67 @RunWith(AndroidJUnit4.class)
68 public class LocaleManagerServiceTest {
69     private static final String DEFAULT_PACKAGE_NAME = "com.android.myapp";
70     private static final String DEFAULT_INSTALLER_PACKAGE_NAME = "com.android.myapp.installer";
71     private static final int DEFAULT_USER_ID = 0;
72     private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
73     private static final int INVALID_UID = -1;
74     private static final String DEFAULT_LOCALE_TAGS = "en-XC,ar-XB";
75     private static final LocaleList DEFAULT_LOCALES =
76             LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
77     private static final InstallSourceInfo DEFAULT_INSTALL_SOURCE_INFO = new InstallSourceInfo(
78             /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
79             /* originatingPackageName = */ null,
80             /* installingPackageName = */ DEFAULT_INSTALLER_PACKAGE_NAME,
81             /* updateOwnerPackageName = */ null,
82             /* packageSource = */ PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
83 
84     private LocaleManagerService mLocaleManagerService;
85     private LocaleManagerBackupHelper mMockBackupHelper;
86 
87     @Mock
88     private Context mMockContext;
89     @Mock
90     private PackageManager mMockPackageManager;
91     @Mock
92     private FakePackageConfigurationUpdater mFakePackageConfigurationUpdater;
93     @Mock
94     private ActivityTaskManagerInternal mMockActivityTaskManager;
95     @Mock
96     private ActivityManagerInternal mMockActivityManager;
97     @Mock
98     PackageMonitor mMockPackageMonitor;
99 
100     @Before
setUp()101     public void setUp() throws Exception {
102         mMockContext = mock(Context.class);
103         mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class);
104         mMockActivityManager = mock(ActivityManagerInternal.class);
105         mMockPackageManager = mock(PackageManager.class);
106         mMockPackageMonitor = mock(PackageMonitor.class);
107 
108         doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
109         // For unit tests, set the default installer info
110         doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
111                 .getInstallSourceInfo(anyString());
112         doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
113 
114         mFakePackageConfigurationUpdater = new FakePackageConfigurationUpdater();
115         doReturn(mFakePackageConfigurationUpdater)
116                 .when(mMockActivityTaskManager)
117                 .createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
118         doReturn(mFakePackageConfigurationUpdater)
119                 .when(mMockActivityTaskManager).createPackageConfigurationUpdater();
120 
121         doReturn(DEFAULT_USER_ID).when(mMockActivityManager)
122                 .handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(),
123                         anyString(), anyString());
124         doReturn(InstrumentationRegistry.getContext().getContentResolver())
125                 .when(mMockContext).getContentResolver();
126 
127         mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
128         mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager,
129                 mMockActivityManager, mMockPackageManager,
130                 mMockBackupHelper, mMockPackageMonitor);
131     }
132 
133     @Test(expected = SecurityException.class)
testSetApplicationLocales_arbitraryAppWithoutPermissions_fails()134     public void testSetApplicationLocales_arbitraryAppWithoutPermissions_fails() throws Exception {
135         doReturn(DEFAULT_UID)
136                 .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
137         setUpFailingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
138 
139         try {
140             mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
141                     LocaleList.getEmptyLocaleList(), false, FrameworkStatsLog
142                             .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
143             fail("Expected SecurityException");
144         } finally {
145             verify(mMockContext).enforceCallingOrSelfPermission(
146                     eq(android.Manifest.permission.CHANGE_CONFIGURATION),
147                     anyString());
148             verify(mMockBackupHelper, times(0)).notifyBackupManager();
149             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
150         }
151     }
152 
153     @Test(expected = NullPointerException.class)
testSetApplicationLocales_nullPackageName_fails()154     public void testSetApplicationLocales_nullPackageName_fails() throws Exception {
155         try {
156             mLocaleManagerService.setApplicationLocales(/* appPackageName = */ null,
157                     DEFAULT_USER_ID, LocaleList.getEmptyLocaleList(), false,
158                     FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
159             fail("Expected NullPointerException");
160         } finally {
161             verify(mMockBackupHelper, times(0)).notifyBackupManager();
162             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
163         }
164     }
165 
166     @Test(expected = NullPointerException.class)
testSetApplicationLocales_nullLocaleList_fails()167     public void testSetApplicationLocales_nullLocaleList_fails() throws Exception {
168         setUpPassingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
169 
170         try {
171             mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
172                     /* locales = */ null, false, FrameworkStatsLog
173                             .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
174             fail("Expected NullPointerException");
175         } finally {
176             verify(mMockBackupHelper, times(0)).notifyBackupManager();
177             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
178         }
179     }
180 
181 
182     @Test
testSetApplicationLocales_arbitraryAppWithPermission_succeeds()183     public void testSetApplicationLocales_arbitraryAppWithPermission_succeeds() throws Exception {
184         doReturn(DEFAULT_UID)
185                 .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
186         // if package is not owned by the caller, the calling app should have the following
187         //   permission. We will mock this to succeed to imitate that.
188         setUpPassingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
189 
190         mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
191                 DEFAULT_LOCALES, true, FrameworkStatsLog
192                         .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_DELEGATE);
193 
194         assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
195         verify(mMockBackupHelper, times(1)).notifyBackupManager();
196 
197     }
198 
199     @Test
testSetApplicationLocales_callerOwnsPackage_succeeds()200     public void testSetApplicationLocales_callerOwnsPackage_succeeds() throws Exception {
201         doReturn(Binder.getCallingUid())
202                 .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
203 
204         mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
205                 DEFAULT_LOCALES, false, FrameworkStatsLog
206                         .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
207 
208         assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
209         verify(mMockBackupHelper, times(1)).notifyBackupManager();
210     }
211 
212     @Test(expected = IllegalArgumentException.class)
testSetApplicationLocales_invalidPackageOrUserId_fails()213     public void testSetApplicationLocales_invalidPackageOrUserId_fails() throws Exception {
214         doThrow(new PackageManager.NameNotFoundException("Mock"))
215                 .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
216         try {
217             mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
218                     LocaleList.getEmptyLocaleList(), false, FrameworkStatsLog
219                             .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
220             fail("Expected IllegalArgumentException");
221         } finally {
222             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
223             verify(mMockBackupHelper, times(0)).notifyBackupManager();
224         }
225     }
226 
227     @Test(expected = SecurityException.class)
testGetApplicationLocales_arbitraryAppWithoutPermission_fails()228     public void testGetApplicationLocales_arbitraryAppWithoutPermission_fails() throws Exception {
229         doReturn(DEFAULT_UID).when(mMockPackageManager)
230                 .getPackageUidAsUser(anyString(), any(), anyInt());
231         setUpFailingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
232 
233         try {
234             mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
235             fail("Expected SecurityException");
236         } finally {
237             verify(mMockContext).enforceCallingOrSelfPermission(
238                     eq(android.Manifest.permission.READ_APP_SPECIFIC_LOCALES),
239                     anyString());
240         }
241     }
242 
243     @Test(expected = SecurityException.class)
testGetApplicationLocales_currentImeQueryNonForegroundAppLocales_fails()244     public void testGetApplicationLocales_currentImeQueryNonForegroundAppLocales_fails()
245             throws Exception {
246         doReturn(DEFAULT_UID).when(mMockPackageManager)
247                 .getPackageUidAsUser(anyString(), any(), anyInt());
248         doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
249                 GRAMMATICAL_GENDER_NOT_SPECIFIED))
250                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
251         String imPkgName = getCurrentInputMethodPackageName();
252         doReturn(Binder.getCallingUid()).when(mMockPackageManager)
253                 .getPackageUidAsUser(eq(imPkgName), any(), anyInt());
254         doReturn(false).when(mMockActivityManager).isAppForeground(anyInt());
255         setUpFailingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
256 
257         try {
258             mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
259             fail("Expected SecurityException");
260         } finally {
261             verify(mMockContext).enforceCallingOrSelfPermission(
262                     eq(android.Manifest.permission.READ_APP_SPECIFIC_LOCALES),
263                     anyString());
264         }
265     }
266 
267     @Test
testGetApplicationLocales_appSpecificConfigAbsent_returnsEmptyList()268     public void testGetApplicationLocales_appSpecificConfigAbsent_returnsEmptyList()
269             throws Exception {
270         // any valid app calling for its own package or having appropriate permission
271         doReturn(DEFAULT_UID).when(mMockPackageManager)
272                 .getPackageUidAsUser(anyString(), any(), anyInt());
273         setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
274         doReturn(null)
275                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
276 
277         LocaleList locales = mLocaleManagerService.getApplicationLocales(
278                 DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
279 
280         assertEquals(LocaleList.getEmptyLocaleList(), locales);
281     }
282 
283     @Test
testGetApplicationLocales_appSpecificLocalesAbsent_returnsEmptyList()284     public void testGetApplicationLocales_appSpecificLocalesAbsent_returnsEmptyList()
285             throws Exception {
286         doReturn(DEFAULT_UID).when(mMockPackageManager)
287                 .getPackageUidAsUser(anyString(), any(), anyInt());
288         setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
289         doReturn(new PackageConfig(/* nightMode = */ 0, /* locales = */ null,
290                 GRAMMATICAL_GENDER_NOT_SPECIFIED))
291                 .when(mMockActivityTaskManager).getApplicationConfig(any(), anyInt());
292 
293         LocaleList locales = mLocaleManagerService.getApplicationLocales(
294                 DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
295 
296         assertEquals(LocaleList.getEmptyLocaleList(), locales);
297     }
298 
299     @Test
testGetApplicationLocales_callerOwnsAppAndConfigPresent_returnsLocales()300     public void testGetApplicationLocales_callerOwnsAppAndConfigPresent_returnsLocales()
301             throws Exception {
302         doReturn(Binder.getCallingUid()).when(mMockPackageManager)
303                 .getPackageUidAsUser(anyString(), any(), anyInt());
304         doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
305                 GRAMMATICAL_GENDER_NOT_SPECIFIED))
306                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
307 
308         LocaleList locales =
309                 mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
310 
311         assertEquals(DEFAULT_LOCALES, locales);
312     }
313 
314     @Test
testGetApplicationLocales_arbitraryCallerWithPermissions_returnsLocales()315     public void testGetApplicationLocales_arbitraryCallerWithPermissions_returnsLocales()
316             throws Exception {
317         doReturn(DEFAULT_UID).when(mMockPackageManager)
318                 .getPackageUidAsUser(anyString(), any(), anyInt());
319         setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
320         doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
321                 GRAMMATICAL_GENDER_NOT_SPECIFIED))
322                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
323 
324         LocaleList locales =
325                 mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
326 
327         assertEquals(DEFAULT_LOCALES, locales);
328     }
329 
330     @Test
testGetApplicationLocales_callerIsInstaller_returnsLocales()331     public void testGetApplicationLocales_callerIsInstaller_returnsLocales()
332             throws Exception {
333         doReturn(DEFAULT_UID).when(mMockPackageManager)
334                 .getPackageUidAsUser(eq(DEFAULT_PACKAGE_NAME), any(), anyInt());
335         doReturn(Binder.getCallingUid()).when(mMockPackageManager)
336                 .getPackageUidAsUser(eq(DEFAULT_INSTALLER_PACKAGE_NAME), any(), anyInt());
337         doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
338                 GRAMMATICAL_GENDER_NOT_SPECIFIED))
339                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
340 
341         LocaleList locales =
342                 mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
343 
344         verify(mMockContext, never()).enforceCallingOrSelfPermission(any(), any());
345         assertEquals(DEFAULT_LOCALES, locales);
346     }
347 
348     @Test
testGetApplicationLocales_currentImeQueryForegroundAppLocales_returnsLocales()349     public void testGetApplicationLocales_currentImeQueryForegroundAppLocales_returnsLocales()
350             throws Exception {
351         doReturn(DEFAULT_UID).when(mMockPackageManager)
352                 .getPackageUidAsUser(anyString(), any(), anyInt());
353         doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
354                 GRAMMATICAL_GENDER_NOT_SPECIFIED))
355                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
356         String imPkgName = getCurrentInputMethodPackageName();
357         doReturn(Binder.getCallingUid()).when(mMockPackageManager)
358                 .getPackageUidAsUser(eq(imPkgName), any(), anyInt());
359         doReturn(true).when(mMockActivityManager).isAppForeground(anyInt());
360 
361         LocaleList locales =
362                 mLocaleManagerService.getApplicationLocales(
363                         DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
364 
365         verify(mMockContext, never()).enforceCallingOrSelfPermission(any(), any());
366         assertEquals(DEFAULT_LOCALES, locales);
367     }
368 
assertNoLocalesStored(LocaleList locales)369     private static void assertNoLocalesStored(LocaleList locales) {
370         assertNull(locales);
371     }
372 
setUpFailingPermissionCheckFor(String permission)373     private void setUpFailingPermissionCheckFor(String permission) {
374         doThrow(new SecurityException("Mock"))
375                 .when(mMockContext).enforceCallingOrSelfPermission(eq(permission), any());
376     }
377 
setUpPassingPermissionCheckFor(String permission)378     private void setUpPassingPermissionCheckFor(String permission) {
379         doNothing().when(mMockContext).enforceCallingOrSelfPermission(eq(permission), any());
380     }
381 
getCurrentInputMethodPackageName()382     private String getCurrentInputMethodPackageName() {
383         String im = Settings.Secure.getString(
384                 InstrumentationRegistry.getContext().getContentResolver(),
385                 Settings.Secure.DEFAULT_INPUT_METHOD);
386         ComponentName cn = ComponentName.unflattenFromString(im);
387         assertThat(cn).isNotNull();
388         return cn.getPackageName();
389     }
390 }
391