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