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.util.Log; 30 31 import com.android.internal.os.BackgroundThread; 32 33 import java.util.Objects; 34 35 /** 36 * A service to generate time zone callbacks to the platform. Developers must extend this class. 37 * 38 * <p>Provider implementations are started via a call to {@link #onStartUpdates(long)} and stopped 39 * via a call to {@link #onStopUpdates()}. 40 * 41 * <p>Once started, providers are expected to detect the time zone if possible, and report the 42 * result via {@link #reportSuggestion(TimeZoneProviderSuggestion)} or {@link 43 * #reportUncertain()}. Providers may also report that they have permanently failed 44 * by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each 45 * method for details. 46 * 47 * <p>After starting, providers are expected to issue their first callback within the timeout 48 * duration specified in {@link #onStartUpdates(long)}, or they will be implicitly considered to be 49 * uncertain. 50 * 51 * <p>Once stopped or failed, providers are required to stop generating callbacks. 52 * 53 * <p>Provider types: 54 * 55 * <p>Android supports up to two <em>location-derived</em> time zone providers. These are called the 56 * "primary" and "secondary" location time zone providers. When a location-derived time zone is 57 * required, the primary location time zone provider is started first and used until it becomes 58 * uncertain or fails, at which point the secondary provider will be started. The secondary will be 59 * started and stopped as needed. 60 * 61 * <p>Provider discovery: 62 * 63 * <p>Each provider is optional and can be disabled. When enabled, a provider's package name must 64 * be explicitly configured in the system server, see {@code 65 * config_primaryLocationTimeZoneProviderPackageName} and {@code 66 * config_secondaryLocationTimeZoneProviderPackageName} for details. 67 * 68 * <p>You must declare the service in the AndroidManifest of the app hosting the provider with the 69 * {@link android.Manifest.permission#BIND_TIME_ZONE_PROVIDER_SERVICE} permission, 70 * and include an intent filter with the necessary action indicating that it is the primary 71 * provider ({@link #PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE}) or the secondary 72 * provider ({@link #SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE}). 73 * 74 * <p>Besides declaring the android:permission attribute mentioned above, the application supplying 75 * a location provider must be granted the {@link 76 * android.Manifest.permission#INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE} permission to be 77 * accepted by the system server. 78 * 79 * <p>{@link TimeZoneProviderService}s may be deployed into processes that run once-per-user 80 * or once-per-device (i.e. they service multiple users). See serviceIsMultiuser metadata below for 81 * configuration details. 82 * 83 * <p>The service may specify metadata on its capabilities: 84 * 85 * <ul> 86 * <li> 87 * "serviceIsMultiuser": A boolean property, indicating if the service wishes to take 88 * responsibility for handling changes to the current user on the device. If true, the 89 * service will always be bound from the system user. If false, the service will always be 90 * bound from the current user. If the current user changes, the old binding will be 91 * released, and a new binding established under the new user. Assumed to be false if not 92 * specified. 93 * </li> 94 * </ul> 95 * 96 * <p>For example: 97 * <pre> 98 * <uses-permission 99 * android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE"/> 100 * 101 * ... 102 * 103 * <service android:name=".ExampleTimeZoneProviderService" 104 * android:exported="true" 105 * android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"> 106 * <intent-filter> 107 * <action 108 * android:name="android.service.timezone.SecondaryLocationTimeZoneProviderService" 109 * /> 110 * </intent-filter> 111 * <meta-data android:name="serviceIsMultiuser" android:value="true" /> 112 * </service> 113 * </pre> 114 * 115 * <p>Threading: 116 * 117 * <p>Calls to {@code report} methods can be made on on any thread and will be passed asynchronously 118 * to the system server. Calls to {@link #onStartUpdates(long)} and {@link #onStopUpdates()} will 119 * occur on a single thread. 120 * 121 * @hide 122 */ 123 @SystemApi 124 public abstract class TimeZoneProviderService extends Service { 125 126 private static final String TAG = "TimeZoneProviderService"; 127 128 /** 129 * The test command result key indicating whether a command succeeded. Value type: boolean 130 * @hide 131 */ 132 public static final String TEST_COMMAND_RESULT_SUCCESS_KEY = "SUCCESS"; 133 134 /** 135 * The test command result key for the error message present when {@link 136 * #TEST_COMMAND_RESULT_SUCCESS_KEY} is false. Value type: string 137 * @hide 138 */ 139 public static final String TEST_COMMAND_RESULT_ERROR_KEY = "ERROR"; 140 141 /** 142 * The Intent action that the primary location-derived time zone provider service must respond 143 * to. Add it to the intent filter of the service in its manifest. 144 */ 145 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 146 public static final String PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = 147 "android.service.timezone.PrimaryLocationTimeZoneProviderService"; 148 149 /** 150 * The Intent action that the secondary location-based time zone provider service must respond 151 * to. Add it to the intent filter of the service in its manifest. 152 */ 153 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 154 public static final String SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = 155 "android.service.timezone.SecondaryLocationTimeZoneProviderService"; 156 157 private final TimeZoneProviderServiceWrapper mWrapper = new TimeZoneProviderServiceWrapper(); 158 159 private final Handler mHandler = BackgroundThread.getHandler(); 160 161 /** Set by {@link #mHandler} thread. */ 162 @Nullable 163 private ITimeZoneProviderManager mManager; 164 165 @Override 166 @NonNull onBind(@onNull Intent intent)167 public final IBinder onBind(@NonNull Intent intent) { 168 return mWrapper; 169 } 170 171 /** 172 * Indicates a successful time zone detection. See {@link TimeZoneProviderSuggestion} for 173 * details. 174 */ reportSuggestion(@onNull TimeZoneProviderSuggestion suggestion)175 public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion) { 176 Objects.requireNonNull(suggestion); 177 178 mHandler.post(() -> { 179 ITimeZoneProviderManager manager = mManager; 180 if (manager != null) { 181 try { 182 manager.onTimeZoneProviderSuggestion(suggestion); 183 } catch (RemoteException | RuntimeException e) { 184 Log.w(TAG, e); 185 } 186 } 187 }); 188 } 189 190 /** 191 * Indicates the time zone is not known because of an expected runtime state or error, e.g. when 192 * the provider is unable to detect location, or there was a problem when resolving the location 193 * to a time zone. 194 */ reportUncertain()195 public final void reportUncertain() { 196 mHandler.post(() -> { 197 ITimeZoneProviderManager manager = mManager; 198 if (manager != null) { 199 try { 200 manager.onTimeZoneProviderUncertain(); 201 } catch (RemoteException | RuntimeException e) { 202 Log.w(TAG, e); 203 } 204 } 205 }); 206 } 207 208 /** 209 * Indicates there was a permanent failure. This is not generally expected, and probably means a 210 * required backend service has been turned down, or the client is unreasonably old. 211 */ reportPermanentFailure(@onNull Throwable cause)212 public final void reportPermanentFailure(@NonNull Throwable cause) { 213 Objects.requireNonNull(cause); 214 215 mHandler.post(() -> { 216 ITimeZoneProviderManager manager = mManager; 217 if (manager != null) { 218 try { 219 manager.onTimeZoneProviderPermanentFailure(cause.getMessage()); 220 } catch (RemoteException | RuntimeException e) { 221 Log.w(TAG, e); 222 } 223 } 224 }); 225 } 226 onStartUpdatesInternal(@onNull ITimeZoneProviderManager manager, @DurationMillisLong long initializationTimeoutMillis)227 private void onStartUpdatesInternal(@NonNull ITimeZoneProviderManager manager, 228 @DurationMillisLong long initializationTimeoutMillis) { 229 mManager = manager; 230 onStartUpdates(initializationTimeoutMillis); 231 } 232 233 /** 234 * Informs the provider that it should start detecting and reporting the detected time zone 235 * state via the various {@code report} methods. Implementations of {@link 236 * #onStartUpdates(long)} should return immediately, and will typically be used to start 237 * worker threads or begin asynchronous location listening. 238 * 239 * <p>Between {@link #onStartUpdates(long)} and {@link #onStopUpdates()} calls, the Android 240 * system server holds the latest report from the provider in memory. After an initial report, 241 * provider implementations are only required to send a report via {@link 242 * #reportSuggestion(TimeZoneProviderSuggestion)} or via {@link #reportUncertain()} when it 243 * differs from the previous report. 244 * 245 * <p>{@link #reportPermanentFailure(Throwable)} can also be called by provider implementations 246 * in rare cases, after which the provider should consider itself stopped and not make any 247 * further reports. {@link #onStopUpdates()} will not be called in this case. 248 * 249 * <p>The {@code initializationTimeoutMillis} parameter indicates how long the provider has been 250 * granted to call one of the {@code report} methods for the first time. If the provider does 251 * not call one of the {@code report} methods in this time, it may be judged uncertain and the 252 * Android system server may move on to use other providers or detection methods. Providers 253 * should therefore make best efforts during this time to generate a report, which could involve 254 * increased power usage. Providers should preferably report an explicit {@link 255 * #reportUncertain()} if the time zone(s) cannot be detected within the initialization timeout. 256 * 257 * @see #onStopUpdates() for the signal from the system server to stop sending reports 258 */ onStartUpdates(@urationMillisLong long initializationTimeoutMillis)259 public abstract void onStartUpdates(@DurationMillisLong long initializationTimeoutMillis); 260 onStopUpdatesInternal()261 private void onStopUpdatesInternal() { 262 onStopUpdates(); 263 mManager = null; 264 } 265 266 /** 267 * Stops the provider sending further updates. This will be called after {@link 268 * #onStartUpdates(long)}. 269 */ onStopUpdates()270 public abstract void onStopUpdates(); 271 272 private class TimeZoneProviderServiceWrapper extends ITimeZoneProvider.Stub { 273 startUpdates(@onNull ITimeZoneProviderManager manager, @DurationMillisLong long initializationTimeoutMillis)274 public void startUpdates(@NonNull ITimeZoneProviderManager manager, 275 @DurationMillisLong long initializationTimeoutMillis) { 276 Objects.requireNonNull(manager); 277 mHandler.post(() -> onStartUpdatesInternal(manager, initializationTimeoutMillis)); 278 } 279 stopUpdates()280 public void stopUpdates() { 281 mHandler.post(TimeZoneProviderService.this::onStopUpdatesInternal); 282 } 283 } 284 } 285