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 package android.app.time.cts.shell; 17 18 import static org.junit.Assert.assertEquals; 19 import static org.junit.Assert.assertFalse; 20 import static org.junit.Assert.assertTrue; 21 import static org.junit.Assert.fail; 22 23 import java.util.Collections; 24 import java.util.HashMap; 25 import java.util.List; 26 import java.util.Map; 27 import java.util.Objects; 28 import java.util.regex.Matcher; 29 import java.util.regex.Pattern; 30 31 /** 32 * A class for interacting with the fake {@link android.service.timezone.TimeZoneProviderService} in 33 * the fake TimeZoneProviderService app. 34 */ 35 public final class FakeTimeZoneProviderAppShellHelper { 36 37 /** The name of the app's APK. */ 38 public static final String FAKE_TZPS_APP_APK = "CtsFakeTimeZoneProvidersApp.apk"; 39 40 /** The package name of the app. */ 41 public static final String FAKE_TZPS_APP_PACKAGE = "com.android.time.cts.fake_tzps_app"; 42 43 /** The ID of the primary location time zone provider. */ 44 public static final String FAKE_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ID = 45 "FakeLocationTimeZoneProviderService1"; 46 47 /** The ID of the secondary location time zone provider. */ 48 public static final String FAKE_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ID = 49 "FakeLocationTimeZoneProviderService2"; 50 51 // The following constant values correspond to enum values from 52 // frameworks/base/core/proto/android/app/location_time_zone_manager.proto 53 public static final int PROVIDER_STATE_UNKNOWN = 0; 54 public static final int PROVIDER_STATE_INITIALIZING = 1; 55 public static final int PROVIDER_STATE_CERTAIN = 2; 56 public static final int PROVIDER_STATE_UNCERTAIN = 3; 57 public static final int PROVIDER_STATE_DISABLED = 4; 58 public static final int PROVIDER_STATE_PERM_FAILED = 5; 59 public static final int PROVIDER_STATE_DESTROYED = 6; 60 61 private static final String METHOD_GET_STATE = "get_state"; 62 private static final String CALL_RESULT_KEY_GET_STATE_STATE = "state"; 63 private static final String METHOD_REPORT_PERMANENT_FAILURE = "perm_fail"; 64 private static final String METHOD_REPORT_UNCERTAIN = "uncertain"; 65 private static final String METHOD_REPORT_UNCERTAIN_LEGACY = "uncertain_legacy"; 66 private static final String METHOD_REPORT_SUCCESS = "success"; 67 private static final String METHOD_REPORT_SUCCESS_LEGACY = "success_legacy"; 68 private static final String METHOD_PING = "ping"; 69 70 /** A single string, comma separated, may be empty. */ 71 private static final String CALL_EXTRA_KEY_SUCCESS_SUGGESTION_ZONE_IDS = "zone_ids"; 72 73 /** A provider's location detection status. */ 74 private static final String CALL_EXTRA_KEY_LOCATION_DETECTION_STATUS = 75 "location_detection_status"; 76 /** A provider's connectivity status. */ 77 private static final String CALL_EXTRA_KEY_CONNECTIVITY_STATUS = "connectivity_status"; 78 /** A provider's time zone resolution status. */ 79 private static final String CALL_EXTRA_KEY_TIME_ZONE_RESOLUTION_STATUS = 80 "time_zone_resolution_status"; 81 82 public static final String DEPENDENCY_STATUS_OK = "OK"; 83 public static final String DEPENDENCY_STATUS_NOT_APPLICABLE = "NOT_APPLICABLE"; 84 public static final String DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE = 85 "TEMPORARILY_UNAVAILABLE"; 86 public static final String DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT = "BLOCKED_BY_ENVIRONMENT"; 87 public static final String DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS = "BLOCKED_BY_SETTINGS"; 88 89 public static final String OPERATION_STATUS_NOT_APPLICABLE = "NOT_APPLICABLE"; 90 public static final String OPERATION_STATUS_OK = "OK"; 91 public static final String OPERATION_STATUS_FAILED = "FAILED"; 92 93 private static final String SHELL_COMMAND_PREFIX = "content "; 94 private static final String AUTHORITY = "faketzpsapp "; 95 96 private final DeviceShellCommandExecutor mShellCommandExecutor; 97 FakeTimeZoneProviderAppShellHelper(DeviceShellCommandExecutor shellCommandExecutor)98 public FakeTimeZoneProviderAppShellHelper(DeviceShellCommandExecutor shellCommandExecutor) { 99 mShellCommandExecutor = Objects.requireNonNull(shellCommandExecutor); 100 } 101 102 /** 103 * Throws an exception if the app is not installed / available within a reasonable time. 104 */ waitForInstallation()105 public void waitForInstallation() throws Exception { 106 long timeoutMs = 10000; 107 long delayUntilMillis = System.currentTimeMillis() + timeoutMs; 108 while (System.currentTimeMillis() <= delayUntilMillis) { 109 try { 110 ping(); 111 return; 112 } catch (AssertionError e) { 113 // Not present yet. 114 } 115 Thread.sleep(100); 116 } 117 fail("Installation did not happen in time"); 118 } 119 getPrimaryLocationProviderHelper()120 public FakeTimeZoneProviderShellHelper getPrimaryLocationProviderHelper() { 121 return getProviderHelper(FAKE_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ID); 122 } 123 getSecondaryLocationProviderHelper()124 public FakeTimeZoneProviderShellHelper getSecondaryLocationProviderHelper() { 125 return getProviderHelper(FAKE_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ID); 126 } 127 getProviderHelper(String providerId)128 private FakeTimeZoneProviderShellHelper getProviderHelper(String providerId) { 129 return new FakeTimeZoneProviderShellHelper(providerId); 130 } 131 132 /** 133 * A helper for interacting with a specific {@link 134 * android.service.timezone.TimeZoneProviderService}. 135 */ 136 public final class FakeTimeZoneProviderShellHelper { 137 138 private final String mProviderId; 139 FakeTimeZoneProviderShellHelper(String providerId)140 private FakeTimeZoneProviderShellHelper(String providerId) { 141 mProviderId = Objects.requireNonNull(providerId); 142 } 143 144 /** Causes {@link TimeZoneProviderService#reportUncertain()} to be called. */ reportUncertainLegacy()145 public void reportUncertainLegacy() throws Exception { 146 Map<String, String> extras = new HashMap<>(); 147 executeContentProviderCall(mProviderId, METHOD_REPORT_UNCERTAIN_LEGACY, extras); 148 } 149 150 /** 151 * Causes {@link TimeZoneProviderService#reportUncertain(TimeZoneProviderStatus)} to be 152 * called. 153 */ reportUncertain(String locationDetectionStatus, String connectivityStatus, String timeZoneResolutionStatus)154 public void reportUncertain(String locationDetectionStatus, 155 String connectivityStatus, String timeZoneResolutionStatus) throws Exception { 156 Map<String, String> extras = new HashMap<>(); 157 extras.put(CALL_EXTRA_KEY_LOCATION_DETECTION_STATUS, 158 Objects.requireNonNull(locationDetectionStatus)); 159 extras.put(CALL_EXTRA_KEY_CONNECTIVITY_STATUS, 160 Objects.requireNonNull(connectivityStatus)); 161 extras.put(CALL_EXTRA_KEY_TIME_ZONE_RESOLUTION_STATUS, 162 Objects.requireNonNull(timeZoneResolutionStatus)); 163 164 executeContentProviderCall(mProviderId, METHOD_REPORT_UNCERTAIN, extras); 165 } 166 reportPermanentFailure()167 public void reportPermanentFailure() throws Exception { 168 executeContentProviderCall(mProviderId, METHOD_REPORT_PERMANENT_FAILURE, null); 169 } 170 171 /** 172 * Causes TimeZoneProviderService.reportSuggestion(TimeZoneProviderSuggestion) to be called. 173 */ reportSuccessLegacy(String zoneId)174 public void reportSuccessLegacy(String zoneId) throws Exception { 175 reportSuccessLegacy(Collections.singletonList(zoneId)); 176 } 177 178 /** 179 * Causes TimeZoneProviderService.reportSuggestion(TimeZoneProviderSuggestion) to be called. 180 */ reportSuccessLegacy(List<String> zoneIds)181 public void reportSuccessLegacy(List<String> zoneIds) throws Exception { 182 String zoneIdsExtra = String.join(",", zoneIds); 183 Map<String, String> extras = new HashMap<>(); 184 extras.put(CALL_EXTRA_KEY_SUCCESS_SUGGESTION_ZONE_IDS, zoneIdsExtra); 185 186 executeContentProviderCall(mProviderId, METHOD_REPORT_SUCCESS_LEGACY, extras); 187 } 188 189 /** 190 * Causes TimeZoneProviderService.reportSuggestion(TimeZoneProviderSuggestion, 191 * TimeZoneProviderStatus) to be called. 192 */ reportSuccess(String zoneId, String locationDetectionStatus, String connectivityStatus)193 public void reportSuccess(String zoneId, String locationDetectionStatus, 194 String connectivityStatus) throws Exception { 195 reportSuccess(Collections.singletonList(zoneId), locationDetectionStatus, 196 connectivityStatus); 197 } 198 199 /** 200 * Causes TimeZoneProviderService#reportSuggestion(TimeZoneProviderSuggestion, 201 * TimeZoneProviderStatus) to be called. 202 */ reportSuccess(List<String> zoneIds, String locationDetectionStatus, String connectivityStatus)203 public void reportSuccess(List<String> zoneIds, String locationDetectionStatus, 204 String connectivityStatus) throws Exception { 205 String zoneIdsExtra = String.join(",", zoneIds); 206 Map<String, String> extras = new HashMap<>(); 207 extras.put(CALL_EXTRA_KEY_SUCCESS_SUGGESTION_ZONE_IDS, zoneIdsExtra); 208 extras.put(CALL_EXTRA_KEY_LOCATION_DETECTION_STATUS, 209 Objects.requireNonNull(locationDetectionStatus)); 210 extras.put(CALL_EXTRA_KEY_CONNECTIVITY_STATUS, 211 Objects.requireNonNull(connectivityStatus)); 212 extras.put(CALL_EXTRA_KEY_TIME_ZONE_RESOLUTION_STATUS, OPERATION_STATUS_OK); 213 214 executeContentProviderCall(mProviderId, METHOD_REPORT_SUCCESS, extras); 215 } 216 getState()217 public int getState() throws Exception { 218 String stateResult = executeContentProviderCall(mProviderId, METHOD_GET_STATE, null); 219 Pattern pattern = Pattern.compile(".*" + CALL_RESULT_KEY_GET_STATE_STATE + "=(.).*"); 220 Matcher matcher = pattern.matcher(stateResult); 221 if (!matcher.matches()) { 222 throw new RuntimeException("Unknown result format: " + stateResult); 223 } 224 return Integer.parseInt(matcher.group(1)); 225 } 226 assertCurrentState(int expectedState)227 public void assertCurrentState(int expectedState) throws Exception { 228 assertEquals(expectedState, getState()); 229 } 230 exists()231 public boolean exists() throws Exception { 232 try { 233 getState(); 234 return true; 235 } catch (AssertionError e) { 236 return false; 237 } 238 } 239 assertCreated()240 public void assertCreated() throws Exception { 241 assertTrue(exists()); 242 } 243 assertNotCreated()244 public void assertNotCreated() throws Exception { 245 assertFalse(exists()); 246 } 247 } 248 ping()249 private void ping() throws Exception { 250 String cmd = String.format("call --uri content://%s --method %s", AUTHORITY, METHOD_PING); 251 mShellCommandExecutor.executeToTrimmedString(SHELL_COMMAND_PREFIX + cmd); 252 } 253 executeContentProviderCall( String providerId, String method, Map<String, String> extras)254 private String executeContentProviderCall( 255 String providerId, String method, Map<String, String> extras) throws Exception { 256 String cmd = String.format("call --uri content://%s --method %s --arg %s", 257 AUTHORITY, method, providerId); 258 if (extras != null) { 259 for (Map.Entry<String, String> entry : extras.entrySet()) { 260 cmd += String.format(" --extra %s:s:%s", entry.getKey(), entry.getValue()); 261 } 262 } 263 return mShellCommandExecutor.executeToTrimmedString(SHELL_COMMAND_PREFIX + cmd); 264 } 265 } 266