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.time.cts.host;
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 import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FAKE_TZPS_APP_APK;
22 import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FAKE_TZPS_APP_PACKAGE;
23 
24 import static java.util.stream.Collectors.toList;
25 
26 import android.app.time.cts.shell.DeviceConfigShellHelper;
27 import android.app.time.cts.shell.DeviceShellCommandExecutor;
28 import android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper;
29 import android.app.time.cts.shell.LocationShellHelper;
30 import android.app.time.cts.shell.LocationTimeZoneManagerShellHelper;
31 import android.app.time.cts.shell.TimeZoneDetectorShellHelper;
32 import android.app.time.cts.shell.host.HostShellCommandExecutor;
33 import android.cts.statsdatom.lib.AtomTestUtils;
34 import android.cts.statsdatom.lib.ConfigUtils;
35 import android.cts.statsdatom.lib.DeviceUtils;
36 import android.cts.statsdatom.lib.ReportUtils;
37 
38 import com.android.os.AtomsProto;
39 import com.android.os.AtomsProto.LocationTimeZoneProviderStateChanged;
40 import com.android.os.StatsLog;
41 import com.android.tradefed.device.ITestDevice;
42 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
43 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
44 import com.android.tradefed.util.RunUtil;
45 
46 import org.junit.After;
47 import org.junit.Before;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 
51 import java.util.Arrays;
52 import java.util.Collections;
53 import java.util.List;
54 import java.util.Set;
55 import java.util.function.Function;
56 
57 /**
58  * Host-side CTS tests for the location time zone manager service stats logging. Very similar to
59  * {@link LocationTimeZoneManagerHostTest} but focused on stats logging.
60  */
61 @RunWith(DeviceJUnit4ClassRunner.class)
62 public class LocationTimeZoneManagerStatsTest extends BaseHostJUnit4Test {
63 
64     private static final String NON_EXISTENT_TZPS_APP_PACKAGE = "foobar";
65 
66     private static final int PRIMARY_PROVIDER_INDEX = 0;
67     private static final int SECONDARY_PROVIDER_INDEX = 1;
68 
69     private static final int PROVIDER_STATES_COUNT =
70             LocationTimeZoneProviderStateChanged.State.values().length;
71 
72     private TimeZoneDetectorShellHelper mTimeZoneDetectorShellHelper;
73     private LocationTimeZoneManagerShellHelper mLocationTimeZoneManagerShellHelper;
74     private LocationShellHelper mLocationShellHelper;
75     private DeviceConfigShellHelper mDeviceConfigShellHelper;
76     private DeviceConfigShellHelper.PreTestState mDeviceConfigPreTestState;
77 
78     private boolean mIsTelephonyDetectionSupported;
79     private boolean mOriginalLocationEnabled;
80     private boolean mOriginalAutoDetectionEnabled;
81     private boolean mOriginalGeoDetectionEnabled;
82 
83     @Before
setUp()84     public void setUp() throws Exception {
85         ITestDevice device = getDevice();
86         DeviceShellCommandExecutor shellCommandExecutor = new HostShellCommandExecutor(device);
87         mLocationTimeZoneManagerShellHelper =
88                 new LocationTimeZoneManagerShellHelper(shellCommandExecutor);
89 
90         // Confirm the service being tested is present. It can be turned off permanently in config,
91         // in which case there's nothing about it to test.
92         mLocationTimeZoneManagerShellHelper.assumeLocationTimeZoneManagerIsPresent();
93 
94         // Install the app that hosts the fake providers.
95         // Installations are tracked in BaseHostJUnit4Test and uninstalled automatically.
96         installPackage(FAKE_TZPS_APP_APK);
97 
98         mTimeZoneDetectorShellHelper = new TimeZoneDetectorShellHelper(shellCommandExecutor);
99         mLocationShellHelper = new LocationShellHelper(shellCommandExecutor);
100         mDeviceConfigShellHelper = new DeviceConfigShellHelper(shellCommandExecutor);
101 
102         // Stop device_config updates for the duration of the test.
103         mDeviceConfigPreTestState = mDeviceConfigShellHelper.setSyncModeForTest(
104                 SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME);
105 
106         // The isGeoDetectionEnabled() setting is only used when both time zone detection algorithms
107         // are supported on a device. It allows the user to select between them. When only
108         // location-based detection is supported then the setting is not used, i.e. the device
109         // behaves like it is hardcode to ON. Attempts to change the geo detection enabled setting
110         // when it isn't used by a device will fail so must be avoided.
111         // Location detection must be supported to reach this point. Capture the state of telephony
112         // algorithm support for later checks.
113         mIsTelephonyDetectionSupported =
114                 mTimeZoneDetectorShellHelper.isTelephonyDetectionSupported();
115 
116         // These original values try to record the raw value of the settings before the test ran:
117         // they may be ignored by the location_time_zone_manager service when they have no meaning.
118         // Unfortunately, we cannot tell if the value returned is the result of setting defaults or
119         // real values, which means we may not return things exactly as they were. To do better
120         // would require looking at raw settings values and use internal knowledge of settings keys.
121         mOriginalAutoDetectionEnabled = mTimeZoneDetectorShellHelper.isAutoDetectionEnabled();
122         mOriginalGeoDetectionEnabled = mTimeZoneDetectorShellHelper.isGeoDetectionEnabled();
123 
124         mLocationTimeZoneManagerShellHelper.stop();
125 
126         // Make sure location is enabled, otherwise the geo detection feature cannot operate.
127         mOriginalLocationEnabled = mLocationShellHelper.isLocationEnabledForCurrentUser();
128         if (!mOriginalLocationEnabled) {
129             mLocationShellHelper.setLocationEnabledForCurrentUser(true);
130         }
131 
132         // Restart the location_time_zone_manager with a do-nothing test config; some settings
133         // values cannot be set when the service knows that the settings won't be used. Devices
134         // can be encountered with the location_time_zone_manager enabled but with no providers
135         // installed. Starting the service with a valid-looking test provider config means we know
136         // settings changes will be accepted regardless of the real config.
137         String testPrimaryLocationTimeZoneProviderPackageName = NON_EXISTENT_TZPS_APP_PACKAGE;
138         String testSecondaryLocationTimeZoneProviderPackageName = null;
139         mLocationTimeZoneManagerShellHelper.startWithTestProviders(
140                 testPrimaryLocationTimeZoneProviderPackageName,
141                 testSecondaryLocationTimeZoneProviderPackageName,
142                 false /* recordProviderStates */);
143 
144         // Begin all tests with auto detection turned off.
145         if (mOriginalAutoDetectionEnabled) {
146             mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(false);
147         }
148 
149         if (mIsTelephonyDetectionSupported) {
150             // When the telephony detection algorithm is also supported on the device, we need to
151             // set the device settings so that location detection will be used. This behavior is
152             // implicit when the device only supports the location detection algorithm.
153             if (!mOriginalGeoDetectionEnabled) {
154                 mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
155             }
156         }
157 
158         // All tests begin with the location_time_zone_manager stopped so that fake providers can be
159         // configured.
160         mLocationTimeZoneManagerShellHelper.stop();
161 
162         // Make sure the fake provider APK install started above has completed before tests try to
163         // use the fake providers.
164         FakeTimeZoneProviderAppShellHelper fakeTimeZoneProviderAppShellHelper =
165                 new FakeTimeZoneProviderAppShellHelper(shellCommandExecutor);
166         // Delay until the fake TZPS app can be found.
167         fakeTimeZoneProviderAppShellHelper.waitForInstallation();
168 
169         ConfigUtils.removeConfig(device);
170         ReportUtils.clearReports(device);
171     }
172 
173     @After
tearDown()174     public void tearDown() throws Exception {
175         if (!mLocationTimeZoneManagerShellHelper.isLocationTimeZoneManagerPresent()) {
176             // Nothing to tear down.
177             return;
178         }
179 
180         // Restart the location_time_zone_manager with a test config so that the device can be set
181         // back to the starting state regardless of how the test left things.
182         mLocationTimeZoneManagerShellHelper.stop();
183         String testPrimaryLocationTimeZoneProviderPackageName = NON_EXISTENT_TZPS_APP_PACKAGE;
184         String testSecondaryLocationTimeZoneProviderPackageName = null;
185         mLocationTimeZoneManagerShellHelper.startWithTestProviders(
186                 testPrimaryLocationTimeZoneProviderPackageName,
187                 testSecondaryLocationTimeZoneProviderPackageName,
188                 false /* recordProviderStates */);
189 
190         if (mIsTelephonyDetectionSupported) {
191             if (mTimeZoneDetectorShellHelper.isGeoDetectionEnabled()
192                     != mOriginalGeoDetectionEnabled) {
193                 mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(mOriginalGeoDetectionEnabled);
194             }
195         }
196 
197         if (mTimeZoneDetectorShellHelper.isAutoDetectionEnabled()
198                 != mOriginalAutoDetectionEnabled) {
199             mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(mOriginalAutoDetectionEnabled);
200         }
201 
202         // Everything else can be reset without worrying about the providers.
203         mLocationTimeZoneManagerShellHelper.stop();
204 
205         mLocationShellHelper.setLocationEnabledForCurrentUser(mOriginalLocationEnabled);
206 
207         ConfigUtils.removeConfig(getDevice());
208         ReportUtils.clearReports(getDevice());
209         mDeviceConfigShellHelper.restoreDeviceConfigStateForTest(mDeviceConfigPreTestState);
210 
211         // Attempt to start the service without test providers. It may not start if there are no
212         // providers configured, but that is ok.
213         mLocationTimeZoneManagerShellHelper.start();
214     }
215 
216     @Test
testAtom_locationTimeZoneProviderStateChanged()217     public void testAtom_locationTimeZoneProviderStateChanged() throws Exception {
218         ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
219                 AtomsProto.Atom.LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED_FIELD_NUMBER);
220 
221         String testPrimaryLocationTimeZoneProviderPackageName = null;
222         String testSecondaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
223         mLocationTimeZoneManagerShellHelper.startWithTestProviders(
224                 testPrimaryLocationTimeZoneProviderPackageName,
225                 testSecondaryLocationTimeZoneProviderPackageName,
226                 true /* recordProviderStates */);
227 
228         // Turn the location detection algorithm on and off, twice.
229         for (int i = 0; i < 2; i++) {
230             RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
231             mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(true);
232             RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
233             mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(false);
234         }
235 
236         // Sorted list of events in order in which they occurred.
237         List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
238 
239         // States.
240         Set<Integer> primaryProviderCreated = singletonStateId(PRIMARY_PROVIDER_INDEX,
241                 LocationTimeZoneProviderStateChanged.State.STOPPED);
242         Set<Integer> primaryProviderFailed = singletonStateId(PRIMARY_PROVIDER_INDEX,
243                 LocationTimeZoneProviderStateChanged.State.PERM_FAILED);
244         Set<Integer> secondaryProviderCreated = singletonStateId(SECONDARY_PROVIDER_INDEX,
245                 LocationTimeZoneProviderStateChanged.State.STOPPED);
246         Set<Integer> secondaryProviderStarted = singletonStateId(SECONDARY_PROVIDER_INDEX,
247                 LocationTimeZoneProviderStateChanged.State.INITIALIZING);
248         Set<Integer> secondaryProviderStopped = singletonStateId(SECONDARY_PROVIDER_INDEX,
249                 LocationTimeZoneProviderStateChanged.State.STOPPED);
250         Function<AtomsProto.Atom, Integer> eventToStateFunction = atom -> {
251             int providerIndex = atom.getLocationTimeZoneProviderStateChanged().getProviderIndex();
252             return stateId(providerIndex,
253                     atom.getLocationTimeZoneProviderStateChanged().getState());
254         };
255 
256         // Add state sets to the list in order.
257         // Assert that the events happened in the expected order. This does not check "wait" (the
258         // time between events).
259         List<Set<Integer>> stateSets = Arrays.asList(
260                 primaryProviderCreated, primaryProviderFailed,
261                 secondaryProviderCreated,
262                 secondaryProviderStarted, secondaryProviderStopped,
263                 secondaryProviderStarted, secondaryProviderStopped);
264         AtomTestUtils.assertStatesOccurredInOrder(stateSets, data,
265                 0 /* wait */, eventToStateFunction);
266     }
267 
singletonStateId(int providerIndex, LocationTimeZoneProviderStateChanged.State state)268     private static Set<Integer> singletonStateId(int providerIndex,
269             LocationTimeZoneProviderStateChanged.State state) {
270         return Collections.singleton(stateId(providerIndex, state));
271     }
272 
extractEventsForProviderIndex( List<StatsLog.EventMetricData> data, int providerIndex)273     private static List<StatsLog.EventMetricData> extractEventsForProviderIndex(
274             List<StatsLog.EventMetricData> data, int providerIndex) {
275         return data.stream().filter(event -> {
276             if (!event.getAtom().hasLocationTimeZoneProviderStateChanged()) {
277                 return false;
278             }
279             return event.getAtom().getLocationTimeZoneProviderStateChanged().getProviderIndex()
280                     == providerIndex;
281         }).collect(toList());
282     }
283 
284     /** Maps a (provider index, provider state) pair to an integer state ID. */
285     private static Integer stateId(
286             int providerIndex, LocationTimeZoneProviderStateChanged.State providerState) {
287         return (providerIndex * PROVIDER_STATES_COUNT) + providerState.getNumber();
288     }
289 }
290