| /* |
| * 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.annotation.NonNull; |
| import android.os.SystemProperties; |
| import android.text.TextUtils; |
| import android.util.LocalLog; |
| import android.util.Slog; |
| |
| import com.android.i18n.timezone.ZoneInfoDb; |
| |
| import java.io.PrintWriter; |
| 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; |
| |
| /** |
| * An in-memory log that records the debug info related to the device's time zone setting. |
| * This is logged in bug reports to assist with debugging time zone detection issues. |
| */ |
| @NonNull |
| private static final LocalLog sTimeZoneDebugLog = |
| new LocalLog(30, false /* useLocalTimestamps */); |
| |
| 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)) { |
| String logInfo = "initializeTimeZoneSettingsIfRequired():" + TIME_ZONE_SYSTEM_PROPERTY |
| + " is not valid (" + timezoneProperty + "); setting to " |
| + DEFAULT_TIME_ZONE_ID; |
| Slog.w(TAG, logInfo); |
| setTimeZoneId(DEFAULT_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW, logInfo); |
| } |
| } |
| |
| /** |
| * Adds an entry to the system time zone debug log that is included in bug reports. This method |
| * is intended to be used to record event that may lead to a time zone change, e.g. config or |
| * mode changes. |
| */ |
| public static void addDebugLogEntry(@NonNull String logMsg) { |
| sTimeZoneDebugLog.log(logMsg); |
| } |
| |
| /** |
| * Updates the device's time zone system property, associated metadata and adds an entry to the |
| * debug log. 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 |
| * AlarmManagerInternal#setTimeZone(String, int, String)} to ensure that important |
| * system-wide side effects occur. |
| */ |
| public static boolean setTimeZoneId( |
| @NonNull String timeZoneId, @TimeZoneConfidence int confidence, |
| @NonNull String logInfo) { |
| if (TextUtils.isEmpty(timeZoneId) || !isValidTimeZoneId(timeZoneId)) { |
| addDebugLogEntry("setTimeZoneId: Invalid time zone ID." |
| + " timeZoneId=" + timeZoneId |
| + ", confidence=" + confidence |
| + ", logInfo=" + logInfo); |
| 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; |
| } |
| boolean timeZoneConfidenceChanged = setTimeZoneConfidence(confidence); |
| if (timeZoneChanged || timeZoneConfidenceChanged) { |
| String logMsg = "Time zone or confidence set: " |
| + " (new) timeZoneId=" + timeZoneId |
| + ", (new) confidence=" + confidence |
| + ", logInfo=" + logInfo; |
| addDebugLogEntry(logMsg); |
| } |
| } |
| |
| return timeZoneChanged; |
| } |
| |
| /** |
| * Sets the time zone confidence value if required. See {@link TimeZoneConfidence} for details. |
| */ |
| private static boolean setTimeZoneConfidence(@TimeZoneConfidence int newConfidence) { |
| int currentConfidence = getTimeZoneConfidence(); |
| if (currentConfidence != newConfidence) { |
| SystemProperties.set( |
| TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY, Integer.toString(newConfidence)); |
| if (DEBUG) { |
| Slog.v(TAG, "Time zone confidence changed: old=" + currentConfidence |
| + ", newConfidence=" + newConfidence); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /** 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); |
| } |
| |
| /** |
| * Dumps information about recent time zone decisions / changes to the supplied writer. |
| */ |
| public static void dump(PrintWriter writer) { |
| sTimeZoneDebugLog.dump(writer); |
| } |
| |
| 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); |
| } |
| } |