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.app.time;
18 
19 import android.annotation.NonNull;
20 import android.annotation.RequiresPermission;
21 import android.annotation.SystemApi;
22 import android.annotation.SystemService;
23 import android.app.timedetector.ITimeDetectorService;
24 import android.app.timedetector.ManualTimeSuggestion;
25 import android.app.timezonedetector.ITimeZoneDetectorService;
26 import android.app.timezonedetector.ManualTimeZoneSuggestion;
27 import android.content.Context;
28 import android.os.RemoteException;
29 import android.os.ServiceManager;
30 import android.os.ServiceManager.ServiceNotFoundException;
31 import android.util.ArrayMap;
32 import android.util.Log;
33 
34 import com.android.internal.annotations.GuardedBy;
35 
36 import java.util.concurrent.Executor;
37 
38 /**
39  * The interface through which system components can interact with time and time zone services.
40  *
41  * @hide
42  */
43 @SystemApi
44 @SystemService(Context.TIME_MANAGER_SERVICE)
45 public final class TimeManager {
46     private static final String TAG = "time.TimeManager";
47     private static final boolean DEBUG = false;
48 
49     private final Object mLock = new Object();
50     private final ITimeZoneDetectorService mITimeZoneDetectorService;
51     private final ITimeDetectorService mITimeDetectorService;
52 
53     @GuardedBy("mLock")
54     private ITimeZoneDetectorListener mTimeZoneDetectorReceiver;
55 
56     /**
57      * The registered listeners. The key is the actual listener that was registered, the value is a
58      * wrapper that ensures the listener is executed on the correct Executor.
59      */
60     @GuardedBy("mLock")
61     private ArrayMap<TimeZoneDetectorListener, TimeZoneDetectorListener> mTimeZoneDetectorListeners;
62 
63     /** @hide */
TimeManager()64     public TimeManager() throws ServiceNotFoundException {
65         // TimeManager is an API over one or possibly more services. At least until there's an
66         // internal refactoring.
67         mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
68                 ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
69         mITimeDetectorService = ITimeDetectorService.Stub.asInterface(
70                 ServiceManager.getServiceOrThrow(Context.TIME_DETECTOR_SERVICE));
71     }
72 
73     /**
74      * Returns the calling user's time zone capabilities and configuration.
75      */
76     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
77     @NonNull
getTimeZoneCapabilitiesAndConfig()78     public TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig() {
79         if (DEBUG) {
80             Log.d(TAG, "getTimeZoneCapabilitiesAndConfig called");
81         }
82         try {
83             return mITimeZoneDetectorService.getCapabilitiesAndConfig();
84         } catch (RemoteException e) {
85             throw e.rethrowFromSystemServer();
86         }
87     }
88 
89     /**
90      * Returns the calling user's time capabilities and configuration.
91      */
92     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
93     @NonNull
getTimeCapabilitiesAndConfig()94     public TimeCapabilitiesAndConfig getTimeCapabilitiesAndConfig() {
95         if (DEBUG) {
96             Log.d(TAG, "getTimeCapabilitiesAndConfig called");
97         }
98         try {
99             return mITimeDetectorService.getCapabilitiesAndConfig();
100         } catch (RemoteException e) {
101             throw e.rethrowFromSystemServer();
102         }
103     }
104 
105     /**
106      * Modifies the time detection configuration.
107      *
108      * <p>The ability to modify configuration settings can be subject to restrictions. For
109      * example, they may be determined by device hardware, general policy (i.e. only the primary
110      * user can set them), or by a managed device policy. Use {@link
111      * #getTimeCapabilitiesAndConfig()} to obtain information at runtime about the user's
112      * capabilities.
113      *
114      * <p>Attempts to modify configuration settings with capabilities that are {@link
115      * Capabilities#CAPABILITY_NOT_SUPPORTED} or {@link
116      * Capabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
117      * will be returned. Modifying configuration settings with capabilities that are {@link
118      * Capabilities#CAPABILITY_NOT_APPLICABLE} or {@link
119      * Capabilities#CAPABILITY_POSSESSED} will succeed. See {@link
120      * TimeZoneCapabilities} for further details.
121      *
122      * <p>If the supplied configuration only has some values set, then only the specified settings
123      * will be updated (where the user's capabilities allow) and other settings will be left
124      * unchanged.
125      *
126      * @return {@code true} if all the configuration settings specified have been set to the
127      *   new values, {@code false} if none have
128      */
129     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
updateTimeConfiguration(@onNull TimeConfiguration configuration)130     public boolean updateTimeConfiguration(@NonNull TimeConfiguration configuration) {
131         if (DEBUG) {
132             Log.d(TAG, "updateTimeConfiguration called: " + configuration);
133         }
134         try {
135             return mITimeDetectorService.updateConfiguration(configuration);
136         } catch (RemoteException e) {
137             throw e.rethrowFromSystemServer();
138         }
139     }
140 
141     /**
142      * Modifies the time zone detection configuration.
143      *
144      * <p>Configuration settings vary in scope: some may be global (affect all users), others may be
145      * specific to the current user.
146      *
147      * <p>The ability to modify configuration settings can be subject to restrictions. For
148      * example, they may be determined by device hardware, general policy (i.e. only the primary
149      * user can set them), or by a managed device policy. Use {@link
150      * #getTimeZoneCapabilitiesAndConfig()} to obtain information at runtime about the user's
151      * capabilities.
152      *
153      * <p>Attempts to modify configuration settings with capabilities that are {@link
154      * Capabilities#CAPABILITY_NOT_SUPPORTED} or {@link
155      * Capabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
156      * will be returned. Modifying configuration settings with capabilities that are {@link
157      * Capabilities#CAPABILITY_NOT_APPLICABLE} or {@link
158      * Capabilities#CAPABILITY_POSSESSED} will succeed. See {@link
159      * TimeZoneCapabilities} for further details.
160      *
161      * <p>If the supplied configuration only has some values set, then only the specified settings
162      * will be updated (where the user's capabilities allow) and other settings will be left
163      * unchanged.
164      *
165      * @return {@code true} if all the configuration settings specified have been set to the
166      *   new values, {@code false} if none have
167      */
168     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
updateTimeZoneConfiguration(@onNull TimeZoneConfiguration configuration)169     public boolean updateTimeZoneConfiguration(@NonNull TimeZoneConfiguration configuration) {
170         if (DEBUG) {
171             Log.d(TAG, "updateTimeZoneConfiguration called: " + configuration);
172         }
173         try {
174             return mITimeZoneDetectorService.updateConfiguration(configuration);
175         } catch (RemoteException e) {
176             throw e.rethrowFromSystemServer();
177         }
178     }
179 
180     /**
181      * An interface that can be used to listen for changes to the time zone detector behavior.
182      */
183     @FunctionalInterface
184     public interface TimeZoneDetectorListener {
185         /**
186          * Called when something about the time zone detector behavior on the device has changed.
187          * For example, this could be because the current user has switched, one of the global or
188          * user's settings been changed, or something that could affect a user's capabilities with
189          * respect to the time zone detector has changed. Because different users can have different
190          * configuration and capabilities, this method may be called when nothing has changed for
191          * the receiving user.
192          */
onChange()193         void onChange();
194     }
195 
196     /**
197      * Registers a listener that will be informed when something about the time zone detector
198      * behavior changes.
199      */
200     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
addTimeZoneDetectorListener(@onNull Executor executor, @NonNull TimeZoneDetectorListener listener)201     public void addTimeZoneDetectorListener(@NonNull Executor executor,
202             @NonNull TimeZoneDetectorListener listener) {
203 
204         if (DEBUG) {
205             Log.d(TAG, "addTimeZoneDetectorListener called: " + listener);
206         }
207         synchronized (mLock) {
208             if (mTimeZoneDetectorListeners == null) {
209                 mTimeZoneDetectorListeners = new ArrayMap<>();
210             } else if (mTimeZoneDetectorListeners.containsKey(listener)) {
211                 return;
212             }
213 
214             if (mTimeZoneDetectorReceiver == null) {
215                 ITimeZoneDetectorListener iListener = new ITimeZoneDetectorListener.Stub() {
216                     @Override
217                     public void onChange() {
218                         notifyTimeZoneDetectorListeners();
219                     }
220                 };
221                 mTimeZoneDetectorReceiver = iListener;
222                 try {
223                     mITimeZoneDetectorService.addListener(mTimeZoneDetectorReceiver);
224                 } catch (RemoteException e) {
225                     throw e.rethrowFromSystemServer();
226                 }
227             }
228             mTimeZoneDetectorListeners.put(listener, () -> executor.execute(listener::onChange));
229         }
230     }
231 
notifyTimeZoneDetectorListeners()232     private void notifyTimeZoneDetectorListeners() {
233         ArrayMap<TimeZoneDetectorListener, TimeZoneDetectorListener> timeZoneDetectorListeners;
234         synchronized (mLock) {
235             if (mTimeZoneDetectorListeners == null || mTimeZoneDetectorListeners.isEmpty()) {
236                 return;
237             }
238             timeZoneDetectorListeners = new ArrayMap<>(mTimeZoneDetectorListeners);
239         }
240         int size = timeZoneDetectorListeners.size();
241         for (int i = 0; i < size; i++) {
242             timeZoneDetectorListeners.valueAt(i).onChange();
243         }
244     }
245 
246     /**
247      * Removes a listener previously passed to
248      * {@link #addTimeZoneDetectorListener(Executor, TimeZoneDetectorListener)}
249      */
250     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
removeTimeZoneDetectorListener(@onNull TimeZoneDetectorListener listener)251     public void removeTimeZoneDetectorListener(@NonNull TimeZoneDetectorListener listener) {
252         if (DEBUG) {
253             Log.d(TAG, "removeConfigurationListener called: " + listener);
254         }
255 
256         synchronized (mLock) {
257             if (mTimeZoneDetectorListeners == null || mTimeZoneDetectorListeners.isEmpty()) {
258                 return;
259             }
260             mTimeZoneDetectorListeners.remove(listener);
261 
262             // If the last local listener has been removed, remove and discard the
263             // mTimeZoneDetectorReceiver.
264             if (mTimeZoneDetectorListeners.isEmpty()) {
265                 try {
266                     mITimeZoneDetectorService.removeListener(mTimeZoneDetectorReceiver);
267                 } catch (RemoteException e) {
268                     throw e.rethrowFromSystemServer();
269                 } finally {
270                     mTimeZoneDetectorReceiver = null;
271                 }
272             }
273         }
274     }
275 
276     /**
277      * Suggests the current time from an external time source. For example, a form factor-specific
278      * HAL. This time <em>may</em> be used to set the device system clock, depending on the device
279      * configuration and user settings. This method call is processed asynchronously.
280      * See {@link ExternalTimeSuggestion} for more details.
281      */
282     @RequiresPermission(android.Manifest.permission.SUGGEST_EXTERNAL_TIME)
suggestExternalTime(@onNull ExternalTimeSuggestion timeSuggestion)283     public void suggestExternalTime(@NonNull ExternalTimeSuggestion timeSuggestion) {
284         if (DEBUG) {
285             Log.d(TAG, "suggestExternalTime called: " + timeSuggestion);
286         }
287         try {
288             mITimeDetectorService.suggestExternalTime(timeSuggestion);
289         } catch (RemoteException e) {
290             throw e.rethrowFromSystemServer();
291         }
292     }
293 
294     /**
295      * Returns a snapshot of the device's current system clock time state. See also {@link
296      * #confirmTime(UnixEpochTime)} for how this information can be used.
297      */
298     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
299     @NonNull
getTimeState()300     public TimeState getTimeState() {
301         if (DEBUG) {
302             Log.d(TAG, "getTimeState called");
303         }
304         try {
305             return mITimeDetectorService.getTimeState();
306         } catch (RemoteException e) {
307             throw e.rethrowFromSystemServer();
308         }
309     }
310 
311     /**
312      * Confirms the device's current time during device setup, raising the system's confidence in
313      * the time if needed. Unlike {@link #setManualTime(UnixEpochTime)}, which can only be used when
314      * automatic time detection is currently disabled, this method can be used regardless of the
315      * automatic time detection setting, but only to confirm the current time (which may have been
316      * set via automatic means). Use {@link #getTimeState()} to obtain the time state to confirm.
317      *
318      * <p>Returns {@code false} if the confirmation is invalid, i.e. if the time being
319      * confirmed is no longer the time the device is currently set to. Confirming a time
320      * in which the system already has high confidence will return {@code true}.
321      */
322     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
confirmTime(@onNull UnixEpochTime unixEpochTime)323     public boolean confirmTime(@NonNull UnixEpochTime unixEpochTime) {
324         if (DEBUG) {
325             Log.d(TAG, "confirmTime called: " + unixEpochTime);
326         }
327         try {
328             return mITimeDetectorService.confirmTime(unixEpochTime);
329         } catch (RemoteException e) {
330             throw e.rethrowFromSystemServer();
331         }
332     }
333 
334     /**
335      * Attempts to set the device's time, expected to be determined from the user's manually entered
336      * information.
337      *
338      * <p>Returns {@code false} if the time is invalid, or the device configuration / user
339      * capabilities prevents the time being accepted, e.g. if the device is currently set to
340      * "automatic time detection". This method returns {@code true} if the time was accepted even
341      * if it is the same as the current device time.
342      */
343     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
setManualTime(@onNull UnixEpochTime unixEpochTime)344     public boolean setManualTime(@NonNull UnixEpochTime unixEpochTime) {
345         if (DEBUG) {
346             Log.d(TAG, "setTime called: " + unixEpochTime);
347         }
348         try {
349             ManualTimeSuggestion manualTimeSuggestion = new ManualTimeSuggestion(unixEpochTime);
350             manualTimeSuggestion.addDebugInfo("TimeManager.setTime()");
351             manualTimeSuggestion.addDebugInfo("UID: " + android.os.Process.myUid());
352             manualTimeSuggestion.addDebugInfo("UserHandle: " + android.os.Process.myUserHandle());
353             manualTimeSuggestion.addDebugInfo("Process: " + android.os.Process.myProcessName());
354             return mITimeDetectorService.setManualTime(manualTimeSuggestion);
355         } catch (RemoteException e) {
356             throw e.rethrowFromSystemServer();
357         }
358     }
359 
360     /**
361      * Returns a snapshot of the device's current time zone state. See also {@link
362      * #confirmTimeZone(String)} and {@link #setManualTimeZone(String)} for how this information may
363      * be used.
364      */
365     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
366     @NonNull
getTimeZoneState()367     public TimeZoneState getTimeZoneState() {
368         if (DEBUG) {
369             Log.d(TAG, "getTimeZoneState called");
370         }
371         try {
372             return mITimeZoneDetectorService.getTimeZoneState();
373         } catch (RemoteException e) {
374             throw e.rethrowFromSystemServer();
375         }
376     }
377 
378     /**
379      * Confirms the device's current time zone ID, raising the system's confidence in the time zone
380      * if needed. Unlike {@link #setManualTimeZone(String)}, which can only be used when automatic
381      * time zone detection is currently disabled, this method can be used regardless of the
382      * automatic time zone detection setting, but only to confirm the current value (which may have
383      * been set via automatic means).
384      *
385      * <p>Returns {@code false} if the confirmation is invalid, i.e. if the time zone ID being
386      * confirmed is no longer the time zone ID the device is currently set to. Confirming a time
387      * zone ID in which the system already has high confidence returns {@code true}.
388      */
389     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
confirmTimeZone(@onNull String timeZoneId)390     public boolean confirmTimeZone(@NonNull String timeZoneId) {
391         if (DEBUG) {
392             Log.d(TAG, "confirmTimeZone called: " + timeZoneId);
393         }
394         try {
395             return mITimeZoneDetectorService.confirmTimeZone(timeZoneId);
396         } catch (RemoteException e) {
397             throw e.rethrowFromSystemServer();
398         }
399     }
400 
401     /**
402      * Attempts to set the device's time zone, expected to be determined from a user's manually
403      * entered information.
404      *
405      * <p>Returns {@code false} if the time zone is invalid, or the device configuration / user
406      * capabilities prevents the time zone being accepted, e.g. if the device is currently set to
407      * "automatic time zone detection". {@code true} is returned if the time zone is accepted. A
408      * time zone that is accepted and matches the current device time zone returns {@code true}.
409      */
410     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
setManualTimeZone(@onNull String timeZoneId)411     public boolean setManualTimeZone(@NonNull String timeZoneId) {
412         if (DEBUG) {
413             Log.d(TAG, "setManualTimeZone called: " + timeZoneId);
414         }
415         try {
416             ManualTimeZoneSuggestion manualTimeZoneSuggestion =
417                     new ManualTimeZoneSuggestion(timeZoneId);
418             manualTimeZoneSuggestion.addDebugInfo("TimeManager.setManualTimeZone()");
419             manualTimeZoneSuggestion.addDebugInfo("UID: " + android.os.Process.myUid());
420             manualTimeZoneSuggestion.addDebugInfo("Process: " + android.os.Process.myProcessName());
421             return mITimeZoneDetectorService.setManualTimeZone(manualTimeZoneSuggestion);
422         } catch (RemoteException e) {
423             throw e.rethrowFromSystemServer();
424         }
425     }
426 }
427