blob: 4f6034e5d4c376208c0fd149a38d02e675cb3936 [file] [log] [blame]
/*
* Copyright (C) 2022 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 com.android.cts.localeconfig;
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 com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
import android.Manifest;
import android.app.LocaleConfig;
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.ShellUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Tests for {@link com.android.server.locales.AppUpdateTracker} API(s).
*
* Build/Install/Run: atest LocaleConfigAppUpdateTest
*/
@RunWith(AndroidJUnit4.class)
public class LocaleConfigAppUpdateTest extends ActivityManagerTestBase {
private static final String APK_PATH = "/data/local/tmp/cts/localeconfig/";
private static final String APK_WITH_LOCALECONFIG = APK_PATH + "ApkWithLocaleConfig.apk";
private static final String APK_WITHOUT_LOCALECONFIG = APK_PATH + "ApkWithoutLocaleConfig.apk";
private static final String APK_REMOVEAPPLOCALES_INLOCALECONFIG =
APK_PATH + "ApkRemoveAppLocalesInLocaleConfig.apk";
private static final String TEST_PACKAGE = "com.android.cts.localeconfiginorout";
private static final LocaleList EMPTY_LOCALES = LocaleList.getEmptyLocaleList();
private Context mContext;
private LocaleManager mLocaleManager;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
mLocaleManager = mContext.getSystemService(LocaleManager.class);
}
@After
public void tearDown() throws Exception {
uninstall(TEST_PACKAGE);
}
/**
* Tests the scenario where the per-app locales are kept after the app upgrades.
*
* <p>If the per-app locales are set by a delegate selector and the same LocaleConfig is still
* existed after the app upgrades, the per-app locales should be kept without clearing.
*/
@Test
public void testUpgradedApk_KeepLocaleConfig_keepAppLocales() throws Exception {
install(APK_WITH_LOCALECONFIG);
LocaleList expectedLocales = LocaleList.forLanguageTags("fr");
runWithShellPermissionIdentity(
() -> mLocaleManager.setApplicationLocales(TEST_PACKAGE, expectedLocales),
Manifest.permission.CHANGE_CONFIGURATION);
Thread.sleep(1000);
verifyLocalesCorrectlySetForAnotherApp(TEST_PACKAGE, expectedLocales);
install(APK_WITH_LOCALECONFIG);
LocaleList getAppLocales_afterUpgraded = callWithShellPermissionIdentity(() ->
mLocaleManager.getApplicationLocales(TEST_PACKAGE),
Manifest.permission.READ_APP_SPECIFIC_LOCALES);
assertEquals(expectedLocales, getAppLocales_afterUpgraded);
}
/**
* Tests the scenario where the per-app locales set from the app should be kept regardless of
* whether there is a LocaleConfig before and after the app upgrades.
*
* <p>What we want to avoid is when the per-app locales are set from a delegate selector, and
* the LocaleConfig is removed after the app upgrades, then previously set per-app locales can't
* be changed. So if the per-app locales are set from the app in the same scenario, they will
* still be kept without clearing.
*/
@Test
public void testUpgradedApk_setFromApp_KeepAppLocales() throws Exception {
install(APK_WITHOUT_LOCALECONFIG);
// Tell the app to set its app-specific locales
launchActivity(new ComponentName(TEST_PACKAGE, TEST_PACKAGE + ".MainActivity"),
extraString("set_locales", "fr"));
Thread.sleep(1000);
LocaleList expectedLocales = LocaleList.forLanguageTags("fr");
verifyLocalesCorrectlySetForAnotherApp(TEST_PACKAGE, expectedLocales);
install(APK_WITHOUT_LOCALECONFIG);
Context appContext = mContext.createPackageContext(TEST_PACKAGE, 0);
LocaleConfig localeConfig = new LocaleConfig(appContext);
LocaleList localeList = localeConfig.getSupportedLocales();
assertNull(localeList);
assertEquals(LocaleConfig.STATUS_NOT_SPECIFIED, localeConfig.getStatus());
LocaleList upgradedAppLocales = callWithShellPermissionIdentity(() ->
mLocaleManager.getApplicationLocales(TEST_PACKAGE),
Manifest.permission.READ_APP_SPECIFIC_LOCALES);
assertEquals(expectedLocales, upgradedAppLocales);
}
/**
* Tests the scenario where the per-app locales set from a delegate selector should be cleared
* when the LocaleConfig is removed after the app upgrades.
*
* <p>What we want to avoid is when the per-app locales are set from a delegate selector, and
* the LocaleConfig is removed after the app upgrades, then previously set per-app locales can't
* be changed.
*/
@Test
public void testUpgradedApk_removeLocaleConfig_ClearAppLocales() throws Exception {
install(APK_WITH_LOCALECONFIG);
LocaleList expectedLocales = LocaleList.forLanguageTags("fr");
runWithShellPermissionIdentity(
() -> mLocaleManager.setApplicationLocales(TEST_PACKAGE, expectedLocales),
Manifest.permission.CHANGE_CONFIGURATION);
Thread.sleep(1000);
verifyLocalesCorrectlySetForAnotherApp(TEST_PACKAGE, expectedLocales);
BlockingBroadcastReceiver appSpecificLocaleBroadcastReceiver =
new BlockingBroadcastReceiver();
mContext.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(() -> {
// Installation will clear per-app locale, which internally calls setApplicationLocales
// which sends out the ACTION_APPLICATION_LOCALE_CHANGED broadcast.
install(APK_WITHOUT_LOCALECONFIG);
appSpecificLocaleBroadcastReceiver.await();
}, Manifest.permission.READ_APP_SPECIFIC_LOCALES);
appSpecificLocaleBroadcastReceiver.assertReceivedBroadcastContains(TEST_PACKAGE,
EMPTY_LOCALES);
Context appContext = mContext.createPackageContext(TEST_PACKAGE, 0);
LocaleConfig localeConfig = new LocaleConfig(appContext);
LocaleList localeList = localeConfig.getSupportedLocales();
assertNull(localeList);
assertEquals(LocaleConfig.STATUS_NOT_SPECIFIED, localeConfig.getStatus());
}
/**
* Tests the scenario where the per-app locales set from a delegate selector should be cleared
* when the locales are removed from the LocaleConfig after the app upgrades.
*
* <p>What we want to avoid is when the per-app locales are set from a delegate selector, and
* the locales are removed from the LocaleConfig after the app upgrades, it would confuse the
* user since the removed locales are still shown to the user and the they are not present in
* the delegate selector.
*/
@Test
public void testUpgradedApk_removeAppLocalesInLocaleConfig_ClearAppLocales() throws Exception {
install(APK_WITH_LOCALECONFIG);
LocaleList expectedLocales = LocaleList.forLanguageTags("fr");
runWithShellPermissionIdentity(
() -> mLocaleManager.setApplicationLocales(TEST_PACKAGE, expectedLocales),
Manifest.permission.CHANGE_CONFIGURATION);
Thread.sleep(1000);
verifyLocalesCorrectlySetForAnotherApp(TEST_PACKAGE, expectedLocales);
BlockingBroadcastReceiver appSpecificLocaleBroadcastReceiver =
new BlockingBroadcastReceiver();
mContext.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(() -> {
// Installation will clear per-app locale, which internally calls setApplicationLocales
// which sends out the ACTION_APPLICATION_LOCALE_CHANGED broadcast.
install(APK_REMOVEAPPLOCALES_INLOCALECONFIG);
appSpecificLocaleBroadcastReceiver.await();
}, Manifest.permission.READ_APP_SPECIFIC_LOCALES);
appSpecificLocaleBroadcastReceiver.assertReceivedBroadcastContains(TEST_PACKAGE,
EMPTY_LOCALES);
}
private void install(String apk) throws InterruptedException {
String installResult = ShellUtils.runShellCommand("pm install -r " + apk);
assertThat(installResult.trim()).isEqualTo("Success");
}
private void uninstall(String packageName) throws InterruptedException {
String uninstallResult = ShellUtils.runShellCommand("pm uninstall " + packageName);
assertThat(uninstallResult.trim()).isEqualTo("Success");
}
private void verifyLocalesCorrectlySetForAnotherApp(String packageName,
LocaleList expectedLocales) throws Exception {
LocaleList getAppLocales = callWithShellPermissionIdentity(
() -> mLocaleManager.getApplicationLocales(packageName),
Manifest.permission.READ_APP_SPECIFIC_LOCALES);
assertEquals(expectedLocales, getAppLocales);
}
private static final class BlockingBroadcastReceiver extends BroadcastReceiver {
private CountDownLatch mLatch = new CountDownLatch(1);
private String mPackageName;
private LocaleList mLocales;
@Override
public void onReceive(Context context, Intent intent) {
if (intent.hasExtra(Intent.EXTRA_PACKAGE_NAME)) {
mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
}
if (intent.hasExtra(Intent.EXTRA_LOCALE_LIST)) {
mLocales = intent.getParcelableExtra(Intent.EXTRA_LOCALE_LIST);
}
mLatch.countDown();
}
public void await() throws Exception {
mLatch.await(/* timeout= */ 5, TimeUnit.SECONDS);
}
/**
* 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>
*/
public void assertReceivedBroadcastContains(String expectedPackageName,
LocaleList expectedLocales) {
assertEquals(expectedPackageName, mPackageName);
assertEquals(expectedLocales, mLocales);
}
}
}