blob: 23143fcff12d83e50f37575541fffa8b508f7a10 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.localemanager.cts;
import static android.localemanager.cts.util.LocaleConstants.CALLING_PACKAGE;
import static android.localemanager.cts.util.LocaleConstants.DEFAULT_APP_LOCALES;
import static android.localemanager.cts.util.LocaleConstants.DEFAULT_SYSTEM_LOCALES;
import static android.localemanager.cts.util.LocaleConstants.EXTRA_QUERY_LOCALES;
import static android.localemanager.cts.util.LocaleConstants.EXTRA_SET_LOCALES;
import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION;
import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_BROADCAST_RECEIVER;
import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_CREATION_INFO_PROVIDER_ACTION;
import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_MAIN_ACTIVITY;
import static android.localemanager.cts.util.LocaleConstants.INSTALLER_PACKAGE;
import static android.localemanager.cts.util.LocaleConstants.TEST_APP_BROADCAST_INFO_PROVIDER_ACTION;
import static android.localemanager.cts.util.LocaleConstants.TEST_APP_BROADCAST_RECEIVER;
import static android.localemanager.cts.util.LocaleConstants.TEST_APP_CONFIG_CHANGED_INFO_PROVIDER_ACTION;
import static android.localemanager.cts.util.LocaleConstants.TEST_APP_CREATION_INFO_PROVIDER_ACTION;
import static android.localemanager.cts.util.LocaleConstants.TEST_APP_MAIN_ACTIVITY;
import static android.localemanager.cts.util.LocaleConstants.TEST_APP_PACKAGE;
import static android.server.wm.CliIntentExtra.extraString;
import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.Manifest;
import android.app.Activity;
import android.app.LocaleManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.LocaleList;
import android.server.wm.ActivityManagerTestBase;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.AmUtils;
import com.android.compatibility.common.util.ShellIdentityUtils;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Tests for {@link android.app.LocaleManager} API(s).
*
* Build/Install/Run: atest CtsLocaleManagerTestCases
*/
@RunWith(AndroidJUnit4.class)
public class LocaleManagerTests extends ActivityManagerTestBase {
private static Context sContext;
private static LocaleManager sLocaleManager;
/* System locales that were set on the device prior to running tests */
private static LocaleList sPreviousSystemLocales;
/* Receiver to listen to the broadcast in the calling (instrumentation) app. */
private BlockingBroadcastReceiver mCallingAppBroadcastReceiver;
/* Receiver to listen to the response from the test app's broadcast receiver. */
private BlockingBroadcastReceiver mTestAppBroadcastInfoProvider;
/* Receiver to listen to the response from the test app's activity. */
private BlockingBroadcastReceiver mTestAppCreationInfoProvider;
/* Receiver to listen to the response from the test app's onConfigChanged method. */
private BlockingBroadcastReceiver mTestAppConfigChangedInfoProvider;
/* Receiver to listen to the response from the installer app. */
private BlockingBroadcastReceiver mInstallerBroadcastInfoProvider;
/* Receiver to listen to the response from the installer app's activity. */
private BlockingBroadcastReceiver mInstallerAppCreationInfoProvider;
@BeforeClass
public static void setUpClass() {
sContext = InstrumentationRegistry.getTargetContext();
sLocaleManager = sContext.getSystemService(LocaleManager.class);
sPreviousSystemLocales = sLocaleManager.getSystemLocales();
runWithShellPermissionIdentity(() ->
sLocaleManager.setSystemLocales(DEFAULT_SYSTEM_LOCALES),
Manifest.permission.CHANGE_CONFIGURATION, Manifest.permission.WRITE_SETTINGS);
}
@AfterClass
public static void tearDownClass() {
runWithShellPermissionIdentity(() ->
sLocaleManager.setSystemLocales(sPreviousSystemLocales),
Manifest.permission.CHANGE_CONFIGURATION, Manifest.permission.WRITE_SETTINGS);
}
@Before
public void setUp() throws Exception {
// Unlocks the device if locked, since we have tests where the app/activity needs
// to be in the foreground/background.
super.setUp();
resetAppLocalesAsEmpty();
AmUtils.waitForBroadcastIdle();
mCallingAppBroadcastReceiver = new BlockingBroadcastReceiver();
mTestAppBroadcastInfoProvider = new BlockingBroadcastReceiver();
mInstallerBroadcastInfoProvider = new BlockingBroadcastReceiver();
mTestAppCreationInfoProvider = new BlockingBroadcastReceiver();
mTestAppConfigChangedInfoProvider = new BlockingBroadcastReceiver();
mInstallerAppCreationInfoProvider = new BlockingBroadcastReceiver();
sContext.registerReceiver(mCallingAppBroadcastReceiver,
new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
sContext.registerReceiver(mTestAppBroadcastInfoProvider,
new IntentFilter(TEST_APP_BROADCAST_INFO_PROVIDER_ACTION));
sContext.registerReceiver(mInstallerBroadcastInfoProvider,
new IntentFilter(INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION));
sContext.registerReceiver(mTestAppCreationInfoProvider,
new IntentFilter(TEST_APP_CREATION_INFO_PROVIDER_ACTION));
sContext.registerReceiver(mTestAppConfigChangedInfoProvider,
new IntentFilter(TEST_APP_CONFIG_CHANGED_INFO_PROVIDER_ACTION));
sContext.registerReceiver(mInstallerAppCreationInfoProvider,
new IntentFilter(INSTALLER_APP_CREATION_INFO_PROVIDER_ACTION));
setInstallerForPackage(CALLING_PACKAGE);
setInstallerForPackage(TEST_APP_PACKAGE);
bindToBroadcastReceiverOfApp(TEST_APP_PACKAGE, TEST_APP_BROADCAST_RECEIVER);
bindToBroadcastReceiverOfApp(INSTALLER_PACKAGE, INSTALLER_APP_BROADCAST_RECEIVER);
resetReceivers();
}
@After
public void tearDown() throws Exception {
unRegisterReceiver(mCallingAppBroadcastReceiver);
unRegisterReceiver(mTestAppBroadcastInfoProvider);
unRegisterReceiver(mInstallerBroadcastInfoProvider);
unRegisterReceiver(mTestAppCreationInfoProvider);
unRegisterReceiver(mInstallerAppCreationInfoProvider);
}
private void unRegisterReceiver(BlockingBroadcastReceiver receiver) {
if (receiver != null) {
sContext.unregisterReceiver(receiver);
receiver = null;
}
}
/**
* Send broadcast to given app's receiver with flag {@link Intent#FLAG_INCLUDE_STOPPED_PACKAGES}
* and wait for it to be received.
*
* <p/> This is required since app is in stopped state after installation by tradefed,
* and the receivers in another apps don't receive the broadcast without
* FLAG_INCLUDE_STOPPED_PACKAGES because they have never been opened even once
* after installation.
* By doing this we make sure that app is un-stopped when the system sends the broadcasts
* in the tests.
*/
private void bindToBroadcastReceiverOfApp(String packageName, String broadcastReceiver) {
final Intent intent = new Intent()
.setComponent(new ComponentName(packageName, broadcastReceiver))
.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
CountDownLatch latch = new CountDownLatch(1);
sContext.sendOrderedBroadcast(
intent,
null /* receiverPermission */,
new BroadcastReceiver() { /* resultReceiver */
@Override public void onReceive(Context context, Intent intent) {
latch.countDown();
}
},
null /* scheduler */,
Activity.RESULT_OK, /* initialCode */
null /* initialData */,
null /* initialExtras */);
try {
assertTrue("Timed out waiting for test broadcast to be received",
latch.await(5_000, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
throw new IllegalStateException("Interrupted", e);
}
}
/**
* Sets the installer as {@link #INSTALLER_PACKAGE} for the target package.
*/
private void setInstallerForPackage(String targetPackageName) {
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(sContext.getPackageManager(),
(pm) -> pm.setInstallerPackageName(targetPackageName, INSTALLER_PACKAGE));
}
/**
* Resets the countdown latch in all the receivers.
*/
private void resetReceivers() {
mCallingAppBroadcastReceiver.reset();
mInstallerBroadcastInfoProvider.reset();
mTestAppBroadcastInfoProvider.reset();
mTestAppCreationInfoProvider.reset();
mTestAppConfigChangedInfoProvider.reset();
mInstallerAppCreationInfoProvider.reset();
}
@Test
public void testSetApplicationLocales_persistsAndSendsBroadcast() throws Exception {
sLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
mCallingAppBroadcastReceiver.await();
assertReceivedBroadcastContains(mCallingAppBroadcastReceiver,
CALLING_PACKAGE, DEFAULT_APP_LOCALES);
mInstallerBroadcastInfoProvider.await();
assertReceivedBroadcastContains(mInstallerBroadcastInfoProvider,
CALLING_PACKAGE, DEFAULT_APP_LOCALES);
}
@Test
public void testSetApplicationLocales_resetToEmptyLocales_persistsAndSendsBroadcast()
throws Exception {
// First set the locales to non-empty
sLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
mCallingAppBroadcastReceiver.await();
assertReceivedBroadcastContains(mCallingAppBroadcastReceiver,
CALLING_PACKAGE, DEFAULT_APP_LOCALES);
mCallingAppBroadcastReceiver.reset();
sLocaleManager.setApplicationLocales(LocaleList.getEmptyLocaleList());
assertLocalesCorrectlySetForCallingApp(LocaleList.getEmptyLocaleList());
mCallingAppBroadcastReceiver.await();
assertReceivedBroadcastContains(mCallingAppBroadcastReceiver,
CALLING_PACKAGE, LocaleList.getEmptyLocaleList());
}
@Test
public void testSetApplicationLocales_sameLocalesEmpty_noBroadcastSent() throws Exception {
// At the start of the test, no app-specific locales are set, so just try to set it to
// empty again
sLocaleManager.setApplicationLocales(LocaleList.getEmptyLocaleList());
assertLocalesCorrectlySetForCallingApp(LocaleList.getEmptyLocaleList());
mCallingAppBroadcastReceiver.assertNoBroadcastReceived();
}
@Test
public void testSetApplicationLocales_sameLocalesNonEmpty_noBroadcastSent() throws Exception {
// First set the locales to non-empty
sLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
mCallingAppBroadcastReceiver.await();
assertReceivedBroadcastContains(mCallingAppBroadcastReceiver,
CALLING_PACKAGE, DEFAULT_APP_LOCALES);
mCallingAppBroadcastReceiver.reset();
// Then reset to the same app-specific locales, and verify no broadcasts are sent
sLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
mCallingAppBroadcastReceiver.assertNoBroadcastReceived();
}
@Test
public void testSetApplicationLocales_getDefaultLocaleList_returnsCorrectList()
throws Exception {
// Fetch the current system locales when there are no app-locales set.
LocaleList systemLocales = LocaleList.getDefault();
assertEquals(DEFAULT_SYSTEM_LOCALES, systemLocales);
sLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
// Wait for a while since LocaleList::getDefault could take a while to
// reflect the new app locales.
mCallingAppBroadcastReceiver.await();
assertEquals(combineLocales(DEFAULT_APP_LOCALES, systemLocales), LocaleList.getDefault());
}
@Test
public void testSetApplicationLocales_forAnotherAppInForeground_persistsAndSendsBroadcast()
throws Exception {
// Bring the TestApp to the foreground by invoking an activity and verify its visibility.
launchActivity(TEST_APP_MAIN_ACTIVITY);
mWmState.assertVisibility(TEST_APP_MAIN_ACTIVITY, /* visible*/ true);
runWithShellPermissionIdentity(() ->
sLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES),
Manifest.permission.CHANGE_CONFIGURATION);
assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
mTestAppBroadcastInfoProvider.await();
assertReceivedBroadcastContains(mTestAppBroadcastInfoProvider, TEST_APP_PACKAGE,
DEFAULT_APP_LOCALES);
mInstallerBroadcastInfoProvider.await();
assertReceivedBroadcastContains(mInstallerBroadcastInfoProvider, TEST_APP_PACKAGE,
DEFAULT_APP_LOCALES);
}
@Test
public void testSetApplicationLocales_forAnotherAppInBackground_persistsAndSendsBroadcast()
throws Exception {
// Invoke the app by launching an activity.
launchActivity(TEST_APP_MAIN_ACTIVITY);
// Send the TestApp to the background.
launchHomeActivity();
mWmState.waitAndAssertVisibilityGone(TEST_APP_MAIN_ACTIVITY);
runWithShellPermissionIdentity(() ->
sLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES),
Manifest.permission.CHANGE_CONFIGURATION);
assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
mTestAppBroadcastInfoProvider.await();
assertReceivedBroadcastContains(mTestAppBroadcastInfoProvider, TEST_APP_PACKAGE,
DEFAULT_APP_LOCALES);
mInstallerBroadcastInfoProvider.await();
assertReceivedBroadcastContains(mInstallerBroadcastInfoProvider, TEST_APP_PACKAGE,
DEFAULT_APP_LOCALES);
}
@Test
public void testSetApplicationLocales_forAnotherApp_persistsOnAppRestart() throws Exception {
runWithShellPermissionIdentity(() ->
sLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES),
Manifest.permission.CHANGE_CONFIGURATION);
// Re-start the app by starting an activity and check if locales correctly
// received by the app and listen to the broadcast for result from the app.
launchActivity(TEST_APP_MAIN_ACTIVITY, extraString(EXTRA_QUERY_LOCALES, "true"));
mTestAppCreationInfoProvider.await();
assertReceivedBroadcastContains(mTestAppCreationInfoProvider, TEST_APP_PACKAGE,
DEFAULT_APP_LOCALES);
}
@Test
public void
testSetApplicationLocales_wthoutPermissionforAnotherApp_throwsExceptionAndNoBroadcast()
throws Exception {
try {
sLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
} catch (SecurityException e) {
// expected as not having appropriate permission to change locales for another app.
}
// Since the locales weren't allowed to persist, no broadcasts should be sent by the system.
mTestAppBroadcastInfoProvider.assertNoBroadcastReceived();
mInstallerBroadcastInfoProvider.assertNoBroadcastReceived();
}
@Test
public void testSetApplicationLocales_forAnotherAppInForeground_callsOnConfigChanged()
throws Exception {
// Bring the TestApp to the foreground by invoking an activity and verify its visibility.
launchActivity(TEST_APP_MAIN_ACTIVITY);
mWmState.assertVisibility(TEST_APP_MAIN_ACTIVITY, /* visible*/ true);
runWithShellPermissionIdentity(() ->
sLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES),
Manifest.permission.CHANGE_CONFIGURATION);
assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
mTestAppConfigChangedInfoProvider.await();
assertReceivedBroadcastContains(mTestAppConfigChangedInfoProvider, TEST_APP_PACKAGE,
combineLocales(DEFAULT_APP_LOCALES, DEFAULT_SYSTEM_LOCALES));
}
@Test
public void testSetApplicationLocales_withReadAppSpecificLocalesPermission_receivesBroadcast()
throws Exception {
BlockingBroadcastReceiver appSpecificLocaleBroadcastReceiver =
new BlockingBroadcastReceiver();
sContext.registerReceiver(appSpecificLocaleBroadcastReceiver,
new IntentFilter(Intent.ACTION_APPLICATION_LOCALE_CHANGED));
// Hold Manifest.permission.READ_APP_SPECIFIC_LOCALES while the broadcast is sent,
// so that we receive it
runWithShellPermissionIdentity(() -> {
// Tell the test app to change its app-specific locales
launchActivity(TEST_APP_MAIN_ACTIVITY, extraString(EXTRA_SET_LOCALES,
DEFAULT_APP_LOCALES.toLanguageTags()));
assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
appSpecificLocaleBroadcastReceiver.await();
}, Manifest.permission.READ_APP_SPECIFIC_LOCALES);
assertReceivedBroadcastContains(appSpecificLocaleBroadcastReceiver,
TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
}
@Test
public void testSetApplicationLocales_appWithoutPermission_noBroadcastsReceived()
throws Exception {
BlockingBroadcastReceiver appSpecificLocaleBroadcastReceiver =
new BlockingBroadcastReceiver();
sContext.registerReceiver(appSpecificLocaleBroadcastReceiver,
new IntentFilter(Intent.ACTION_APPLICATION_LOCALE_CHANGED));
// Tell the test app to change its app-specific locales
launchActivity(TEST_APP_MAIN_ACTIVITY, extraString(EXTRA_SET_LOCALES,
DEFAULT_APP_LOCALES.toLanguageTags()));
assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
// Ensure that no broadcasts were received since the change was for another app (the Test
// App) and we are neither the Test App's installer, nor do we hold
// Manifest.permission.READ_APP_SPECIFIC_LOCALES
appSpecificLocaleBroadcastReceiver.assertNoBroadcastReceived();
mCallingAppBroadcastReceiver.assertNoBroadcastReceived();
}
@Test
public void testGetApplicationLocales_callerIsInstaller_returnsLocales() throws Exception {
// Set locales for calling app
sLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
// Make sure that locales were set for the app.
assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
// Tell the installer app to fetch locales for calling package.
launchActivity(INSTALLER_APP_MAIN_ACTIVITY,
extraString(EXTRA_QUERY_LOCALES, CALLING_PACKAGE));
mInstallerAppCreationInfoProvider.await();
assertReceivedBroadcastContains(mInstallerAppCreationInfoProvider,
CALLING_PACKAGE, DEFAULT_APP_LOCALES);
}
@Test(expected = SecurityException.class)
public void testGetApplicationLocales_withoutPermissionforAnotherApp_throwsException()
throws Exception {
sLocaleManager.getApplicationLocales(TEST_APP_PACKAGE);
fail("Expected SecurityException due to not having appropriate permission "
+ "for querying locales of another app.");
}
@Test
public void testGetApplicationLocales_noAppLocalesSet_returnsEmptyList() {
// When app-specific locales aren't set, we expect get api to return empty list
// and not throw any error.
assertEquals(LocaleList.getEmptyLocaleList(), sLocaleManager.getApplicationLocales());
}
/**
* Verifies that the locales are correctly set for calling(instrumentation) app
* by fetching locales of the app with a binder call.
*/
private void assertLocalesCorrectlySetForCallingApp(LocaleList expectedLocales) {
assertEquals(expectedLocales, sLocaleManager.getApplicationLocales());
}
/**
* Verifies that the locales are correctly set for another package
* by fetching locales of the app with a binder call.
*/
private void assertLocalesCorrectlySetForAnotherApp(String packageName,
LocaleList expectedLocales) throws Exception {
assertEquals(expectedLocales, getApplicationLocales(packageName));
}
private LocaleList getApplicationLocales(String packageName) throws Exception {
return callWithShellPermissionIdentity(() ->
sLocaleManager.getApplicationLocales(packageName),
Manifest.permission.READ_APP_SPECIFIC_LOCALES);
}
/**
* Verifies that the broadcast received in the relevant apps have the correct information
* in the intent extras. It verifies the below extras:
* <ul>
* <li> {@link Intent#EXTRA_PACKAGE_NAME}
* <li> {@link Intent#EXTRA_LOCALE_LIST}
* </ul>
*/
private void assertReceivedBroadcastContains(BlockingBroadcastReceiver receiver,
String expectedPackageName, LocaleList expectedLocales) {
assertEquals(expectedPackageName, receiver.getPackageName());
assertEquals(expectedLocales, receiver.getLocales());
}
/**
* Creates a combined {@link LocaleList} by placing app locales before system locales and
* dropping any duplicates.
*
* We need to combine them since {@link LocaleList} does not have any direct way like
* a normal list to check if locale or subset of locales is present.
*/
private LocaleList combineLocales(LocaleList appLocales, LocaleList systemLocales) {
Locale[] combinedLocales = new Locale[appLocales.size() + systemLocales.size()];
for (int i = 0; i < appLocales.size(); i++) {
combinedLocales[i] = appLocales.get(i);
}
for (int i = 0; i < systemLocales.size(); i++) {
combinedLocales[i + appLocales.size()] = systemLocales.get(i);
}
// Constructor of {@link LocaleList} removes duplicates.
return new LocaleList(combinedLocales);
}
/**
* Reset the locales for the apps to empty list as they could have been set previously
* by some other test.
*/
private void resetAppLocalesAsEmpty() throws Exception {
// Reset locales for the calling app.
sLocaleManager.setApplicationLocales(LocaleList.getEmptyLocaleList());
// Reset locales for the TestApp.
runWithShellPermissionIdentity(() ->
sLocaleManager.setApplicationLocales(TEST_APP_PACKAGE,
LocaleList.getEmptyLocaleList()),
Manifest.permission.CHANGE_CONFIGURATION);
}
}