• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  *   &lt;uses-permission
99  *       android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE"/&gt;
100  *
101  * ...
102  *
103  *     &lt;service android:name=".ExampleTimeZoneProviderService"
104  *             android:exported="true"
105  *             android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"&gt;
106  *         &lt;intent-filter&gt;
107  *             &lt;action
108  *             android:name="android.service.timezone.SecondaryLocationTimeZoneProviderService"
109  *             /&gt;
110  *         &lt;/intent-filter&gt;
111  *         &lt;meta-data android:name="serviceIsMultiuser" android:value="true" /&gt;
112  *     &lt;/service&gt;
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