| /* |
| * 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.backup.cts; |
| |
| import static android.app.time.cts.shell.DeviceConfigKeys.NAMESPACE_SYSTEM_TIME; |
| import static android.app.time.cts.shell.DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT; |
| |
| import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity; |
| import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; |
| |
| import android.Manifest; |
| import android.app.Instrumentation; |
| import android.app.LocaleManager; |
| import android.app.time.ExternalTimeSuggestion; |
| import android.app.time.TimeManager; |
| import android.app.time.cts.shell.DeviceConfigKeys; |
| import android.app.time.cts.shell.DeviceConfigShellHelper; |
| import android.app.time.cts.shell.DeviceShellCommandExecutor; |
| import android.app.time.cts.shell.device.InstrumentationShellCommandExecutor; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.os.LocaleList; |
| import android.os.SystemClock; |
| import android.platform.test.annotations.AppModeFull; |
| |
| import androidx.test.InstrumentationRegistry; |
| |
| import com.android.compatibility.common.util.AmUtils; |
| import com.android.compatibility.common.util.ShellUtils; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| |
| import java.time.Duration; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| |
| @AppModeFull |
| public class AppLocalesBackupTest extends BaseBackupCtsTest { |
| private static final String APK_PATH = "/data/local/tmp/cts/backup/"; |
| private static final String TEST_APP_APK_1 = APK_PATH + "CtsAppLocalesBackupApp1.apk"; |
| private static final String TEST_APP_PACKAGE_1 = |
| "android.cts.backup.applocalesbackupapp1"; |
| |
| private static final String TEST_APP_APK_2 = APK_PATH + "CtsAppLocalesBackupApp2.apk"; |
| private static final String TEST_APP_PACKAGE_2 = |
| "android.cts.backup.applocalesbackupapp2"; |
| private static final String SYSTEM_PACKAGE = "android"; |
| |
| private static final LocaleList DEFAULT_LOCALES_1 = LocaleList.forLanguageTags("hi-IN,de-DE"); |
| private static final LocaleList DEFAULT_LOCALES_2 = LocaleList.forLanguageTags("fr-CA"); |
| private static final LocaleList EMPTY_LOCALES = LocaleList.getEmptyLocaleList(); |
| |
| // An identifier for the backup dataset. Since we're using localtransport, it's set to "1". |
| private static final String RESTORE_TOKEN = "1"; |
| |
| private static final Duration RETENTION_PERIOD = Duration.ofDays(3); |
| |
| private Context mContext; |
| private LocaleManager mLocaleManager; |
| |
| @Before |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| |
| mContext = InstrumentationRegistry.getTargetContext(); |
| mLocaleManager = mContext.getSystemService(LocaleManager.class); |
| |
| install(TEST_APP_APK_1); |
| install(TEST_APP_APK_2); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| uninstall(TEST_APP_PACKAGE_1); |
| uninstall(TEST_APP_PACKAGE_2); |
| } |
| |
| /** |
| * Tests the scenario where all apps are installed on the device when restore is triggered. |
| * |
| * <p>In this case, all the apps should have their locales restored as soon as the restore |
| * operation finishes. The only condition is that the apps should not have the locales set |
| * already before restore. |
| */ |
| public void testBackupRestore_allAppsInstalledNoAppLocalesSet_restoresImmediately() |
| throws Exception { |
| if (!isBackupSupported()) { |
| return; |
| } |
| setAndBackupDefaultAppLocales(); |
| |
| resetAppLocales(); |
| |
| getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE); |
| |
| assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); |
| assertLocalesForApp(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2); |
| } |
| |
| /** |
| * Tests the scenario where the user sets the app-locales before the restore could be applied. |
| * |
| * <p>The locales from the backup data should be ignored in this case. |
| */ |
| public void testBackupRestore_localeAlreadySet_doesNotRestore() throws Exception { |
| if (!isBackupSupported()) { |
| return; |
| } |
| setAndBackupDefaultAppLocales(); |
| |
| LocaleList newLocales = LocaleList.forLanguageTags("zh,hi"); |
| setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, newLocales); |
| setApplicationLocalesAndVerify(TEST_APP_PACKAGE_2, EMPTY_LOCALES); |
| |
| getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE); |
| |
| // Should restore only for app_2. |
| assertLocalesForApp(TEST_APP_PACKAGE_1, newLocales); |
| assertLocalesForApp(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2); |
| } |
| |
| /** |
| * Tests the scenario when some apps are installed after the restore finishes. |
| * |
| * <p>More specifically, this tests the lazy restore where the locales are fetched and |
| * restored from the stage file if the app is installed within a certain amount of time after |
| * the initial restore. |
| */ |
| public void testBackupRestore_appInstalledAfterRestore_doesLazyRestore() throws Exception { |
| if (!isBackupSupported()) { |
| return; |
| } |
| setAndBackupDefaultAppLocales(); |
| |
| resetAppLocales(); |
| |
| uninstall(TEST_APP_PACKAGE_2); |
| |
| getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE); |
| |
| // Locales for App1 should be restored immediately since that's present already. |
| assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); |
| |
| // This is to ensure there are no lingering broadcasts (could be from the setUp method |
| // where we are calling setApplicationLocales). |
| AmUtils.waitForBroadcastIdle(); |
| |
| 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 trigger lazy restore, which internally calls setApplicationLocales |
| // which sends out the ACTION_APPLICATION_LOCALE_CHANGED broadcast. |
| install(TEST_APP_APK_2); |
| appSpecificLocaleBroadcastReceiver.await(); |
| }, Manifest.permission.READ_APP_SPECIFIC_LOCALES); |
| |
| appSpecificLocaleBroadcastReceiver.assertOneBroadcastReceived(); |
| appSpecificLocaleBroadcastReceiver.assertReceivedBroadcastContains(TEST_APP_PACKAGE_2, |
| DEFAULT_LOCALES_2); |
| |
| // Verify that lazy restore occurred upon package install. |
| assertLocalesForApp(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2); |
| |
| // APP2's entry is removed from the stage file after restore so nothing should be restored |
| // when APP2 is installed for the second time. |
| uninstall(TEST_APP_PACKAGE_2); |
| install(TEST_APP_APK_2); |
| assertLocalesForApp(TEST_APP_PACKAGE_2, EMPTY_LOCALES); |
| } |
| |
| /** |
| * Tests the scenario when an application is removed from the device. |
| * |
| * <p>The data for the uninstalled app should be removed from the next backup pass. |
| */ |
| public void testBackupRestore_uninstallApp_deletesDataFromBackup() throws Exception { |
| if (!isBackupSupported()) { |
| return; |
| } |
| setAndBackupDefaultAppLocales(); |
| |
| // Uninstall an app and run the backup pass. The locales for the uninstalled app should |
| // be removed from the backup. |
| uninstall(TEST_APP_PACKAGE_2); |
| setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); |
| getBackupUtils().backupNowAndAssertSuccess(SYSTEM_PACKAGE); |
| |
| install(TEST_APP_APK_2); |
| // Remove app1's locales so that it can be restored. |
| setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, EMPTY_LOCALES); |
| |
| getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE); |
| |
| // Restores only app1's locales because app2's data is no longer present in the backup. |
| assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); |
| assertLocalesForApp(TEST_APP_PACKAGE_2, EMPTY_LOCALES); |
| } |
| |
| /** |
| * Tests the scenario when backup pass is run after retention period has expired. |
| * |
| * <p>Stage data should be removed since retention period has expired. |
| * <p><b>Note:</b>Manipulates device's system clock directly to simulate the passage of time. |
| */ |
| public void testRetentionPeriod_backupPassAfterRetentionPeriod_removesStagedData() |
| throws Exception { |
| if (!isBackupSupported()) { |
| return; |
| } |
| setAndBackupDefaultAppLocales(); |
| |
| uninstall(TEST_APP_PACKAGE_2); |
| |
| getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE); |
| |
| // Locales for App1 should be restored immediately since that's present already. |
| assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); |
| |
| Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); |
| DeviceShellCommandExecutor shellCommandExecutor = new InstrumentationShellCommandExecutor( |
| instrumentation.getUiAutomation()); |
| DeviceConfigShellHelper deviceConfigShellHelper = new DeviceConfigShellHelper( |
| shellCommandExecutor); |
| |
| // This anticipates a future state where a generally applied target preparer may disable |
| // device_config sync for all CTS tests: only suspend syncing if it isn't already suspended, |
| // and only resume it if this test suspended it. |
| DeviceConfigShellHelper.PreTestState deviceConfigPreTestState = |
| deviceConfigShellHelper.setSyncModeForTest( |
| SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME); |
| |
| TimeManager timeManager = mContext.getSystemService(TimeManager.class); |
| assertNotNull(timeManager); |
| |
| // Capture clock values so that the system clock can be set back after the test. |
| long startCurrentTimeMillis = System.currentTimeMillis(); |
| long elapsedRealtimeMillis = SystemClock.elapsedRealtime(); |
| |
| try { |
| |
| // Set the time detector to only use ORIGIN_EXTERNAL. |
| deviceConfigShellHelper.put(NAMESPACE_SYSTEM_TIME, |
| DeviceConfigKeys.TimeDetector.KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE, |
| DeviceConfigKeys.TimeDetector.ORIGIN_EXTERNAL); |
| sleepForAsyncOperation(); |
| |
| // 1 second elapses after retention period. |
| long afterRetentionPeriodMillis = Duration.ofMillis(startCurrentTimeMillis).plusMillis( |
| RETENTION_PERIOD.plusSeconds(1).toMillis()).toMillis(); |
| ExternalTimeSuggestion futureTimeSuggestion = |
| new ExternalTimeSuggestion(elapsedRealtimeMillis, afterRetentionPeriodMillis); |
| |
| runWithShellPermissionIdentity(() -> { |
| timeManager.suggestExternalTime(futureTimeSuggestion); |
| }, Manifest.permission.SUGGEST_EXTERNAL_TIME); |
| sleepForAsyncOperation(); |
| |
| // The suggestion should have been accepted so the system clock should have advanced. |
| assertTrue(System.currentTimeMillis() >= afterRetentionPeriodMillis); |
| |
| // Run the backup pass now. |
| getBackupUtils().backupNowAndAssertSuccess(SYSTEM_PACKAGE); |
| } finally { |
| |
| // Now do our best to return the device to its original state. |
| ExternalTimeSuggestion originalTimeSuggestion = |
| new ExternalTimeSuggestion(elapsedRealtimeMillis, startCurrentTimeMillis); |
| runWithShellPermissionIdentity(() -> { |
| timeManager.suggestExternalTime(originalTimeSuggestion); |
| }, Manifest.permission.SUGGEST_EXTERNAL_TIME); |
| sleepForAsyncOperation(); |
| |
| deviceConfigShellHelper.restoreDeviceConfigStateForTest(deviceConfigPreTestState); |
| } |
| |
| // We install the app after restoring the device time so that retention check during lazy |
| // restore doesn't try to delete the stage data. Hence, ensuring that stage data is deleted |
| // during the backup pass. |
| 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 trigger lazy restore, which internally calls setApplicationLocales |
| // which sends out the ACTION_APPLICATION_LOCALE_CHANGED broadcast. |
| install(TEST_APP_APK_2); |
| appSpecificLocaleBroadcastReceiver.await(); |
| }, Manifest.permission.READ_APP_SPECIFIC_LOCALES); |
| |
| appSpecificLocaleBroadcastReceiver.assertNoBroadcastReceived(); |
| |
| // Does not restore the locales on package install. |
| assertLocalesForApp(TEST_APP_PACKAGE_2, EMPTY_LOCALES); |
| } |
| |
| /** |
| * Tests the scenario when lazy restore happens after retention period has expired. |
| * |
| * <p>Stage data should be removed since retention period has expired. |
| * <p><b>Note:</b>Manipulates device's system clock directly to simulate the passage of time. |
| */ |
| public void testRetentionPeriod_lazyRestoreAfterRetentionPeriod_removesStagedData() |
| throws Exception { |
| if (!isBackupSupported()) { |
| return; |
| } |
| setAndBackupDefaultAppLocales(); |
| |
| uninstall(TEST_APP_PACKAGE_1); |
| uninstall(TEST_APP_PACKAGE_2); |
| |
| getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE); |
| |
| Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); |
| DeviceShellCommandExecutor shellCommandExecutor = new InstrumentationShellCommandExecutor( |
| instrumentation.getUiAutomation()); |
| DeviceConfigShellHelper deviceConfigShellHelper = new DeviceConfigShellHelper( |
| shellCommandExecutor); |
| |
| // This anticipates a future state where a generally applied target preparer may disable |
| // device_config sync for all CTS tests: only suspend syncing if it isn't already suspended, |
| // and only resume it if this test suspended it. |
| DeviceConfigShellHelper.PreTestState deviceConfigPreTestState = |
| deviceConfigShellHelper.setSyncModeForTest( |
| SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME); |
| |
| TimeManager timeManager = mContext.getSystemService(TimeManager.class); |
| assertNotNull(timeManager); |
| |
| // Capture clock values so that the system clock can be set back after the test. |
| long startCurrentTimeMillis = System.currentTimeMillis(); |
| long elapsedRealtimeMillis = SystemClock.elapsedRealtime(); |
| |
| try { |
| |
| 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 trigger lazy restore, which internally calls |
| // setApplicationLocales |
| // which sends out the ACTION_APPLICATION_LOCALE_CHANGED broadcast. |
| install(TEST_APP_APK_1); |
| appSpecificLocaleBroadcastReceiver.await(); |
| }, Manifest.permission.READ_APP_SPECIFIC_LOCALES); |
| |
| appSpecificLocaleBroadcastReceiver.assertOneBroadcastReceived(); |
| appSpecificLocaleBroadcastReceiver.assertReceivedBroadcastContains(TEST_APP_PACKAGE_1, |
| DEFAULT_LOCALES_1); |
| assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); |
| appSpecificLocaleBroadcastReceiver.reset(); |
| |
| // Set the time detector to only use ORIGIN_EXTERNAL. |
| deviceConfigShellHelper.put(NAMESPACE_SYSTEM_TIME, |
| DeviceConfigKeys.TimeDetector.KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE, |
| DeviceConfigKeys.TimeDetector.ORIGIN_EXTERNAL); |
| sleepForAsyncOperation(); |
| |
| // 1 second elapses after retention period. |
| long afterRetentionPeriodMillis = Duration.ofMillis(startCurrentTimeMillis).plusMillis( |
| RETENTION_PERIOD.plusSeconds(1).toMillis()).toMillis(); |
| ExternalTimeSuggestion futureTimeSuggestion = |
| new ExternalTimeSuggestion(elapsedRealtimeMillis, afterRetentionPeriodMillis); |
| |
| runWithShellPermissionIdentity(() -> { |
| timeManager.suggestExternalTime(futureTimeSuggestion); |
| }, Manifest.permission.SUGGEST_EXTERNAL_TIME); |
| sleepForAsyncOperation(); |
| |
| // The suggestion should have been accepted so the system clock should have advanced. |
| assertTrue(System.currentTimeMillis() >= afterRetentionPeriodMillis); |
| |
| // Hold Manifest.permission.READ_APP_SPECIFIC_LOCALES while the broadcast is sent, |
| // so that we receive it. |
| runWithShellPermissionIdentity(() -> { |
| // Installation will trigger lazy restore, which internally calls |
| // setApplicationLocales |
| // which sends out the ACTION_APPLICATION_LOCALE_CHANGED broadcast. |
| install(TEST_APP_APK_2); |
| appSpecificLocaleBroadcastReceiver.await(); |
| }, Manifest.permission.READ_APP_SPECIFIC_LOCALES); |
| |
| appSpecificLocaleBroadcastReceiver.assertNoBroadcastReceived(); |
| |
| // Does not restore the locales on package install. |
| assertLocalesForApp(TEST_APP_PACKAGE_2, EMPTY_LOCALES); |
| } finally { |
| |
| // Now do our best to return the device to its original state. |
| ExternalTimeSuggestion originalTimeSuggestion = |
| new ExternalTimeSuggestion(elapsedRealtimeMillis, startCurrentTimeMillis); |
| runWithShellPermissionIdentity(() -> { |
| timeManager.suggestExternalTime(originalTimeSuggestion); |
| }, Manifest.permission.SUGGEST_EXTERNAL_TIME); |
| sleepForAsyncOperation(); |
| |
| deviceConfigShellHelper.restoreDeviceConfigStateForTest(deviceConfigPreTestState); |
| } |
| } |
| |
| /** |
| * Sleeps for a length of time sufficient to allow async operations to complete. Many time |
| * manager APIs are or could be asynchronous and deal with time, so there are no practical |
| * alternatives. |
| */ |
| private static void sleepForAsyncOperation() throws Exception { |
| Thread.sleep(5_000); |
| } |
| |
| // TODO(b/210593602): Add a test to check staged data removal after the retention period. |
| |
| private void setApplicationLocalesAndVerify(String packageName, LocaleList locales) |
| throws Exception { |
| runWithShellPermissionIdentity(() -> |
| mLocaleManager.setApplicationLocales(packageName, locales), |
| Manifest.permission.CHANGE_CONFIGURATION); |
| assertLocalesForApp(packageName, locales); |
| } |
| |
| /** |
| * Verifies that the locales are correctly set for another package |
| * by fetching locales of the app with a binder call. |
| */ |
| private void assertLocalesForApp(String packageName, |
| LocaleList expectedLocales) throws Exception { |
| assertEquals(expectedLocales, getApplicationLocales(packageName)); |
| } |
| |
| private LocaleList getApplicationLocales(String packageName) throws Exception { |
| return callWithShellPermissionIdentity(() -> |
| mLocaleManager.getApplicationLocales(packageName), |
| Manifest.permission.READ_APP_SPECIFIC_LOCALES); |
| } |
| |
| private void install(String apk) { |
| ShellUtils.runShellCommand("pm install -r " + apk); |
| } |
| |
| private void uninstall(String packageName) { |
| ShellUtils.runShellCommand("pm uninstall " + packageName); |
| } |
| |
| private void setAndBackupDefaultAppLocales() throws Exception { |
| setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); |
| setApplicationLocalesAndVerify(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2); |
| // Backup the data for SYSTEM_PACKAGE which includes app-locales. |
| getBackupUtils().backupNowAndAssertSuccess(SYSTEM_PACKAGE); |
| } |
| |
| private void resetAppLocales() throws Exception { |
| setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, EMPTY_LOCALES); |
| setApplicationLocalesAndVerify(TEST_APP_PACKAGE_2, EMPTY_LOCALES); |
| } |
| |
| private static final class BlockingBroadcastReceiver extends BroadcastReceiver { |
| private CountDownLatch mLatch = new CountDownLatch(1); |
| private String mPackageName; |
| private LocaleList mLocales; |
| private int mCalls; |
| |
| @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); |
| } |
| mCalls += 1; |
| mLatch.countDown(); |
| } |
| |
| public void await() throws Exception { |
| mLatch.await(/* timeout= */ 5, TimeUnit.SECONDS); |
| } |
| |
| public void reset() { |
| mLatch = new CountDownLatch(1); |
| mCalls = 0; |
| mPackageName = null; |
| mLocales = null; |
| } |
| |
| public void assertOneBroadcastReceived() { |
| assertEquals(1, mCalls); |
| } |
| |
| public void assertNoBroadcastReceived() { |
| assertEquals(0, mCalls); |
| } |
| |
| /** |
| * 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); |
| } |
| } |
| } |