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