• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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