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.timezonedetector.ITimeZoneDetectorService;
25 import android.content.Context;
26 import android.os.RemoteException;
27 import android.os.ServiceManager;
28 import android.os.ServiceManager.ServiceNotFoundException;
29 import android.util.ArrayMap;
30 import android.util.Log;
31 
32 import com.android.internal.annotations.GuardedBy;
33 
34 import java.util.concurrent.Executor;
35 
36 /**
37  * The interface through which system components can interact with time and time zone services.
38  *
39  * @hide
40  */
41 @SystemApi
42 @SystemService(Context.TIME_MANAGER)
43 public final class TimeManager {
44     private static final String TAG = "time.TimeManager";
45     private static final boolean DEBUG = false;
46 
47     private final Object mLock = new Object();
48     private final ITimeZoneDetectorService mITimeZoneDetectorService;
49     private final ITimeDetectorService mITimeDetectorService;
50 
51     @GuardedBy("mLock")
52     private ITimeZoneDetectorListener mTimeZoneDetectorReceiver;
53 
54     /**
55      * The registered listeners. The key is the actual listener that was registered, the value is a
56      * wrapper that ensures the listener is executed on the correct Executor.
57      */
58     @GuardedBy("mLock")
59     private ArrayMap<TimeZoneDetectorListener, TimeZoneDetectorListener> mTimeZoneDetectorListeners;
60 
61     /** @hide */
TimeManager()62     public TimeManager() throws ServiceNotFoundException {
63         // TimeManager is an API over one or possibly more services. At least until there's an
64         // internal refactoring.
65         mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
66                 ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
67         mITimeDetectorService = ITimeDetectorService.Stub.asInterface(
68                 ServiceManager.getServiceOrThrow(Context.TIME_DETECTOR_SERVICE));
69     }
70 
71     /**
72      * Returns the calling user's time zone capabilities and configuration.
73      */
74     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
75     @NonNull
getTimeZoneCapabilitiesAndConfig()76     public TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig() {
77         if (DEBUG) {
78             Log.d(TAG, "getTimeZoneCapabilitiesAndConfig called");
79         }
80         try {
81             return mITimeZoneDetectorService.getCapabilitiesAndConfig();
82         } catch (RemoteException e) {
83             throw e.rethrowFromSystemServer();
84         }
85     }
86 
87     /**
88      * Returns the calling user's time capabilities and configuration.
89      *
90      * @hide
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      * @return {@code true} if all the configuration settings specified have been set to the
109      * new values, {@code false} if none have
110      *
111      * @hide
112      */
113     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
updateTimeConfiguration(@onNull TimeConfiguration configuration)114     public boolean updateTimeConfiguration(@NonNull TimeConfiguration configuration) {
115         if (DEBUG) {
116             Log.d(TAG, "updateTimeConfiguration called: " + configuration);
117         }
118         try {
119             return mITimeDetectorService.updateConfiguration(configuration);
120         } catch (RemoteException e) {
121             throw e.rethrowFromSystemServer();
122         }
123     }
124 
125     /**
126      * Modifies the time zone detection configuration.
127      *
128      * <p>Configuration settings vary in scope: some may be global (affect all users), others may be
129      * specific to the current user.
130      *
131      * <p>The ability to modify configuration settings can be subject to restrictions. For
132      * example, they may be determined by device hardware, general policy (i.e. only the primary
133      * user can set them), or by a managed device policy. Use {@link
134      * #getTimeZoneCapabilitiesAndConfig()} to obtain information at runtime about the user's
135      * capabilities.
136      *
137      * <p>Attempts to modify configuration settings with capabilities that are {@link
138      * Capabilities#CAPABILITY_NOT_SUPPORTED} or {@link
139      * Capabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
140      * will be returned. Modifying configuration settings with capabilities that are {@link
141      * Capabilities#CAPABILITY_NOT_APPLICABLE} or {@link
142      * Capabilities#CAPABILITY_POSSESSED} will succeed. See {@link
143      * TimeZoneCapabilities} for further details.
144      *
145      * <p>If the supplied configuration only has some values set, then only the specified settings
146      * will be updated (where the user's capabilities allow) and other settings will be left
147      * unchanged.
148      *
149      * @return {@code true} if all the configuration settings specified have been set to the
150      *   new values, {@code false} if none have
151      */
152     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
updateTimeZoneConfiguration(@onNull TimeZoneConfiguration configuration)153     public boolean updateTimeZoneConfiguration(@NonNull TimeZoneConfiguration configuration) {
154         if (DEBUG) {
155             Log.d(TAG, "updateTimeZoneConfiguration called: " + configuration);
156         }
157         try {
158             return mITimeZoneDetectorService.updateConfiguration(configuration);
159         } catch (RemoteException e) {
160             throw e.rethrowFromSystemServer();
161         }
162     }
163 
164     /**
165      * An interface that can be used to listen for changes to the time zone detector behavior.
166      */
167     @FunctionalInterface
168     public interface TimeZoneDetectorListener {
169         /**
170          * Called when something about the time zone detector behavior on the device has changed.
171          * For example, this could be because the current user has switched, one of the global or
172          * user's settings been changed, or something that could affect a user's capabilities with
173          * respect to the time zone detector has changed. Because different users can have different
174          * configuration and capabilities, this method may be called when nothing has changed for
175          * the receiving user.
176          */
onChange()177         void onChange();
178     }
179 
180     /**
181      * Registers a listener that will be informed when something about the time zone detector
182      * behavior changes.
183      */
184     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
addTimeZoneDetectorListener(@onNull Executor executor, @NonNull TimeZoneDetectorListener listener)185     public void addTimeZoneDetectorListener(@NonNull Executor executor,
186             @NonNull TimeZoneDetectorListener listener) {
187 
188         if (DEBUG) {
189             Log.d(TAG, "addTimeZoneDetectorListener called: " + listener);
190         }
191         synchronized (mLock) {
192             if (mTimeZoneDetectorListeners == null) {
193                 mTimeZoneDetectorListeners = new ArrayMap<>();
194             } else if (mTimeZoneDetectorListeners.containsKey(listener)) {
195                 return;
196             }
197 
198             if (mTimeZoneDetectorReceiver == null) {
199                 ITimeZoneDetectorListener iListener = new ITimeZoneDetectorListener.Stub() {
200                     @Override
201                     public void onChange() {
202                         notifyTimeZoneDetectorListeners();
203                     }
204                 };
205                 mTimeZoneDetectorReceiver = iListener;
206                 try {
207                     mITimeZoneDetectorService.addListener(mTimeZoneDetectorReceiver);
208                 } catch (RemoteException e) {
209                     throw e.rethrowFromSystemServer();
210                 }
211             }
212             mTimeZoneDetectorListeners.put(listener, () -> executor.execute(listener::onChange));
213         }
214     }
215 
notifyTimeZoneDetectorListeners()216     private void notifyTimeZoneDetectorListeners() {
217         ArrayMap<TimeZoneDetectorListener, TimeZoneDetectorListener> timeZoneDetectorListeners;
218         synchronized (mLock) {
219             if (mTimeZoneDetectorListeners == null || mTimeZoneDetectorListeners.isEmpty()) {
220                 return;
221             }
222             timeZoneDetectorListeners = new ArrayMap<>(mTimeZoneDetectorListeners);
223         }
224         int size = timeZoneDetectorListeners.size();
225         for (int i = 0; i < size; i++) {
226             timeZoneDetectorListeners.valueAt(i).onChange();
227         }
228     }
229 
230     /**
231      * Removes a listener previously passed to
232      * {@link #addTimeZoneDetectorListener(Executor, TimeZoneDetectorListener)}
233      */
234     @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
removeTimeZoneDetectorListener(@onNull TimeZoneDetectorListener listener)235     public void removeTimeZoneDetectorListener(@NonNull TimeZoneDetectorListener listener) {
236         if (DEBUG) {
237             Log.d(TAG, "removeConfigurationListener called: " + listener);
238         }
239 
240         synchronized (mLock) {
241             if (mTimeZoneDetectorListeners == null || mTimeZoneDetectorListeners.isEmpty()) {
242                 return;
243             }
244             mTimeZoneDetectorListeners.remove(listener);
245 
246             // If the last local listener has been removed, remove and discard the
247             // mTimeZoneDetectorReceiver.
248             if (mTimeZoneDetectorListeners.isEmpty()) {
249                 try {
250                     mITimeZoneDetectorService.removeListener(mTimeZoneDetectorReceiver);
251                 } catch (RemoteException e) {
252                     throw e.rethrowFromSystemServer();
253                 } finally {
254                     mTimeZoneDetectorReceiver = null;
255                 }
256             }
257         }
258     }
259 
260     /**
261      * Suggests the current time from an external time source. For example, a form factor-specific
262      * HAL. This time <em>may</em> be used to set the device system clock, depending on the device
263      * configuration and user settings. This method call is processed asynchronously.
264      * See {@link ExternalTimeSuggestion} for more details.
265      */
266     @RequiresPermission(android.Manifest.permission.SUGGEST_EXTERNAL_TIME)
suggestExternalTime(@onNull ExternalTimeSuggestion timeSuggestion)267     public void suggestExternalTime(@NonNull ExternalTimeSuggestion timeSuggestion) {
268         if (DEBUG) {
269             Log.d(TAG, "suggestExternalTime called: " + timeSuggestion);
270         }
271         try {
272             mITimeDetectorService.suggestExternalTime(timeSuggestion);
273         } catch (RemoteException e) {
274             throw e.rethrowFromSystemServer();
275         }
276     }
277 }
278