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.service.timezone; 18 19 import android.annotation.DurationMillisLong; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.SdkConstant; 23 import android.annotation.SystemApi; 24 import android.app.Service; 25 import android.content.Intent; 26 import android.os.Handler; 27 import android.os.IBinder; 28 import android.os.RemoteException; 29 import android.os.SystemClock; 30 import android.util.Log; 31 32 import com.android.internal.annotations.GuardedBy; 33 import com.android.internal.os.BackgroundThread; 34 35 import java.io.FileDescriptor; 36 import java.io.PrintWriter; 37 import java.util.Objects; 38 39 /** 40 * A service to generate time zone callbacks to the platform. Developers must extend this class. 41 * 42 * <p>Provider implementations are started via a call to {@link #onStartUpdates(long)} and stopped 43 * via a call to {@link #onStopUpdates()}. 44 * 45 * <p>Once started, providers are expected to detect the time zone if possible, and report the 46 * result via {@link #reportSuggestion(TimeZoneProviderSuggestion)} or {@link 47 * #reportUncertain(TimeZoneProviderStatus)}. Providers may also report that they have permanently 48 * failed by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each 49 * method for details. 50 * 51 * <p>After starting, providers are expected to issue their first callback within the timeout 52 * duration specified in {@link #onStartUpdates(long)}, or they will be implicitly considered to be 53 * uncertain. 54 * 55 * <p>Once stopped or failed, providers are required to stop generating callbacks. 56 * 57 * <p>Provider types: 58 * 59 * <p>Android supports up to two <em>location-derived</em> time zone providers. These are called the 60 * "primary" and "secondary" location time zone providers. When a location-derived time zone is 61 * required, the primary location time zone provider is started first and used until it becomes 62 * uncertain or fails, at which point the secondary provider will be started. The secondary will be 63 * started and stopped as needed. 64 * 65 * <p>Provider discovery: 66 * 67 * <p>Each provider is optional and can be disabled. When enabled, a provider's package name must 68 * be explicitly configured in the system server, see {@code 69 * config_primaryLocationTimeZoneProviderPackageName} and {@code 70 * config_secondaryLocationTimeZoneProviderPackageName} for details. 71 * 72 * <p>You must declare the service in the AndroidManifest of the app hosting the provider with the 73 * {@link android.Manifest.permission#BIND_TIME_ZONE_PROVIDER_SERVICE} permission, 74 * and include an intent filter with the necessary action indicating that it is the primary 75 * provider ({@link #PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE}) or the secondary 76 * provider ({@link #SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE}). 77 * 78 * <p>Besides declaring the android:permission attribute mentioned above, the application supplying 79 * a location provider must be granted the {@link 80 * android.Manifest.permission#INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE} permission to be 81 * accepted by the system server. 82 * 83 * <p>{@link TimeZoneProviderService}s may be deployed into processes that run once-per-user 84 * or once-per-device (i.e. they service multiple users). See serviceIsMultiuser metadata below for 85 * configuration details. 86 * 87 * <p>The service may specify metadata on its capabilities: 88 * 89 * <ul> 90 * <li> 91 * "serviceIsMultiuser": A boolean property, indicating if the service wishes to take 92 * responsibility for handling changes to the current user on the device. If true, the 93 * service will always be bound from the system user. If false, the service will always be 94 * bound from the current user. If the current user changes, the old binding will be 95 * released, and a new binding established under the new user. Assumed to be false if not 96 * specified. 97 * </li> 98 * </ul> 99 * 100 * <p>For example: 101 * <pre> 102 * <uses-permission 103 * android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE"/> 104 * 105 * ... 106 * 107 * <service android:name=".ExampleTimeZoneProviderService" 108 * android:exported="true" 109 * android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"> 110 * <intent-filter> 111 * <action 112 * android:name="android.service.timezone.SecondaryLocationTimeZoneProviderService" 113 * /> 114 * </intent-filter> 115 * <meta-data android:name="serviceIsMultiuser" android:value="true" /> 116 * </service> 117 * </pre> 118 * 119 * <p>Threading: 120 * 121 * <p>Outgoing calls to {@code report} methods can be made on any thread and will be delivered 122 * asynchronously to the system server. Incoming calls to {@link TimeZoneProviderService}-defined 123 * service methods like {@link #onStartUpdates(long)} and {@link #onStopUpdates()} are also 124 * asynchronous with respect to the system server caller and will be delivered to this service using 125 * a single thread. {@link Service} lifecycle method calls like {@link #onCreate()} and {@link 126 * #onDestroy()} can occur on a different thread from those made to {@link 127 * TimeZoneProviderService}-defined service methods, so implementations must be defensive and not 128 * assume an ordering between them, e.g. a call to {@link #onStopUpdates()} can occur after {@link 129 * #onDestroy()} and should be handled safely. {@link #mLock} is used to ensure that synchronous 130 * calls like {@link #dump(FileDescriptor, PrintWriter, String[])} are safe with respect to 131 * asynchronous behavior. 132 * 133 * @hide 134 */ 135 @SystemApi 136 public abstract class TimeZoneProviderService extends Service { 137 138 private static final String TAG = "TimeZoneProviderService"; 139 140 /** 141 * The test command result key indicating whether a command succeeded. Value type: boolean 142 * @hide 143 */ 144 public static final String TEST_COMMAND_RESULT_SUCCESS_KEY = "SUCCESS"; 145 146 /** 147 * The test command result key for the error message present when {@link 148 * #TEST_COMMAND_RESULT_SUCCESS_KEY} is false. Value type: string 149 * @hide 150 */ 151 public static final String TEST_COMMAND_RESULT_ERROR_KEY = "ERROR"; 152 153 /** 154 * The Intent action that the primary location-derived time zone provider service must respond 155 * to. Add it to the intent filter of the service in its manifest. 156 */ 157 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 158 public static final String PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = 159 "android.service.timezone.PrimaryLocationTimeZoneProviderService"; 160 161 /** 162 * The Intent action that the secondary location-based time zone provider service must respond 163 * to. Add it to the intent filter of the service in its manifest. 164 */ 165 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 166 public static final String SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = 167 "android.service.timezone.SecondaryLocationTimeZoneProviderService"; 168 169 private final TimeZoneProviderServiceWrapper mWrapper = new TimeZoneProviderServiceWrapper(); 170 171 /** The object used for operations that occur between the main / handler thread. */ 172 private final Object mLock = new Object(); 173 174 /** The handler used for most operations. */ 175 private final Handler mHandler = BackgroundThread.getHandler(); 176 177 /** Set by {@link #mHandler} thread. */ 178 @GuardedBy("mLock") 179 @Nullable 180 private ITimeZoneProviderManager mManager; 181 182 /** Set by {@link #mHandler} thread. */ 183 @GuardedBy("mLock") 184 private long mEventFilteringAgeThresholdMillis; 185 186 /** 187 * The type of the last suggestion sent to the system server. Used to de-dupe suggestions client 188 * side and avoid calling into the system server unnecessarily. {@code null} means no previous 189 * event has been sent this cycle; this field is cleared when the service is started. 190 */ 191 @GuardedBy("mLock") 192 @Nullable 193 private TimeZoneProviderEvent mLastEventSent; 194 195 @Override 196 @NonNull onBind(@onNull Intent intent)197 public final IBinder onBind(@NonNull Intent intent) { 198 return mWrapper; 199 } 200 201 /** 202 * Indicates a successful time zone detection. See {@link TimeZoneProviderSuggestion} for 203 * details. 204 */ reportSuggestion(@onNull TimeZoneProviderSuggestion suggestion)205 public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion) { 206 TimeZoneProviderStatus providerStatus = null; 207 reportSuggestionInternal(suggestion, providerStatus); 208 } 209 210 /** 211 * Indicates a successful time zone detection. See {@link TimeZoneProviderSuggestion} for 212 * details. 213 * 214 * @param providerStatus provider status information that can influence detector service 215 * behavior and/or be reported via the device UI 216 */ reportSuggestion(@onNull TimeZoneProviderSuggestion suggestion, @NonNull TimeZoneProviderStatus providerStatus)217 public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion, 218 @NonNull TimeZoneProviderStatus providerStatus) { 219 Objects.requireNonNull(providerStatus); 220 reportSuggestionInternal(suggestion, providerStatus); 221 } 222 reportSuggestionInternal(@onNull TimeZoneProviderSuggestion suggestion, @Nullable TimeZoneProviderStatus providerStatus)223 private void reportSuggestionInternal(@NonNull TimeZoneProviderSuggestion suggestion, 224 @Nullable TimeZoneProviderStatus providerStatus) { 225 Objects.requireNonNull(suggestion); 226 227 mHandler.post(() -> { 228 synchronized (mLock) { 229 ITimeZoneProviderManager manager = mManager; 230 if (manager != null) { 231 try { 232 TimeZoneProviderEvent thisEvent = 233 TimeZoneProviderEvent.createSuggestionEvent( 234 SystemClock.elapsedRealtime(), suggestion, providerStatus); 235 if (shouldSendEvent(thisEvent)) { 236 manager.onTimeZoneProviderEvent(thisEvent); 237 mLastEventSent = thisEvent; 238 } 239 } catch (RemoteException | RuntimeException e) { 240 Log.w(TAG, e); 241 } 242 } 243 } 244 }); 245 } 246 247 /** 248 * Indicates the time zone is not known because of an expected runtime state or error, e.g. when 249 * the provider is unable to detect location, or there was connectivity issue. 250 * 251 * <p>See {@link #reportUncertain(TimeZoneProviderStatus)} for a more expressive version 252 */ reportUncertain()253 public final void reportUncertain() { 254 TimeZoneProviderStatus providerStatus = null; 255 reportUncertainInternal(providerStatus); 256 } 257 258 /** 259 * Indicates the time zone is not known because of an expected runtime state or error. 260 * 261 * <p>When the status changes then a certain or uncertain report must be made to move the 262 * detector service to the new status. 263 * 264 * @param providerStatus provider status information that can influence detector service 265 * behavior and/or be reported via the device UI 266 */ reportUncertain(@onNull TimeZoneProviderStatus providerStatus)267 public final void reportUncertain(@NonNull TimeZoneProviderStatus providerStatus) { 268 Objects.requireNonNull(providerStatus); 269 reportUncertainInternal(providerStatus); 270 } 271 reportUncertainInternal(@ullable TimeZoneProviderStatus providerStatus)272 private void reportUncertainInternal(@Nullable TimeZoneProviderStatus providerStatus) { 273 mHandler.post(() -> { 274 synchronized (mLock) { 275 ITimeZoneProviderManager manager = mManager; 276 if (manager != null) { 277 try { 278 TimeZoneProviderEvent thisEvent = 279 TimeZoneProviderEvent.createUncertainEvent( 280 SystemClock.elapsedRealtime(), providerStatus); 281 if (shouldSendEvent(thisEvent)) { 282 manager.onTimeZoneProviderEvent(thisEvent); 283 mLastEventSent = thisEvent; 284 } 285 } catch (RemoteException | RuntimeException e) { 286 Log.w(TAG, e); 287 } 288 } 289 } 290 }); 291 } 292 293 /** 294 * Indicates there was a permanent failure. This is not generally expected, and probably means a 295 * required backend service has been turned down, or the client is unreasonably old. 296 */ reportPermanentFailure(@onNull Throwable cause)297 public final void reportPermanentFailure(@NonNull Throwable cause) { 298 Objects.requireNonNull(cause); 299 300 mHandler.post(() -> { 301 synchronized (mLock) { 302 ITimeZoneProviderManager manager = mManager; 303 if (manager != null) { 304 try { 305 String causeString = cause.getMessage(); 306 TimeZoneProviderEvent thisEvent = 307 TimeZoneProviderEvent.createPermanentFailureEvent( 308 SystemClock.elapsedRealtime(), causeString); 309 if (shouldSendEvent(thisEvent)) { 310 manager.onTimeZoneProviderEvent(thisEvent); 311 mLastEventSent = thisEvent; 312 } 313 } catch (RemoteException | RuntimeException e) { 314 Log.w(TAG, e); 315 } 316 } 317 } 318 }); 319 } 320 321 @GuardedBy("mLock") shouldSendEvent(TimeZoneProviderEvent newEvent)322 private boolean shouldSendEvent(TimeZoneProviderEvent newEvent) { 323 // Always send an event if it indicates a state or suggestion change. 324 if (!newEvent.isEquivalentTo(mLastEventSent)) { 325 return true; 326 } 327 328 // Guard against implementations that generate a lot of uninteresting events in a short 329 // space of time and would cause the time_zone_detector to evaluate time zone suggestions 330 // too frequently. 331 // 332 // If the new event and last event sent are equivalent, the client will still send an update 333 // if their creation times are sufficiently different. This enables the time_zone_detector 334 // to better understand how recently the location time zone provider was certain / 335 // uncertain, which can be useful when working out ordering of events, e.g. to work out 336 // whether a suggestion was generated before or after a device left airplane mode. 337 long timeSinceLastEventMillis = 338 newEvent.getCreationElapsedMillis() - mLastEventSent.getCreationElapsedMillis(); 339 return timeSinceLastEventMillis > mEventFilteringAgeThresholdMillis; 340 } 341 onStartUpdatesInternal(@onNull ITimeZoneProviderManager manager, @DurationMillisLong long initializationTimeoutMillis, @DurationMillisLong long eventFilteringAgeThresholdMillis)342 private void onStartUpdatesInternal(@NonNull ITimeZoneProviderManager manager, 343 @DurationMillisLong long initializationTimeoutMillis, 344 @DurationMillisLong long eventFilteringAgeThresholdMillis) { 345 synchronized (mLock) { 346 mManager = manager; 347 mEventFilteringAgeThresholdMillis = eventFilteringAgeThresholdMillis; 348 mLastEventSent = null; 349 onStartUpdates(initializationTimeoutMillis); 350 } 351 } 352 353 /** 354 * Informs the provider that it should start detecting and reporting the detected time zone 355 * state via the various {@code report} methods. Implementations of {@link 356 * #onStartUpdates(long)} should return immediately, and will typically be used to start 357 * worker threads or begin asynchronous location listening. 358 * 359 * <p>Between {@link #onStartUpdates(long)} and {@link #onStopUpdates()} calls, the Android 360 * system server holds the latest report from the provider in memory. After an initial report, 361 * provider implementations are only required to send a report via {@link 362 * #reportSuggestion(TimeZoneProviderSuggestion, TimeZoneProviderStatus)} or via {@link 363 * #reportUncertain(TimeZoneProviderStatus)} when it differs from the previous report. 364 * 365 * <p>{@link #reportPermanentFailure(Throwable)} can also be called by provider implementations 366 * in rare cases, after which the provider should consider itself stopped and not make any 367 * further reports. {@link #onStopUpdates()} will not be called in this case. 368 * 369 * <p>The {@code initializationTimeoutMillis} parameter indicates how long the provider has been 370 * granted to call one of the {@code report} methods for the first time. If the provider does 371 * not call one of the {@code report} methods in this time, it may be judged uncertain and the 372 * Android system server may move on to use other providers or detection methods. Providers 373 * should therefore make best efforts during this time to generate a report, which could involve 374 * increased power usage. Providers should preferably report an explicit {@link 375 * #reportUncertain(TimeZoneProviderStatus)} if the time zone(s) cannot be detected within the 376 * initialization timeout. 377 * 378 * @see #onStopUpdates() for the signal from the system server to stop sending reports 379 */ onStartUpdates(@urationMillisLong long initializationTimeoutMillis)380 public abstract void onStartUpdates(@DurationMillisLong long initializationTimeoutMillis); 381 onStopUpdatesInternal()382 private void onStopUpdatesInternal() { 383 synchronized (mLock) { 384 onStopUpdates(); 385 mManager = null; 386 } 387 } 388 389 /** 390 * Stops the provider sending further updates. This will be called after {@link 391 * #onStartUpdates(long)}. 392 */ onStopUpdates()393 public abstract void onStopUpdates(); 394 395 /** @hide */ 396 @Override dump(FileDescriptor fd, PrintWriter writer, String[] args)397 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 398 synchronized (mLock) { 399 writer.append("mLastEventSent=" + mLastEventSent); 400 } 401 } 402 403 private class TimeZoneProviderServiceWrapper extends ITimeZoneProvider.Stub { 404 startUpdates(@onNull ITimeZoneProviderManager manager, @DurationMillisLong long initializationTimeoutMillis, @DurationMillisLong long eventFilteringAgeThresholdMillis)405 public void startUpdates(@NonNull ITimeZoneProviderManager manager, 406 @DurationMillisLong long initializationTimeoutMillis, 407 @DurationMillisLong long eventFilteringAgeThresholdMillis) { 408 Objects.requireNonNull(manager); 409 mHandler.post(() -> onStartUpdatesInternal( 410 manager, initializationTimeoutMillis, eventFilteringAgeThresholdMillis)); 411 } 412 stopUpdates()413 public void stopUpdates() { 414 mHandler.post(TimeZoneProviderService.this::onStopUpdatesInternal); 415 } 416 } 417 } 418