Implement proposed device initialization APIs

APIs to improve time / time zone initialization flow.

Firstly, this change provides a way for SetUp Wizard and similar apps to
ask how confident the device is about time and time zone. This will
remove the need for SUW to watch for changes to the time / time zone
made by the system and infer confidence from that. This SUW "watching"
behavior means that the system cannot set low confidence time / time
zones while SUW is running, as the SUW will interpret them as meaning
the user doesn't need to confirm them.

The remaining methods are to remove the need for SUW and similar apps to
use low-level APIs to force the device's time / time zone into the state
the user wants:

Auto time / time zone detection is usually on by default when SUW runs.
Using low-level APIs to set the time and time zone bypass the system
server time and time zone services' automatic detection behavior. This
leads to either:
(a) the user not getting what they want, i.e. if SUW sets a value, the
detection systems may override them immediately because those auto
detection systems are still active.
(b) the auto detection systems leaving the device set to the values the
user provided incorrectly because they are not aware the low-level APIs
were used.  Typically, the detection systems will kick in at some point
later, leading to an unexpected "random" change if the SUW has forced an
"incorrect" setting.

Instead, what SUW should do is either:
1) Confirm the existing settings.
2) If the user wishes to diverge from the current time or time zone
   settings, there are two cases:
   2a) The device is currently set incorrectly because time / time zone
   detection has not yet determined values yet or has determined them
   incorrectly.
   2b) The user wishes to deliberately set the device to incorrect
   values.

For (1) there are new, dedicated "confirm" APIs. These explicitly handle
if the time / time zone changes while the user is looking at the UI; the
user would presumably be asked to reflect on their choice if they try to
confirm and it now differs from what they are confirming, i.e.  because
it has become case (2).

For (2), the current value is considered wrong by a user. New "get
state" APIs can be used by SUW to understand device confidence. For (2b)
the user is overriding a "confident" value and so SUW may want to inform
the user / ask if they are sure and and leave auto detection turned off
after setting the incorrect value. For a user to set a new value, the
high level APIs only allows this if auto detection is off. Auto
detection state can be determined by APIs exposed here. Auto detection
can be turned off via the APIs too and "manual" values can be set via
other APIs. Auto detection can then be turned back on (if required).[*]

[*] At any point after auto detection goes back on the device can choose
a different time / time zone from the one the user chose. The SUW could
wait for a short period to "settle" and re-check or just ignore this
possibility and proceed to the next step immediately.

Test: atest services/tests/servicestests/src/com/android/server/timedetector
Test: atest services/tests/servicestests/src/com/android/server/timezonedetector
Test: atest core/tests/coretests/src/android/app/time
Bug: 236612872
Change-Id: I1903569907e7e268155814193379b7eef2e75a8d
This commit is contained in:
Neil Fuller
2022-09-14 14:18:11 +01:00
parent c5a6044ad6
commit 2537e5d5b6
39 changed files with 2151 additions and 163 deletions

View File

@@ -57,21 +57,21 @@ public final class TimeCapabilities implements Parcelable {
@NonNull
private final UserHandle mUserHandle;
private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
private final @CapabilityState int mSuggestManualTimeCapability;
private final @CapabilityState int mSetManualTimeCapability;
private TimeCapabilities(@NonNull Builder builder) {
this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
this.mConfigureAutoDetectionEnabledCapability =
builder.mConfigureAutoDetectionEnabledCapability;
this.mSuggestManualTimeCapability = builder.mSuggestManualTimeCapability;
this.mSetManualTimeCapability = builder.mSetManualTimeCapability;
}
@NonNull
private static TimeCapabilities createFromParcel(Parcel in) {
private static TimeCapabilities createFromParcel(@NonNull Parcel in) {
UserHandle userHandle = UserHandle.readFromParcel(in);
return new TimeCapabilities.Builder(userHandle)
.setConfigureAutoDetectionEnabledCapability(in.readInt())
.setSuggestManualTimeCapability(in.readInt())
.setSetManualTimeCapability(in.readInt())
.build();
}
@@ -79,7 +79,7 @@ public final class TimeCapabilities implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
UserHandle.writeToParcel(mUserHandle, dest);
dest.writeInt(mConfigureAutoDetectionEnabledCapability);
dest.writeInt(mSuggestManualTimeCapability);
dest.writeInt(mSetManualTimeCapability);
}
/**
@@ -94,11 +94,12 @@ public final class TimeCapabilities implements Parcelable {
/**
* Returns the capability state associated with the user's ability to manually set time on a
* device.
* device. The setting can be updated via {@link
* TimeManager#updateTimeConfiguration(TimeConfiguration)}.
*/
@CapabilityState
public int getSuggestManualTimeCapability() {
return mSuggestManualTimeCapability;
public int getSetManualTimeCapability() {
return mSetManualTimeCapability;
}
/**
@@ -136,14 +137,14 @@ public final class TimeCapabilities implements Parcelable {
TimeCapabilities that = (TimeCapabilities) o;
return mConfigureAutoDetectionEnabledCapability
== that.mConfigureAutoDetectionEnabledCapability
&& mSuggestManualTimeCapability == that.mSuggestManualTimeCapability
&& mSetManualTimeCapability == that.mSetManualTimeCapability
&& mUserHandle.equals(that.mUserHandle);
}
@Override
public int hashCode() {
return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability,
mSuggestManualTimeCapability);
mSetManualTimeCapability);
}
@Override
@@ -152,7 +153,7 @@ public final class TimeCapabilities implements Parcelable {
+ "mUserHandle=" + mUserHandle
+ ", mConfigureAutoDetectionEnabledCapability="
+ mConfigureAutoDetectionEnabledCapability
+ ", mSuggestManualTimeCapability=" + mSuggestManualTimeCapability
+ ", mSetManualTimeCapability=" + mSetManualTimeCapability
+ '}';
}
@@ -165,7 +166,7 @@ public final class TimeCapabilities implements Parcelable {
@NonNull private final UserHandle mUserHandle;
private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
private @CapabilityState int mSuggestManualTimeCapability;
private @CapabilityState int mSetManualTimeCapability;
public Builder(@NonNull UserHandle userHandle) {
this.mUserHandle = Objects.requireNonNull(userHandle);
@@ -176,18 +177,18 @@ public final class TimeCapabilities implements Parcelable {
this.mUserHandle = timeCapabilities.mUserHandle;
this.mConfigureAutoDetectionEnabledCapability =
timeCapabilities.mConfigureAutoDetectionEnabledCapability;
this.mSuggestManualTimeCapability = timeCapabilities.mSuggestManualTimeCapability;
this.mSetManualTimeCapability = timeCapabilities.mSetManualTimeCapability;
}
/** Sets the state for automatic time detection config. */
/** Sets the value for the "configure automatic time detection" capability. */
public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) {
this.mConfigureAutoDetectionEnabledCapability = value;
return this;
}
/** Sets the state for manual time change. */
public Builder setSuggestManualTimeCapability(@CapabilityState int value) {
this.mSuggestManualTimeCapability = value;
/** Sets the value for the "set manual time" capability. */
public Builder setSetManualTimeCapability(@CapabilityState int value) {
this.mSetManualTimeCapability = value;
return this;
}
@@ -195,7 +196,7 @@ public final class TimeCapabilities implements Parcelable {
public TimeCapabilities build() {
verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability,
"configureAutoDetectionEnabledCapability");
verifyCapabilitySet(mSuggestManualTimeCapability, "mSuggestManualTimeCapability");
verifyCapabilitySet(mSetManualTimeCapability, "mSetManualTimeCapability");
return new TimeCapabilities(this);
}

View File

@@ -71,8 +71,6 @@ public final class TimeCapabilitiesAndConfig implements Parcelable {
/**
* Returns the user's time behaviour capabilities.
*
* @hide
*/
@NonNull
public TimeCapabilities getCapabilities() {
@@ -81,8 +79,6 @@ public final class TimeCapabilitiesAndConfig implements Parcelable {
/**
* Returns the user's time behaviour configuration.
*
* @hide
*/
@NonNull
public TimeConfiguration getConfiguration() {

View File

@@ -55,10 +55,16 @@ public final class TimeConfiguration implements Parcelable {
}
};
/**
* All configuration properties
*
* @hide
*/
@StringDef(SETTING_AUTO_DETECTION_ENABLED)
@Retention(RetentionPolicy.SOURCE)
@interface Setting {}
/** See {@link TimeConfiguration#isAutoDetectionEnabled()} for details. */
@Setting
private static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";

View File

@@ -21,11 +21,14 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timezonedetector.ITimeZoneDetectorService;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.TimestampedValue;
import android.util.ArrayMap;
import android.util.Log;
@@ -274,4 +277,152 @@ public final class TimeManager {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns a snapshot of the device's current system clock time state. See also {@link
* #confirmTime(UnixEpochTime)} for how this information can be used.
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
@NonNull
public TimeState getTimeState() {
if (DEBUG) {
Log.d(TAG, "getTimeState called");
}
try {
return mITimeDetectorService.getTimeState();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Confirms the device's current time during device setup, raising the system's confidence in
* the time if needed. Unlike {@link #setManualTime(UnixEpochTime)}, which can only be used when
* automatic time detection is currently disabled, this method can be used regardless of the
* automatic time detection setting, but only to confirm the current time (which may have been
* set via automatic means). Use {@link #getTimeState()} to obtain the time state to confirm.
*
* <p>Returns {@code false} if the confirmation is invalid, i.e. if the time being
* confirmed is no longer the time the device is currently set to. Confirming a time
* in which the system already has high confidence will return {@code true}.
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
public boolean confirmTime(@NonNull UnixEpochTime unixEpochTime) {
if (DEBUG) {
Log.d(TAG, "confirmTime called: " + unixEpochTime);
}
try {
return mITimeDetectorService.confirmTime(unixEpochTime);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Attempts to set the device's time, expected to be determined from the user's manually entered
* information.
*
* <p>Returns {@code false} if the time is invalid, or the device configuration / user
* capabilities prevents the time being accepted, e.g. if the device is currently set to
* "automatic time detection". This method returns {@code true} if the time was accepted even
* if it is the same as the current device time.
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
public boolean setManualTime(@NonNull UnixEpochTime unixEpochTime) {
if (DEBUG) {
Log.d(TAG, "setTime called: " + unixEpochTime);
}
try {
TimestampedValue<Long> manualTime = new TimestampedValue<>(
unixEpochTime.getElapsedRealtimeMillis(),
unixEpochTime.getUnixEpochTimeMillis());
ManualTimeSuggestion manualTimeSuggestion = new ManualTimeSuggestion(manualTime);
manualTimeSuggestion.addDebugInfo("TimeManager.setTime()");
manualTimeSuggestion.addDebugInfo("UID: " + android.os.Process.myUid());
manualTimeSuggestion.addDebugInfo("UserHandle: " + android.os.Process.myUserHandle());
manualTimeSuggestion.addDebugInfo("Process: " + android.os.Process.myProcessName());
return mITimeDetectorService.setManualTime(manualTimeSuggestion);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns a snapshot of the device's current time zone state. See also {@link
* #confirmTimeZone(String)} and {@link #setManualTimeZone(String)} for how this information may
* be used.
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
@NonNull
public TimeZoneState getTimeZoneState() {
if (DEBUG) {
Log.d(TAG, "getTimeZoneState called");
}
try {
return mITimeZoneDetectorService.getTimeZoneState();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Confirms the device's current time zone ID, raising the system's confidence in the time zone
* if needed. Unlike {@link #setManualTimeZone(String)}, which can only be used when automatic
* time zone detection is currently disabled, this method can be used regardless of the
* automatic time zone detection setting, but only to confirm the current value (which may have
* been set via automatic means).
*
* <p>Returns {@code false} if the confirmation is invalid, i.e. if the time zone ID being
* confirmed is no longer the time zone ID the device is currently set to. Confirming a time
* zone ID in which the system already has high confidence returns {@code true}.
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
public boolean confirmTimeZone(@NonNull String timeZoneId) {
if (DEBUG) {
Log.d(TAG, "confirmTimeZone called: " + timeZoneId);
}
try {
return mITimeZoneDetectorService.confirmTimeZone(timeZoneId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Attempts to set the device's time zone, expected to be determined from a user's manually
* entered information.
*
* <p>Returns {@code false} if the time zone is invalid, or the device configuration / user
* capabilities prevents the time zone being accepted, e.g. if the device is currently set to
* "automatic time zone detection". {@code true} is returned if the time zone is accepted. A
* time zone that is accepted and matches the current device time zone returns {@code true}.
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
public boolean setManualTimeZone(@NonNull String timeZoneId) {
if (DEBUG) {
Log.d(TAG, "setManualTimeZone called: " + timeZoneId);
}
try {
ManualTimeZoneSuggestion manualTimeZoneSuggestion =
new ManualTimeZoneSuggestion(timeZoneId);
manualTimeZoneSuggestion.addDebugInfo("TimeManager.setManualTimeZone()");
manualTimeZoneSuggestion.addDebugInfo("UID: " + android.os.Process.myUid());
manualTimeZoneSuggestion.addDebugInfo("Process: " + android.os.Process.myProcessName());
return mITimeZoneDetectorService.setManualTimeZone(manualTimeZoneSuggestion);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 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 android.app.time;
parcelable TimeState;

View File

@@ -0,0 +1,162 @@
/*
* Copyright (C) 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 android.app.time;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ShellCommand;
import java.io.PrintWriter;
import java.util.Objects;
/**
* A snapshot of the system time state.
*
* <p>{@code mUnixEpochTime} contains a snapshot of the system clock time and elapsed realtime clock
* time.
*
* <p>{@code mUserShouldConfirmTime} is {@code true} if the system has low confidence in the system
* clock time.
*
* @hide
*/
public final class TimeState implements Parcelable {
public static final @NonNull Creator<TimeState> CREATOR = new Creator<>() {
public TimeState createFromParcel(Parcel in) {
return TimeState.createFromParcel(in);
}
public TimeState[] newArray(int size) {
return new TimeState[size];
}
};
@NonNull private final UnixEpochTime mUnixEpochTime;
private final boolean mUserShouldConfirmTime;
/** @hide */
public TimeState(@NonNull UnixEpochTime unixEpochTime, boolean userShouldConfirmTime) {
mUnixEpochTime = Objects.requireNonNull(unixEpochTime);
mUserShouldConfirmTime = userShouldConfirmTime;
}
private static TimeState createFromParcel(Parcel in) {
UnixEpochTime unixEpochTime = in.readParcelable(null, UnixEpochTime.class);
boolean userShouldConfirmId = in.readBoolean();
return new TimeState(unixEpochTime, userShouldConfirmId);
}
/** @hide */
@Nullable
public static TimeState parseCommandLineArgs(@NonNull ShellCommand cmd) {
Long elapsedRealtimeMillis = null;
Long unixEpochTimeMillis = null;
Boolean userShouldConfirmTime = null;
String opt;
while ((opt = cmd.getNextArg()) != null) {
switch (opt) {
case "--elapsed_realtime": {
elapsedRealtimeMillis = Long.parseLong(cmd.getNextArgRequired());
break;
}
case "--unix_epoch_time": {
unixEpochTimeMillis = Long.parseLong(cmd.getNextArgRequired());
break;
}
case "--user_should_confirm_time": {
userShouldConfirmTime = Boolean.parseBoolean(cmd.getNextArgRequired());
break;
}
default: {
throw new IllegalArgumentException("Unknown option: " + opt);
}
}
}
if (elapsedRealtimeMillis == null) {
throw new IllegalArgumentException("No elapsedRealtimeMillis specified.");
}
if (unixEpochTimeMillis == null) {
throw new IllegalArgumentException("No unixEpochTimeMillis specified.");
}
if (userShouldConfirmTime == null) {
throw new IllegalArgumentException("No userShouldConfirmTime specified.");
}
UnixEpochTime unixEpochTime = new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
return new TimeState(unixEpochTime, userShouldConfirmTime);
}
/** @hide */
public static void printCommandLineOpts(@NonNull PrintWriter pw) {
pw.println("TimeState options:");
pw.println(" --elapsed_realtime <elapsed realtime millis>");
pw.println(" --unix_epoch_time <Unix epoch time millis>");
pw.println(" --user_should_confirm_time {true|false}");
pw.println();
pw.println("See " + TimeState.class.getName() + " for more information");
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelable(mUnixEpochTime, 0);
dest.writeBoolean(mUserShouldConfirmTime);
}
@NonNull
public UnixEpochTime getUnixEpochTime() {
return mUnixEpochTime;
}
public boolean getUserShouldConfirmTime() {
return mUserShouldConfirmTime;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TimeState that = (TimeState) o;
return Objects.equals(mUnixEpochTime, that.mUnixEpochTime)
&& mUserShouldConfirmTime == that.mUserShouldConfirmTime;
}
@Override
public int hashCode() {
return Objects.hash(mUnixEpochTime, mUserShouldConfirmTime);
}
@Override
public String toString() {
return "TimeState{"
+ "mUnixEpochTime=" + mUnixEpochTime
+ ", mUserShouldConfirmTime=" + mUserShouldConfirmTime
+ '}';
}
}

View File

@@ -22,8 +22,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.time.Capabilities.CapabilityState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TimeZoneDetector;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
@@ -61,7 +59,7 @@ public final class TimeZoneCapabilities implements Parcelable {
@NonNull private final UserHandle mUserHandle;
private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
private final @CapabilityState int mConfigureGeoDetectionEnabledCapability;
private final @CapabilityState int mSuggestManualTimeZoneCapability;
private final @CapabilityState int mSetManualTimeZoneCapability;
private TimeZoneCapabilities(@NonNull Builder builder) {
this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
@@ -69,16 +67,16 @@ public final class TimeZoneCapabilities implements Parcelable {
builder.mConfigureAutoDetectionEnabledCapability;
this.mConfigureGeoDetectionEnabledCapability =
builder.mConfigureGeoDetectionEnabledCapability;
this.mSuggestManualTimeZoneCapability = builder.mSuggestManualTimeZoneCapability;
this.mSetManualTimeZoneCapability = builder.mSetManualTimeZoneCapability;
}
@NonNull
private static TimeZoneCapabilities createFromParcel(Parcel in) {
private static TimeZoneCapabilities createFromParcel(@NonNull Parcel in) {
UserHandle userHandle = UserHandle.readFromParcel(in);
return new TimeZoneCapabilities.Builder(userHandle)
.setConfigureAutoDetectionEnabledCapability(in.readInt())
.setConfigureGeoDetectionEnabledCapability(in.readInt())
.setSuggestManualTimeZoneCapability(in.readInt())
.setSetManualTimeZoneCapability(in.readInt())
.build();
}
@@ -87,7 +85,7 @@ public final class TimeZoneCapabilities implements Parcelable {
UserHandle.writeToParcel(mUserHandle, dest);
dest.writeInt(mConfigureAutoDetectionEnabledCapability);
dest.writeInt(mConfigureGeoDetectionEnabledCapability);
dest.writeInt(mSuggestManualTimeZoneCapability);
dest.writeInt(mSetManualTimeZoneCapability);
}
/**
@@ -112,17 +110,17 @@ public final class TimeZoneCapabilities implements Parcelable {
/**
* Returns the capability state associated with the user's ability to manually set the time zone
* on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
* on a device.
*
* <p>The suggestion will be ignored in all cases unless the value is {@link
* <p>The time zone will be ignored in all cases unless the value is {@link
* Capabilities#CAPABILITY_POSSESSED}. See also
* {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
*
* @hide
*/
@CapabilityState
public int getSuggestManualTimeZoneCapability() {
return mSuggestManualTimeZoneCapability;
public int getSetManualTimeZoneCapability() {
return mSetManualTimeZoneCapability;
}
/**
@@ -174,13 +172,13 @@ public final class TimeZoneCapabilities implements Parcelable {
== that.mConfigureAutoDetectionEnabledCapability
&& mConfigureGeoDetectionEnabledCapability
== that.mConfigureGeoDetectionEnabledCapability
&& mSuggestManualTimeZoneCapability == that.mSuggestManualTimeZoneCapability;
&& mSetManualTimeZoneCapability == that.mSetManualTimeZoneCapability;
}
@Override
public int hashCode() {
return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability,
mConfigureGeoDetectionEnabledCapability, mSuggestManualTimeZoneCapability);
mConfigureGeoDetectionEnabledCapability, mSetManualTimeZoneCapability);
}
@Override
@@ -191,17 +189,21 @@ public final class TimeZoneCapabilities implements Parcelable {
+ mConfigureAutoDetectionEnabledCapability
+ ", mConfigureGeoDetectionEnabledCapability="
+ mConfigureGeoDetectionEnabledCapability
+ ", mSuggestManualTimeZoneCapability=" + mSuggestManualTimeZoneCapability
+ ", mSetManualTimeZoneCapability=" + mSetManualTimeZoneCapability
+ '}';
}
/** @hide */
/**
* A builder of {@link TimeZoneCapabilities} objects.
*
* @hide
*/
public static class Builder {
@NonNull private UserHandle mUserHandle;
private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
private @CapabilityState int mConfigureGeoDetectionEnabledCapability;
private @CapabilityState int mSuggestManualTimeZoneCapability;
private @CapabilityState int mSetManualTimeZoneCapability;
public Builder(@NonNull UserHandle userHandle) {
mUserHandle = Objects.requireNonNull(userHandle);
@@ -214,25 +216,27 @@ public final class TimeZoneCapabilities implements Parcelable {
capabilitiesToCopy.mConfigureAutoDetectionEnabledCapability;
mConfigureGeoDetectionEnabledCapability =
capabilitiesToCopy.mConfigureGeoDetectionEnabledCapability;
mSuggestManualTimeZoneCapability =
capabilitiesToCopy.mSuggestManualTimeZoneCapability;
mSetManualTimeZoneCapability =
capabilitiesToCopy.mSetManualTimeZoneCapability;
}
/** Sets the state for the automatic time zone detection enabled config. */
/** Sets the value for the "configure automatic time zone detection enabled" capability. */
public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) {
this.mConfigureAutoDetectionEnabledCapability = value;
return this;
}
/** Sets the state for the geolocation time zone detection enabled config. */
/**
* Sets the value for the "configure geolocation time zone detection enabled" capability.
*/
public Builder setConfigureGeoDetectionEnabledCapability(@CapabilityState int value) {
this.mConfigureGeoDetectionEnabledCapability = value;
return this;
}
/** Sets the state for the suggestManualTimeZone action. */
public Builder setSuggestManualTimeZoneCapability(@CapabilityState int value) {
this.mSuggestManualTimeZoneCapability = value;
/** Sets the value for the "set manual time zone" capability. */
public Builder setSetManualTimeZoneCapability(@CapabilityState int value) {
this.mSetManualTimeZoneCapability = value;
return this;
}
@@ -243,8 +247,8 @@ public final class TimeZoneCapabilities implements Parcelable {
"configureAutoDetectionEnabledCapability");
verifyCapabilitySet(mConfigureGeoDetectionEnabledCapability,
"configureGeoDetectionEnabledCapability");
verifyCapabilitySet(mSuggestManualTimeZoneCapability,
"suggestManualTimeZoneCapability");
verifyCapabilitySet(mSetManualTimeZoneCapability,
"mSetManualTimeZoneCapability");
return new TimeZoneCapabilities(this);
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 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 android.app.time;
parcelable TimeZoneState;

View File

@@ -0,0 +1,150 @@
/*
* Copyright (C) 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 android.app.time;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ShellCommand;
import java.io.PrintWriter;
import java.util.Objects;
/**
* A snapshot of the system's time zone state.
*
* <p>{@code id} contains the system's time zone ID setting, e.g. "America/Los_Angeles". This
* will usually agree with {@code TimeZone.getDefault().getID()} but it can be empty in rare cases.
*
* <p>{@code userShouldConfirmId} is {@code true} if the system has low confidence in the current
* time zone.
*
* @hide
*/
public final class TimeZoneState implements Parcelable {
public static final @NonNull Creator<TimeZoneState> CREATOR = new Creator<>() {
public TimeZoneState createFromParcel(Parcel in) {
return TimeZoneState.createFromParcel(in);
}
public TimeZoneState[] newArray(int size) {
return new TimeZoneState[size];
}
};
@NonNull private final String mId;
private final boolean mUserShouldConfirmId;
/** @hide */
public TimeZoneState(@NonNull String id, boolean userShouldConfirmId) {
mId = Objects.requireNonNull(id);
mUserShouldConfirmId = userShouldConfirmId;
}
private static TimeZoneState createFromParcel(Parcel in) {
String zoneId = in.readString();
boolean userShouldConfirmId = in.readBoolean();
return new TimeZoneState(zoneId, userShouldConfirmId);
}
/** @hide */
@Nullable
public static TimeZoneState parseCommandLineArgs(@NonNull ShellCommand cmd) {
String zoneIdString = null;
Boolean userShouldConfirmId = null;
String opt;
while ((opt = cmd.getNextArg()) != null) {
switch (opt) {
case "--zone_id": {
zoneIdString = cmd.getNextArgRequired();
break;
}
case "--user_should_confirm_id": {
userShouldConfirmId = Boolean.parseBoolean(cmd.getNextArgRequired());
break;
}
default: {
throw new IllegalArgumentException("Unknown option: " + opt);
}
}
}
if (zoneIdString == null) {
throw new IllegalArgumentException("No zoneId specified.");
}
if (userShouldConfirmId == null) {
throw new IllegalArgumentException("No userShouldConfirmId specified.");
}
return new TimeZoneState(zoneIdString, userShouldConfirmId);
}
/** @hide */
public static void printCommandLineOpts(@NonNull PrintWriter pw) {
pw.println("TimeZoneState options:");
pw.println(" --zone_id {<Olson ID>}");
pw.println(" --user_should_confirm_id {true|false}");
pw.println();
pw.println("See " + TimeZoneState.class.getName() + " for more information");
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mId);
dest.writeBoolean(mUserShouldConfirmId);
}
@NonNull
public String getId() {
return mId;
}
public boolean getUserShouldConfirmId() {
return mUserShouldConfirmId;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TimeZoneState that = (TimeZoneState) o;
return Objects.equals(mId, that.mId)
&& mUserShouldConfirmId == that.mUserShouldConfirmId;
}
@Override
public int hashCode() {
return Objects.hash(mId, mUserShouldConfirmId);
}
@Override
public String toString() {
return "TimeZoneState{"
+ "mZoneId=" + mId
+ ", mUserShouldConfirmId=" + mUserShouldConfirmId
+ '}';
}
}

View File

@@ -0,0 +1,19 @@
/*
* 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 android.app.time;
parcelable UnixEpochTime;

View File

@@ -0,0 +1,190 @@
/*
* Copyright (C) 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 android.app.time;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.TimestampedValue;
import java.io.PrintWriter;
import java.util.Objects;
/**
* A Unix epoch time value with an associated reading from the elapsed realtime clock.
* When representing a device's system clock time, the Unix epoch time can be obtained using {@link
* System#currentTimeMillis()}. The Unix epoch time might also come from an external source
* depending on usage.
*
* <p>The elapsed realtime clock can be obtained using methods like {@link
* SystemClock#elapsedRealtime()} or {@link SystemClock#elapsedRealtimeClock()}.
*
* @hide
*/
public final class UnixEpochTime implements Parcelable {
@ElapsedRealtimeLong private final long mElapsedRealtimeMillis;
private final long mUnixEpochTimeMillis;
public UnixEpochTime(@ElapsedRealtimeLong long elapsedRealtimeMillis,
long unixEpochTimeMillis) {
mElapsedRealtimeMillis = elapsedRealtimeMillis;
mUnixEpochTimeMillis = unixEpochTimeMillis;
}
/** @hide */
@NonNull
public static UnixEpochTime parseCommandLineArgs(ShellCommand cmd) {
Long elapsedRealtimeMillis = null;
Long unixEpochTimeMillis = null;
String opt;
while ((opt = cmd.getNextArg()) != null) {
switch (opt) {
case "--elapsed_realtime": {
elapsedRealtimeMillis = Long.parseLong(cmd.getNextArgRequired());
break;
}
case "--unix_epoch_time": {
unixEpochTimeMillis = Long.parseLong(cmd.getNextArgRequired());
break;
}
default: {
throw new IllegalArgumentException("Unknown option: " + opt);
}
}
}
if (elapsedRealtimeMillis == null) {
throw new IllegalArgumentException("No elapsedRealtimeMillis specified.");
}
if (unixEpochTimeMillis == null) {
throw new IllegalArgumentException("No unixEpochTimeMillis specified.");
}
return new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
}
/** @hide */
public static void printCommandLineOpts(PrintWriter pw) {
pw.println("UnixEpochTime options:\n");
pw.println(" --elapsed_realtime <elapsed realtime millis>");
pw.println(" --unix_epoch_time <Unix epoch time millis>");
pw.println();
pw.println("See " + UnixEpochTime.class.getName() + " for more information");
}
/** Returns the elapsed realtime clock value. See {@link UnixEpochTime} for more information. */
public @ElapsedRealtimeLong long getElapsedRealtimeMillis() {
return mElapsedRealtimeMillis;
}
/** Returns the unix epoch time value. See {@link UnixEpochTime} for more information. */
@Nullable
public long getUnixEpochTimeMillis() {
return mUnixEpochTimeMillis;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
UnixEpochTime that = (UnixEpochTime) o;
return mElapsedRealtimeMillis == that.mElapsedRealtimeMillis
&& Objects.equals(mUnixEpochTimeMillis, that.mUnixEpochTimeMillis);
}
@Override
public int hashCode() {
return Objects.hash(mElapsedRealtimeMillis, mUnixEpochTimeMillis);
}
@Override
public String toString() {
return "UnixEpochTime{"
+ "mElapsedRealtimeTimeMillis=" + mElapsedRealtimeMillis
+ ", mUnixEpochTimeMillis=" + mUnixEpochTimeMillis
+ '}';
}
public static final @NonNull Creator<UnixEpochTime> CREATOR =
new ClassLoaderCreator<UnixEpochTime>() {
@Override
public UnixEpochTime createFromParcel(@NonNull Parcel source) {
return createFromParcel(source, null);
}
@Override
public UnixEpochTime createFromParcel(
@NonNull Parcel source, @Nullable ClassLoader classLoader) {
long elapsedRealtimeMillis = source.readLong();
long unixEpochTimeMillis = source.readLong();
return new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
}
@Override
public UnixEpochTime[] newArray(int size) {
return new UnixEpochTime[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeLong(mElapsedRealtimeMillis);
dest.writeLong(mUnixEpochTimeMillis);
}
/**
* Creates a new Unix epoch time value at {@code elapsedRealtimeTimeMillis} by adjusting this
* Unix epoch time by the difference between the elapsed realtime value supplied and the one
* associated with this instance.
*
* @hide
*/
public UnixEpochTime at(@ElapsedRealtimeLong long elapsedRealtimeTimeMillis) {
long adjustedUnixEpochTimeMillis =
(elapsedRealtimeTimeMillis - mElapsedRealtimeMillis) + mUnixEpochTimeMillis;
return new UnixEpochTime(elapsedRealtimeTimeMillis, adjustedUnixEpochTimeMillis);
}
/**
* Returns the difference in milliseconds between two instance's elapsed realtimes.
*
* @hide
*/
public static long elapsedRealtimeDifference(
@NonNull UnixEpochTime one, @NonNull UnixEpochTime two) {
return one.mElapsedRealtimeMillis - two.mElapsedRealtimeMillis;
}
// TODO(b/246256335) Switch to using UnixEpochTime where possible and remove this method.
/** @hide */
public TimestampedValue<Long> toTimestampedValue() {
return new TimestampedValue<>(mElapsedRealtimeMillis, mUnixEpochTimeMillis);
}
}

View File

@@ -20,20 +20,23 @@ import android.app.time.ExternalTimeSuggestion;
import android.app.time.ITimeDetectorListener;
import android.app.time.TimeCapabilitiesAndConfig;
import android.app.time.TimeConfiguration;
import android.app.time.TimeState;
import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.app.timedetector.TimePoint;
/**
* System private API to communicate with time detector service.
* Binder APIs to communicate with the time detector service.
*
* <p>Used by parts of the Android system with signals associated with the device's time to provide
* information to the Time Detector Service.
*
* <p>Use the {@link android.app.timedetector.TimeDetector} class rather than going through
* this Binder interface directly. See {@link android.app.timedetector.TimeDetectorService} for
* more complete documentation.
* <p>Used to provide information to the Time Detector Service from other parts of the Android
* system that have access to time-related signals, e.g. telephony. Over time, System APIs have
* been added to support unbundled parts of the platform, e.g. SetUp Wizard.
*
* <p>Use the {@link android.app.timedetector.TimeDetector} (internal API) and
* {@link android.app.time.TimeManager} (system API) classes rather than going through this Binder
* interface directly. See {@link android.app.timedetector.TimeDetectorService} for more complete
* documentation.
*
* {@hide}
*/
@@ -44,6 +47,10 @@ interface ITimeDetectorService {
boolean updateConfiguration(in TimeConfiguration timeConfiguration);
TimeState getTimeState();
boolean confirmTime(in UnixEpochTime time);
boolean setManualTime(in ManualTimeSuggestion timeZoneSuggestion);
void suggestExternalTime(in ExternalTimeSuggestion timeSuggestion);
boolean suggestManualTime(in ManualTimeSuggestion timeSuggestion);
void suggestTelephonyTime(in TelephonyTimeSuggestion timeSuggestion);

View File

@@ -84,6 +84,24 @@ public interface TimeDetector {
*/
String SHELL_COMMAND_SUGGEST_EXTERNAL_TIME = "suggest_external_time";
/**
* A shell command that retrieves the current system clock time state.
* @hide
*/
String SHELL_COMMAND_GET_TIME_STATE = "get_time_state";
/**
* A shell command that sets the current time state for testing.
* @hide
*/
String SHELL_COMMAND_SET_TIME_STATE = "set_time_state_for_tests";
/**
* A shell command that sets the confidence in the current time state for testing.
* @hide
*/
String SHELL_COMMAND_CONFIRM_TIME = "confirm_time";
/**
* A shared utility method to create a {@link ManualTimeSuggestion}.
*

View File

@@ -19,16 +19,19 @@ package android.app.timezonedetector;
import android.app.time.ITimeZoneDetectorListener;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
/**
* System private API to communicate with time zone detector service.
* Binder APIs to communicate with time zone detector service.
*
* <p>Used to provide information to the Time Zone Detector Service from other parts of the Android
* system that have access to time zone-related signals, e.g. telephony.
* system that have access to time zone-related signals, e.g. telephony. Over time, System APIs have
* been added to support unbundled parts of the platform, e.g. SetUp Wizard.
*
* <p>Use the {@link android.app.timezonedetector.TimeZoneDetector} class rather than going through
* <p>Use the {@link android.app.timezonedetector.TimeZoneDetector} (internal API) and
* {@link android.app.time.TimeManager} (system API) classes rather than going through
* this Binder interface directly. See {@link android.app.timezonedetector.TimeZoneDetectorService}
* for more complete documentation.
*
@@ -41,6 +44,10 @@ interface ITimeZoneDetectorService {
boolean updateConfiguration(in TimeZoneConfiguration configuration);
TimeZoneState getTimeZoneState();
boolean confirmTimeZone(in String timeZoneId);
boolean setManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
boolean suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
void suggestTelephonyTimeZone(in TelephonyTimeZoneSuggestion timeZoneSuggestion);
}

View File

@@ -107,6 +107,24 @@ public interface TimeZoneDetector {
*/
String SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK = "enable_telephony_fallback";
/**
* A shell command that retrieves the current time zone setting state.
* @hide
*/
String SHELL_COMMAND_GET_TIME_ZONE_STATE = "get_time_zone_state";
/**
* A shell command that sets the current time zone state for testing.
* @hide
*/
String SHELL_COMMAND_SET_TIME_ZONE_STATE = "set_time_zone_state_for_tests";
/**
* A shell command that sets the confidence in the current time zone state for testing.
* @hide
*/
String SHELL_COMMAND_CONFIRM_TIME_ZONE = "confirm_time_zone";
/**
* A shell command that dumps a {@link
* com.android.server.timezonedetector.MetricsTimeZoneDetectorState} object to stdout for

View File

@@ -48,10 +48,10 @@ public class TimeCapabilitiesTest {
public void testEquals() {
TimeCapabilities.Builder builder1 = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSuggestManualTimeCapability(CAPABILITY_POSSESSED);
.setSetManualTimeCapability(CAPABILITY_POSSESSED);
TimeCapabilities.Builder builder2 = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSuggestManualTimeCapability(CAPABILITY_POSSESSED);
.setSetManualTimeCapability(CAPABILITY_POSSESSED);
{
TimeCapabilities one = builder1.build();
TimeCapabilities two = builder2.build();
@@ -72,14 +72,14 @@ public class TimeCapabilitiesTest {
assertEquals(one, two);
}
builder2.setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED);
builder2.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED);
{
TimeCapabilities one = builder1.build();
TimeCapabilities two = builder2.build();
assertNotEquals(one, two);
}
builder1.setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED);
builder1.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED);
{
TimeCapabilities one = builder1.build();
TimeCapabilities two = builder2.build();
@@ -91,12 +91,12 @@ public class TimeCapabilitiesTest {
public void userHandle_notIgnoredInEquals() {
TimeCapabilities firstUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(1))
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
.setSetManualTimeCapability(CAPABILITY_POSSESSED)
.build();
TimeCapabilities secondUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(2))
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
.setSetManualTimeCapability(CAPABILITY_POSSESSED)
.build();
assertThat(firstUserCapabilities).isNotEqualTo(secondUserCapabilities);
@@ -106,12 +106,12 @@ public class TimeCapabilitiesTest {
public void testBuilder() {
TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_APPLICABLE)
.setSuggestManualTimeCapability(CAPABILITY_NOT_SUPPORTED)
.setSetManualTimeCapability(CAPABILITY_NOT_SUPPORTED)
.build();
assertThat(capabilities.getConfigureAutoDetectionEnabledCapability())
.isEqualTo(CAPABILITY_NOT_APPLICABLE);
assertThat(capabilities.getSuggestManualTimeCapability())
assertThat(capabilities.getSetManualTimeCapability())
.isEqualTo(CAPABILITY_NOT_SUPPORTED);
try {
@@ -133,7 +133,7 @@ public class TimeCapabilitiesTest {
try {
new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setSuggestManualTimeCapability(CAPABILITY_NOT_APPLICABLE)
.setSetManualTimeCapability(CAPABILITY_NOT_APPLICABLE)
.build();
fail("Should throw IllegalStateException");
} catch (IllegalStateException ignored) {
@@ -145,11 +145,11 @@ public class TimeCapabilitiesTest {
public void testParcelable() {
TimeCapabilities.Builder builder = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_SUPPORTED)
.setSuggestManualTimeCapability(CAPABILITY_NOT_SUPPORTED);
.setSetManualTimeCapability(CAPABILITY_NOT_SUPPORTED);
assertRoundTripParcelable(builder.build());
builder.setSuggestManualTimeCapability(CAPABILITY_POSSESSED);
builder.setSetManualTimeCapability(CAPABILITY_POSSESSED);
assertRoundTripParcelable(builder.build());
builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED);
@@ -164,7 +164,7 @@ public class TimeCapabilitiesTest {
.build();
TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
.setSetManualTimeCapability(CAPABILITY_POSSESSED)
.build();
TimeConfiguration configChange = new TimeConfiguration.Builder()
@@ -185,7 +185,7 @@ public class TimeCapabilitiesTest {
.build();
TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED)
.build();
TimeConfiguration configChange = new TimeConfiguration.Builder()
@@ -199,7 +199,7 @@ public class TimeCapabilitiesTest {
public void copyBuilder_copiesAllFields() {
TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED)
.build();
{
@@ -210,7 +210,7 @@ public class TimeCapabilitiesTest {
TimeCapabilities expectedCapabilities =
new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -219,13 +219,13 @@ public class TimeCapabilitiesTest {
{
TimeCapabilities updatedCapabilities =
new TimeCapabilities.Builder(capabilities)
.setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
.setSetManualTimeCapability(CAPABILITY_POSSESSED)
.build();
TimeCapabilities expectedCapabilities =
new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
.setSetManualTimeCapability(CAPABILITY_POSSESSED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2018 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 android.app.time;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import android.os.Parcel;
import android.os.ShellCommand;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for non-SDK methods on {@link TimeState}.
*/
@RunWith(AndroidJUnit4.class)
public class TimeStateTest {
@Test
public void testEqualsAndHashcode() {
UnixEpochTime time1 = new UnixEpochTime(1, 1);
TimeState time1False_1 = new TimeState(time1, false);
assertEqualsAndHashCode(time1False_1, time1False_1);
TimeState time1False_2 = new TimeState(time1, false);
assertEqualsAndHashCode(time1False_1, time1False_2);
TimeState time1True = new TimeState(time1, true);
assertNotEquals(time1False_1, time1True);
UnixEpochTime time2 = new UnixEpochTime(2, 2);
TimeState time2False = new TimeState(time2, false);
assertNotEquals(time1False_1, time2False);
}
private static void assertEqualsAndHashCode(Object one, Object two) {
assertEquals(one, two);
assertEquals(one.hashCode(), two.hashCode());
}
@Test
public void testParceling() {
UnixEpochTime time = new UnixEpochTime(1, 2);
TimeState value = new TimeState(time, true);
Parcel parcel = Parcel.obtain();
try {
parcel.writeParcelable(value, 0);
parcel.setDataPosition(0);
TimeState stringValueCopy =
parcel.readParcelable(null /* classLoader */, TimeState.class);
assertEquals(value, stringValueCopy);
} finally {
parcel.recycle();
}
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_noElapsedRealtime() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--unix_epoch_time 12345 --user_should_confirm_time true");
TimeState.parseCommandLineArgs(testShellCommand);
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_noUnixEpochTime() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--elapsed_realtime 54321 --user_should_confirm_time true");
TimeState.parseCommandLineArgs(testShellCommand);
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_noUserShouldConfirmTime() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--unix_epoch_time 12345 --elapsed_realtime 54321");
TimeState.parseCommandLineArgs(testShellCommand);
}
@Test
public void testParseCommandLineArg_validSuggestion() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--elapsed_realtime 54321 --unix_epoch_time 12345 --user_should_confirm_time true");
TimeState expectedValue = new TimeState(new UnixEpochTime(54321L, 12345L), true);
TimeState actualValue = TimeState.parseCommandLineArgs(testShellCommand);
assertEquals(expectedValue, actualValue);
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_unknownArgument() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--elapsed_realtime 54321 --unix_epoch_time 12345 --user_should_confirm_time true"
+ " --bad_arg 0");
TimeState.parseCommandLineArgs(testShellCommand);
}
}

View File

@@ -45,11 +45,11 @@ public class TimeZoneCapabilitiesTest {
TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
.setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
.setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
{
TimeZoneCapabilities one = builder1.build();
TimeZoneCapabilities two = builder2.build();
@@ -84,14 +84,14 @@ public class TimeZoneCapabilitiesTest {
assertEquals(one, two);
}
builder2.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
builder2.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
{
TimeZoneCapabilities one = builder1.build();
TimeZoneCapabilities two = builder2.build();
assertNotEquals(one, two);
}
builder1.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
builder1.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
{
TimeZoneCapabilities one = builder1.build();
TimeZoneCapabilities two = builder2.build();
@@ -104,7 +104,7 @@ public class TimeZoneCapabilitiesTest {
TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
.setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
assertRoundTripParcelable(builder.build());
builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
@@ -113,7 +113,7 @@ public class TimeZoneCapabilitiesTest {
builder.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
assertRoundTripParcelable(builder.build());
builder.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
builder.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
assertRoundTripParcelable(builder.build());
}
@@ -127,7 +127,7 @@ public class TimeZoneCapabilitiesTest {
TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED)
.setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
.build();
TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
@@ -150,7 +150,7 @@ public class TimeZoneCapabilitiesTest {
TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.build();
TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
@@ -165,7 +165,7 @@ public class TimeZoneCapabilitiesTest {
TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.build();
{
@@ -177,7 +177,7 @@ public class TimeZoneCapabilitiesTest {
new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -193,7 +193,7 @@ public class TimeZoneCapabilitiesTest {
new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -202,14 +202,14 @@ public class TimeZoneCapabilitiesTest {
{
TimeZoneCapabilities updatedCapabilities =
new TimeZoneCapabilities.Builder(capabilities)
.setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED)
.setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
.build();
TimeZoneCapabilities expectedCapabilities =
new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED)
.setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2018 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 android.app.time;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import android.os.Parcel;
import android.os.ShellCommand;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for non-SDK methods on {@link TimeZoneState}.
*/
@RunWith(AndroidJUnit4.class)
public class TimeZoneStateTest {
@Test
public void testEqualsAndHashcode() {
String zone1 = "Europe/London";
TimeZoneState zone1False_1 = new TimeZoneState(zone1, false);
assertEqualsAndHashCode(zone1False_1, zone1False_1);
TimeZoneState zone1False_2 = new TimeZoneState(zone1, false);
assertEqualsAndHashCode(zone1False_1, zone1False_2);
TimeZoneState zone1True = new TimeZoneState(zone1, true);
assertNotEquals(zone1False_1, zone1True);
String zone2 = "Europe/Parise";
TimeZoneState zone2False = new TimeZoneState(zone2, false);
assertNotEquals(zone1False_1, zone2False);
}
private static void assertEqualsAndHashCode(Object one, Object two) {
assertEquals(one, two);
assertEquals(one.hashCode(), two.hashCode());
}
@Test
public void testParceling() {
TimeZoneState value = new TimeZoneState("Europe/London", true);
Parcel parcel = Parcel.obtain();
try {
parcel.writeParcelable(value, 0);
parcel.setDataPosition(0);
TimeZoneState stringValueCopy =
parcel.readParcelable(null /* classLoader */, TimeZoneState.class);
assertEquals(value, stringValueCopy);
} finally {
parcel.recycle();
}
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_noZoneId() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--user_should_confirm_id true");
TimeZoneState.parseCommandLineArgs(testShellCommand);
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_noUserShouldConfirmId() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--zone_id Europe/London");
TimeZoneState.parseCommandLineArgs(testShellCommand);
}
@Test
public void testParseCommandLineArg_validSuggestion() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--zone_id Europe/London --user_should_confirm_id true");
TimeZoneState expectedValue = new TimeZoneState("Europe/London", true);
TimeZoneState actualValue = TimeZoneState.parseCommandLineArgs(testShellCommand);
assertEquals(expectedValue, actualValue);
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_unknownArgument() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--zone_id Europe/London --user_should_confirm_id true --bad_arg 0");
TimeZoneState.parseCommandLineArgs(testShellCommand);
}
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright (C) 2018 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 android.app.time;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import android.os.Parcel;
import android.os.ShellCommand;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for non-SDK methods on {@link UnixEpochTime}.
*/
@RunWith(AndroidJUnit4.class)
public class UnixEpochTimeTest {
@Test
public void testEqualsAndHashcode() {
UnixEpochTime one1000one = new UnixEpochTime(1000, 1);
assertEqualsAndHashCode(one1000one, one1000one);
UnixEpochTime one1000two = new UnixEpochTime(1000, 1);
assertEqualsAndHashCode(one1000one, one1000two);
UnixEpochTime two1000 = new UnixEpochTime(1000, 2);
assertNotEquals(one1000one, two1000);
UnixEpochTime one2000 = new UnixEpochTime(2000, 1);
assertNotEquals(one1000one, one2000);
}
private static void assertEqualsAndHashCode(Object one, Object two) {
assertEquals(one, two);
assertEquals(one.hashCode(), two.hashCode());
}
@Test
public void testParceling() {
UnixEpochTime value = new UnixEpochTime(1000, 1);
Parcel parcel = Parcel.obtain();
try {
parcel.writeParcelable(value, 0);
parcel.setDataPosition(0);
UnixEpochTime stringValueCopy =
parcel.readParcelable(null /* classLoader */, UnixEpochTime.class);
assertEquals(value, stringValueCopy);
} finally {
parcel.recycle();
}
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_noElapsedRealtime() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--unix_epoch_time 12345");
UnixEpochTime.parseCommandLineArgs(testShellCommand);
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_noUnixEpochTime() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--elapsed_realtime 54321");
UnixEpochTime.parseCommandLineArgs(testShellCommand);
}
@Test
public void testParseCommandLineArg_validSuggestion() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--elapsed_realtime 54321 --unix_epoch_time 12345");
UnixEpochTime expectedValue = new UnixEpochTime(54321L, 12345L);
UnixEpochTime actualValue = UnixEpochTime.parseCommandLineArgs(testShellCommand);
assertEquals(expectedValue, actualValue);
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_unknownArgument() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--elapsed_realtime 54321 --unix_epoch_time 12345 --bad_arg 0");
UnixEpochTime.parseCommandLineArgs(testShellCommand);
}
@Test
public void testAt() {
long timeMillis = 1000L;
int elapsedRealtimeMillis = 100;
UnixEpochTime unixEpochTime = new UnixEpochTime(elapsedRealtimeMillis, timeMillis);
// Reference time is after the timestamp.
UnixEpochTime at125 = unixEpochTime.at(125);
assertEquals(timeMillis + (125 - elapsedRealtimeMillis), at125.getUnixEpochTimeMillis());
assertEquals(125, at125.getElapsedRealtimeMillis());
// Reference time is before the timestamp.
UnixEpochTime at75 = unixEpochTime.at(75);
assertEquals(timeMillis + (75 - elapsedRealtimeMillis), at75.getUnixEpochTimeMillis());
assertEquals(75, at75.getElapsedRealtimeMillis());
}
@Test
public void testElapsedRealtimeDifference() {
UnixEpochTime value1 = new UnixEpochTime(1000, 123L);
assertEquals(0, UnixEpochTime.elapsedRealtimeDifference(value1, value1));
UnixEpochTime value2 = new UnixEpochTime(1, 321L);
assertEquals(999, UnixEpochTime.elapsedRealtimeDifference(value1, value2));
assertEquals(-999, UnixEpochTime.elapsedRealtimeDifference(value2, value1));
}
}

View File

@@ -46,7 +46,7 @@ public final class ConfigurationInternal {
private final boolean mAutoDetectionSupported;
private final int mSystemClockUpdateThresholdMillis;
private final int mSystemClockConfidenceUpgradeThresholdMillis;
private final int mSystemClockConfidenceThresholdMillis;
private final Instant mAutoSuggestionLowerBound;
private final Instant mManualSuggestionLowerBound;
private final Instant mSuggestionUpperBound;
@@ -58,8 +58,8 @@ public final class ConfigurationInternal {
private ConfigurationInternal(Builder builder) {
mAutoDetectionSupported = builder.mAutoDetectionSupported;
mSystemClockUpdateThresholdMillis = builder.mSystemClockUpdateThresholdMillis;
mSystemClockConfidenceUpgradeThresholdMillis =
builder.mSystemClockConfidenceUpgradeThresholdMillis;
mSystemClockConfidenceThresholdMillis =
builder.mSystemClockConfidenceThresholdMillis;
mAutoSuggestionLowerBound = Objects.requireNonNull(builder.mAutoSuggestionLowerBound);
mManualSuggestionLowerBound = Objects.requireNonNull(builder.mManualSuggestionLowerBound);
mSuggestionUpperBound = Objects.requireNonNull(builder.mSuggestionUpperBound);
@@ -85,14 +85,14 @@ public final class ConfigurationInternal {
}
/**
* Return the absolute threshold at/below which the system clock confidence can be upgraded.
* i.e. if the detector receives a high-confidence time and the current system clock is +/- this
* value from that time and the confidence in the time is low, then the device's confidence in
* the current system clock time can be upgraded. This needs to be an amount users would
* consider "close enough".
* Return the absolute threshold for Unix epoch time comparison at/below which the system clock
* confidence can be said to be "close enough", e.g. if the detector receives a high-confidence
* time and the current system clock is +/- this value from that time and the current confidence
* in the time is low, then the device's confidence in the current system clock time can be
* upgraded.
*/
public int getSystemClockConfidenceUpgradeThresholdMillis() {
return mSystemClockConfidenceUpgradeThresholdMillis;
public int getSystemClockConfidenceThresholdMillis() {
return mSystemClockConfidenceThresholdMillis;
}
/**
@@ -194,7 +194,7 @@ public final class ConfigurationInternal {
} else {
suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
}
builder.setSuggestManualTimeCapability(suggestManualTimeZoneCapability);
builder.setSetManualTimeCapability(suggestManualTimeZoneCapability);
return builder.build();
}
@@ -256,8 +256,8 @@ public final class ConfigurationInternal {
return "ConfigurationInternal{"
+ "mAutoDetectionSupported=" + mAutoDetectionSupported
+ ", mSystemClockUpdateThresholdMillis=" + mSystemClockUpdateThresholdMillis
+ ", mSystemClockConfidenceUpgradeThresholdMillis="
+ mSystemClockConfidenceUpgradeThresholdMillis
+ ", mSystemClockConfidenceThresholdMillis="
+ mSystemClockConfidenceThresholdMillis
+ ", mAutoSuggestionLowerBound=" + mAutoSuggestionLowerBound
+ "(" + mAutoSuggestionLowerBound.toEpochMilli() + ")"
+ ", mManualSuggestionLowerBound=" + mManualSuggestionLowerBound
@@ -274,7 +274,7 @@ public final class ConfigurationInternal {
static final class Builder {
private boolean mAutoDetectionSupported;
private int mSystemClockUpdateThresholdMillis;
private int mSystemClockConfidenceUpgradeThresholdMillis;
private int mSystemClockConfidenceThresholdMillis;
@NonNull private Instant mAutoSuggestionLowerBound;
@NonNull private Instant mManualSuggestionLowerBound;
@NonNull private Instant mSuggestionUpperBound;
@@ -321,9 +321,9 @@ public final class ConfigurationInternal {
return this;
}
/** See {@link ConfigurationInternal#getSystemClockConfidenceUpgradeThresholdMillis()}. */
public Builder setSystemClockConfidenceUpgradeThresholdMillis(int thresholdMillis) {
mSystemClockConfidenceUpgradeThresholdMillis = thresholdMillis;
/** See {@link ConfigurationInternal#getSystemClockConfidenceThresholdMillis()}. */
public Builder setSystemClockConfidenceThresholdMillis(int thresholdMillis) {
mSystemClockConfidenceThresholdMillis = thresholdMillis;
return this;
}

View File

@@ -245,7 +245,7 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
.setAutoDetectionSupported(isAutoDetectionSupported())
.setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting())
.setSystemClockUpdateThresholdMillis(getSystemClockUpdateThresholdMillis())
.setSystemClockConfidenceUpgradeThresholdMillis(
.setSystemClockConfidenceThresholdMillis(
getSystemClockConfidenceUpgradeThresholdMillis())
.setAutoSuggestionLowerBound(getAutoSuggestionLowerBound())
.setManualSuggestionLowerBound(timeDetectorHelper.getManualSuggestionLowerBound())

View File

@@ -24,6 +24,8 @@ import android.app.time.ExternalTimeSuggestion;
import android.app.time.ITimeDetectorListener;
import android.app.time.TimeCapabilitiesAndConfig;
import android.app.time.TimeConfiguration;
import android.app.time.TimeState;
import android.app.time.UnixEpochTime;
import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
@@ -271,6 +273,58 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
}
}
@Override
public TimeState getTimeState() {
enforceManageTimeDetectorPermission();
final long token = Binder.clearCallingIdentity();
try {
return mTimeDetectorStrategy.getTimeState();
} finally {
Binder.restoreCallingIdentity(token);
}
}
void setTimeState(@NonNull TimeState timeState) {
enforceManageTimeDetectorPermission();
final long token = Binder.clearCallingIdentity();
try {
mTimeDetectorStrategy.setTimeState(timeState);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean confirmTime(@NonNull UnixEpochTime time) {
enforceManageTimeDetectorPermission();
Objects.requireNonNull(time);
final long token = Binder.clearCallingIdentity();
try {
return mTimeDetectorStrategy.confirmTime(time);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean setManualTime(@NonNull ManualTimeSuggestion timeSignal) {
enforceManageTimeDetectorPermission();
Objects.requireNonNull(timeSignal);
// This calls suggestManualTime() as the logic is identical, it only differs in the
// permission required, which is handled on the line above.
int userId = mCallerIdentityInjector.getCallingUserId();
final long token = Binder.clearCallingIdentity();
try {
return mTimeDetectorStrategy.suggestManualTime(userId, timeSignal);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSignal) {
enforceSuggestTelephonyTimePermission();

View File

@@ -15,9 +15,12 @@
*/
package com.android.server.timedetector;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CONFIRM_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_TIME_STATE;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SERVICE_NAME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_TIME_STATE;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_EXTERNAL_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_GNSS_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME;
@@ -30,6 +33,8 @@ import static com.android.server.timedetector.ServerFlags.KEY_TIME_DETECTOR_ORIG
import android.app.time.ExternalTimeSuggestion;
import android.app.time.TimeConfiguration;
import android.app.time.TimeState;
import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.os.ShellCommand;
@@ -69,6 +74,12 @@ class TimeDetectorShellCommand extends ShellCommand {
return runSuggestGnssTime();
case SHELL_COMMAND_SUGGEST_EXTERNAL_TIME:
return runSuggestExternalTime();
case SHELL_COMMAND_GET_TIME_STATE:
return runGetTimeState();
case SHELL_COMMAND_SET_TIME_STATE:
return runSetTimeState();
case SHELL_COMMAND_CONFIRM_TIME:
return runConfirmTime();
default: {
return handleDefaultCommands(cmd);
}
@@ -140,6 +151,24 @@ class TimeDetectorShellCommand extends ShellCommand {
}
}
private int runGetTimeState() {
TimeState timeState = mInterface.getTimeState();
getOutPrintWriter().println(timeState);
return 0;
}
private int runSetTimeState() {
TimeState timeState = TimeState.parseCommandLineArgs(this);
mInterface.setTimeState(timeState);
return 0;
}
private int runConfirmTime() {
UnixEpochTime unixEpochTime = UnixEpochTime.parseCommandLineArgs(this);
getOutPrintWriter().println(mInterface.confirmTime(unixEpochTime));
return 0;
}
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -161,6 +190,12 @@ class TimeDetectorShellCommand extends ShellCommand {
pw.printf(" Suggests a time as if via the \"gnss\" origin.\n");
pw.printf(" %s <external suggestion opts>\n", SHELL_COMMAND_SUGGEST_EXTERNAL_TIME);
pw.printf(" Suggests a time as if via the \"external\" origin.\n");
pw.printf(" %s\n", SHELL_COMMAND_GET_TIME_STATE);
pw.printf(" Returns the current time setting state.\n");
pw.printf(" %s <time state options>\n", SHELL_COMMAND_SET_TIME_STATE);
pw.printf(" Sets the current time state for tests.\n");
pw.printf(" %s <unix epoch time options>\n", SHELL_COMMAND_CONFIRM_TIME);
pw.printf(" Tries to confirms the time, raising the confidence.\n");
pw.println();
ManualTimeSuggestion.printCommandLineOpts(pw);
pw.println();
@@ -172,6 +207,10 @@ class TimeDetectorShellCommand extends ShellCommand {
pw.println();
ExternalTimeSuggestion.printCommandLineOpts(pw);
pw.println();
TimeState.printCommandLineOpts(pw);
pw.println();
UnixEpochTime.printCommandLineOpts(pw);
pw.println();
pw.printf("This service is also affected by the following device_config flags in the"
+ " %s namespace:\n", NAMESPACE_SYSTEM_TIME);
pw.printf(" %s\n", KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE);

View File

@@ -20,6 +20,8 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.ExternalTimeSuggestion;
import android.app.time.TimeState;
import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.os.TimestampedValue;
@@ -65,6 +67,25 @@ public interface TimeDetectorStrategy extends Dumpable {
/** Used when a time value originated from an externally specified signal. */
@Origin int ORIGIN_EXTERNAL = 5;
/** Returns a snapshot of the system clock's state. See {@link TimeState} for details. */
@NonNull
TimeState getTimeState();
/**
* Sets the system time state. See {@link TimeState} for details. Intended for use during
* testing to force the device's state, this bypasses the time detection logic.
*/
void setTimeState(@NonNull TimeState timeState);
/**
* Signals that a user has confirmed the supplied time. If the {@code confirmationTime},
* adjusted for elapsed time since it was created (expected to be with {@link
* #getTimeState()}), is very close to the clock's current state, then this can be used to
* raise the system's confidence in that time. Returns {@code true} if confirmation was
* successful (i.e. the time matched), {@code false} otherwise.
*/
boolean confirmTime(@NonNull UnixEpochTime confirmationTime);
/** Processes the suggested time from telephony sources. */
void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion);
@@ -100,6 +121,7 @@ public interface TimeDetectorStrategy extends Dumpable {
* Converts one of the {@code ORIGIN_} constants to a human readable string suitable for config
* and debug usage. Throws an {@link IllegalArgumentException} if the value is unrecognized.
*/
@NonNull
static String originToString(@Origin int origin) {
switch (origin) {
case ORIGIN_MANUAL:

View File

@@ -17,6 +17,7 @@
package com.android.server.timedetector;
import static com.android.server.SystemClockTime.TIME_CONFIDENCE_HIGH;
import static com.android.server.SystemClockTime.TIME_CONFIDENCE_LOW;
import static com.android.server.timedetector.TimeDetectorStrategy.originToString;
import android.annotation.CurrentTimeMillisLong;
@@ -25,6 +26,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.time.ExternalTimeSuggestion;
import android.app.time.TimeState;
import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.content.Context;
@@ -297,6 +300,75 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
doAutoTimeDetection(reason);
}
@Override
public synchronized TimeState getTimeState() {
boolean userShouldConfirmTime = mEnvironment.systemClockConfidence() < TIME_CONFIDENCE_HIGH;
UnixEpochTime unixEpochTime = new UnixEpochTime(
mEnvironment.elapsedRealtimeMillis(), mEnvironment.systemClockMillis());
return new TimeState(unixEpochTime, userShouldConfirmTime);
}
@Override
public synchronized void setTimeState(@NonNull TimeState timeState) {
Objects.requireNonNull(timeState);
@TimeConfidence int confidence = timeState.getUserShouldConfirmTime()
? TIME_CONFIDENCE_LOW : TIME_CONFIDENCE_HIGH;
mEnvironment.acquireWakeLock();
try {
// The origin is a lie but this method is only used for command line / manual testing
// to force the device into a specific state.
@Origin int origin = ORIGIN_MANUAL;
UnixEpochTime unixEpochTime = timeState.getUnixEpochTime();
setSystemClockAndConfidenceUnderWakeLock(
origin, unixEpochTime.toTimestampedValue(), confidence, "setTimeZoneState()");
} finally {
mEnvironment.releaseWakeLock();
}
}
@Override
public synchronized boolean confirmTime(@NonNull UnixEpochTime confirmationTime) {
Objects.requireNonNull(confirmationTime);
@TimeConfidence int newTimeConfidence = TIME_CONFIDENCE_HIGH;
@TimeConfidence int currentTimeConfidence = mEnvironment.systemClockConfidence();
boolean timeNeedsConfirmation = currentTimeConfidence < newTimeConfidence;
// All system clock calculation take place under a wake lock.
mEnvironment.acquireWakeLock();
try {
// Check if the specified time matches the current system clock time (closely
// enough) to raise the confidence.
long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
long actualSystemClockMillis = mEnvironment.systemClockMillis();
long adjustedAutoDetectedUnixEpochMillis =
confirmationTime.at(elapsedRealtimeMillis).getUnixEpochTimeMillis();
long absTimeDifferenceMillis =
Math.abs(adjustedAutoDetectedUnixEpochMillis - actualSystemClockMillis);
int confidenceUpgradeThresholdMillis =
mCurrentConfigurationInternal.getSystemClockConfidenceThresholdMillis();
boolean timeConfirmed = absTimeDifferenceMillis <= confidenceUpgradeThresholdMillis;
boolean updateConfidenceRequired = timeNeedsConfirmation && timeConfirmed;
if (updateConfidenceRequired) {
String logMsg = "Confirm system clock time."
+ " confirmationTime=" + confirmationTime
+ " newTimeConfidence=" + newTimeConfidence
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ " (old) actualSystemClockMillis=" + actualSystemClockMillis
+ " currentTimeConfidence=" + currentTimeConfidence;
if (DBG) {
Slog.d(LOG_TAG, logMsg);
}
mEnvironment.setSystemClockConfidence(newTimeConfidence, logMsg);
}
return timeConfirmed;
} finally {
mEnvironment.releaseWakeLock();
}
}
@Override
public synchronized void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion) {
// Empty time suggestion means that telephony network connectivity has been lost.
@@ -799,7 +871,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
long absTimeDifferenceMillis =
Math.abs(adjustedAutoDetectedUnixEpochMillis - actualSystemClockMillis);
int confidenceUpgradeThresholdMillis =
mCurrentConfigurationInternal.getSystemClockConfidenceUpgradeThresholdMillis();
mCurrentConfigurationInternal.getSystemClockConfidenceThresholdMillis();
boolean updateConfidenceRequired =
absTimeDifferenceMillis <= confidenceUpgradeThresholdMillis;
if (updateConfidenceRequired) {
@@ -866,7 +938,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
boolean updateSystemClockRequired = absTimeDifference >= systemClockUpdateThreshold;
@TimeConfidence int currentTimeConfidence = mEnvironment.systemClockConfidence();
boolean updateConfidenceRequired = newTimeConfidence > currentTimeConfidence;
boolean updateConfidenceRequired = newTimeConfidence != currentTimeConfidence;
if (updateSystemClockRequired) {
String logMsg = "Set system clock & confidence."

View File

@@ -243,7 +243,7 @@ public final class ConfigurationInternal {
} else {
suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
}
builder.setSuggestManualTimeZoneCapability(suggestManualTimeZoneCapability);
builder.setSetManualTimeZoneCapability(suggestManualTimeZoneCapability);
return builder.build();
}

View File

@@ -23,6 +23,7 @@ import android.app.ActivityManager;
import android.app.time.ITimeZoneDetectorListener;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ITimeZoneDetectorService;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -312,6 +313,57 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
() -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion));
}
@Override
@NonNull
public TimeZoneState getTimeZoneState() {
enforceManageTimeZoneDetectorPermission();
final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
return mTimeZoneDetectorStrategy.getTimeZoneState();
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
}
}
void setTimeZoneState(@NonNull TimeZoneState timeZoneState) {
enforceManageTimeZoneDetectorPermission();
final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
mTimeZoneDetectorStrategy.setTimeZoneState(timeZoneState);
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
}
}
@Override
public boolean confirmTimeZone(@NonNull String timeZoneId) {
enforceManageTimeZoneDetectorPermission();
final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
return mTimeZoneDetectorStrategy.confirmTimeZone(timeZoneId);
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
}
}
@Override
public boolean setManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
enforceManageTimeZoneDetectorPermission();
// This calls suggestManualTimeZone() as the logic is identical, it only differs in the
// permission required, which is handled on the line above.
int userId = mCallerIdentityInjector.getCallingUserId();
final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
return mTimeZoneDetectorStrategy.suggestManualTimeZone(userId, timeZoneSuggestion);
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
}
}
@Override
public boolean suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
enforceSuggestManualTimeZonePermission();

View File

@@ -15,8 +15,10 @@
*/
package com.android.server.timezonedetector;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_CONFIRM_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_DUMP_METRICS;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_GET_TIME_ZONE_STATE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED;
@@ -24,6 +26,7 @@ import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_TEL
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SERVICE_NAME;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_TIME_ZONE_STATE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
@@ -38,6 +41,7 @@ import static com.android.server.timedetector.ServerFlags.KEY_TIME_ZONE_DETECTOR
import android.app.time.LocationTimeZoneManager;
import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.os.ShellCommand;
@@ -83,6 +87,12 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
return runSuggestTelephonyTimeZone();
case SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK:
return runEnableTelephonyFallback();
case SHELL_COMMAND_GET_TIME_ZONE_STATE:
return runGetTimeZoneState();
case SHELL_COMMAND_SET_TIME_ZONE_STATE:
return runSetTimeZoneState();
case SHELL_COMMAND_CONFIRM_TIME_ZONE:
return runConfirmTimeZone();
case SHELL_COMMAND_DUMP_METRICS:
return runDumpMetrics();
default: {
@@ -183,6 +193,45 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
return 0;
}
private int runGetTimeZoneState() {
TimeZoneState timeZoneState = mInterface.getTimeZoneState();
getOutPrintWriter().println(timeZoneState);
return 0;
}
private int runSetTimeZoneState() {
TimeZoneState timeZoneState = TimeZoneState.parseCommandLineArgs(this);
mInterface.setTimeZoneState(timeZoneState);
return 0;
}
private int runConfirmTimeZone() {
String timeZoneId = parseTimeZoneIdArg(this);
getOutPrintWriter().println(mInterface.confirmTimeZone(timeZoneId));
return 0;
}
private static String parseTimeZoneIdArg(ShellCommand cmd) {
String zoneId = null;
String opt;
while ((opt = cmd.getNextArg()) != null) {
switch (opt) {
case "--zone_id": {
zoneId = cmd.getNextArgRequired();
break;
}
default: {
throw new IllegalArgumentException("Unknown option: " + opt);
}
}
}
if (zoneId == null) {
throw new IllegalArgumentException("No zoneId specified.");
}
return zoneId;
}
private int runDumpMetrics() {
final PrintWriter pw = getOutPrintWriter();
MetricsTimeZoneDetectorState metricsState = mInterface.generateMetricsState();
@@ -226,6 +275,12 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
pw.printf(" Suggests a time zone as if via the \"manual\" origin.\n");
pw.printf(" %s <telephony suggestion opts>\n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
pw.printf(" Suggests a time zone as if via the \"telephony\" origin.\n");
pw.printf(" %s\n", SHELL_COMMAND_GET_TIME_ZONE_STATE);
pw.printf(" Returns the current time zone setting state.\n");
pw.printf(" %s <time zone state options>\n", SHELL_COMMAND_SET_TIME_ZONE_STATE);
pw.printf(" Sets the current time zone state for tests.\n");
pw.printf(" %s <--zone_id Olson ID>\n", SHELL_COMMAND_CONFIRM_TIME_ZONE);
pw.printf(" Tries to confirms the time zone, raising the confidence.\n");
pw.printf(" %s\n", SHELL_COMMAND_DUMP_METRICS);
pw.printf(" Dumps the service metrics to stdout for inspection.\n");
pw.println();
@@ -235,6 +290,8 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
pw.println();
TelephonyTimeZoneSuggestion.printCommandLineOpts(pw);
pw.println();
TimeZoneState.printCommandLineOpts(pw);
pw.println();
pw.printf("This service is also affected by the following device_config flags in the"
+ " %s namespace:\n", NAMESPACE_SYSTEM_TIME);
pw.printf(" %s\n", KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED);

View File

@@ -17,6 +17,7 @@ package com.android.server.timezonedetector;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.util.IndentingPrintWriter;
@@ -93,6 +94,24 @@ import android.util.IndentingPrintWriter;
*/
public interface TimeZoneDetectorStrategy extends Dumpable {
/** Returns a snapshot of the system time zone state. See {@link TimeZoneState} for details. */
@NonNull
TimeZoneState getTimeZoneState();
/**
* Sets the system time zone state. See {@link TimeZoneState} for details. Intended for use
* during testing to force the device's state, this bypasses the time zone detection logic.
*/
void setTimeZoneState(@NonNull TimeZoneState timeZoneState);
/**
* Signals that a user has confirmed the time zone. If the {@code timeZoneId} is the same as
* the current time zone then this can be used to raise the system's confidence in that time
* zone. Returns {@code true} if confirmation was successful (i.e. the ID matched),
* {@code false} otherwise.
*/
boolean confirmTimeZone(@NonNull String timeZoneId);
/**
* Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
* {@link GeolocationTimeZoneSuggestion#getZoneIds()} is {@code null}.

View File

@@ -23,6 +23,7 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_M
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
@@ -30,6 +31,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilities;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.content.Context;
@@ -76,7 +78,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
@NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
/**
* Returns the device's currently configured time zone.
* Returns the device's currently configured time zone. May return an empty string.
*/
@NonNull String getDeviceTimeZone();
@@ -249,6 +251,39 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
}
}
@Override
public synchronized boolean confirmTimeZone(@NonNull String timeZoneId) {
Objects.requireNonNull(timeZoneId);
String currentTimeZoneId = mEnvironment.getDeviceTimeZone();
if (!currentTimeZoneId.equals(timeZoneId)) {
return false;
}
if (mEnvironment.getDeviceTimeZoneConfidence() < TIME_ZONE_CONFIDENCE_HIGH) {
mEnvironment.setDeviceTimeZoneAndConfidence(currentTimeZoneId,
TIME_ZONE_CONFIDENCE_HIGH, "confirmTimeZone: timeZoneId=" + timeZoneId);
}
return true;
}
@Override
public synchronized TimeZoneState getTimeZoneState() {
boolean userShouldConfirmId =
mEnvironment.getDeviceTimeZoneConfidence() < TIME_ZONE_CONFIDENCE_HIGH;
return new TimeZoneState(mEnvironment.getDeviceTimeZone(), userShouldConfirmId);
}
@Override
public void setTimeZoneState(@NonNull TimeZoneState timeZoneState) {
Objects.requireNonNull(timeZoneState);
@TimeZoneConfidence int confidence = timeZoneState.getUserShouldConfirmId()
? TIME_ZONE_CONFIDENCE_LOW : TIME_ZONE_CONFIDENCE_HIGH;
mEnvironment.setDeviceTimeZoneAndConfidence(
timeZoneState.getId(), confidence, "setTimeZoneState()");
}
@Override
public synchronized void suggestGeolocationTimeZone(
@NonNull GeolocationTimeZoneSuggestion suggestion) {
@@ -301,7 +336,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
currentUserConfig.createCapabilitiesAndConfig();
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
if (capabilities.getSuggestManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
if (capabilities.getSetManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
+ ": capabilities=" + capabilities
+ ", timeZoneId=" + timeZoneId

View File

@@ -79,7 +79,7 @@ public class ConfigurationInternalTest {
TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeCapability());
assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSetManualTimeCapability());
TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
@@ -98,7 +98,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_POSSESSED,
capabilities.getSuggestManualTimeCapability());
capabilities.getSetManualTimeCapability());
TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
@@ -131,7 +131,7 @@ public class ConfigurationInternalTest {
TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeCapability());
assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSetManualTimeCapability());
TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
@@ -149,7 +149,7 @@ public class ConfigurationInternalTest {
TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeCapability());
assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSetManualTimeCapability());
TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
@@ -181,7 +181,7 @@ public class ConfigurationInternalTest {
TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeCapability());
assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeCapability());
TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
@@ -199,7 +199,7 @@ public class ConfigurationInternalTest {
TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeCapability());
assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeCapability());
TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());

View File

@@ -21,6 +21,8 @@ import static org.junit.Assert.assertTrue;
import android.annotation.UserIdInt;
import android.app.time.ExternalTimeSuggestion;
import android.app.time.TimeState;
import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.util.IndentingPrintWriter;
@@ -30,6 +32,8 @@ import android.util.IndentingPrintWriter;
* in tests.
*/
class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
// State
private TimeState mTimeState;
// Call tracking.
private TelephonyTimeSuggestion mLastTelephonySuggestion;
@@ -38,8 +42,25 @@ class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
private NetworkTimeSuggestion mLastNetworkSuggestion;
private GnssTimeSuggestion mLastGnssSuggestion;
private ExternalTimeSuggestion mLastExternalSuggestion;
private UnixEpochTime mLastConfirmedTime;
private boolean mDumpCalled;
@Override
public TimeState getTimeState() {
return mTimeState;
}
@Override
public void setTimeState(TimeState timeState) {
mTimeState = timeState;
}
@Override
public boolean confirmTime(UnixEpochTime confirmationTime) {
mLastConfirmedTime = confirmationTime;
return false;
}
@Override
public void suggestTelephonyTime(TelephonyTimeSuggestion timeSuggestion) {
mLastTelephonySuggestion = timeSuggestion;
@@ -79,6 +100,7 @@ class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
mLastNetworkSuggestion = null;
mLastGnssSuggestion = null;
mLastExternalSuggestion = null;
mLastConfirmedTime = null;
mDumpCalled = false;
}
@@ -104,6 +126,10 @@ class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
assertEquals(expectedSuggestion, mLastExternalSuggestion);
}
void verifyConfirmTimeCalled(UnixEpochTime expectedConfirmationTime) {
assertEquals(mLastConfirmedTime, expectedConfirmationTime);
}
void verifyDumpCalled() {
assertTrue(mDumpCalled);
}

View File

@@ -19,6 +19,7 @@ package com.android.server.timedetector;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -38,6 +39,8 @@ import static org.mockito.Mockito.when;
import android.app.time.ExternalTimeSuggestion;
import android.app.time.ITimeDetectorListener;
import android.app.time.TimeConfiguration;
import android.app.time.TimeState;
import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.app.timedetector.TimePoint;
@@ -423,6 +426,125 @@ public class TimeDetectorServiceTest {
assertThrows(ParcelableException.class, () -> mTimeDetectorService.latestNetworkTime());
}
@Test
public void testGetTimeState() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
TimeState fakeState = new TimeState(new UnixEpochTime(12345L, 98765L), true);
mFakeTimeDetectorStrategy.setTimeState(fakeState);
TimeState actualState = mTimeDetectorService.getTimeState();
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
assertEquals(actualState, fakeState);
}
@Test(expected = SecurityException.class)
public void testGetTimeState_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
try {
mTimeDetectorService.getTimeState();
} finally {
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test
public void testSetTimeState() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
TimeState state = new TimeState(new UnixEpochTime(12345L, 98765L), true);
mTimeDetectorService.setTimeState(state);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
assertEquals(mFakeTimeDetectorStrategy.getTimeState(), state);
}
@Test(expected = SecurityException.class)
public void testSetTimeState_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
TimeState state = new TimeState(new UnixEpochTime(12345L, 98765L), true);
try {
mTimeDetectorService.setTimeState(state);
} finally {
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test
public void testConfirmTime() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
UnixEpochTime confirmationTime = new UnixEpochTime(12345L, 98765L);
// The fake strategy always returns false.
assertFalse(mTimeDetectorService.confirmTime(confirmationTime));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
mFakeTimeDetectorStrategy.verifyConfirmTimeCalled(confirmationTime);
}
@Test(expected = SecurityException.class)
public void testConfirmTime_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
try {
mTimeDetectorService.confirmTime(new UnixEpochTime(12345L, 98765L));
} finally {
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test
public void testSetManualTime() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
ManualTimeSuggestion timeSuggestion = createManualTimeSuggestion();
boolean expectedResult = true; // The test strategy always returns true.
assertEquals(expectedResult,
mTimeDetectorService.setManualTime(timeSuggestion));
// The service calls "suggestManualTime()" because the logic is the same.
mFakeTimeDetectorStrategy.verifySuggestManualTimeCalled(
mTestCallerIdentityInjector.getCallingUserId(), timeSuggestion);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
@Test(expected = SecurityException.class)
public void testSetManualTime_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
ManualTimeSuggestion timeSuggestion = createManualTimeSuggestion();
try {
mTimeDetectorService.setManualTime(timeSuggestion);
fail("Expected SecurityException");
} finally {
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test
public void testDump() {
when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))

View File

@@ -18,6 +18,7 @@ package com.android.server.timedetector;
import static com.android.server.SystemClockTime.TIME_CONFIDENCE_HIGH;
import static com.android.server.SystemClockTime.TIME_CONFIDENCE_LOW;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_EXTERNAL;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_GNSS;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
@@ -31,6 +32,8 @@ import static org.junit.Assert.fail;
import android.annotation.UserIdInt;
import android.app.time.ExternalTimeSuggestion;
import android.app.time.TimeState;
import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.os.TimestampedValue;
@@ -300,7 +303,7 @@ public class TimeDetectorStrategyImplTest {
final int confidenceUpgradeThresholdMillis = 1000;
ConfigurationInternal configInternal =
new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
.setSystemClockConfidenceUpgradeThresholdMillis(
.setSystemClockConfidenceThresholdMillis(
confidenceUpgradeThresholdMillis)
.build();
Script script = new Script()
@@ -338,7 +341,7 @@ public class TimeDetectorStrategyImplTest {
final int confidenceUpgradeThresholdMillis = 1000;
ConfigurationInternal configInternal =
new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
.setSystemClockConfidenceUpgradeThresholdMillis(
.setSystemClockConfidenceThresholdMillis(
confidenceUpgradeThresholdMillis)
.build();
Script script = new Script().pokeFakeClocks(initialClockTime, TIME_CONFIDENCE_LOW)
@@ -375,7 +378,7 @@ public class TimeDetectorStrategyImplTest {
final int confidenceUpgradeThresholdMillis = 1000;
ConfigurationInternal configInternal =
new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
.setSystemClockConfidenceUpgradeThresholdMillis(
.setSystemClockConfidenceThresholdMillis(
confidenceUpgradeThresholdMillis)
.build();
Script script = new Script().pokeFakeClocks(initialClockTime, TIME_CONFIDENCE_HIGH)
@@ -1136,6 +1139,126 @@ public class TimeDetectorStrategyImplTest {
.verifySystemClockWasSetAndResetCallTracking(belowUpperBound.toEpochMilli());
}
@Test
public void testGetTimeState() {
TimestampedValue<Instant> deviceTime = ARBITRARY_CLOCK_INITIALIZATION_INFO;
Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
.pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
.verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
UnixEpochTime systemClockTime = new UnixEpochTime(deviceTime.getReferenceTimeMillis(),
deviceTime.getValue().toEpochMilli());
// When confidence is low, the user should confirm.
script.assertGetTimeStateReturns(new TimeState(systemClockTime, true));
// When confidence is high, no need for the user to confirm.
script.pokeFakeClocks(deviceTime, TIME_ZONE_CONFIDENCE_HIGH)
.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH);
script.assertGetTimeStateReturns(new TimeState(systemClockTime, false));
}
@Test
public void testSetTimeState() {
TimestampedValue<Instant> deviceTime = ARBITRARY_CLOCK_INITIALIZATION_INFO;
Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
.pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
.verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
UnixEpochTime systemClockTime = new UnixEpochTime(11111L, 222222L);
boolean userShouldConfirmTime = false;
TimeState state = new TimeState(systemClockTime, userShouldConfirmTime);
script.simulateSetTimeState(state);
UnixEpochTime expectedTime = systemClockTime.at(script.peekElapsedRealtimeMillis());
long expectedTimeMillis = expectedTime.getUnixEpochTimeMillis();
// userShouldConfirmTime == high confidence
script.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
.verifySystemClockWasSetAndResetCallTracking(expectedTimeMillis);
TimeState expectedTimeState = new TimeState(expectedTime, userShouldConfirmTime);
script.assertGetTimeStateReturns(expectedTimeState);
}
@Test
public void testConfirmTime_autoDisabled() {
testConfirmTime(CONFIG_AUTO_ENABLED);
}
@Test
public void testConfirmTime_autoEnabled() {
testConfirmTime(CONFIG_AUTO_ENABLED);
}
private void testConfirmTime(ConfigurationInternal config) {
TimestampedValue<Instant> deviceTime = ARBITRARY_CLOCK_INITIALIZATION_INFO;
Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
.pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
.verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
long maxConfidenceThreshold = config.getSystemClockConfidenceThresholdMillis();
UnixEpochTime incorrectTime1 =
new UnixEpochTime(
deviceTime.getReferenceTimeMillis() + maxConfidenceThreshold + 1,
deviceTime.getValue().toEpochMilli());
script.simulateConfirmTime(incorrectTime1, false)
.verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
.verifySystemClockWasNotSetAndResetCallTracking();
UnixEpochTime incorrectTime2 =
new UnixEpochTime(
deviceTime.getReferenceTimeMillis(),
deviceTime.getValue().toEpochMilli() + maxConfidenceThreshold + 1);
script.simulateConfirmTime(incorrectTime2, false)
.verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
.verifySystemClockWasNotSetAndResetCallTracking();
// Confirm using a time that is at the threshold.
UnixEpochTime correctTime1 =
new UnixEpochTime(
deviceTime.getReferenceTimeMillis(),
deviceTime.getValue().toEpochMilli() + maxConfidenceThreshold);
script.simulateConfirmTime(correctTime1, true)
.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
.verifySystemClockWasNotSetAndResetCallTracking();
// Reset back to low confidence.
script.pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
.verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
.verifySystemClockWasNotSetAndResetCallTracking();
// Confirm using a time that is at the threshold.
UnixEpochTime correctTime2 =
new UnixEpochTime(
deviceTime.getReferenceTimeMillis() + maxConfidenceThreshold,
deviceTime.getValue().toEpochMilli());
script.simulateConfirmTime(correctTime2, true)
.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
.verifySystemClockWasNotSetAndResetCallTracking();
// Reset back to low confidence.
script.pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
.verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
.verifySystemClockWasNotSetAndResetCallTracking();
// Confirm using a time that exactly matches.
UnixEpochTime correctTime3 =
new UnixEpochTime(
deviceTime.getReferenceTimeMillis(),
deviceTime.getValue().toEpochMilli());
script.simulateConfirmTime(correctTime3, true)
.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
.verifySystemClockWasNotSetAndResetCallTracking();
// Now try to confirm using another incorrect time: Confidence should remain high as the
// confirmation is ignored / returns false.
script.simulateConfirmTime(incorrectTime1, false)
.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
.verifySystemClockWasNotSetAndResetCallTracking();
}
@Test
public void highPrioritySuggestionsBeatLowerPrioritySuggestions_telephonyNetworkOrigins() {
ConfigurationInternal configInternal =
@@ -1771,7 +1894,7 @@ public class TimeDetectorStrategyImplTest {
assertEquals(expectedSystemClockMillis, mSystemClockMillis);
}
public void verifySystemClockConfidence(@TimeConfidence int expectedConfidence) {
public void verifySystemClockConfidenceLatest(@TimeConfidence int expectedConfidence) {
assertEquals(expectedConfidence, mSystemClockConfidence);
}
@@ -1879,6 +2002,12 @@ public class TimeDetectorStrategyImplTest {
return simulateTimePassing(999);
}
/** Calls {@link TimeDetectorStrategy#setTimeState(TimeState)}. */
Script simulateSetTimeState(TimeState timeState) {
mTimeDetectorStrategy.setTimeState(timeState);
return this;
}
Script verifySystemClockWasNotSetAndResetCallTracking() {
mFakeEnvironment.verifySystemClockNotSet();
mFakeEnvironment.resetCallTracking();
@@ -1892,7 +2021,7 @@ public class TimeDetectorStrategyImplTest {
}
Script verifySystemClockConfidence(@TimeConfidence int expectedConfidence) {
mFakeEnvironment.verifySystemClockConfidence(expectedConfidence);
mFakeEnvironment.verifySystemClockConfidenceLatest(expectedConfidence);
return this;
}
@@ -1931,6 +2060,11 @@ public class TimeDetectorStrategyImplTest {
return this;
}
Script assertGetTimeStateReturns(TimeState expected) {
assertEquals(expected, mTimeDetectorStrategy.getTimeState());
return this;
}
/**
* White box test info: Returns the telephony suggestion that would be used, if any, given
* the current elapsed real time clock and regardless of origin prioritization.
@@ -2037,6 +2171,11 @@ public class TimeDetectorStrategyImplTest {
long calculateTimeInMillisForNow(TimestampedValue<Long> unixEpochTime) {
return TimeDetectorStrategy.getTimeAt(unixEpochTime, peekElapsedRealtimeMillis());
}
Script simulateConfirmTime(UnixEpochTime confirmationTime, boolean expectedReturnValue) {
assertEquals(expectedReturnValue, mTimeDetectorStrategy.confirmTime(confirmationTime));
return this;
}
}
private static TelephonyTimeSuggestion createTelephonyTimeSuggestion(int slotIndex,

View File

@@ -76,7 +76,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_NOT_APPLICABLE,
capabilities.getSuggestManualTimeZoneCapability());
capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -102,7 +102,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_POSSESSED,
capabilities.getSuggestManualTimeZoneCapability());
capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_NOT_APPLICABLE,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -143,7 +143,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getSuggestManualTimeZoneCapability());
capabilities.getSetManualTimeZoneCapability());
// This has user privacy implications so it is not restricted in the same way as others.
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -170,7 +170,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getSuggestManualTimeZoneCapability());
capabilities.getSetManualTimeZoneCapability());
// This has user privacy implications so it is not restricted in the same way as others.
assertEquals(CAPABILITY_NOT_APPLICABLE,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -211,7 +211,7 @@ public class ConfigurationInternalTest {
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -235,7 +235,7 @@ public class ConfigurationInternalTest {
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -279,7 +279,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_NOT_APPLICABLE,
capabilities.getSuggestManualTimeZoneCapability());
capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -303,7 +303,7 @@ public class ConfigurationInternalTest {
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());

View File

@@ -20,18 +20,39 @@ import static org.junit.Assert.assertTrue;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.util.IndentingPrintWriter;
class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
private TimeZoneState mTimeZoneState;
// Call tracking.
private GeolocationTimeZoneSuggestion mLastGeolocationSuggestion;
private ManualTimeZoneSuggestion mLastManualSuggestion;
private Integer mLastManualSuggestionUserId;
private TelephonyTimeZoneSuggestion mLastTelephonySuggestion;
private String mLastConfirmedTimeZone;
private boolean mDumpCalled;
@Override
public boolean confirmTimeZone(String timeZoneId) {
mLastConfirmedTimeZone = timeZoneId;
return false;
}
@Override
public TimeZoneState getTimeZoneState() {
return mTimeZoneState;
}
@Override
public void setTimeZoneState(TimeZoneState timeZoneState) {
mTimeZoneState = timeZoneState;
}
@Override
public void suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion timeZoneSuggestion) {
mLastGeolocationSuggestion = timeZoneSuggestion;
@@ -40,6 +61,7 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
@Override
public boolean suggestManualTimeZone(
@UserIdInt int userId, @NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
mLastManualSuggestionUserId = userId;
mLastManualSuggestion = timeZoneSuggestion;
return true;
}
@@ -78,8 +100,10 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
void resetCallTracking() {
mLastGeolocationSuggestion = null;
mLastManualSuggestion = null;
mLastManualSuggestionUserId = null;
mLastTelephonySuggestion = null;
mDumpCalled = false;
mLastConfirmedTimeZone = null;
}
void verifySuggestGeolocationTimeZoneCalled(
@@ -87,7 +111,9 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
assertEquals(expectedSuggestion, mLastGeolocationSuggestion);
}
void verifySuggestManualTimeZoneCalled(ManualTimeZoneSuggestion expectedSuggestion) {
void verifySuggestManualTimeZoneCalled(
@UserIdInt int expectedUserId, ManualTimeZoneSuggestion expectedSuggestion) {
assertEquals((Integer) expectedUserId, mLastManualSuggestionUserId);
assertEquals(expectedSuggestion, mLastManualSuggestion);
}
@@ -98,4 +124,8 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
void verifyDumpCalled() {
assertTrue(mDumpCalled);
}
void verifyConfirmTimeZoneCalled(String expectedTimeZoneId) {
assertEquals(expectedTimeZoneId, mLastConfirmedTimeZone);
}
}

View File

@@ -17,6 +17,7 @@
package com.android.server.timezonedetector;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -33,6 +34,7 @@ import static org.mockito.Mockito.when;
import android.app.time.ITimeZoneDetectorListener;
import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.content.Context;
@@ -294,7 +296,8 @@ public class TimeZoneDetectorServiceTest {
assertEquals(expectedResult,
mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion));
mFakeTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(timeZoneSuggestion);
mFakeTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(
mTestCallerIdentityInjector.getCallingUserId(), timeZoneSuggestion);
verify(mMockContext).enforceCallingOrSelfPermission(
eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
@@ -349,6 +352,124 @@ public class TimeZoneDetectorServiceTest {
mFakeTimeZoneDetectorStrategy.verifySuggestTelephonyTimeZoneCalled(timeZoneSuggestion);
}
@Test
public void testGetTimeZoneState() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
TimeZoneState fakeState = new TimeZoneState("Europe/Narnia", true);
mFakeTimeZoneDetectorStrategy.setTimeZoneState(fakeState);
TimeZoneState actualState = mTimeZoneDetectorService.getTimeZoneState();
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
assertEquals(actualState, fakeState);
}
@Test(expected = SecurityException.class)
public void testGetTimeZoneState_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
try {
mTimeZoneDetectorService.getTimeZoneState();
} finally {
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test
public void testSetTimeZoneState() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
TimeZoneState state = new TimeZoneState("Europe/Narnia", true);
mTimeZoneDetectorService.setTimeZoneState(state);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
assertEquals(mFakeTimeZoneDetectorStrategy.getTimeZoneState(), state);
}
@Test(expected = SecurityException.class)
public void testSetTimeZoneState_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
TimeZoneState state = new TimeZoneState("Europe/Narnia", true);
try {
mTimeZoneDetectorService.setTimeZoneState(state);
} finally {
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test
public void testConfirmTimeZone() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
// The fake strategy always returns false.
assertFalse(mTimeZoneDetectorService.confirmTimeZone("Europe/Narnia"));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
mFakeTimeZoneDetectorStrategy.verifyConfirmTimeZoneCalled("Europe/Narnia");
}
@Test(expected = SecurityException.class)
public void testConfirmTimeZone_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
try {
mTimeZoneDetectorService.confirmTimeZone("Europe/Narnia");
} finally {
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test
public void testSetManualTimeZone() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
boolean expectedResult = true; // The test strategy always returns true.
assertEquals(expectedResult,
mTimeZoneDetectorService.setManualTimeZone(timeZoneSuggestion));
// The service calls "suggestManualTimeZone()" because the logic is the same.
mFakeTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(
mTestCallerIdentityInjector.getCallingUserId(), timeZoneSuggestion);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
@Test(expected = SecurityException.class)
public void testSetManualTimeZone_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
try {
mTimeZoneDetectorService.setManualTimeZone(timeZoneSuggestion);
fail("Expected SecurityException");
} finally {
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test
public void testDump() {
when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))

View File

@@ -25,6 +25,7 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_M
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
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;
@@ -33,6 +34,7 @@ import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.T
import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_USAGE_THRESHOLD;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -40,6 +42,7 @@ import static org.junit.Assert.assertTrue;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
@@ -183,7 +186,7 @@ public class TimeZoneDetectorStrategyImplTest {
TelephonyTimeZoneSuggestion slotIndex2TimeZoneSuggestion =
createEmptySlotIndex2Suggestion();
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
.resetConfigurationTracking();
@@ -296,7 +299,7 @@ public class TimeZoneDetectorStrategyImplTest {
for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
// Start with the device in a known state.
script.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
script.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
.resetConfigurationTracking();
@@ -348,7 +351,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testTelephonySuggestionsSingleSlotId() {
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
.resetConfigurationTracking();
@@ -414,7 +417,7 @@ public class TimeZoneDetectorStrategyImplTest {
TELEPHONY_SCORE_NONE);
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
.resetConfigurationTracking()
@@ -553,7 +556,7 @@ public class TimeZoneDetectorStrategyImplTest {
.setGeoDetectionEnabledSetting(geoDetectionEnabled)
.build();
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(geoTzEnabledConfig)
.resetConfigurationTracking();
@@ -568,7 +571,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_restricted_simulateAutoTimeZoneEnabled() {
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_USER_RESTRICTED_AUTO_ENABLED)
.resetConfigurationTracking();
@@ -583,7 +586,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_unrestricted_autoTimeZoneDetectionDisabled() {
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
.resetConfigurationTracking();
@@ -599,7 +602,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_restricted_autoTimeZoneDetectionDisabled() {
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_USER_RESTRICTED_AUTO_DISABLED)
.resetConfigurationTracking();
@@ -615,7 +618,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_autoDetectNotSupported() {
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_DETECT_NOT_SUPPORTED)
.resetConfigurationTracking();
@@ -631,7 +634,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testGeoSuggestion_uncertain() {
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
@@ -648,7 +651,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testGeoSuggestion_noZones() {
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
@@ -667,7 +670,7 @@ public class TimeZoneDetectorStrategyImplTest {
createCertainGeolocationSuggestion("Europe/London");
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
@@ -693,7 +696,7 @@ public class TimeZoneDetectorStrategyImplTest {
createCertainGeolocationSuggestion("Europe/Paris");
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
@@ -734,7 +737,7 @@ public class TimeZoneDetectorStrategyImplTest {
"Europe/Paris");
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
.resetConfigurationTracking();
@@ -780,7 +783,7 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script()
.initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(config)
.resetConfigurationTracking();
@@ -914,7 +917,7 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script()
.initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(config)
.resetConfigurationTracking();
@@ -969,6 +972,78 @@ public class TimeZoneDetectorStrategyImplTest {
}
}
@Test
public void testGetTimeZoneState() {
Script script = new Script()
.initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
.resetConfigurationTracking();
String timeZoneId = "Europe/London";
// When confidence is low, the user should confirm.
script.initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_LOW);
assertEquals(new TimeZoneState(timeZoneId, true),
mTimeZoneDetectorStrategy.getTimeZoneState());
// When confidence is high, no need for the user to confirm.
script.initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
assertEquals(new TimeZoneState(timeZoneId, false),
mTimeZoneDetectorStrategy.getTimeZoneState());
}
@Test
public void testSetTimeZoneState() {
Script script = new Script()
.initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
.resetConfigurationTracking();
String timeZoneId = "Europe/London";
boolean userShouldConfirmId = false;
TimeZoneState state = new TimeZoneState(timeZoneId, userShouldConfirmId);
mTimeZoneDetectorStrategy.setTimeZoneState(state);
script.verifyTimeZoneChangedAndReset(timeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
assertEquals(state, mTimeZoneDetectorStrategy.getTimeZoneState());
}
@Test
public void testConfirmTimeZone_autoDisabled() {
testConfirmTimeZone(CONFIG_AUTO_DISABLED_GEO_DISABLED);
}
@Test
public void testConfirmTimeZone_autoEnabled() {
testConfirmTimeZone(CONFIG_AUTO_ENABLED_GEO_DISABLED);
}
private void testConfirmTimeZone(ConfigurationInternal config) {
String timeZoneId = "Europe/London";
Script script = new Script()
.initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
.initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(config)
.resetConfigurationTracking();
String incorrectTimeZoneId = "Europe/Paris";
assertFalse(mTimeZoneDetectorStrategy.confirmTimeZone(incorrectTimeZoneId));
script.verifyTimeZoneNotChanged();
assertTrue(mTimeZoneDetectorStrategy.confirmTimeZone(timeZoneId));
script.verifyTimeZoneChangedAndReset(timeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
assertTrue(mTimeZoneDetectorStrategy.confirmTimeZone(timeZoneId));
// The strategy checks the current confidence and if it is already high it takes no action.
script.verifyTimeZoneNotChanged();
assertFalse(mTimeZoneDetectorStrategy.confirmTimeZone(incorrectTimeZoneId));
script.verifyTimeZoneNotChanged();
}
@Test
public void testGenerateMetricsState_enhancedMetricsCollection() {
testGenerateMetricsState(true);
@@ -987,7 +1062,7 @@ public class TimeZoneDetectorStrategyImplTest {
String expectedDeviceTimeZoneId = "InitialZoneId";
Script script = new Script()
.initializeTimeZoneSetting(expectedDeviceTimeZoneId)
.initializeTimeZoneSetting(expectedDeviceTimeZoneId, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(expectedInternalConfig)
.resetConfigurationTracking();
@@ -1113,11 +1188,16 @@ public class TimeZoneDetectorStrategyImplTest {
static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment {
private final TestState<String> mTimeZoneId = new TestState<>();
private int mTimeZoneConfidence;
private final TestState<Integer> mTimeZoneConfidence = new TestState<>();
private ConfigurationInternal mConfigurationInternal;
private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
private ConfigurationChangeListener mConfigurationInternalChangeListener;
FakeEnvironment() {
// Ensure the fake environment starts with the defaults a fresh device would.
initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW);
}
void initializeConfig(ConfigurationInternal configurationInternal) {
mConfigurationInternal = configurationInternal;
}
@@ -1126,8 +1206,9 @@ public class TimeZoneDetectorStrategyImplTest {
mElapsedRealtimeMillis = elapsedRealtimeMillis;
}
void initializeTimeZoneSetting(String zoneId) {
void initializeTimeZoneSetting(String zoneId, @TimeZoneConfidence int timeZoneConfidence) {
mTimeZoneId.init(zoneId);
mTimeZoneConfidence.init(timeZoneConfidence);
}
void incrementClock() {
@@ -1151,14 +1232,14 @@ public class TimeZoneDetectorStrategyImplTest {
@Override
public int getDeviceTimeZoneConfidence() {
return mTimeZoneConfidence;
return mTimeZoneConfidence.getLatest();
}
@Override
public void setDeviceTimeZoneAndConfidence(
String zoneId, @TimeZoneConfidence int confidence, String logInfo) {
mTimeZoneId.set(zoneId);
mTimeZoneConfidence = confidence;
mTimeZoneConfidence.set(confidence);
}
void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
@@ -1168,17 +1249,22 @@ public class TimeZoneDetectorStrategyImplTest {
void assertTimeZoneNotChanged() {
mTimeZoneId.assertHasNotBeenSet();
mTimeZoneConfidence.assertHasNotBeenSet();
}
void assertTimeZoneChangedTo(String timeZoneId, @TimeZoneConfidence int confidence) {
mTimeZoneId.assertHasBeenSet();
mTimeZoneId.assertChangeCount(1);
mTimeZoneId.assertLatestEquals(timeZoneId);
assertEquals(confidence, mTimeZoneConfidence);
mTimeZoneConfidence.assertHasBeenSet();
mTimeZoneConfidence.assertChangeCount(1);
mTimeZoneConfidence.assertLatestEquals(confidence);
}
void commitAllChanges() {
mTimeZoneId.commitLatest();
mTimeZoneConfidence.commitLatest();
}
@Override
@@ -1204,8 +1290,9 @@ public class TimeZoneDetectorStrategyImplTest {
*/
private class Script {
Script initializeTimeZoneSetting(String zoneId) {
mFakeEnvironment.initializeTimeZoneSetting(zoneId);
Script initializeTimeZoneSetting(
String zoneId, @TimeZoneConfidence int timeZoneConfidence) {
mFakeEnvironment.initializeTimeZoneSetting(zoneId, timeZoneConfidence);
return this;
}
@@ -1304,25 +1391,20 @@ public class TimeZoneDetectorStrategyImplTest {
}
Script verifyTimeZoneChangedAndReset(ManualTimeZoneSuggestion suggestion) {
mFakeEnvironment.assertTimeZoneChangedTo(
suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
mFakeEnvironment.commitAllChanges();
verifyTimeZoneChangedAndReset(suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
return this;
}
Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) {
mFakeEnvironment.assertTimeZoneChangedTo(
suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
mFakeEnvironment.commitAllChanges();
verifyTimeZoneChangedAndReset(suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
return this;
}
Script verifyTimeZoneChangedAndReset(GeolocationTimeZoneSuggestion suggestion) {
assertEquals("Only use this method with unambiguous geo suggestions",
1, suggestion.getZoneIds().size());
mFakeEnvironment.assertTimeZoneChangedTo(
verifyTimeZoneChangedAndReset(
suggestion.getZoneIds().get(0), TIME_ZONE_CONFIDENCE_HIGH);
mFakeEnvironment.commitAllChanges();
return this;
}