summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Neil Fuller <nfuller@google.com> 2022-09-07 18:12:52 +0100
committer Neil Fuller <nfuller@google.com> 2022-09-29 14:22:11 +0100
commit6bfc89fe6215a1ecaaad5706d46ecfc893b88ef0 (patch)
treeed606d130287da9af3103ce73421b5da31842ea8
parent8aa891d51542608aeb8f759cfb61e31e103d14f5 (diff)
Extract time zone storage logic from AlarmManager
Extract time zone storage logic from AlarmManagerService and add storage for new time zone metadata. This simplifies the AlarmManagerService, which would otherwise have to expose the time zone metadata (or more knowledge of the storage would be duplicated elsewhere). The new metadata describes the confidence Android has in the current time zone setting. It is intended to support upcoming APIs for SetUp Wizard, which should ask the user to confirm the time zone. Currently the SUW tries to infer this by watching for time zone changes, but this is error prone and prevents the time zone detector from defaulting the time zone using low-confidence (but still better than the hardcoded "GMT") time zones today. This change also includes small refactorings to how AlarmManagerService tries to keep the kernel's time zone offset in sync with Android's time zone setting. Code comments have also been added to try to clarify behavior. The system property defaulting behavior previously in SystemServer is moved to SystemTimeZone too so that all the logic associated with the system property is now in one place. Bug: 236612872 Test: manual testing + inspection / treehugger Test: atest services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java Change-Id: I493d31043f22d32f12793e0c35110233c850ed85
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java83
-rw-r--r--services/core/java/com/android/server/AlarmManagerInternal.java11
-rw-r--r--services/core/java/com/android/server/SystemTimeZone.java160
-rw-r--r--services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java32
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java45
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java9
-rw-r--r--services/java/com/android/server/SystemServer.java11
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java33
12 files changed, 320 insertions, 92 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 5b61f9abc357..8a823e22785c 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -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;
@@ -144,6 +146,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;
@@ -199,7 +203,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;
@@ -1881,9 +1884,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
@@ -2117,12 +2121,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
@@ -2131,31 +2137,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) {
@@ -2168,7 +2171,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,
@@ -2685,6 +2688,11 @@ public class AlarmManagerService extends SystemService {
}
@Override
+ public void setTimeZone(String tzId, @TimeZoneConfidence int confidence) {
+ setTimeZoneImpl(tzId, confidence);
+ }
+
+ @Override
public void registerInFlightListener(InFlightListener callback) {
synchronized (mLock) {
mInFlightListeners.add(callback);
@@ -2969,7 +2977,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);
}
@@ -4541,8 +4553,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) {
@@ -5009,13 +5031,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();
}
}
diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java
index e3c8afabcf6f..b2abdbd144b8 100644
--- a/services/core/java/com/android/server/AlarmManagerInternal.java
+++ b/services/core/java/com/android/server/AlarmManagerInternal.java
@@ -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
@@ -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);
}
diff --git a/services/core/java/com/android/server/SystemTimeZone.java b/services/core/java/com/android/server/SystemTimeZone.java
new file mode 100644
index 000000000000..7ce0831480dc
--- /dev/null
+++ b/services/core/java/com/android/server/SystemTimeZone.java
@@ -0,0 +1,160 @@
+/*
+ * 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);
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index 0ec8826cb7eb..d973ca689600 100644
--- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -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;
/**
@@ -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
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 66c23f5d01a4..9b35779f6820 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -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;
@@ -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;
@@ -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
@@ -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);
}
@@ -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
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 42dfee375ae4..a86222ea23e2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -150,6 +150,7 @@ import static android.security.keystore.AttestationUtils.USE_INDIVIDUAL_ATTESTAT
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER;
import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER;
@@ -363,6 +364,7 @@ import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.PasswordValidationError;
import com.android.net.module.util.ProxyUtils;
+import com.android.server.AlarmManagerInternal;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.PersistentDataBlockManagerInternal;
@@ -1466,6 +1468,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return mContext.getSystemService(AlarmManager.class);
}
+ AlarmManagerInternal getAlarmManagerInternal() {
+ return LocalServices.getService(AlarmManagerInternal.class);
+ }
+
ConnectivityManager getConnectivityManager() {
return mContext.getSystemService(ConnectivityManager.class);
}
@@ -12561,7 +12567,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false;
}
mInjector.binderWithCleanCallingIdentity(() ->
- mInjector.getAlarmManager().setTimeZone(timeZone));
+ mInjector.getAlarmManagerInternal()
+ .setTimeZone(timeZone, TIME_ZONE_CONFIDENCE_HIGH));
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_TIME_ZONE)
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f921ccf62a3b..c4e9b4fcdffb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -762,15 +762,8 @@ public final class SystemServer implements Dumpable {
EventLog.writeEvent(EventLogTags.SYSTEM_SERVER_START,
mStartCount, mRuntimeStartUptime, mRuntimeStartElapsedTime);
- //
- // Default the timezone property to GMT if not set.
- //
- String timezoneProperty = SystemProperties.get("persist.sys.timezone");
- if (!isValidTimeZoneId(timezoneProperty)) {
- Slog.w(TAG, "persist.sys.timezone is not valid (" + timezoneProperty
- + "); setting to GMT.");
- SystemProperties.set("persist.sys.timezone", "GMT");
- }
+ // Set the device's time zone (a system property) if it is not set or is invalid.
+ SystemTimeZone.initializeTimeZoneSettingsIfRequired();
// If the system has "persist.sys.language" and friends set, replace them with
// "persist.sys.locale". Note that the default locale at this point is calculated
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index d9c622d1efb5..446317e8829f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -52,6 +52,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_ALLOW_LIST;
import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_COMPAT;
import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_NOT_APPLICABLE;
@@ -111,6 +112,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.ActivityManager;
@@ -323,7 +325,12 @@ public class AlarmManagerServiceTest {
}
@Override
- void setKernelTimezone(int minutesWest) {
+ void setKernelTimeZoneOffset(int utcOffsetMillis) {
+ // Do nothing.
+ }
+
+ @Override
+ void syncKernelTimeZoneOffset() {
// Do nothing.
}
@@ -3421,12 +3428,13 @@ public class AlarmManagerServiceTest {
public void setTimeZoneImpl() {
final long durationMs = 20000L;
when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
- mService.setTimeZoneImpl("UTC");
+ mService.setTimeZoneImpl("UTC", TIME_ZONE_CONFIDENCE_HIGH);
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL),
isNull(), bundleCaptor.capture());
assertEquals(Intent.ACTION_TIMEZONE_CHANGED, intentCaptor.getValue().getAction());
+ assertEquals("UTC", intentCaptor.getValue().getStringExtra(Intent.EXTRA_TIMEZONE));
final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue());
assertEquals(durationMs, bOptions.getTemporaryAppAllowlistDuration());
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index c771998de862..3f0022de1386 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -51,6 +51,7 @@ import androidx.annotation.NonNull;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.AlarmManagerInternal;
import com.android.server.LocalServices;
import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -225,6 +226,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi
AlarmManager getAlarmManager() {return services.alarmManager;}
@Override
+ AlarmManagerInternal getAlarmManagerInternal() {
+ return services.alarmManagerInternal;
+ }
+
+ @Override
LockPatternUtils newLockPatternUtils() {
return services.lockPatternUtils;
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 171b895b93aa..0bdcac655a14 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -60,6 +60,7 @@ import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_PROFILE_OFF_DEADLINE;
import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_TURN_PROFILE_ON_NOTIFICATION;
import static com.android.server.devicepolicy.DpmMockContext.CALLER_USER_HANDLE;
@@ -4501,7 +4502,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
dpm.setTimeZone(admin1, "Asia/Shanghai");
- verify(getServices().alarmManager).setTimeZone("Asia/Shanghai");
+ verify(getServices().alarmManagerInternal)
+ .setTimeZone("Asia/Shanghai", TIME_ZONE_CONFIDENCE_HIGH);
}
@Test
@@ -4516,7 +4518,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
setupProfileOwner();
configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
dpm.setTimeZone(admin1, "Asia/Shanghai");
- verify(getServices().alarmManager).setTimeZone("Asia/Shanghai");
+ verify(getServices().alarmManagerInternal)
+ .setTimeZone("Asia/Shanghai", TIME_ZONE_CONFIDENCE_HIGH);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 46cc68ffb914..cec9d50ea69c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -73,6 +73,7 @@ import android.view.IWindowManager;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.AlarmManagerInternal;
import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.UserManagerInternal;
@@ -124,6 +125,7 @@ public class MockSystemServices {
public final ConnectivityManager connectivityManager;
public final AccountManager accountManager;
public final AlarmManager alarmManager;
+ public final AlarmManagerInternal alarmManagerInternal;
public final KeyChain.KeyChainConnection keyChainConnection;
public final CrossProfileApps crossProfileApps;
public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal;
@@ -176,6 +178,7 @@ public class MockSystemServices {
connectivityManager = mock(ConnectivityManager.class);
accountManager = mock(AccountManager.class);
alarmManager = mock(AlarmManager.class);
+ alarmManagerInternal = mock(AlarmManagerInternal.class);
keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
crossProfileApps = mock(CrossProfileApps.class);
persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 23a9013abd96..ff838fdf9d73 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -24,6 +24,7 @@ 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 static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGH;
import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGHEST;
import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_LOW;
@@ -44,6 +45,7 @@ import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
+import com.android.server.SystemTimeZone.TimeZoneConfidence;
import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
import org.junit.Before;
@@ -1024,7 +1026,7 @@ public class TimeZoneDetectorStrategyImplTest {
expectedDeviceTimeZoneId = geolocationTimeZoneSuggestion.getZoneIds().get(0);
script.simulateConfigurationInternalChange(expectedInternalConfig)
- .verifyTimeZoneChangedAndReset(expectedDeviceTimeZoneId);
+ .verifyTimeZoneChangedAndReset(expectedDeviceTimeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion,
MetricsTimeZoneDetectorState.DETECTION_MODE_GEO);
@@ -1110,6 +1112,7 @@ public class TimeZoneDetectorStrategyImplTest {
static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment {
private final TestState<String> mTimeZoneId = new TestState<>();
+ private int mTimeZoneConfidence;
private ConfigurationInternal mConfigurationInternal;
private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
private ConfigurationChangeListener mConfigurationInternalChangeListener;
@@ -1141,18 +1144,20 @@ public class TimeZoneDetectorStrategyImplTest {
}
@Override
- public boolean isDeviceTimeZoneInitialized() {
- return mTimeZoneId.getLatest() != null;
+ public String getDeviceTimeZone() {
+ return mTimeZoneId.getLatest();
}
@Override
- public String getDeviceTimeZone() {
- return mTimeZoneId.getLatest();
+ public int getDeviceTimeZoneConfidence() {
+ return mTimeZoneConfidence;
}
@Override
- public void setDeviceTimeZone(String zoneId) {
+ public void setDeviceTimeZoneAndConfidence(
+ String zoneId, @TimeZoneConfidence int confidence) {
mTimeZoneId.set(zoneId);
+ mTimeZoneConfidence = confidence;
}
void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
@@ -1164,10 +1169,11 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneId.assertHasNotBeenSet();
}
- void assertTimeZoneChangedTo(String timeZoneId) {
+ void assertTimeZoneChangedTo(String timeZoneId, @TimeZoneConfidence int confidence) {
mTimeZoneId.assertHasBeenSet();
mTimeZoneId.assertChangeCount(1);
mTimeZoneId.assertLatestEquals(timeZoneId);
+ assertEquals(confidence, mTimeZoneConfidence);
}
void commitAllChanges() {
@@ -1280,20 +1286,22 @@ public class TimeZoneDetectorStrategyImplTest {
}
/** Verifies the device's time zone has been set and clears change tracking history. */
- Script verifyTimeZoneChangedAndReset(String zoneId) {
- mFakeEnvironment.assertTimeZoneChangedTo(zoneId);
+ Script verifyTimeZoneChangedAndReset(String zoneId, @TimeZoneConfidence int confidence) {
+ mFakeEnvironment.assertTimeZoneChangedTo(zoneId, confidence);
mFakeEnvironment.commitAllChanges();
return this;
}
Script verifyTimeZoneChangedAndReset(ManualTimeZoneSuggestion suggestion) {
- mFakeEnvironment.assertTimeZoneChangedTo(suggestion.getZoneId());
+ mFakeEnvironment.assertTimeZoneChangedTo(
+ suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
mFakeEnvironment.commitAllChanges();
return this;
}
Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) {
- mFakeEnvironment.assertTimeZoneChangedTo(suggestion.getZoneId());
+ mFakeEnvironment.assertTimeZoneChangedTo(
+ suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
mFakeEnvironment.commitAllChanges();
return this;
}
@@ -1301,7 +1309,8 @@ public class TimeZoneDetectorStrategyImplTest {
Script verifyTimeZoneChangedAndReset(GeolocationTimeZoneSuggestion suggestion) {
assertEquals("Only use this method with unambiguous geo suggestions",
1, suggestion.getZoneIds().size());
- mFakeEnvironment.assertTimeZoneChangedTo(suggestion.getZoneIds().get(0));
+ mFakeEnvironment.assertTimeZoneChangedTo(
+ suggestion.getZoneIds().get(0), TIME_ZONE_CONFIDENCE_HIGH);
mFakeEnvironment.commitAllChanges();
return this;
}