1 /*
2  * Copyright (C) 2020 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.time.cts.host;
18 
19 
20 import static android.app.time.cts.shell.DeviceConfigKeys.LocationTimeZoneManager.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS;
21 import static android.app.time.cts.shell.DeviceConfigKeys.NAMESPACE_SYSTEM_TIME;
22 import static android.app.time.cts.shell.DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT;
23 import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.DEPENDENCY_STATUS_OK;
24 import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE;
25 import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FAKE_TZPS_APP_APK;
26 import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FAKE_TZPS_APP_PACKAGE;
27 import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.OPERATION_STATUS_OK;
28 import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_CERTAIN;
29 import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_DISABLED;
30 import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_INITIALIZING;
31 import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_UNCERTAIN;
32 
33 import static org.junit.Assert.assertEquals;
34 import static org.junit.Assert.assertFalse;
35 import static org.junit.Assert.assertTrue;
36 
37 import android.app.time.ControllerStateEnum;
38 import android.app.time.DetectionAlgorithmStatusEnum;
39 import android.app.time.LocationTimeZoneManagerServiceStateProto;
40 import android.app.time.LocationTimeZoneProviderEventProto;
41 import android.app.time.TimeZoneProviderStateEnum;
42 import android.app.time.TimeZoneProviderStateProto;
43 import android.app.time.cts.shell.DeviceConfigShellHelper;
44 import android.app.time.cts.shell.DeviceShellCommandExecutor;
45 import android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper;
46 import android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FakeTimeZoneProviderShellHelper;
47 import android.app.time.cts.shell.LocationShellHelper;
48 import android.app.time.cts.shell.LocationTimeZoneManagerShellHelper;
49 import android.app.time.cts.shell.TimeZoneDetectorShellHelper;
50 import android.app.time.cts.shell.host.HostShellCommandExecutor;
51 
52 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
53 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
54 
55 import com.google.protobuf.Parser;
56 
57 import org.junit.After;
58 import org.junit.Before;
59 import org.junit.Test;
60 import org.junit.runner.RunWith;
61 
62 import java.time.Duration;
63 import java.util.Arrays;
64 import java.util.List;
65 
66 /**
67  * Host-side CTS tests for the location time zone manager service. There are plenty of unit tests
68  * for individual components but manufacturers don't have to run them. This test is intended to
69  * provide confidence that the specific device configuration is likely to work as it should, i.e.
70  * this tests the actual location_time_zone_manager service on a given device.
71  *
72  * <p>Because there are a large set of possibilities, this test has to handle them all:
73  * <ul>
74  *     <li>location_time_zone_manager service disabled</li>
75  *     <li>location_time_zone_manager service enabled, but no LTZPs configured</li>
76  *     <li>location_time_zone_manager service enabled, with LTZPs configured</li>
77  * </ul>
78  *
79  * <p>Further complicating things is that devices may or may not have telephony detection, which
80  * influences what settings are used.
81  */
82 @RunWith(DeviceJUnit4ClassRunner.class)
83 public class LocationTimeZoneManagerHostTest extends BaseHostJUnit4Test {
84 
85     private static final String NON_EXISTENT_TZPS_APP_PACKAGE = "foobar";
86 
87     private boolean mIsTelephonyDetectionSupported;
88     private boolean mOriginalLocationEnabled;
89     private boolean mOriginalAutoDetectionEnabled;
90     private boolean mOriginalGeoDetectionEnabled;
91     private TimeZoneDetectorShellHelper mTimeZoneDetectorShellHelper;
92     private LocationTimeZoneManagerShellHelper mLocationTimeZoneManagerShellHelper;
93     private DeviceConfigShellHelper mDeviceConfigShellHelper;
94     private DeviceConfigShellHelper.PreTestState mDeviceConfigPreTestState;
95     private LocationShellHelper mLocationShellHelper;
96     private FakeTimeZoneProviderShellHelper mPrimaryFakeTimeZoneProviderShellHelper;
97     private FakeTimeZoneProviderShellHelper mSecondaryFakeTimeZoneProviderShellHelper;
98 
99     @Before
setUp()100     public void setUp() throws Exception {
101         DeviceShellCommandExecutor shellCommandExecutor = new HostShellCommandExecutor(getDevice());
102         mLocationTimeZoneManagerShellHelper =
103                 new LocationTimeZoneManagerShellHelper(shellCommandExecutor);
104 
105         // Confirm the service being tested is present. It can be turned off permanently in config,
106         // in which case there's nothing about it to test.
107         mLocationTimeZoneManagerShellHelper.assumeLocationTimeZoneManagerIsPresent();
108 
109         // Install the app that hosts the fake providers.
110         // Installations are tracked in BaseHostJUnit4Test and uninstalled automatically.
111         installPackage(FAKE_TZPS_APP_APK);
112 
113         mTimeZoneDetectorShellHelper = new TimeZoneDetectorShellHelper(shellCommandExecutor);
114         mLocationShellHelper = new LocationShellHelper(shellCommandExecutor);
115         mDeviceConfigShellHelper = new DeviceConfigShellHelper(shellCommandExecutor);
116 
117         // Stop device_config updates for the duration of the test.
118         mDeviceConfigPreTestState = mDeviceConfigShellHelper.setSyncModeForTest(
119                 SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME);
120 
121         // The isGeoDetectionEnabled() setting is only used when both time zone detection algorithms
122         // are supported on a device. It allows the user to select between them. When only
123         // location-based detection is supported then the setting is not used, i.e. the device
124         // behaves like it is hardcode to ON. Attempts to change the geo detection enabled setting
125         // when it isn't used by a device will fail so must be avoided.
126         // Location detection must be supported to reach this point. Capture the state of telephony
127         // algorithm support for later checks.
128         mIsTelephonyDetectionSupported =
129                 mTimeZoneDetectorShellHelper.isTelephonyDetectionSupported();
130 
131         // These original values try to record the raw value of the settings before the test ran:
132         // they may be ignored by the location_time_zone_manager service when they have no meaning.
133         // Unfortunately, we cannot tell if the value returned is the result of setting defaults or
134         // real values, which means we may not return things exactly as they were. To do better
135         // would require looking at raw settings values and use internal knowledge of settings keys.
136         mOriginalAutoDetectionEnabled = mTimeZoneDetectorShellHelper.isAutoDetectionEnabled();
137         mOriginalGeoDetectionEnabled = mTimeZoneDetectorShellHelper.isGeoDetectionEnabled();
138 
139         mLocationTimeZoneManagerShellHelper.stop();
140 
141         // Make sure location is enabled, otherwise the geo detection feature cannot operate.
142         mOriginalLocationEnabled = mLocationShellHelper.isLocationEnabledForCurrentUser();
143         if (!mOriginalLocationEnabled) {
144             mLocationShellHelper.setLocationEnabledForCurrentUser(true);
145         }
146 
147         // Restart the location_time_zone_manager with a do-nothing test config; some settings
148         // values cannot be set when the service knows that the settings won't be used. Devices
149         // can be encountered with the location_time_zone_manager enabled but with no providers
150         // installed. Starting the service with a valid-looking test provider config means we know
151         // settings changes will be accepted regardless of the real config.
152         String testPrimaryLocationTimeZoneProviderPackageName = NON_EXISTENT_TZPS_APP_PACKAGE;
153         String testSecondaryLocationTimeZoneProviderPackageName = null;
154         mLocationTimeZoneManagerShellHelper.startWithTestProviders(
155                 testPrimaryLocationTimeZoneProviderPackageName,
156                 testSecondaryLocationTimeZoneProviderPackageName,
157                 false /* recordProviderStates */);
158 
159         // Begin all tests with auto detection turned off.
160         if (mOriginalAutoDetectionEnabled) {
161             mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(false);
162         }
163 
164         if (mIsTelephonyDetectionSupported) {
165             // When the telephony detection algorithm is also supported on the device, we need to
166             // set the device settings so that location detection will be used. This behavior is
167             // implicit when the device only supports the location detection algorithm.
168             if (!mOriginalGeoDetectionEnabled) {
169                 mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
170             }
171         }
172 
173         // All tests begin with the location_time_zone_manager stopped so that fake providers can be
174         // configured.
175         mLocationTimeZoneManagerShellHelper.stop();
176 
177         // Make sure the fake provider APK install started above has completed before tests try to
178         // use the fake providers.
179         FakeTimeZoneProviderAppShellHelper fakeTimeZoneProviderAppShellHelper =
180                 new FakeTimeZoneProviderAppShellHelper(shellCommandExecutor);
181         // Delay until the fake TZPS app can be found.
182         fakeTimeZoneProviderAppShellHelper.waitForInstallation();
183         mPrimaryFakeTimeZoneProviderShellHelper =
184                 fakeTimeZoneProviderAppShellHelper.getPrimaryLocationProviderHelper();
185         mSecondaryFakeTimeZoneProviderShellHelper =
186                 fakeTimeZoneProviderAppShellHelper.getSecondaryLocationProviderHelper();
187     }
188 
189     @After
tearDown()190     public void tearDown() throws Exception {
191         if (!mLocationTimeZoneManagerShellHelper.isLocationTimeZoneManagerPresent()) {
192             // Nothing to tear down.
193             return;
194         }
195 
196         // Restart the location_time_zone_manager with a test config so that the device can be set
197         // back to the starting state regardless of how the test left things.
198         mLocationTimeZoneManagerShellHelper.stop();
199         String testPrimaryLocationTimeZoneProviderPackageName = NON_EXISTENT_TZPS_APP_PACKAGE;
200         String testSecondaryLocationTimeZoneProviderPackageName = null;
201         mLocationTimeZoneManagerShellHelper.startWithTestProviders(
202                 testPrimaryLocationTimeZoneProviderPackageName,
203                 testSecondaryLocationTimeZoneProviderPackageName,
204                 false /* recordProviderStates */);
205 
206         if (mIsTelephonyDetectionSupported) {
207             if (mTimeZoneDetectorShellHelper.isGeoDetectionEnabled()
208                     != mOriginalGeoDetectionEnabled) {
209                 mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(mOriginalGeoDetectionEnabled);
210             }
211         }
212 
213         if (mTimeZoneDetectorShellHelper.isAutoDetectionEnabled()
214                 != mOriginalAutoDetectionEnabled) {
215             mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(mOriginalAutoDetectionEnabled);
216         }
217 
218         // Everything else can be reset without worrying about the providers.
219         mLocationTimeZoneManagerShellHelper.stop();
220 
221         mLocationShellHelper.setLocationEnabledForCurrentUser(mOriginalLocationEnabled);
222         mDeviceConfigShellHelper.restoreDeviceConfigStateForTest(mDeviceConfigPreTestState);
223 
224         // Attempt to start the service without test providers. It may not start if there are no
225         // providers configured, but that is ok.
226         mLocationTimeZoneManagerShellHelper.start();
227     }
228 
229     @Test
testOnlyPrimary_suggestionMade()230     public void testOnlyPrimary_suggestionMade() throws Exception {
231         testOnlyPrimary_suggestionMade(false);
232     }
233 
234     @Test
testOnlyPrimary_suggestionMade_legacy()235     public void testOnlyPrimary_suggestionMade_legacy() throws Exception {
236         testOnlyPrimary_suggestionMade(true);
237     }
238 
239     /** Tests what happens when there's only a primary provider and it makes a suggestion. */
testOnlyPrimary_suggestionMade(boolean useLegacyApi)240     private void testOnlyPrimary_suggestionMade(boolean useLegacyApi) throws Exception {
241         String testPrimaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
242         String testSecondaryLocationTimeZoneProviderPackageName = null;
243         mLocationTimeZoneManagerShellHelper.startWithTestProviders(
244                 testPrimaryLocationTimeZoneProviderPackageName,
245                 testSecondaryLocationTimeZoneProviderPackageName,
246                 true /* recordProviderStates */);
247 
248         // Turn on auto detection, which should activate the location time zone algorithm.
249         mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(true);
250 
251         mPrimaryFakeTimeZoneProviderShellHelper.assertCreated();
252         mSecondaryFakeTimeZoneProviderShellHelper.assertNotCreated();
253 
254         {
255             LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
256             assertControllerStateHistory(serviceState,
257                     ControllerStateEnum.CONTROLLER_STATE_PROVIDERS_INITIALIZING,
258                     ControllerStateEnum.CONTROLLER_STATE_STOPPED,
259                     ControllerStateEnum.CONTROLLER_STATE_INITIALIZING);
260             assertProviderStates(serviceState.getPrimaryProviderStatesList(),
261                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED,
262                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING);
263             mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_INITIALIZING);
264 
265             assertProviderStates(serviceState.getSecondaryProviderStatesList(),
266                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED,
267                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_PERM_FAILED);
268         }
269         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
270 
271         reportSuccess(mPrimaryFakeTimeZoneProviderShellHelper, "Europe/London", useLegacyApi);
272 
273         {
274             LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
275             assertControllerStateHistory(serviceState,
276                     ControllerStateEnum.CONTROLLER_STATE_CERTAIN);
277             assertLastEventWithSuggestion(serviceState, "Europe/London");
278             assertProviderStates(serviceState.getPrimaryProviderStatesList(),
279                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
280             mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_CERTAIN);
281 
282             assertProviderStates(serviceState.getSecondaryProviderStatesList());
283         }
284     }
285 
286     @Test
test_dupeSuggestionsMade_rateLimited()287     public void test_dupeSuggestionsMade_rateLimited() throws Exception {
288         test_dupeSuggestionsMade_rateLimited(false);
289     }
290 
291     @Test
test_dupeSuggestionsMade_rateLimited_legacy()292     public void test_dupeSuggestionsMade_rateLimited_legacy() throws Exception {
293         test_dupeSuggestionsMade_rateLimited(true);
294     }
295 
296     /**
297      * Demonstrates that duplicate equivalent reports made by location time zone providers within
298      * a threshold time are ignored. It focuses on a single LTZP setup (primary only); the behavior
299      * for the secondary is assumed to be identical.
300      */
test_dupeSuggestionsMade_rateLimited(boolean useLegacyApis)301     private void test_dupeSuggestionsMade_rateLimited(boolean useLegacyApis) throws Exception {
302         // Set the rate setting sufficiently high that rate limiting will definitely take place.
303         mDeviceConfigShellHelper.put(NAMESPACE_SYSTEM_TIME,
304                 KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
305                 Long.toString(Duration.ofMinutes(10).toMillis()));
306 
307         String testPrimaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
308         String testSecondaryLocationTimeZoneProviderPackageName = null;
309         mLocationTimeZoneManagerShellHelper.startWithTestProviders(
310                 testPrimaryLocationTimeZoneProviderPackageName,
311                 testSecondaryLocationTimeZoneProviderPackageName,
312                 true /* recordProviderStates */);
313         // Turn on auto detection, which should activate the location time zone algorithm.
314         mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(true);
315 
316         mPrimaryFakeTimeZoneProviderShellHelper.assertCreated();
317         mSecondaryFakeTimeZoneProviderShellHelper.assertNotCreated();
318 
319         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
320 
321         // Report a new time zone.
322         reportSuccess(mPrimaryFakeTimeZoneProviderShellHelper, "Europe/London", useLegacyApis);
323         assertPrimaryReportedCertain();
324         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
325 
326         // Duplicate time zone suggestion.
327         reportSuccess(mPrimaryFakeTimeZoneProviderShellHelper, "Europe/London", useLegacyApis);
328         assertPrimaryMadeNoReport();
329         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
330 
331         // Report a new time zone.
332         reportSuccess(mPrimaryFakeTimeZoneProviderShellHelper, "Europe/Paris", useLegacyApis);
333         assertPrimaryReportedCertain();
334         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
335 
336         // Duplicate time zone suggestion.
337         reportSuccess(mPrimaryFakeTimeZoneProviderShellHelper, "Europe/Paris", useLegacyApis);
338         assertPrimaryMadeNoReport();
339         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
340 
341         // Report uncertain.
342         reportUncertain(mPrimaryFakeTimeZoneProviderShellHelper, useLegacyApis);
343         assertPrimaryReportedUncertain();
344         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
345 
346         // Duplicate uncertain report.
347         reportUncertain(mPrimaryFakeTimeZoneProviderShellHelper, useLegacyApis);
348         assertPrimaryMadeNoReport();
349         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
350 
351         // Report a new time zone.
352         reportSuccess(mPrimaryFakeTimeZoneProviderShellHelper, "Europe/Paris", useLegacyApis);
353         assertPrimaryReportedCertain();
354         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
355     }
356 
357     @Test
test_dupeSuggestionsMade_notRateLimited()358     public void test_dupeSuggestionsMade_notRateLimited() throws Exception {
359         test_dupeSuggestionsMade_notRateLimited(false);
360     }
361 
362     @Test
test_dupeSuggestionsMade_notRateLimited_legacy()363     public void test_dupeSuggestionsMade_notRateLimited_legacy() throws Exception {
364         test_dupeSuggestionsMade_notRateLimited(true);
365     }
366 
367     /**
368      * Demonstrates that duplicate equivalent reports made by location time zone providers above
369      * a threshold time are not filtered. It focuses on a single LTZP setup (primary only); the
370      * behavior for the secondary is assumed to be identical.
371      */
test_dupeSuggestionsMade_notRateLimited(boolean useLegacyApis)372     private void test_dupeSuggestionsMade_notRateLimited(boolean useLegacyApis) throws Exception {
373         // Set the rate sufficiently low that rate limiting will not take place.
374         mDeviceConfigShellHelper.put(NAMESPACE_SYSTEM_TIME,
375                 KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
376                 "0");
377 
378         String testPrimaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
379         String testSecondaryLocationTimeZoneProviderPackageName = null;
380         mLocationTimeZoneManagerShellHelper.startWithTestProviders(
381                 testPrimaryLocationTimeZoneProviderPackageName,
382                 testSecondaryLocationTimeZoneProviderPackageName,
383                 true /* recordProviderStates */);
384         // Turn on auto detection, which should activate the location time zone algorithm.
385         mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(true);
386         mPrimaryFakeTimeZoneProviderShellHelper.assertCreated();
387         mSecondaryFakeTimeZoneProviderShellHelper.assertNotCreated();
388 
389         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
390 
391         // Report a new time zone.
392         reportSuccess(mPrimaryFakeTimeZoneProviderShellHelper, "Europe/London", useLegacyApis);
393         assertPrimaryReportedCertain();
394         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
395 
396         // Duplicate time zone suggestion.
397         reportSuccess(mPrimaryFakeTimeZoneProviderShellHelper, "Europe/London", useLegacyApis);
398         assertPrimaryReportedCertain();
399         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
400 
401         // Report uncertain.
402         reportUncertain(mPrimaryFakeTimeZoneProviderShellHelper, useLegacyApis);
403         assertPrimaryReportedUncertain();
404         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
405 
406         // Duplicate uncertain report.
407         reportUncertain(mPrimaryFakeTimeZoneProviderShellHelper, useLegacyApis);
408         assertPrimaryReportedUncertain();
409         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
410     }
411 
assertPrimaryReportedCertain()412     private void assertPrimaryReportedCertain() throws Exception {
413         LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
414         assertProviderStates(serviceState.getPrimaryProviderStatesList(),
415                 TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
416     }
417 
assertPrimaryMadeNoReport()418     private void assertPrimaryMadeNoReport() throws Exception {
419         LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
420         assertProviderStates(serviceState.getPrimaryProviderStatesList());
421     }
422 
assertPrimaryReportedUncertain()423     private void assertPrimaryReportedUncertain() throws Exception {
424         LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
425         assertProviderStates(serviceState.getPrimaryProviderStatesList(),
426                 TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_UNCERTAIN);
427     }
428 
429     /** Tests what happens when there's only a secondary provider and it makes a suggestion. */
430     @Test
testOnlySecondary_suggestionMade()431     public void testOnlySecondary_suggestionMade() throws Exception {
432         testOnlySecondary_suggestionMade(false);
433     }
434 
435     /** Tests what happens when there's only a secondary provider and it makes a suggestion. */
436     @Test
testOnlySecondary_suggestionMade_legacy()437     public void testOnlySecondary_suggestionMade_legacy() throws Exception {
438         testOnlySecondary_suggestionMade(true);
439     }
440 
testOnlySecondary_suggestionMade(boolean useLegacyApis)441     private void testOnlySecondary_suggestionMade(boolean useLegacyApis) throws Exception {
442         String testPrimaryLocationTimeZoneProviderPackageName = null;
443         String testSecondaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
444         mLocationTimeZoneManagerShellHelper.startWithTestProviders(
445                 testPrimaryLocationTimeZoneProviderPackageName,
446                 testSecondaryLocationTimeZoneProviderPackageName,
447                 true /* recordProviderStates */);
448         // Turn on auto detection, which should activate the location time zone algorithm.
449         mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(true);
450         mPrimaryFakeTimeZoneProviderShellHelper.assertNotCreated();
451         mSecondaryFakeTimeZoneProviderShellHelper.assertCreated();
452 
453         {
454             LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
455             assertControllerStateHistory(serviceState,
456                     ControllerStateEnum.CONTROLLER_STATE_PROVIDERS_INITIALIZING,
457                     ControllerStateEnum.CONTROLLER_STATE_STOPPED,
458                     ControllerStateEnum.CONTROLLER_STATE_INITIALIZING);
459             assertLastEventWithoutSuggestion(serviceState);
460             assertProviderStates(serviceState.getPrimaryProviderStatesList(),
461                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED,
462                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_PERM_FAILED);
463 
464             assertProviderStates(serviceState.getSecondaryProviderStatesList(),
465                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED,
466                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING);
467         }
468         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
469 
470         reportSuccess(mSecondaryFakeTimeZoneProviderShellHelper, "Europe/London", useLegacyApis);
471 
472         {
473             LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
474             assertControllerStateHistory(serviceState,
475                     ControllerStateEnum.CONTROLLER_STATE_CERTAIN);
476             assertLastEventWithSuggestion(serviceState, "Europe/London");
477             assertProviderStates(serviceState.getPrimaryProviderStatesList());
478 
479             assertProviderStates(serviceState.getSecondaryProviderStatesList(),
480                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
481             mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_CERTAIN);
482         }
483     }
484 
485     @Test
testPrimaryAndSecondary()486     public void testPrimaryAndSecondary() throws Exception {
487         testPrimaryAndSecondary(false);
488     }
489 
490     @Test
testPrimaryAndSecondary_legacy()491     public void testPrimaryAndSecondary_legacy() throws Exception {
492         testPrimaryAndSecondary(true);
493     }
494 
495     /**
496      * Tests what happens when there's both a primary and a secondary provider, the primary starts
497      * by being uncertain, the secondary makes a suggestion, then the primary makes a suggestion.
498      */
testPrimaryAndSecondary(boolean useLegacyApis)499     private void testPrimaryAndSecondary(boolean useLegacyApis) throws Exception {
500         String testPrimaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
501         String testSecondaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
502         mLocationTimeZoneManagerShellHelper.startWithTestProviders(
503                 testPrimaryLocationTimeZoneProviderPackageName,
504                 testSecondaryLocationTimeZoneProviderPackageName,
505                 true /* recordProviderStates*/);
506         // Turn on auto detection, which should activate the location time zone algorithm.
507         mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(true);
508         mPrimaryFakeTimeZoneProviderShellHelper.assertCreated();
509         mSecondaryFakeTimeZoneProviderShellHelper.assertCreated();
510 
511         {
512             LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
513             assertControllerStateHistory(serviceState,
514                     ControllerStateEnum.CONTROLLER_STATE_PROVIDERS_INITIALIZING,
515                     ControllerStateEnum.CONTROLLER_STATE_STOPPED,
516                     ControllerStateEnum.CONTROLLER_STATE_INITIALIZING);
517             assertLastEventWithoutSuggestion(serviceState);
518             assertProviderStates(serviceState.getPrimaryProviderStatesList(),
519                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED,
520                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING);
521             mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_INITIALIZING);
522 
523             assertProviderStates(serviceState.getSecondaryProviderStatesList(),
524                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED);
525             mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_DISABLED);
526         }
527         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
528 
529         // Make the primary report being uncertain. This should cause the secondary to be started.
530         reportUncertain(mPrimaryFakeTimeZoneProviderShellHelper, useLegacyApis);
531 
532         {
533             LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
534             assertControllerStateHistory(serviceState);
535             assertLastEventWithoutSuggestion(serviceState);
536             assertProviderStates(serviceState.getPrimaryProviderStatesList(),
537                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_UNCERTAIN);
538             mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_UNCERTAIN);
539 
540             assertProviderStates(serviceState.getSecondaryProviderStatesList(),
541                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING);
542             mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(
543                     PROVIDER_STATE_INITIALIZING);
544         }
545         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
546 
547         // Make the secondary report being certain.
548         reportSuccess(mSecondaryFakeTimeZoneProviderShellHelper, "Europe/London", useLegacyApis);
549 
550         {
551             LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
552             assertControllerStateHistory(serviceState,
553                     ControllerStateEnum.CONTROLLER_STATE_CERTAIN);
554             assertLastEventWithSuggestion(serviceState, "Europe/London");
555             assertProviderStates(serviceState.getPrimaryProviderStatesList());
556             mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_UNCERTAIN);
557 
558             assertProviderStates(serviceState.getSecondaryProviderStatesList(),
559                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
560             mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_CERTAIN);
561         }
562         mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
563 
564         // Make the primary report being certain.
565         reportSuccess(mPrimaryFakeTimeZoneProviderShellHelper, "Europe/Paris", useLegacyApis);
566 
567         {
568             LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
569             assertControllerStateHistory(serviceState);
570             assertLastEventWithSuggestion(serviceState, "Europe/Paris");
571             assertProviderStates(serviceState.getPrimaryProviderStatesList(),
572                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
573             mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_CERTAIN);
574 
575             assertProviderStates(serviceState.getSecondaryProviderStatesList(),
576                     TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED);
577             mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_DISABLED);
578         }
579     }
580 
assertControllerStateHistory( LocationTimeZoneManagerServiceStateProto serviceState, ControllerStateEnum... expectedStates)581     private static void assertControllerStateHistory(
582             LocationTimeZoneManagerServiceStateProto serviceState,
583             ControllerStateEnum... expectedStates) {
584         List<ControllerStateEnum> expectedStatesList = Arrays.asList(expectedStates);
585         List<ControllerStateEnum> actualStates = serviceState.getControllerStatesList();
586         assertEquals(expectedStatesList, actualStates);
587     }
588 
assertLastEventWithoutSuggestion( LocationTimeZoneManagerServiceStateProto actualServiceState)589     private static void assertLastEventWithoutSuggestion(
590             LocationTimeZoneManagerServiceStateProto actualServiceState) {
591         assertTrue(actualServiceState.hasLastEvent());
592         assertFalse(actualServiceState.getLastEvent().hasSuggestion());
593 
594         LocationTimeZoneProviderEventProto lastEvent = actualServiceState.getLastEvent();
595         assertEquals(DetectionAlgorithmStatusEnum.DETECTION_ALGORITHM_STATUS_RUNNING,
596                 lastEvent.getAlgorithmStatus().getStatus());
597     }
598 
assertLastEventWithSuggestion( LocationTimeZoneManagerServiceStateProto actualServiceState, String... expectedTimeZones)599     private static void assertLastEventWithSuggestion(
600             LocationTimeZoneManagerServiceStateProto actualServiceState,
601             String... expectedTimeZones) {
602         assertFalse(expectedTimeZones == null || expectedTimeZones.length == 0);
603 
604         assertTrue(actualServiceState.hasLastEvent());
605         LocationTimeZoneProviderEventProto lastEvent = actualServiceState.getLastEvent();
606 
607         assertEquals(DetectionAlgorithmStatusEnum.DETECTION_ALGORITHM_STATUS_RUNNING,
608                 lastEvent.getAlgorithmStatus().getStatus());
609 
610         List<String> expectedTimeZonesList = Arrays.asList(expectedTimeZones);
611         List<String> actualTimeZonesList = lastEvent.getSuggestion().getZoneIdsList();
612         assertEquals(expectedTimeZonesList, actualTimeZonesList);
613     }
614 
assertProviderStates(List<TimeZoneProviderStateProto> actualStates, TimeZoneProviderStateEnum... expectedStates)615     private static void assertProviderStates(List<TimeZoneProviderStateProto> actualStates,
616             TimeZoneProviderStateEnum... expectedStates) {
617         List<TimeZoneProviderStateEnum> expectedStatesList = Arrays.asList(expectedStates);
618         assertEquals("Expected states: " + expectedStatesList + ", but was " + actualStates,
619                 expectedStatesList.size(), actualStates.size());
620         for (int i = 0; i < expectedStatesList.size(); i++) {
621             assertEquals("Expected states: " + expectedStatesList + ", but was " + actualStates,
622                     expectedStates[i], actualStates.get(i).getState());
623         }
624     }
625 
dumpServiceState()626     private LocationTimeZoneManagerServiceStateProto dumpServiceState() throws Exception {
627         byte[] protoBytes = mLocationTimeZoneManagerShellHelper.dumpState();
628         Parser<LocationTimeZoneManagerServiceStateProto> parser =
629                 LocationTimeZoneManagerServiceStateProto.parser();
630         return parser.parseFrom(protoBytes);
631     }
632 
633     /**
634      * Method used to report success when it shouldn't matter whether newer APIs that include status
635      * or older APIs that don't are used. The status provided for newer APIs is a generic "success"
636      * status.
637      */
reportSuccess(FakeTimeZoneProviderShellHelper providerShellHelper, String zoneId, boolean useLegacyApi)638     private void reportSuccess(FakeTimeZoneProviderShellHelper providerShellHelper, String zoneId,
639             boolean useLegacyApi) throws Exception {
640         if (useLegacyApi) {
641             providerShellHelper.reportSuccessLegacy(zoneId);
642         } else {
643             providerShellHelper.reportSuccess(
644                     zoneId, DEPENDENCY_STATUS_OK, DEPENDENCY_STATUS_OK);
645         }
646     }
647 
648     /**
649      * Method used to report uncertainty when it shouldn't matter whether newer APIs that include
650      * status or older APIs that don't are used. The status provided for newer APIs is a generic
651      * "uncertain" status that doesn't trigger any interesting behavior.
652      */
reportUncertain(FakeTimeZoneProviderShellHelper providerShellHelper, boolean useLegacyApis)653     private void reportUncertain(FakeTimeZoneProviderShellHelper providerShellHelper,
654             boolean useLegacyApis) throws Exception {
655         if (useLegacyApis) {
656             providerShellHelper.reportUncertainLegacy();
657         } else {
658             providerShellHelper.reportUncertain(
659                     DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE, DEPENDENCY_STATUS_OK,
660                     OPERATION_STATUS_OK);
661         }
662     }
663 }
664