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