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 android.backup.cts; 18 19 import static android.app.time.cts.shell.DeviceConfigKeys.NAMESPACE_SYSTEM_TIME; 20 import static android.app.time.cts.shell.DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT; 21 22 import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity; 23 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 24 25 import android.Manifest; 26 import android.app.LocaleManager; 27 import android.app.time.ExternalTimeSuggestion; 28 import android.app.time.TimeManager; 29 import android.app.time.cts.shell.DeviceConfigKeys; 30 import android.app.time.cts.shell.DeviceConfigShellHelper; 31 import android.app.time.cts.shell.DeviceShellCommandExecutor; 32 import android.app.time.cts.shell.device.InstrumentationShellCommandExecutor; 33 import android.content.BroadcastReceiver; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.content.IntentFilter; 37 import android.os.LocaleList; 38 import android.os.SystemClock; 39 import android.platform.test.annotations.AppModeFull; 40 import android.provider.Settings; 41 42 import androidx.test.InstrumentationRegistry; 43 44 import com.android.compatibility.common.util.AmUtils; 45 import com.android.compatibility.common.util.ShellUtils; 46 47 import org.junit.After; 48 import org.junit.Before; 49 50 import java.time.Duration; 51 import java.util.concurrent.CountDownLatch; 52 import java.util.concurrent.TimeUnit; 53 54 @AppModeFull 55 public class AppLocalesBackupTest extends BaseBackupCtsTest { 56 private static final String APK_PATH = "/data/local/tmp/cts/backup/"; 57 private static final String TEST_APP_APK_1 = APK_PATH + "CtsAppLocalesBackupApp1.apk"; 58 private static final String TEST_APP_PACKAGE_1 = 59 "android.cts.backup.applocalesbackupapp1"; 60 61 private static final String TEST_APP_APK_2 = APK_PATH + "CtsAppLocalesBackupApp2.apk"; 62 private static final String TEST_APP_PACKAGE_2 = 63 "android.cts.backup.applocalesbackupapp2"; 64 private static final String SYSTEM_PACKAGE = "android"; 65 66 private static final LocaleList DEFAULT_LOCALES_1 = LocaleList.forLanguageTags("hi-IN,de-DE"); 67 private static final LocaleList DEFAULT_LOCALES_2 = LocaleList.forLanguageTags("fr-CA"); 68 private static final LocaleList EMPTY_LOCALES = LocaleList.getEmptyLocaleList(); 69 70 // An identifier for the backup dataset. Since we're using localtransport, it's set to "1". 71 private static final String RESTORE_TOKEN = "1"; 72 73 private static final Duration RETENTION_PERIOD = Duration.ofDays(3); 74 75 private static final String SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED = 76 "cmd time_detector is_auto_detection_enabled"; 77 78 private Context mContext; 79 private LocaleManager mLocaleManager; 80 private boolean mOriginalAutoTime; 81 private DeviceShellCommandExecutor mShellCommandExecutor; 82 83 @Before 84 @Override setUp()85 public void setUp() throws Exception { 86 super.setUp(); 87 88 mContext = InstrumentationRegistry.getTargetContext(); 89 mLocaleManager = mContext.getSystemService(LocaleManager.class); 90 mShellCommandExecutor = new InstrumentationShellCommandExecutor( 91 InstrumentationRegistry.getInstrumentation().getUiAutomation()); 92 93 mOriginalAutoTime = isAutoDetectionEnabled(mShellCommandExecutor); 94 // Auto time needs to be enabled to be able to suggest external time 95 if (!mOriginalAutoTime) { 96 assertTrue(setAutoTimeEnabled(/*enabled*/ true, mShellCommandExecutor)); 97 } 98 99 install(TEST_APP_APK_1); 100 install(TEST_APP_APK_2); 101 } 102 103 @After tearDown()104 public void tearDown() throws Exception { 105 // reset auto time to its original value 106 if (!mOriginalAutoTime) { 107 setAutoTimeEnabled(/*enabled*/ false, mShellCommandExecutor); 108 } 109 110 uninstall(TEST_APP_PACKAGE_1); 111 uninstall(TEST_APP_PACKAGE_2); 112 } 113 114 /** 115 * Tests the scenario where all apps are installed on the device when restore is triggered. 116 * 117 * <p>In this case, all the apps should have their locales restored as soon as the restore 118 * operation finishes. The only condition is that the apps should not have the locales set 119 * already before restore. 120 */ testBackupRestore_allAppsInstalledNoAppLocalesSet_restoresImmediately()121 public void testBackupRestore_allAppsInstalledNoAppLocalesSet_restoresImmediately() 122 throws Exception { 123 if (!isBackupSupported()) { 124 return; 125 } 126 setAndBackupDefaultAppLocales(); 127 128 resetAppLocales(); 129 130 getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE); 131 132 assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); 133 assertLocalesForApp(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2); 134 } 135 136 /** 137 * Tests the scenario where the user sets the app-locales before the restore could be applied. 138 * 139 * <p>The locales from the backup data should be ignored in this case. 140 */ testBackupRestore_localeAlreadySet_doesNotRestore()141 public void testBackupRestore_localeAlreadySet_doesNotRestore() throws Exception { 142 if (!isBackupSupported()) { 143 return; 144 } 145 setAndBackupDefaultAppLocales(); 146 147 LocaleList newLocales = LocaleList.forLanguageTags("zh,hi"); 148 setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, newLocales); 149 setApplicationLocalesAndVerify(TEST_APP_PACKAGE_2, EMPTY_LOCALES); 150 151 getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE); 152 153 // Should restore only for app_2. 154 assertLocalesForApp(TEST_APP_PACKAGE_1, newLocales); 155 assertLocalesForApp(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2); 156 } 157 158 /** 159 * Tests the scenario when some apps are installed after the restore finishes. 160 * 161 * <p>More specifically, this tests the lazy restore where the locales are fetched and 162 * restored from the stage file if the app is installed within a certain amount of time after 163 * the initial restore. 164 */ testBackupRestore_appInstalledAfterRestore_doesLazyRestore()165 public void testBackupRestore_appInstalledAfterRestore_doesLazyRestore() throws Exception { 166 if (!isBackupSupported()) { 167 return; 168 } 169 setAndBackupDefaultAppLocales(); 170 171 resetAppLocales(); 172 173 uninstall(TEST_APP_PACKAGE_2); 174 175 getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE); 176 177 // Locales for App1 should be restored immediately since that's present already. 178 assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); 179 180 // This is to ensure there are no lingering broadcasts (could be from the setUp method 181 // where we are calling setApplicationLocales). 182 AmUtils.waitForBroadcastIdle(); 183 184 BlockingBroadcastReceiver appSpecificLocaleBroadcastReceiver = 185 new BlockingBroadcastReceiver(); 186 mContext.registerReceiver(appSpecificLocaleBroadcastReceiver, 187 new IntentFilter(Intent.ACTION_APPLICATION_LOCALE_CHANGED)); 188 189 // Hold Manifest.permission.READ_APP_SPECIFIC_LOCALES while the broadcast is sent, 190 // so that we receive it. 191 runWithShellPermissionIdentity(() -> { 192 // Installation will trigger lazy restore, which internally calls setApplicationLocales 193 // which sends out the ACTION_APPLICATION_LOCALE_CHANGED broadcast. 194 install(TEST_APP_APK_2); 195 appSpecificLocaleBroadcastReceiver.await(); 196 }, Manifest.permission.READ_APP_SPECIFIC_LOCALES); 197 198 appSpecificLocaleBroadcastReceiver.assertOneBroadcastReceived(); 199 appSpecificLocaleBroadcastReceiver.assertReceivedBroadcastContains(TEST_APP_PACKAGE_2, 200 DEFAULT_LOCALES_2); 201 202 // Verify that lazy restore occurred upon package install. 203 assertLocalesForApp(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2); 204 205 // APP2's entry is removed from the stage file after restore so nothing should be restored 206 // when APP2 is installed for the second time. 207 uninstall(TEST_APP_PACKAGE_2); 208 install(TEST_APP_APK_2); 209 assertLocalesForApp(TEST_APP_PACKAGE_2, EMPTY_LOCALES); 210 } 211 212 /** 213 * Tests the scenario when an application is removed from the device. 214 * 215 * <p>The data for the uninstalled app should be removed from the next backup pass. 216 */ testBackupRestore_uninstallApp_deletesDataFromBackup()217 public void testBackupRestore_uninstallApp_deletesDataFromBackup() throws Exception { 218 if (!isBackupSupported()) { 219 return; 220 } 221 setAndBackupDefaultAppLocales(); 222 223 // Uninstall an app and run the backup pass. The locales for the uninstalled app should 224 // be removed from the backup. 225 uninstall(TEST_APP_PACKAGE_2); 226 setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); 227 getBackupUtils().backupNowAndAssertSuccess(SYSTEM_PACKAGE); 228 229 install(TEST_APP_APK_2); 230 // Remove app1's locales so that it can be restored. 231 setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, EMPTY_LOCALES); 232 233 getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE); 234 235 // Restores only app1's locales because app2's data is no longer present in the backup. 236 assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); 237 assertLocalesForApp(TEST_APP_PACKAGE_2, EMPTY_LOCALES); 238 } 239 240 /** 241 * Tests the scenario when backup pass is run after retention period has expired. 242 * 243 * <p>Stage data should be removed since retention period has expired. 244 * <p><b>Note:</b>Manipulates device's system clock directly to simulate the passage of time. 245 */ testRetentionPeriod_backupPassAfterRetentionPeriod_removesStagedData()246 public void testRetentionPeriod_backupPassAfterRetentionPeriod_removesStagedData() 247 throws Exception { 248 if (!isBackupSupported()) { 249 return; 250 } 251 setAndBackupDefaultAppLocales(); 252 253 uninstall(TEST_APP_PACKAGE_2); 254 255 getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE); 256 257 // Locales for App1 should be restored immediately since that's present already. 258 assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); 259 260 DeviceConfigShellHelper deviceConfigShellHelper = new DeviceConfigShellHelper( 261 mShellCommandExecutor); 262 263 // This anticipates a future state where a generally applied target preparer may disable 264 // device_config sync for all CTS tests: only suspend syncing if it isn't already suspended, 265 // and only resume it if this test suspended it. 266 DeviceConfigShellHelper.PreTestState deviceConfigPreTestState = 267 deviceConfigShellHelper.setSyncModeForTest( 268 SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME); 269 270 TimeManager timeManager = mContext.getSystemService(TimeManager.class); 271 assertNotNull(timeManager); 272 273 // Capture clock values so that the system clock can be set back after the test. 274 long startCurrentTimeMillis = System.currentTimeMillis(); 275 long elapsedRealtimeMillis = SystemClock.elapsedRealtime(); 276 277 try { 278 279 // Set the time detector to only use ORIGIN_EXTERNAL. 280 deviceConfigShellHelper.put(NAMESPACE_SYSTEM_TIME, 281 DeviceConfigKeys.TimeDetector.KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE, 282 DeviceConfigKeys.TimeDetector.ORIGIN_EXTERNAL); 283 sleepForAsyncOperation(); 284 285 // 1 second elapses after retention period. 286 long afterRetentionPeriodMillis = Duration.ofMillis(startCurrentTimeMillis).plusMillis( 287 RETENTION_PERIOD.plusSeconds(1).toMillis()).toMillis(); 288 ExternalTimeSuggestion futureTimeSuggestion = 289 new ExternalTimeSuggestion(elapsedRealtimeMillis, afterRetentionPeriodMillis); 290 291 runWithShellPermissionIdentity(() -> { 292 timeManager.suggestExternalTime(futureTimeSuggestion); 293 }, Manifest.permission.SUGGEST_EXTERNAL_TIME); 294 sleepForAsyncOperation(); 295 296 // The suggestion should have been accepted so the system clock should have advanced. 297 assertTrue(System.currentTimeMillis() >= afterRetentionPeriodMillis); 298 299 // Run the backup pass now. 300 getBackupUtils().backupNowAndAssertSuccess(SYSTEM_PACKAGE); 301 } finally { 302 303 // Now do our best to return the device to its original state. 304 ExternalTimeSuggestion originalTimeSuggestion = 305 new ExternalTimeSuggestion(elapsedRealtimeMillis, startCurrentTimeMillis); 306 runWithShellPermissionIdentity(() -> { 307 timeManager.suggestExternalTime(originalTimeSuggestion); 308 }, Manifest.permission.SUGGEST_EXTERNAL_TIME); 309 sleepForAsyncOperation(); 310 311 deviceConfigShellHelper.restoreDeviceConfigStateForTest(deviceConfigPreTestState); 312 } 313 314 // We install the app after restoring the device time so that retention check during lazy 315 // restore doesn't try to delete the stage data. Hence, ensuring that stage data is deleted 316 // during the backup pass. 317 BlockingBroadcastReceiver appSpecificLocaleBroadcastReceiver = 318 new BlockingBroadcastReceiver(); 319 mContext.registerReceiver(appSpecificLocaleBroadcastReceiver, 320 new IntentFilter(Intent.ACTION_APPLICATION_LOCALE_CHANGED)); 321 322 // Hold Manifest.permission.READ_APP_SPECIFIC_LOCALES while the broadcast is sent, 323 // so that we receive it. 324 runWithShellPermissionIdentity(() -> { 325 // Installation will trigger lazy restore, which internally calls setApplicationLocales 326 // which sends out the ACTION_APPLICATION_LOCALE_CHANGED broadcast. 327 install(TEST_APP_APK_2); 328 appSpecificLocaleBroadcastReceiver.await(); 329 }, Manifest.permission.READ_APP_SPECIFIC_LOCALES); 330 331 appSpecificLocaleBroadcastReceiver.assertNoBroadcastReceived(); 332 333 // Does not restore the locales on package install. 334 assertLocalesForApp(TEST_APP_PACKAGE_2, EMPTY_LOCALES); 335 } 336 337 /** 338 * Tests the scenario when lazy restore happens after retention period has expired. 339 * 340 * <p>Stage data should be removed since retention period has expired. 341 * <p><b>Note:</b>Manipulates device's system clock directly to simulate the passage of time. 342 */ testRetentionPeriod_lazyRestoreAfterRetentionPeriod_removesStagedData()343 public void testRetentionPeriod_lazyRestoreAfterRetentionPeriod_removesStagedData() 344 throws Exception { 345 if (!isBackupSupported()) { 346 return; 347 } 348 setAndBackupDefaultAppLocales(); 349 350 uninstall(TEST_APP_PACKAGE_1); 351 uninstall(TEST_APP_PACKAGE_2); 352 353 getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE); 354 355 356 DeviceConfigShellHelper deviceConfigShellHelper = new DeviceConfigShellHelper( 357 mShellCommandExecutor); 358 359 // This anticipates a future state where a generally applied target preparer may disable 360 // device_config sync for all CTS tests: only suspend syncing if it isn't already suspended, 361 // and only resume it if this test suspended it. 362 DeviceConfigShellHelper.PreTestState deviceConfigPreTestState = 363 deviceConfigShellHelper.setSyncModeForTest( 364 SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME); 365 366 TimeManager timeManager = mContext.getSystemService(TimeManager.class); 367 assertNotNull(timeManager); 368 369 // Capture clock values so that the system clock can be set back after the test. 370 long startCurrentTimeMillis = System.currentTimeMillis(); 371 long elapsedRealtimeMillis = SystemClock.elapsedRealtime(); 372 373 try { 374 375 BlockingBroadcastReceiver appSpecificLocaleBroadcastReceiver = 376 new BlockingBroadcastReceiver(); 377 mContext.registerReceiver(appSpecificLocaleBroadcastReceiver, 378 new IntentFilter(Intent.ACTION_APPLICATION_LOCALE_CHANGED)); 379 380 // Hold Manifest.permission.READ_APP_SPECIFIC_LOCALES while the broadcast is sent, 381 // so that we receive it. 382 runWithShellPermissionIdentity(() -> { 383 // Installation will trigger lazy restore, which internally calls 384 // setApplicationLocales 385 // which sends out the ACTION_APPLICATION_LOCALE_CHANGED broadcast. 386 install(TEST_APP_APK_1); 387 appSpecificLocaleBroadcastReceiver.await(); 388 }, Manifest.permission.READ_APP_SPECIFIC_LOCALES); 389 390 appSpecificLocaleBroadcastReceiver.assertOneBroadcastReceived(); 391 appSpecificLocaleBroadcastReceiver.assertReceivedBroadcastContains(TEST_APP_PACKAGE_1, 392 DEFAULT_LOCALES_1); 393 assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); 394 appSpecificLocaleBroadcastReceiver.reset(); 395 396 // Set the time detector to only use ORIGIN_EXTERNAL. 397 deviceConfigShellHelper.put(NAMESPACE_SYSTEM_TIME, 398 DeviceConfigKeys.TimeDetector.KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE, 399 DeviceConfigKeys.TimeDetector.ORIGIN_EXTERNAL); 400 sleepForAsyncOperation(); 401 402 // 1 second elapses after retention period. 403 long afterRetentionPeriodMillis = Duration.ofMillis(startCurrentTimeMillis).plusMillis( 404 RETENTION_PERIOD.plusSeconds(1).toMillis()).toMillis(); 405 ExternalTimeSuggestion futureTimeSuggestion = 406 new ExternalTimeSuggestion(elapsedRealtimeMillis, afterRetentionPeriodMillis); 407 408 runWithShellPermissionIdentity(() -> { 409 timeManager.suggestExternalTime(futureTimeSuggestion); 410 }, Manifest.permission.SUGGEST_EXTERNAL_TIME); 411 sleepForAsyncOperation(); 412 413 // The suggestion should have been accepted so the system clock should have advanced. 414 assertTrue(System.currentTimeMillis() >= afterRetentionPeriodMillis); 415 416 // Hold Manifest.permission.READ_APP_SPECIFIC_LOCALES while the broadcast is sent, 417 // so that we receive it. 418 runWithShellPermissionIdentity(() -> { 419 // Installation will trigger lazy restore, which internally calls 420 // setApplicationLocales 421 // which sends out the ACTION_APPLICATION_LOCALE_CHANGED broadcast. 422 install(TEST_APP_APK_2); 423 appSpecificLocaleBroadcastReceiver.await(); 424 }, Manifest.permission.READ_APP_SPECIFIC_LOCALES); 425 426 appSpecificLocaleBroadcastReceiver.assertNoBroadcastReceived(); 427 428 // Does not restore the locales on package install. 429 assertLocalesForApp(TEST_APP_PACKAGE_2, EMPTY_LOCALES); 430 } finally { 431 432 // Now do our best to return the device to its original state. 433 ExternalTimeSuggestion originalTimeSuggestion = 434 new ExternalTimeSuggestion(elapsedRealtimeMillis, startCurrentTimeMillis); 435 runWithShellPermissionIdentity(() -> { 436 timeManager.suggestExternalTime(originalTimeSuggestion); 437 }, Manifest.permission.SUGGEST_EXTERNAL_TIME); 438 sleepForAsyncOperation(); 439 440 deviceConfigShellHelper.restoreDeviceConfigStateForTest(deviceConfigPreTestState); 441 } 442 } 443 444 /** 445 * Sleeps for a length of time sufficient to allow async operations to complete. Many time 446 * manager APIs are or could be asynchronous and deal with time, so there are no practical 447 * alternatives. 448 */ sleepForAsyncOperation()449 private static void sleepForAsyncOperation() throws Exception { 450 Thread.sleep(5_000); 451 } 452 453 // TODO(b/210593602): Add a test to check staged data removal after the retention period. 454 setApplicationLocalesAndVerify(String packageName, LocaleList locales)455 private void setApplicationLocalesAndVerify(String packageName, LocaleList locales) 456 throws Exception { 457 runWithShellPermissionIdentity(() -> 458 mLocaleManager.setApplicationLocales(packageName, locales), 459 Manifest.permission.CHANGE_CONFIGURATION); 460 assertLocalesForApp(packageName, locales); 461 } 462 463 /** 464 * Verifies that the locales are correctly set for another package 465 * by fetching locales of the app with a binder call. 466 */ assertLocalesForApp(String packageName, LocaleList expectedLocales)467 private void assertLocalesForApp(String packageName, 468 LocaleList expectedLocales) throws Exception { 469 assertEquals(expectedLocales, getApplicationLocales(packageName)); 470 } 471 getApplicationLocales(String packageName)472 private LocaleList getApplicationLocales(String packageName) throws Exception { 473 return callWithShellPermissionIdentity(() -> 474 mLocaleManager.getApplicationLocales(packageName), 475 Manifest.permission.READ_APP_SPECIFIC_LOCALES); 476 } 477 install(String apk)478 private void install(String apk) { 479 ShellUtils.runShellCommand("pm install -r " + apk); 480 } 481 uninstall(String packageName)482 private void uninstall(String packageName) { 483 ShellUtils.runShellCommand("pm uninstall " + packageName); 484 } 485 setAndBackupDefaultAppLocales()486 private void setAndBackupDefaultAppLocales() throws Exception { 487 setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1); 488 setApplicationLocalesAndVerify(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2); 489 // Backup the data for SYSTEM_PACKAGE which includes app-locales. 490 getBackupUtils().backupNowAndAssertSuccess(SYSTEM_PACKAGE); 491 } 492 resetAppLocales()493 private void resetAppLocales() throws Exception { 494 setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, EMPTY_LOCALES); 495 setApplicationLocalesAndVerify(TEST_APP_PACKAGE_2, EMPTY_LOCALES); 496 } 497 isAutoDetectionEnabled( DeviceShellCommandExecutor shellCommandExecutor)498 private boolean isAutoDetectionEnabled( 499 DeviceShellCommandExecutor shellCommandExecutor) throws Exception { 500 return shellCommandExecutor.executeToBoolean(SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED); 501 } 502 setAutoTimeEnabled( boolean enabled, DeviceShellCommandExecutor shellCommandExecutor)503 private boolean setAutoTimeEnabled( 504 boolean enabled, DeviceShellCommandExecutor shellCommandExecutor) throws Exception { 505 // Android T does not have a dedicated shell command or API to change time auto detection 506 // setting, so direct Settings changes are used. 507 Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME, 508 enabled ? 1 : 0); 509 510 sleepForAsyncOperation(); 511 512 return isAutoDetectionEnabled(shellCommandExecutor) == enabled; 513 } 514 515 private static final class BlockingBroadcastReceiver extends BroadcastReceiver { 516 private CountDownLatch mLatch = new CountDownLatch(1); 517 private String mPackageName; 518 private LocaleList mLocales; 519 private int mCalls; 520 521 @Override onReceive(Context context, Intent intent)522 public void onReceive(Context context, Intent intent) { 523 if (intent.hasExtra(Intent.EXTRA_PACKAGE_NAME)) { 524 mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); 525 } 526 if (intent.hasExtra(Intent.EXTRA_LOCALE_LIST)) { 527 mLocales = intent.getParcelableExtra(Intent.EXTRA_LOCALE_LIST); 528 } 529 mCalls += 1; 530 mLatch.countDown(); 531 } 532 await()533 public void await() throws Exception { 534 mLatch.await(/* timeout= */ 5, TimeUnit.SECONDS); 535 } 536 reset()537 public void reset() { 538 mLatch = new CountDownLatch(1); 539 mCalls = 0; 540 mPackageName = null; 541 mLocales = null; 542 } 543 assertOneBroadcastReceived()544 public void assertOneBroadcastReceived() { 545 assertEquals(1, mCalls); 546 } 547 assertNoBroadcastReceived()548 public void assertNoBroadcastReceived() { 549 assertEquals(0, mCalls); 550 } 551 552 /** 553 * Verifies that the broadcast received in the relevant apps have the correct information 554 * in the intent extras. It verifies the below extras: 555 * <ul> 556 * <li> {@link Intent#EXTRA_PACKAGE_NAME} 557 * <li> {@link Intent#EXTRA_LOCALE_LIST} 558 * </ul> 559 */ assertReceivedBroadcastContains(String expectedPackageName, LocaleList expectedLocales)560 public void assertReceivedBroadcastContains(String expectedPackageName, 561 LocaleList expectedLocales) { 562 assertEquals(expectedPackageName, mPackageName); 563 assertEquals(expectedLocales, mLocales); 564 } 565 } 566 } 567