blob: dd07081bda12cb67bb348e1872b1c7117b5d035b [file] [log] [blame]
/*
* 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);
}
}