Loading apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +51 −32 Original line number Diff line number Diff line Loading @@ -39,6 +39,8 @@ import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROU import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED; import static android.os.UserHandle.USER_SYSTEM; import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import static com.android.server.SystemTimeZone.getTimeZoneId; import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX; import static com.android.server.alarm.Alarm.BATTERY_SAVER_POLICY_INDEX; import static com.android.server.alarm.Alarm.DEVICE_IDLE_POLICY_INDEX; Loading Loading @@ -146,6 +148,8 @@ import com.android.server.JobSchedulerBackgroundThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; import com.android.server.SystemTimeZone; import com.android.server.SystemTimeZone.TimeZoneConfidence; import com.android.server.pm.permission.PermissionManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; Loading Loading @@ -201,7 +205,6 @@ public class AlarmManagerService extends SystemService { static final boolean DEBUG_TARE = localLOGV || false; static final boolean RECORD_ALARMS_IN_HISTORY = true; static final boolean RECORD_DEVICE_IDLE_ALARMS = false; static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; static final int TICK_HISTORY_DEPTH = 10; static final long INDEFINITE_DELAY = 365 * INTERVAL_DAY; Loading Loading @@ -1884,9 +1887,10 @@ public class AlarmManagerService extends SystemService { mNextWakeup = mNextNonWakeup = 0; // We have to set current TimeZone info to kernel // because kernel doesn't keep this after reboot setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY)); // We set the current offset in kernel because the kernel doesn't keep this after a // reboot. Keeping the kernel time zone in sync is "best effort" and can be wrong // for a period after daylight savings transitions. mInjector.syncKernelTimeZoneOffset(); // Ensure that we're booting with a halfway sensible current time. Use the // most recent of Build.TIME, the root file system's timestamp, and the Loading Loading @@ -2122,12 +2126,14 @@ public class AlarmManagerService extends SystemService { synchronized (mLock) { final long currentTimeMillis = mInjector.getCurrentTimeMillis(); mInjector.setKernelTime(millis); final TimeZone timeZone = TimeZone.getDefault(); // Changing the time may cross a DST transition; sync the kernel offset if needed. final TimeZone timeZone = TimeZone.getTimeZone(SystemTimeZone.getTimeZoneId()); final int currentTzOffset = timeZone.getOffset(currentTimeMillis); final int newTzOffset = timeZone.getOffset(millis); if (currentTzOffset != newTzOffset) { Slog.i(TAG, "Timezone offset has changed, updating kernel timezone"); mInjector.setKernelTimezone(-(newTzOffset / 60000)); mInjector.setKernelTimeZoneOffset(newTzOffset); } // The native implementation of setKernelTime can return -1 even when the kernel // time was set correctly, so assume setting kernel time was successful and always Loading @@ -2136,31 +2142,28 @@ public class AlarmManagerService extends SystemService { } } void setTimeZoneImpl(String tz) { if (TextUtils.isEmpty(tz)) { void setTimeZoneImpl(String tzId, @TimeZoneConfidence int confidence) { if (TextUtils.isEmpty(tzId)) { return; } TimeZone zone = TimeZone.getTimeZone(tz); TimeZone newZone = TimeZone.getTimeZone(tzId); // Prevent reentrant calls from stepping on each other when writing // the time zone property boolean timeZoneWasChanged = false; boolean timeZoneWasChanged; synchronized (this) { String current = SystemProperties.get(TIMEZONE_PROPERTY); if (current == null || !current.equals(zone.getID())) { if (localLOGV) { Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID()); } timeZoneWasChanged = true; SystemProperties.set(TIMEZONE_PROPERTY, zone.getID()); } // TimeZone.getTimeZone() can return a time zone with a different ID (e.g. it can return // "GMT" if the ID is unrecognized). The parameter ID is used here rather than // newZone.getId(). It will be rejected if it is invalid. timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence); // Update the kernel timezone information // Kernel tracks time offsets as 'minutes west of GMT' int gmtOffset = zone.getOffset(mInjector.getCurrentTimeMillis()); mInjector.setKernelTimezone(-(gmtOffset / 60000)); int utcOffsetMillis = newZone.getOffset(mInjector.getCurrentTimeMillis()); mInjector.setKernelTimeZoneOffset(utcOffsetMillis); } // Clear the default time zone in the system server process. This forces the next call // to TimeZone.getDefault() to re-read the device settings. TimeZone.setDefault(null); if (timeZoneWasChanged) { Loading @@ -2173,7 +2176,7 @@ public class AlarmManagerService extends SystemService { | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); intent.putExtra(Intent.EXTRA_TIMEZONE, zone.getID()); intent.putExtra(Intent.EXTRA_TIMEZONE, newZone.getID()); mOptsTimeBroadcast.setTemporaryAppAllowlist( mActivityManagerInternal.getBootTimeTempAllowListDuration(), TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, Loading Loading @@ -2689,6 +2692,11 @@ public class AlarmManagerService extends SystemService { || hasUseExactAlarmInternal(packageName, uid); } @Override public void setTimeZone(String tzId, @TimeZoneConfidence int confidence) { setTimeZoneImpl(tzId, confidence); } @Override public void registerInFlightListener(InFlightListener callback) { synchronized (mLock) { Loading Loading @@ -2974,7 +2982,11 @@ public class AlarmManagerService extends SystemService { final long oldId = Binder.clearCallingIdentity(); try { setTimeZoneImpl(tz); // The public API (and the shell command that also uses this method) have no concept // of confidence, but since the time zone ID should come either from apps working on // behalf of the user or a developer, confidence is assumed "high". final int timeZoneConfidence = TIME_ZONE_CONFIDENCE_HIGH; setTimeZoneImpl(tz, timeZoneConfidence); } finally { Binder.restoreCallingIdentity(oldId); } Loading Loading @@ -4546,8 +4558,18 @@ public class AlarmManagerService extends SystemService { return AlarmManagerService.getNextAlarm(mNativeData, type); } void setKernelTimezone(int minutesWest) { AlarmManagerService.setKernelTimezone(mNativeData, minutesWest); void setKernelTimeZoneOffset(int utcOffsetMillis) { // Kernel tracks time offsets as 'minutes west of GMT' AlarmManagerService.setKernelTimezone(mNativeData, -(utcOffsetMillis / 60000)); } void syncKernelTimeZoneOffset() { long currentTimeMillis = getCurrentTimeMillis(); TimeZone currentTimeZone = TimeZone.getTimeZone(getTimeZoneId()); // If the time zone ID is invalid, GMT will be returned and this will set a kernel // offset of zero. int utcOffsetMillis = currentTimeZone.getOffset(currentTimeMillis); setKernelTimeZoneOffset(utcOffsetMillis); } void setKernelTime(long millis) { Loading Loading @@ -5014,13 +5036,10 @@ public class AlarmManagerService extends SystemService { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) { // Since the kernel does not keep track of DST, we need to // reset the TZ information at the beginning of each day // based off of the current Zone gmt offset + userspace tracked // daylight savings information. TimeZone zone = TimeZone.getTimeZone(SystemProperties.get(TIMEZONE_PROPERTY)); int gmtOffset = zone.getOffset(mInjector.getCurrentTimeMillis()); mInjector.setKernelTimezone(-(gmtOffset / 60000)); // Since the kernel does not keep track of DST, we reset the TZ information at the // beginning of each day. This may miss a DST transition, but it will correct itself // within 24 hours. mInjector.syncKernelTimeZoneOffset(); scheduleDateChangedEvent(); } } Loading services/core/java/com/android/server/AlarmManagerInternal.java +11 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.server; import android.app.PendingIntent; import com.android.server.SystemTimeZone.TimeZoneConfidence; public interface AlarmManagerInternal { // Some other components in the system server need to know about // broadcast alarms currently in flight Loading Loading @@ -48,4 +50,13 @@ public interface AlarmManagerInternal { * {@link android.Manifest.permission#USE_EXACT_ALARM}. */ boolean hasExactAlarmPermission(String packageName, int uid); /** * Sets the device's current time zone and time zone confidence. * * @param tzId the time zone ID * @param confidence the confidence that {@code tzId} is correct, see {@link TimeZoneConfidence} * for details */ void setTimeZone(String tzId, @TimeZoneConfidence int confidence); } services/core/java/com/android/server/SystemTimeZone.java 0 → 100644 +160 −0 Original line number Diff line number Diff line /* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server; import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.os.SystemProperties; import android.text.TextUtils; import android.util.Slog; import com.android.i18n.timezone.ZoneInfoDb; import java.lang.annotation.Retention; import java.lang.annotation.Target; /** * A set of constants and static methods that encapsulate knowledge of how time zone and associated * metadata are stored on Android. */ public final class SystemTimeZone { private static final String TAG = "SystemTimeZone"; private static final boolean DEBUG = false; private static final String TIME_ZONE_SYSTEM_PROPERTY = "persist.sys.timezone"; private static final String TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY = "persist.sys.timezone_confidence"; /** * The "special" time zone ID used as a low-confidence default when the device's time zone * is empty or invalid during boot. */ private static final String DEFAULT_TIME_ZONE_ID = "GMT"; /** * An annotation that indicates a "time zone confidence" value is expected. * * <p>The confidence indicates whether the time zone is expected to be correct. The confidence * can be upgraded or downgraded over time. It can be used to decide whether a user could / * should be asked to confirm the time zone. For example, during device set up low confidence * would describe a time zone that has been initialized by default or by using low quality * or ambiguous signals. The user may then be asked to confirm the time zone, moving it to a * high confidence. */ @Retention(SOURCE) @Target(TYPE_USE) @IntDef(prefix = "TIME_ZONE_CONFIDENCE_", value = { TIME_ZONE_CONFIDENCE_LOW, TIME_ZONE_CONFIDENCE_HIGH }) public @interface TimeZoneConfidence { } /** Used when confidence is low and would (ideally) be confirmed by a user. */ public static final @TimeZoneConfidence int TIME_ZONE_CONFIDENCE_LOW = 0; /** * Used when confidence in the time zone is high and does not need to be confirmed by a user. */ public static final @TimeZoneConfidence int TIME_ZONE_CONFIDENCE_HIGH = 100; private SystemTimeZone() {} /** * Called during device boot to validate and set the time zone ID to a low-confidence default. */ public static void initializeTimeZoneSettingsIfRequired() { String timezoneProperty = SystemProperties.get(TIME_ZONE_SYSTEM_PROPERTY); if (!isValidTimeZoneId(timezoneProperty)) { Slog.w(TAG, TIME_ZONE_SYSTEM_PROPERTY + " is not valid (" + timezoneProperty + "); setting to " + DEFAULT_TIME_ZONE_ID); setTimeZoneId(DEFAULT_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW); } } /** * Updates the device's time zone system property and associated metadata. Returns {@code true} * if the device's time zone changed, {@code false} if the ID is invalid or the device is * already set to the supplied ID. * * <p>This method ensures the confidence metadata is set to the supplied value if the supplied * time zone ID is considered valid. * * <p>This method is intended only for use by the AlarmManager. When changing the device's time * zone other system service components must use {@link * com.android.server.AlarmManagerInternal#setTimeZone(String, int)} to ensure that important * system-wide side effects occur. */ public static boolean setTimeZoneId(String timeZoneId, @TimeZoneConfidence int confidence) { if (TextUtils.isEmpty(timeZoneId) || !isValidTimeZoneId(timeZoneId)) { return false; } boolean timeZoneChanged = false; synchronized (SystemTimeZone.class) { String currentTimeZoneId = getTimeZoneId(); if (currentTimeZoneId == null || !currentTimeZoneId.equals(timeZoneId)) { SystemProperties.set(TIME_ZONE_SYSTEM_PROPERTY, timeZoneId); if (DEBUG) { Slog.v(TAG, "Time zone changed: " + currentTimeZoneId + ", new=" + timeZoneId); } timeZoneChanged = true; } setTimeZoneConfidence(confidence); } return timeZoneChanged; } /** * Sets the time zone confidence value if required. See {@link TimeZoneConfidence} for details. */ private static void setTimeZoneConfidence(@TimeZoneConfidence int confidence) { int currentConfidence = getTimeZoneConfidence(); if (currentConfidence != confidence) { SystemProperties.set( TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY, Integer.toString(confidence)); if (DEBUG) { Slog.v(TAG, "Time zone confidence changed: old=" + currentConfidence + ", new=" + confidence); } } } /** Returns the time zone confidence value. See {@link TimeZoneConfidence} for details. */ public static @TimeZoneConfidence int getTimeZoneConfidence() { int confidence = SystemProperties.getInt( TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY, TIME_ZONE_CONFIDENCE_LOW); if (!isValidTimeZoneConfidence(confidence)) { confidence = TIME_ZONE_CONFIDENCE_LOW; } return confidence; } /** Returns the device's time zone ID setting. */ public static String getTimeZoneId() { return SystemProperties.get(TIME_ZONE_SYSTEM_PROPERTY); } private static boolean isValidTimeZoneConfidence(@TimeZoneConfidence int confidence) { return confidence >= TIME_ZONE_CONFIDENCE_LOW && confidence <= TIME_ZONE_CONFIDENCE_HIGH; } private static boolean isValidTimeZoneId(String timeZoneId) { return timeZoneId != null && !timeZoneId.isEmpty() && ZoneInfoDb.getInstance().hasTimeZone(timeZoneId); } } services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java +15 −17 Original line number Diff line number Diff line Loading @@ -18,13 +18,16 @@ package com.android.server.timezonedetector; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AlarmManager; import android.content.Context; import android.os.Handler; import android.os.SystemClock; import android.os.SystemProperties; import com.android.server.AlarmManagerInternal; import com.android.server.LocalServices; import com.android.server.SystemTimeZone; import com.android.server.SystemTimeZone.TimeZoneConfidence; import java.util.Objects; /** Loading Loading @@ -59,27 +62,22 @@ final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment } @Override public boolean isDeviceTimeZoneInitialized() { // timezone.equals("GMT") will be true and only true if the time zone was // set to a default value by the system server (when starting, system server // sets the persist.sys.timezone to "GMT" if it's not set). "GMT" is not used by // any code that sets it explicitly (in case where something sets GMT explicitly, // "Etc/GMT" Olson ID would be used). String timeZoneId = getDeviceTimeZone(); return timeZoneId != null && timeZoneId.length() > 0 && !timeZoneId.equals("GMT"); @NonNull public String getDeviceTimeZone() { return SystemProperties.get(TIMEZONE_PROPERTY); } @Override @Nullable public String getDeviceTimeZone() { return SystemProperties.get(TIMEZONE_PROPERTY); public @TimeZoneConfidence int getDeviceTimeZoneConfidence() { return SystemTimeZone.getTimeZoneConfidence(); } @Override public void setDeviceTimeZone(String zoneId) { AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); alarmManager.setTimeZone(zoneId); public void setDeviceTimeZoneAndConfidence( @NonNull String zoneId, @TimeZoneConfidence int confidence) { AlarmManagerInternal alarmManagerInternal = LocalServices.getService(AlarmManagerInternal.class); alarmManagerInternal.setTimeZone(zoneId, confidence); } @Override Loading services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +28 −17 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_M import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE; import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -39,6 +41,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.SystemTimeZone.TimeZoneConfidence; import java.time.Duration; import java.util.List; Loading Loading @@ -73,19 +76,20 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); /** * Returns true if the device has had an explicit time zone set. * Returns the device's currently configured time zone. */ boolean isDeviceTimeZoneInitialized(); @NonNull String getDeviceTimeZone(); /** * Returns the device's currently configured time zone. * Returns the confidence of the device's current time zone. */ String getDeviceTimeZone(); @TimeZoneConfidence int getDeviceTimeZoneConfidence(); /** * Sets the device's time zone. * Sets the device's time zone and associated confidence. */ void setDeviceTimeZone(@NonNull String zoneId); void setDeviceTimeZoneAndConfidence( @NonNull String zoneId, @TimeZoneConfidence int confidence); /** * Returns the time according to the elapsed realtime clock, the same as {@link Loading Loading @@ -620,25 +624,32 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat @GuardedBy("this") private void setDeviceTimeZoneIfRequired(@NonNull String newZoneId, @NonNull String cause) { String currentZoneId = mEnvironment.getDeviceTimeZone(); // Avoid unnecessary changes / intents. if (newZoneId.equals(currentZoneId)) { // No need to set the device time zone - the setting is already what we would be // suggesting. // All manual and automatic suggestions are considered high confidence as low-quality // suggestions are not currently passed on. int newConfidence = TIME_ZONE_CONFIDENCE_HIGH; int currentConfidence = mEnvironment.getDeviceTimeZoneConfidence(); // Avoid unnecessary changes / intents. If the newConfidence is higher than the stored value // then we want to upgrade it. if (newZoneId.equals(currentZoneId) && newConfidence <= currentConfidence) { // No need to modify the device time zone settings. if (DBG) { Slog.d(LOG_TAG, "No need to change the time zone;" + " device is already set to newZoneId." + ", newZoneId=" + newZoneId + ", cause=" + cause); + ", cause=" + cause + ", currentScore=" + currentConfidence + ", newConfidence=" + newConfidence); } return; } mEnvironment.setDeviceTimeZone(newZoneId); String logMsg = "Set device time zone." mEnvironment.setDeviceTimeZoneAndConfidence(newZoneId, newConfidence); String logMsg = "Set device time zone or higher confidence." + ", currentZoneId=" + currentZoneId + ", newZoneId=" + newZoneId + ", cause=" + cause; + ", cause=" + cause + ", newConfidence=" + newConfidence; logTimeZoneDetectorChange(logMsg); } Loading Loading @@ -710,9 +721,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal); ipw.println("[Capabilities=" + mCurrentConfigurationInternal.createCapabilitiesAndConfig() + "]"); ipw.println("mEnvironment.isDeviceTimeZoneInitialized()=" + mEnvironment.isDeviceTimeZoneInitialized()); ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone()); ipw.println("mEnvironment.getDeviceTimeZoneConfidence()=" + mEnvironment.getDeviceTimeZoneConfidence()); ipw.println("Misc state:"); ipw.increaseIndent(); // level 2 Loading Loading
apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +51 −32 Original line number Diff line number Diff line Loading @@ -39,6 +39,8 @@ import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROU import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED; import static android.os.UserHandle.USER_SYSTEM; import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import static com.android.server.SystemTimeZone.getTimeZoneId; import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX; import static com.android.server.alarm.Alarm.BATTERY_SAVER_POLICY_INDEX; import static com.android.server.alarm.Alarm.DEVICE_IDLE_POLICY_INDEX; Loading Loading @@ -146,6 +148,8 @@ import com.android.server.JobSchedulerBackgroundThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; import com.android.server.SystemTimeZone; import com.android.server.SystemTimeZone.TimeZoneConfidence; import com.android.server.pm.permission.PermissionManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; Loading Loading @@ -201,7 +205,6 @@ public class AlarmManagerService extends SystemService { static final boolean DEBUG_TARE = localLOGV || false; static final boolean RECORD_ALARMS_IN_HISTORY = true; static final boolean RECORD_DEVICE_IDLE_ALARMS = false; static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; static final int TICK_HISTORY_DEPTH = 10; static final long INDEFINITE_DELAY = 365 * INTERVAL_DAY; Loading Loading @@ -1884,9 +1887,10 @@ public class AlarmManagerService extends SystemService { mNextWakeup = mNextNonWakeup = 0; // We have to set current TimeZone info to kernel // because kernel doesn't keep this after reboot setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY)); // We set the current offset in kernel because the kernel doesn't keep this after a // reboot. Keeping the kernel time zone in sync is "best effort" and can be wrong // for a period after daylight savings transitions. mInjector.syncKernelTimeZoneOffset(); // Ensure that we're booting with a halfway sensible current time. Use the // most recent of Build.TIME, the root file system's timestamp, and the Loading Loading @@ -2122,12 +2126,14 @@ public class AlarmManagerService extends SystemService { synchronized (mLock) { final long currentTimeMillis = mInjector.getCurrentTimeMillis(); mInjector.setKernelTime(millis); final TimeZone timeZone = TimeZone.getDefault(); // Changing the time may cross a DST transition; sync the kernel offset if needed. final TimeZone timeZone = TimeZone.getTimeZone(SystemTimeZone.getTimeZoneId()); final int currentTzOffset = timeZone.getOffset(currentTimeMillis); final int newTzOffset = timeZone.getOffset(millis); if (currentTzOffset != newTzOffset) { Slog.i(TAG, "Timezone offset has changed, updating kernel timezone"); mInjector.setKernelTimezone(-(newTzOffset / 60000)); mInjector.setKernelTimeZoneOffset(newTzOffset); } // The native implementation of setKernelTime can return -1 even when the kernel // time was set correctly, so assume setting kernel time was successful and always Loading @@ -2136,31 +2142,28 @@ public class AlarmManagerService extends SystemService { } } void setTimeZoneImpl(String tz) { if (TextUtils.isEmpty(tz)) { void setTimeZoneImpl(String tzId, @TimeZoneConfidence int confidence) { if (TextUtils.isEmpty(tzId)) { return; } TimeZone zone = TimeZone.getTimeZone(tz); TimeZone newZone = TimeZone.getTimeZone(tzId); // Prevent reentrant calls from stepping on each other when writing // the time zone property boolean timeZoneWasChanged = false; boolean timeZoneWasChanged; synchronized (this) { String current = SystemProperties.get(TIMEZONE_PROPERTY); if (current == null || !current.equals(zone.getID())) { if (localLOGV) { Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID()); } timeZoneWasChanged = true; SystemProperties.set(TIMEZONE_PROPERTY, zone.getID()); } // TimeZone.getTimeZone() can return a time zone with a different ID (e.g. it can return // "GMT" if the ID is unrecognized). The parameter ID is used here rather than // newZone.getId(). It will be rejected if it is invalid. timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence); // Update the kernel timezone information // Kernel tracks time offsets as 'minutes west of GMT' int gmtOffset = zone.getOffset(mInjector.getCurrentTimeMillis()); mInjector.setKernelTimezone(-(gmtOffset / 60000)); int utcOffsetMillis = newZone.getOffset(mInjector.getCurrentTimeMillis()); mInjector.setKernelTimeZoneOffset(utcOffsetMillis); } // Clear the default time zone in the system server process. This forces the next call // to TimeZone.getDefault() to re-read the device settings. TimeZone.setDefault(null); if (timeZoneWasChanged) { Loading @@ -2173,7 +2176,7 @@ public class AlarmManagerService extends SystemService { | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); intent.putExtra(Intent.EXTRA_TIMEZONE, zone.getID()); intent.putExtra(Intent.EXTRA_TIMEZONE, newZone.getID()); mOptsTimeBroadcast.setTemporaryAppAllowlist( mActivityManagerInternal.getBootTimeTempAllowListDuration(), TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, Loading Loading @@ -2689,6 +2692,11 @@ public class AlarmManagerService extends SystemService { || hasUseExactAlarmInternal(packageName, uid); } @Override public void setTimeZone(String tzId, @TimeZoneConfidence int confidence) { setTimeZoneImpl(tzId, confidence); } @Override public void registerInFlightListener(InFlightListener callback) { synchronized (mLock) { Loading Loading @@ -2974,7 +2982,11 @@ public class AlarmManagerService extends SystemService { final long oldId = Binder.clearCallingIdentity(); try { setTimeZoneImpl(tz); // The public API (and the shell command that also uses this method) have no concept // of confidence, but since the time zone ID should come either from apps working on // behalf of the user or a developer, confidence is assumed "high". final int timeZoneConfidence = TIME_ZONE_CONFIDENCE_HIGH; setTimeZoneImpl(tz, timeZoneConfidence); } finally { Binder.restoreCallingIdentity(oldId); } Loading Loading @@ -4546,8 +4558,18 @@ public class AlarmManagerService extends SystemService { return AlarmManagerService.getNextAlarm(mNativeData, type); } void setKernelTimezone(int minutesWest) { AlarmManagerService.setKernelTimezone(mNativeData, minutesWest); void setKernelTimeZoneOffset(int utcOffsetMillis) { // Kernel tracks time offsets as 'minutes west of GMT' AlarmManagerService.setKernelTimezone(mNativeData, -(utcOffsetMillis / 60000)); } void syncKernelTimeZoneOffset() { long currentTimeMillis = getCurrentTimeMillis(); TimeZone currentTimeZone = TimeZone.getTimeZone(getTimeZoneId()); // If the time zone ID is invalid, GMT will be returned and this will set a kernel // offset of zero. int utcOffsetMillis = currentTimeZone.getOffset(currentTimeMillis); setKernelTimeZoneOffset(utcOffsetMillis); } void setKernelTime(long millis) { Loading Loading @@ -5014,13 +5036,10 @@ public class AlarmManagerService extends SystemService { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) { // Since the kernel does not keep track of DST, we need to // reset the TZ information at the beginning of each day // based off of the current Zone gmt offset + userspace tracked // daylight savings information. TimeZone zone = TimeZone.getTimeZone(SystemProperties.get(TIMEZONE_PROPERTY)); int gmtOffset = zone.getOffset(mInjector.getCurrentTimeMillis()); mInjector.setKernelTimezone(-(gmtOffset / 60000)); // Since the kernel does not keep track of DST, we reset the TZ information at the // beginning of each day. This may miss a DST transition, but it will correct itself // within 24 hours. mInjector.syncKernelTimeZoneOffset(); scheduleDateChangedEvent(); } } Loading
services/core/java/com/android/server/AlarmManagerInternal.java +11 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.server; import android.app.PendingIntent; import com.android.server.SystemTimeZone.TimeZoneConfidence; public interface AlarmManagerInternal { // Some other components in the system server need to know about // broadcast alarms currently in flight Loading Loading @@ -48,4 +50,13 @@ public interface AlarmManagerInternal { * {@link android.Manifest.permission#USE_EXACT_ALARM}. */ boolean hasExactAlarmPermission(String packageName, int uid); /** * Sets the device's current time zone and time zone confidence. * * @param tzId the time zone ID * @param confidence the confidence that {@code tzId} is correct, see {@link TimeZoneConfidence} * for details */ void setTimeZone(String tzId, @TimeZoneConfidence int confidence); }
services/core/java/com/android/server/SystemTimeZone.java 0 → 100644 +160 −0 Original line number Diff line number Diff line /* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server; import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.os.SystemProperties; import android.text.TextUtils; import android.util.Slog; import com.android.i18n.timezone.ZoneInfoDb; import java.lang.annotation.Retention; import java.lang.annotation.Target; /** * A set of constants and static methods that encapsulate knowledge of how time zone and associated * metadata are stored on Android. */ public final class SystemTimeZone { private static final String TAG = "SystemTimeZone"; private static final boolean DEBUG = false; private static final String TIME_ZONE_SYSTEM_PROPERTY = "persist.sys.timezone"; private static final String TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY = "persist.sys.timezone_confidence"; /** * The "special" time zone ID used as a low-confidence default when the device's time zone * is empty or invalid during boot. */ private static final String DEFAULT_TIME_ZONE_ID = "GMT"; /** * An annotation that indicates a "time zone confidence" value is expected. * * <p>The confidence indicates whether the time zone is expected to be correct. The confidence * can be upgraded or downgraded over time. It can be used to decide whether a user could / * should be asked to confirm the time zone. For example, during device set up low confidence * would describe a time zone that has been initialized by default or by using low quality * or ambiguous signals. The user may then be asked to confirm the time zone, moving it to a * high confidence. */ @Retention(SOURCE) @Target(TYPE_USE) @IntDef(prefix = "TIME_ZONE_CONFIDENCE_", value = { TIME_ZONE_CONFIDENCE_LOW, TIME_ZONE_CONFIDENCE_HIGH }) public @interface TimeZoneConfidence { } /** Used when confidence is low and would (ideally) be confirmed by a user. */ public static final @TimeZoneConfidence int TIME_ZONE_CONFIDENCE_LOW = 0; /** * Used when confidence in the time zone is high and does not need to be confirmed by a user. */ public static final @TimeZoneConfidence int TIME_ZONE_CONFIDENCE_HIGH = 100; private SystemTimeZone() {} /** * Called during device boot to validate and set the time zone ID to a low-confidence default. */ public static void initializeTimeZoneSettingsIfRequired() { String timezoneProperty = SystemProperties.get(TIME_ZONE_SYSTEM_PROPERTY); if (!isValidTimeZoneId(timezoneProperty)) { Slog.w(TAG, TIME_ZONE_SYSTEM_PROPERTY + " is not valid (" + timezoneProperty + "); setting to " + DEFAULT_TIME_ZONE_ID); setTimeZoneId(DEFAULT_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW); } } /** * Updates the device's time zone system property and associated metadata. Returns {@code true} * if the device's time zone changed, {@code false} if the ID is invalid or the device is * already set to the supplied ID. * * <p>This method ensures the confidence metadata is set to the supplied value if the supplied * time zone ID is considered valid. * * <p>This method is intended only for use by the AlarmManager. When changing the device's time * zone other system service components must use {@link * com.android.server.AlarmManagerInternal#setTimeZone(String, int)} to ensure that important * system-wide side effects occur. */ public static boolean setTimeZoneId(String timeZoneId, @TimeZoneConfidence int confidence) { if (TextUtils.isEmpty(timeZoneId) || !isValidTimeZoneId(timeZoneId)) { return false; } boolean timeZoneChanged = false; synchronized (SystemTimeZone.class) { String currentTimeZoneId = getTimeZoneId(); if (currentTimeZoneId == null || !currentTimeZoneId.equals(timeZoneId)) { SystemProperties.set(TIME_ZONE_SYSTEM_PROPERTY, timeZoneId); if (DEBUG) { Slog.v(TAG, "Time zone changed: " + currentTimeZoneId + ", new=" + timeZoneId); } timeZoneChanged = true; } setTimeZoneConfidence(confidence); } return timeZoneChanged; } /** * Sets the time zone confidence value if required. See {@link TimeZoneConfidence} for details. */ private static void setTimeZoneConfidence(@TimeZoneConfidence int confidence) { int currentConfidence = getTimeZoneConfidence(); if (currentConfidence != confidence) { SystemProperties.set( TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY, Integer.toString(confidence)); if (DEBUG) { Slog.v(TAG, "Time zone confidence changed: old=" + currentConfidence + ", new=" + confidence); } } } /** Returns the time zone confidence value. See {@link TimeZoneConfidence} for details. */ public static @TimeZoneConfidence int getTimeZoneConfidence() { int confidence = SystemProperties.getInt( TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY, TIME_ZONE_CONFIDENCE_LOW); if (!isValidTimeZoneConfidence(confidence)) { confidence = TIME_ZONE_CONFIDENCE_LOW; } return confidence; } /** Returns the device's time zone ID setting. */ public static String getTimeZoneId() { return SystemProperties.get(TIME_ZONE_SYSTEM_PROPERTY); } private static boolean isValidTimeZoneConfidence(@TimeZoneConfidence int confidence) { return confidence >= TIME_ZONE_CONFIDENCE_LOW && confidence <= TIME_ZONE_CONFIDENCE_HIGH; } private static boolean isValidTimeZoneId(String timeZoneId) { return timeZoneId != null && !timeZoneId.isEmpty() && ZoneInfoDb.getInstance().hasTimeZone(timeZoneId); } }
services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java +15 −17 Original line number Diff line number Diff line Loading @@ -18,13 +18,16 @@ package com.android.server.timezonedetector; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AlarmManager; import android.content.Context; import android.os.Handler; import android.os.SystemClock; import android.os.SystemProperties; import com.android.server.AlarmManagerInternal; import com.android.server.LocalServices; import com.android.server.SystemTimeZone; import com.android.server.SystemTimeZone.TimeZoneConfidence; import java.util.Objects; /** Loading Loading @@ -59,27 +62,22 @@ final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment } @Override public boolean isDeviceTimeZoneInitialized() { // timezone.equals("GMT") will be true and only true if the time zone was // set to a default value by the system server (when starting, system server // sets the persist.sys.timezone to "GMT" if it's not set). "GMT" is not used by // any code that sets it explicitly (in case where something sets GMT explicitly, // "Etc/GMT" Olson ID would be used). String timeZoneId = getDeviceTimeZone(); return timeZoneId != null && timeZoneId.length() > 0 && !timeZoneId.equals("GMT"); @NonNull public String getDeviceTimeZone() { return SystemProperties.get(TIMEZONE_PROPERTY); } @Override @Nullable public String getDeviceTimeZone() { return SystemProperties.get(TIMEZONE_PROPERTY); public @TimeZoneConfidence int getDeviceTimeZoneConfidence() { return SystemTimeZone.getTimeZoneConfidence(); } @Override public void setDeviceTimeZone(String zoneId) { AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); alarmManager.setTimeZone(zoneId); public void setDeviceTimeZoneAndConfidence( @NonNull String zoneId, @TimeZoneConfidence int confidence) { AlarmManagerInternal alarmManagerInternal = LocalServices.getService(AlarmManagerInternal.class); alarmManagerInternal.setTimeZone(zoneId, confidence); } @Override Loading
services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +28 −17 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_M import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE; import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -39,6 +41,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.SystemTimeZone.TimeZoneConfidence; import java.time.Duration; import java.util.List; Loading Loading @@ -73,19 +76,20 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); /** * Returns true if the device has had an explicit time zone set. * Returns the device's currently configured time zone. */ boolean isDeviceTimeZoneInitialized(); @NonNull String getDeviceTimeZone(); /** * Returns the device's currently configured time zone. * Returns the confidence of the device's current time zone. */ String getDeviceTimeZone(); @TimeZoneConfidence int getDeviceTimeZoneConfidence(); /** * Sets the device's time zone. * Sets the device's time zone and associated confidence. */ void setDeviceTimeZone(@NonNull String zoneId); void setDeviceTimeZoneAndConfidence( @NonNull String zoneId, @TimeZoneConfidence int confidence); /** * Returns the time according to the elapsed realtime clock, the same as {@link Loading Loading @@ -620,25 +624,32 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat @GuardedBy("this") private void setDeviceTimeZoneIfRequired(@NonNull String newZoneId, @NonNull String cause) { String currentZoneId = mEnvironment.getDeviceTimeZone(); // Avoid unnecessary changes / intents. if (newZoneId.equals(currentZoneId)) { // No need to set the device time zone - the setting is already what we would be // suggesting. // All manual and automatic suggestions are considered high confidence as low-quality // suggestions are not currently passed on. int newConfidence = TIME_ZONE_CONFIDENCE_HIGH; int currentConfidence = mEnvironment.getDeviceTimeZoneConfidence(); // Avoid unnecessary changes / intents. If the newConfidence is higher than the stored value // then we want to upgrade it. if (newZoneId.equals(currentZoneId) && newConfidence <= currentConfidence) { // No need to modify the device time zone settings. if (DBG) { Slog.d(LOG_TAG, "No need to change the time zone;" + " device is already set to newZoneId." + ", newZoneId=" + newZoneId + ", cause=" + cause); + ", cause=" + cause + ", currentScore=" + currentConfidence + ", newConfidence=" + newConfidence); } return; } mEnvironment.setDeviceTimeZone(newZoneId); String logMsg = "Set device time zone." mEnvironment.setDeviceTimeZoneAndConfidence(newZoneId, newConfidence); String logMsg = "Set device time zone or higher confidence." + ", currentZoneId=" + currentZoneId + ", newZoneId=" + newZoneId + ", cause=" + cause; + ", cause=" + cause + ", newConfidence=" + newConfidence; logTimeZoneDetectorChange(logMsg); } Loading Loading @@ -710,9 +721,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal); ipw.println("[Capabilities=" + mCurrentConfigurationInternal.createCapabilitiesAndConfig() + "]"); ipw.println("mEnvironment.isDeviceTimeZoneInitialized()=" + mEnvironment.isDeviceTimeZoneInitialized()); ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone()); ipw.println("mEnvironment.getDeviceTimeZoneConfidence()=" + mEnvironment.getDeviceTimeZoneConfidence()); ipw.println("Misc state:"); ipw.increaseIndent(); // level 2 Loading