diff options
37 files changed, 2883 insertions, 679 deletions
diff --git a/core/java/android/app/time/DetectorStatusTypes.java b/core/java/android/app/time/DetectorStatusTypes.java new file mode 100644 index 000000000000..3643fc9a7d86 --- /dev/null +++ b/core/java/android/app/time/DetectorStatusTypes.java @@ -0,0 +1,227 @@ +/* + * 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.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.TextUtils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A set of constants that can relate to time or time zone detector status. + * + * <ul> + * <li>Detector status - the status of the overall detector.</li> + * <li>Detection algorithm status - the status of an algorithm that a detector can use. + * Each detector is expected to have one or more known algorithms to detect its chosen property, + * e.g. for time zone devices can have a "location" detection algorithm, where the device's + * location is used to detect the time zone.</li> + * </ul> + * + * @hide + */ +public final class DetectorStatusTypes { + + /** A status code for a detector. */ + @IntDef(prefix = "DETECTOR_STATUS_", value = { + DETECTOR_STATUS_UNKNOWN, + DETECTOR_STATUS_NOT_SUPPORTED, + DETECTOR_STATUS_NOT_RUNNING, + DETECTOR_STATUS_RUNNING, + }) + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.SOURCE) + public @interface DetectorStatus {} + + /** + * The detector status is unknown. Expected only for use as a placeholder before the actual + * status is known. + */ + public static final @DetectorStatus int DETECTOR_STATUS_UNKNOWN = 0; + + /** The detector is not supported on this device. */ + public static final @DetectorStatus int DETECTOR_STATUS_NOT_SUPPORTED = 1; + + /** The detector is supported but is not running. */ + public static final @DetectorStatus int DETECTOR_STATUS_NOT_RUNNING = 2; + + /** The detector is supported and is running. */ + public static final @DetectorStatus int DETECTOR_STATUS_RUNNING = 3; + + private DetectorStatusTypes() {} + + /** + * A status code for a detection algorithm. + */ + @IntDef(prefix = "DETECTION_ALGORITHM_STATUS_", value = { + DETECTION_ALGORITHM_STATUS_UNKNOWN, + DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED, + DETECTION_ALGORITHM_STATUS_NOT_RUNNING, + DETECTION_ALGORITHM_STATUS_RUNNING, + }) + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.SOURCE) + public @interface DetectionAlgorithmStatus {} + + /** + * The detection algorithm status is unknown. Expected only for use as a placeholder before the + * actual status is known. + */ + public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_UNKNOWN = 0; + + /** The detection algorithm is not supported on this device. */ + public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED = 1; + + /** The detection algorithm supported but is not running. */ + public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_NOT_RUNNING = 2; + + /** The detection algorithm supported and is running. */ + public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_RUNNING = 3; + + /** + * Validates the supplied value is one of the known {@code DETECTOR_STATUS_} constants and + * returns it if it is valid. {@link #DETECTOR_STATUS_UNKNOWN} is considered valid. + * + * @throws IllegalArgumentException if the value is not recognized + */ + public static @DetectorStatus int requireValidDetectorStatus( + @DetectorStatus int detectorStatus) { + if (detectorStatus < DETECTOR_STATUS_UNKNOWN || detectorStatus > DETECTOR_STATUS_RUNNING) { + throw new IllegalArgumentException("Invalid detector status: " + detectorStatus); + } + return detectorStatus; + } + + /** + * Returns a string for each {@code DETECTOR_STATUS_} constant. See also + * {@link #detectorStatusFromString(String)}. + * + * @throws IllegalArgumentException if the value is not recognized + */ + @NonNull + public static String detectorStatusToString(@DetectorStatus int detectorStatus) { + switch (detectorStatus) { + case DETECTOR_STATUS_UNKNOWN: + return "UNKNOWN"; + case DETECTOR_STATUS_NOT_SUPPORTED: + return "NOT_SUPPORTED"; + case DETECTOR_STATUS_NOT_RUNNING: + return "NOT_RUNNING"; + case DETECTOR_STATUS_RUNNING: + return "RUNNING"; + default: + throw new IllegalArgumentException("Unknown status: " + detectorStatus); + } + } + + /** + * Returns {@code DETECTOR_STATUS_} constant value from a string. See also + * {@link #detectorStatusToString(int)}. + * + * @throws IllegalArgumentException if the value is not recognized or is invalid + */ + public static @DetectorStatus int detectorStatusFromString( + @Nullable String detectorStatusString) { + if (TextUtils.isEmpty(detectorStatusString)) { + throw new IllegalArgumentException("Empty status: " + detectorStatusString); + } + + switch (detectorStatusString) { + case "UNKNOWN": + return DETECTOR_STATUS_UNKNOWN; + case "NOT_SUPPORTED": + return DETECTOR_STATUS_NOT_SUPPORTED; + case "NOT_RUNNING": + return DETECTOR_STATUS_NOT_RUNNING; + case "RUNNING": + return DETECTOR_STATUS_RUNNING; + default: + throw new IllegalArgumentException("Unknown status: " + detectorStatusString); + } + } + + /** + * Validates the supplied value is one of the known {@code DETECTION_ALGORITHM_} constants and + * returns it if it is valid. {@link #DETECTION_ALGORITHM_STATUS_UNKNOWN} is considered valid. + * + * @throws IllegalArgumentException if the value is not recognized + */ + public static @DetectionAlgorithmStatus int requireValidDetectionAlgorithmStatus( + @DetectionAlgorithmStatus int detectionAlgorithmStatus) { + if (detectionAlgorithmStatus < DETECTION_ALGORITHM_STATUS_UNKNOWN + || detectionAlgorithmStatus > DETECTION_ALGORITHM_STATUS_RUNNING) { + throw new IllegalArgumentException( + "Invalid detection algorithm: " + detectionAlgorithmStatus); + } + return detectionAlgorithmStatus; + } + + /** + * Returns a string for each {@code DETECTION_ALGORITHM_} constant. See also + * {@link #detectionAlgorithmStatusFromString(String)} + * + * @throws IllegalArgumentException if the value is not recognized + */ + @NonNull + public static String detectionAlgorithmStatusToString( + @DetectionAlgorithmStatus int detectorAlgorithmStatus) { + switch (detectorAlgorithmStatus) { + case DETECTION_ALGORITHM_STATUS_UNKNOWN: + return "UNKNOWN"; + case DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED: + return "NOT_SUPPORTED"; + case DETECTION_ALGORITHM_STATUS_NOT_RUNNING: + return "NOT_RUNNING"; + case DETECTION_ALGORITHM_STATUS_RUNNING: + return "RUNNING"; + default: + throw new IllegalArgumentException("Unknown status: " + detectorAlgorithmStatus); + } + } + + /** + * Returns {@code DETECTION_ALGORITHM_} constant value from a string. See also + * {@link #detectionAlgorithmStatusToString(int)} (String)} + * + * @throws IllegalArgumentException if the value is not recognized or is invalid + */ + public static @DetectionAlgorithmStatus int detectionAlgorithmStatusFromString( + @Nullable String detectorAlgorithmStatusString) { + + if (TextUtils.isEmpty(detectorAlgorithmStatusString)) { + throw new IllegalArgumentException("Empty status: " + detectorAlgorithmStatusString); + } + + switch (detectorAlgorithmStatusString) { + case "UNKNOWN": + return DETECTION_ALGORITHM_STATUS_UNKNOWN; + case "NOT_SUPPORTED": + return DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED; + case "NOT_RUNNING": + return DETECTION_ALGORITHM_STATUS_NOT_RUNNING; + case "RUNNING": + return DETECTION_ALGORITHM_STATUS_RUNNING; + default: + throw new IllegalArgumentException( + "Unknown status: " + detectorAlgorithmStatusString); + } + } +} diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl new file mode 100644 index 000000000000..7184b123af1c --- /dev/null +++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl @@ -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 LocationTimeZoneAlgorithmStatus; diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java new file mode 100644 index 000000000000..710b8c40cefe --- /dev/null +++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java @@ -0,0 +1,363 @@ +/* + * 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 static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN; +import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusFromString; +import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString; +import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus; +import android.os.Parcel; +import android.os.Parcelable; +import android.service.timezone.TimeZoneProviderStatus; +import android.text.TextUtils; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Information about the status of the location-based time zone detection algorithm. + * + * @hide + */ +public final class LocationTimeZoneAlgorithmStatus implements Parcelable { + + /** + * An enum that describes a location time zone provider's status. + * + * @hide + */ + @IntDef(prefix = "PROVIDER_STATUS_", value = { + PROVIDER_STATUS_NOT_PRESENT, + PROVIDER_STATUS_NOT_READY, + PROVIDER_STATUS_IS_CERTAIN, + PROVIDER_STATUS_IS_UNCERTAIN, + }) + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.SOURCE) + public @interface ProviderStatus {} + + /** + * Indicates a provider is not present because it has not been configured, the configuration + * is bad, or the provider has reported a permanent failure. + */ + public static final @ProviderStatus int PROVIDER_STATUS_NOT_PRESENT = 1; + + /** + * Indicates a provider has not reported it is certain or uncertain. This may be because it has + * just started running, or it has been stopped. + */ + public static final @ProviderStatus int PROVIDER_STATUS_NOT_READY = 2; + + /** + * Indicates a provider last reported it is certain. + */ + public static final @ProviderStatus int PROVIDER_STATUS_IS_CERTAIN = 3; + + /** + * Indicates a provider last reported it is uncertain. + */ + public static final @ProviderStatus int PROVIDER_STATUS_IS_UNCERTAIN = 4; + + /** + * An instance that provides no information about algorithm status because the algorithm has not + * yet reported. Effectively a "null" status placeholder. + */ + @NonNull + public static final LocationTimeZoneAlgorithmStatus UNKNOWN = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_UNKNOWN, + PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null); + + private final @DetectionAlgorithmStatus int mStatus; + private final @ProviderStatus int mPrimaryProviderStatus; + // May be populated when mPrimaryProviderReportedStatus == PROVIDER_STATUS_IS_CERTAIN + // or PROVIDER_STATUS_IS_UNCERTAIN + @Nullable private final TimeZoneProviderStatus mPrimaryProviderReportedStatus; + + private final @ProviderStatus int mSecondaryProviderStatus; + // May be populated when mSecondaryProviderReportedStatus == PROVIDER_STATUS_IS_CERTAIN + // or PROVIDER_STATUS_IS_UNCERTAIN + @Nullable private final TimeZoneProviderStatus mSecondaryProviderReportedStatus; + + public LocationTimeZoneAlgorithmStatus( + @DetectionAlgorithmStatus int status, + @ProviderStatus int primaryProviderStatus, + @Nullable TimeZoneProviderStatus primaryProviderReportedStatus, + @ProviderStatus int secondaryProviderStatus, + @Nullable TimeZoneProviderStatus secondaryProviderReportedStatus) { + + mStatus = requireValidDetectionAlgorithmStatus(status); + mPrimaryProviderStatus = requireValidProviderStatus(primaryProviderStatus); + mPrimaryProviderReportedStatus = primaryProviderReportedStatus; + mSecondaryProviderStatus = requireValidProviderStatus(secondaryProviderStatus); + mSecondaryProviderReportedStatus = secondaryProviderReportedStatus; + + boolean primaryProviderHasReported = hasProviderReported(primaryProviderStatus); + boolean primaryProviderReportedStatusPresent = primaryProviderReportedStatus != null; + if (!primaryProviderHasReported && primaryProviderReportedStatusPresent) { + throw new IllegalArgumentException( + "primaryProviderReportedStatus=" + primaryProviderReportedStatus + + ", primaryProviderStatus=" + + providerStatusToString(primaryProviderStatus)); + } + + boolean secondaryProviderHasReported = hasProviderReported(secondaryProviderStatus); + boolean secondaryProviderReportedStatusPresent = secondaryProviderReportedStatus != null; + if (!secondaryProviderHasReported && secondaryProviderReportedStatusPresent) { + throw new IllegalArgumentException( + "secondaryProviderReportedStatus=" + secondaryProviderReportedStatus + + ", secondaryProviderStatus=" + + providerStatusToString(secondaryProviderStatus)); + } + + // If the algorithm isn't running, providers can't report. + if (status != DETECTION_ALGORITHM_STATUS_RUNNING + && (primaryProviderHasReported || secondaryProviderHasReported)) { + throw new IllegalArgumentException( + "algorithmStatus=" + detectionAlgorithmStatusToString(status) + + ", primaryProviderReportedStatus=" + primaryProviderReportedStatus + + ", secondaryProviderReportedStatus=" + + secondaryProviderReportedStatus); + } + } + + /** + * Returns the status value of the detection algorithm. + */ + public @DetectionAlgorithmStatus int getStatus() { + return mStatus; + } + + /** + * Returns the status of the primary location time zone provider as categorized by the detection + * algorithm. + */ + public @ProviderStatus int getPrimaryProviderStatus() { + return mPrimaryProviderStatus; + } + + /** + * Returns the status of the primary location time zone provider as reported by the provider + * itself. Can be {@code null} when the provider hasn't reported, or omitted when it has. + */ + @Nullable + public TimeZoneProviderStatus getPrimaryProviderReportedStatus() { + return mPrimaryProviderReportedStatus; + } + + /** + * Returns the status of the secondary location time zone provider as categorized by the + * detection algorithm. + */ + public @ProviderStatus int getSecondaryProviderStatus() { + return mSecondaryProviderStatus; + } + + /** + * Returns the status of the secondary location time zone provider as reported by the provider + * itself. Can be {@code null} when the provider hasn't reported, or omitted when it has. + */ + @Nullable + public TimeZoneProviderStatus getSecondaryProviderReportedStatus() { + return mSecondaryProviderReportedStatus; + } + + @Override + public String toString() { + return "LocationTimeZoneAlgorithmStatus{" + + "mAlgorithmStatus=" + detectionAlgorithmStatusToString(mStatus) + + ", mPrimaryProviderStatus=" + providerStatusToString(mPrimaryProviderStatus) + + ", mPrimaryProviderReportedStatus=" + mPrimaryProviderReportedStatus + + ", mSecondaryProviderStatus=" + providerStatusToString(mSecondaryProviderStatus) + + ", mSecondaryProviderReportedStatus=" + mSecondaryProviderReportedStatus + + '}'; + } + + /** + * Parses a {@link LocationTimeZoneAlgorithmStatus} from a toString() string for manual + * command-line testing. + */ + @NonNull + public static LocationTimeZoneAlgorithmStatus parseCommandlineArg(@NonNull String arg) { + // Note: "}" has to be escaped on Android with "\\}" because the regexp library is not based + // on OpenJDK code. + Pattern pattern = Pattern.compile("LocationTimeZoneAlgorithmStatus\\{" + + "mAlgorithmStatus=(.+)" + + ", mPrimaryProviderStatus=([^,]+)" + + ", mPrimaryProviderReportedStatus=(null|TimeZoneProviderStatus\\{[^}]+\\})" + + ", mSecondaryProviderStatus=([^,]+)" + + ", mSecondaryProviderReportedStatus=(null|TimeZoneProviderStatus\\{[^}]+\\})" + + "\\}" + ); + Matcher matcher = pattern.matcher(arg); + if (!matcher.matches()) { + throw new IllegalArgumentException("Unable to parse algorithm status arg: " + arg); + } + @DetectionAlgorithmStatus int algorithmStatus = + detectionAlgorithmStatusFromString(matcher.group(1)); + @ProviderStatus int primaryProviderStatus = providerStatusFromString(matcher.group(2)); + TimeZoneProviderStatus primaryProviderReportedStatus = + parseTimeZoneProviderStatusOrNull(matcher.group(3)); + @ProviderStatus int secondaryProviderStatus = providerStatusFromString(matcher.group(4)); + TimeZoneProviderStatus secondaryProviderReportedStatus = + parseTimeZoneProviderStatusOrNull(matcher.group(5)); + return new LocationTimeZoneAlgorithmStatus( + algorithmStatus, primaryProviderStatus, primaryProviderReportedStatus, + secondaryProviderStatus, secondaryProviderReportedStatus); + } + + @Nullable + private static TimeZoneProviderStatus parseTimeZoneProviderStatusOrNull( + String providerReportedStatusString) { + TimeZoneProviderStatus providerReportedStatus; + if ("null".equals(providerReportedStatusString)) { + providerReportedStatus = null; + } else { + providerReportedStatus = + TimeZoneProviderStatus.parseProviderStatus(providerReportedStatusString); + } + return providerReportedStatus; + } + + @NonNull + public static final Creator<LocationTimeZoneAlgorithmStatus> CREATOR = new Creator<>() { + @Override + public LocationTimeZoneAlgorithmStatus createFromParcel(Parcel in) { + @DetectionAlgorithmStatus int algorithmStatus = in.readInt(); + @ProviderStatus int primaryProviderStatus = in.readInt(); + TimeZoneProviderStatus primaryProviderReportedStatus = + in.readParcelable(getClass().getClassLoader(), TimeZoneProviderStatus.class); + @ProviderStatus int secondaryProviderStatus = in.readInt(); + TimeZoneProviderStatus secondaryProviderReportedStatus = + in.readParcelable(getClass().getClassLoader(), TimeZoneProviderStatus.class); + return new LocationTimeZoneAlgorithmStatus( + algorithmStatus, primaryProviderStatus, primaryProviderReportedStatus, + secondaryProviderStatus, secondaryProviderReportedStatus); + } + + @Override + public LocationTimeZoneAlgorithmStatus[] newArray(int size) { + return new LocationTimeZoneAlgorithmStatus[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mStatus); + parcel.writeInt(mPrimaryProviderStatus); + parcel.writeParcelable(mPrimaryProviderReportedStatus, flags); + parcel.writeInt(mSecondaryProviderStatus); + parcel.writeParcelable(mSecondaryProviderReportedStatus, flags); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LocationTimeZoneAlgorithmStatus that = (LocationTimeZoneAlgorithmStatus) o; + return mStatus == that.mStatus + && mPrimaryProviderStatus == that.mPrimaryProviderStatus + && Objects.equals( + mPrimaryProviderReportedStatus, that.mPrimaryProviderReportedStatus) + && mSecondaryProviderStatus == that.mSecondaryProviderStatus + && Objects.equals( + mSecondaryProviderReportedStatus, that.mSecondaryProviderReportedStatus); + } + + @Override + public int hashCode() { + return Objects.hash(mStatus, + mPrimaryProviderStatus, mPrimaryProviderReportedStatus, + mSecondaryProviderStatus, mSecondaryProviderReportedStatus); + } + + /** @hide */ + @VisibleForTesting + @NonNull + public static String providerStatusToString(@ProviderStatus int providerStatus) { + switch (providerStatus) { + case PROVIDER_STATUS_NOT_PRESENT: + return "NOT_PRESENT"; + case PROVIDER_STATUS_NOT_READY: + return "NOT_READY"; + case PROVIDER_STATUS_IS_CERTAIN: + return "IS_CERTAIN"; + case PROVIDER_STATUS_IS_UNCERTAIN: + return "IS_UNCERTAIN"; + default: + throw new IllegalArgumentException("Unknown status: " + providerStatus); + } + } + + /** @hide */ + @VisibleForTesting public static @ProviderStatus int providerStatusFromString( + @Nullable String providerStatusString) { + if (TextUtils.isEmpty(providerStatusString)) { + throw new IllegalArgumentException("Empty status: " + providerStatusString); + } + + switch (providerStatusString) { + case "NOT_PRESENT": + return PROVIDER_STATUS_NOT_PRESENT; + case "NOT_READY": + return PROVIDER_STATUS_NOT_READY; + case "IS_CERTAIN": + return PROVIDER_STATUS_IS_CERTAIN; + case "IS_UNCERTAIN": + return PROVIDER_STATUS_IS_UNCERTAIN; + default: + throw new IllegalArgumentException("Unknown status: " + providerStatusString); + } + } + + private static boolean hasProviderReported(@ProviderStatus int providerStatus) { + return providerStatus == PROVIDER_STATUS_IS_CERTAIN + || providerStatus == PROVIDER_STATUS_IS_UNCERTAIN; + } + + /** @hide */ + @VisibleForTesting public static @ProviderStatus int requireValidProviderStatus( + @ProviderStatus int providerStatus) { + if (providerStatus < PROVIDER_STATUS_NOT_PRESENT + || providerStatus > PROVIDER_STATUS_IS_UNCERTAIN) { + throw new IllegalArgumentException( + "Invalid provider status: " + providerStatus); + } + return providerStatus; + } +} diff --git a/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl new file mode 100644 index 000000000000..0eb5b63b7ffb --- /dev/null +++ b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl @@ -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 TelephonyTimeZoneAlgorithmStatus; diff --git a/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java new file mode 100644 index 000000000000..95240c00fa3f --- /dev/null +++ b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java @@ -0,0 +1,96 @@ +/* + * 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 static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString; +import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus; + +import android.annotation.NonNull; +import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Information about the status of the telephony-based time zone detection algorithm. + * + * @hide + */ +public final class TelephonyTimeZoneAlgorithmStatus implements Parcelable { + + private final @DetectionAlgorithmStatus int mAlgorithmStatus; + + public TelephonyTimeZoneAlgorithmStatus(@DetectionAlgorithmStatus int algorithmStatus) { + mAlgorithmStatus = requireValidDetectionAlgorithmStatus(algorithmStatus); + } + + /** + * Returns the status of the detection algorithm. + */ + public @DetectionAlgorithmStatus int getAlgorithmStatus() { + return mAlgorithmStatus; + } + + @Override + public String toString() { + return "TelephonyTimeZoneAlgorithmStatus{" + + "mAlgorithmStatus=" + detectionAlgorithmStatusToString(mAlgorithmStatus) + + '}'; + } + + @NonNull + public static final Creator<TelephonyTimeZoneAlgorithmStatus> CREATOR = new Creator<>() { + @Override + public TelephonyTimeZoneAlgorithmStatus createFromParcel(Parcel in) { + @DetectionAlgorithmStatus int algorithmStatus = in.readInt(); + return new TelephonyTimeZoneAlgorithmStatus(algorithmStatus); + } + + @Override + public TelephonyTimeZoneAlgorithmStatus[] newArray(int size) { + return new TelephonyTimeZoneAlgorithmStatus[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mAlgorithmStatus); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TelephonyTimeZoneAlgorithmStatus that = (TelephonyTimeZoneAlgorithmStatus) o; + return mAlgorithmStatus == that.mAlgorithmStatus; + } + + @Override + public int hashCode() { + return Objects.hash(mAlgorithmStatus); + } +} diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java index cd91b0431b28..4684c6ad811c 100644 --- a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java +++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java @@ -23,27 +23,40 @@ import android.os.Parcel; import android.os.Parcelable; import java.util.Objects; +import java.util.concurrent.Executor; /** - * A pair containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}. + * An object containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}. * * @hide */ @SystemApi public final class TimeZoneCapabilitiesAndConfig implements Parcelable { - public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR = - new Creator<TimeZoneCapabilitiesAndConfig>() { - public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) { - return TimeZoneCapabilitiesAndConfig.createFromParcel(in); - } - - public TimeZoneCapabilitiesAndConfig[] newArray(int size) { - return new TimeZoneCapabilitiesAndConfig[size]; - } - }; + public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR = new Creator<>() { + public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) { + return TimeZoneCapabilitiesAndConfig.createFromParcel(in); + } + public TimeZoneCapabilitiesAndConfig[] newArray(int size) { + return new TimeZoneCapabilitiesAndConfig[size]; + } + }; + /** + * The time zone detector status. + * + * Implementation note for future platform engineers: This field is only needed by SettingsUI + * initially and so it has not been added to the SDK API. {@link TimeZoneDetectorStatus} + * contains details about the internals of the time zone detector so thought should be given to + * abstraction / exposing a lightweight version if something unbundled needs access to detector + * details. Also, that could be good time to add separate APIs for bundled components, or add + * new APIs that return something more extensible and generic like a Bundle or a less + * constraining name. See also {@link + * TimeManager#addTimeZoneDetectorListener(Executor, TimeManager.TimeZoneDetectorListener)}, + * which notified of changes to any fields in this class, including the detector status. + */ + @NonNull private final TimeZoneDetectorStatus mDetectorStatus; @NonNull private final TimeZoneCapabilities mCapabilities; @NonNull private final TimeZoneConfiguration mConfiguration; @@ -53,26 +66,41 @@ public final class TimeZoneCapabilitiesAndConfig implements Parcelable { * @hide */ public TimeZoneCapabilitiesAndConfig( + @NonNull TimeZoneDetectorStatus detectorStatus, @NonNull TimeZoneCapabilities capabilities, @NonNull TimeZoneConfiguration configuration) { - this.mCapabilities = Objects.requireNonNull(capabilities); - this.mConfiguration = Objects.requireNonNull(configuration); + mDetectorStatus = Objects.requireNonNull(detectorStatus); + mCapabilities = Objects.requireNonNull(capabilities); + mConfiguration = Objects.requireNonNull(configuration); } @NonNull private static TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) { - TimeZoneCapabilities capabilities = in.readParcelable(null, android.app.time.TimeZoneCapabilities.class); - TimeZoneConfiguration configuration = in.readParcelable(null, android.app.time.TimeZoneConfiguration.class); - return new TimeZoneCapabilitiesAndConfig(capabilities, configuration); + TimeZoneDetectorStatus detectorStatus = + in.readParcelable(null, TimeZoneDetectorStatus.class); + TimeZoneCapabilities capabilities = in.readParcelable(null, TimeZoneCapabilities.class); + TimeZoneConfiguration configuration = in.readParcelable(null, TimeZoneConfiguration.class); + return new TimeZoneCapabilitiesAndConfig(detectorStatus, capabilities, configuration); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mDetectorStatus, flags); dest.writeParcelable(mCapabilities, flags); dest.writeParcelable(mConfiguration, flags); } /** + * Returns the time zone detector's status. + * + * @hide + */ + @NonNull + public TimeZoneDetectorStatus getDetectorStatus() { + return mDetectorStatus; + } + + /** * Returns the user's time zone behavior capabilities. */ @NonNull @@ -102,7 +130,8 @@ public final class TimeZoneCapabilitiesAndConfig implements Parcelable { return false; } TimeZoneCapabilitiesAndConfig that = (TimeZoneCapabilitiesAndConfig) o; - return mCapabilities.equals(that.mCapabilities) + return mDetectorStatus.equals(that.mDetectorStatus) + && mCapabilities.equals(that.mCapabilities) && mConfiguration.equals(that.mConfiguration); } @@ -114,7 +143,8 @@ public final class TimeZoneCapabilitiesAndConfig implements Parcelable { @Override public String toString() { return "TimeZoneCapabilitiesAndConfig{" - + "mCapabilities=" + mCapabilities + + "mDetectorStatus=" + mDetectorStatus + + ", mCapabilities=" + mCapabilities + ", mConfiguration=" + mConfiguration + '}'; } diff --git a/core/java/android/app/time/TimeZoneDetectorStatus.aidl b/core/java/android/app/time/TimeZoneDetectorStatus.aidl new file mode 100644 index 000000000000..32204df6d698 --- /dev/null +++ b/core/java/android/app/time/TimeZoneDetectorStatus.aidl @@ -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 TimeZoneDetectorStatus; diff --git a/core/java/android/app/time/TimeZoneDetectorStatus.java b/core/java/android/app/time/TimeZoneDetectorStatus.java new file mode 100644 index 000000000000..16374639b77c --- /dev/null +++ b/core/java/android/app/time/TimeZoneDetectorStatus.java @@ -0,0 +1,124 @@ +/* + * 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 static android.app.time.DetectorStatusTypes.DetectorStatus; +import static android.app.time.DetectorStatusTypes.requireValidDetectorStatus; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Information about the status of the automatic time zone detector. Used by SettingsUI to display + * status information to the user. + * + * @hide + */ +public final class TimeZoneDetectorStatus implements Parcelable { + + private final @DetectorStatus int mDetectorStatus; + @NonNull private final TelephonyTimeZoneAlgorithmStatus mTelephonyTimeZoneAlgorithmStatus; + @NonNull private final LocationTimeZoneAlgorithmStatus mLocationTimeZoneAlgorithmStatus; + + public TimeZoneDetectorStatus( + @DetectorStatus int detectorStatus, + @NonNull TelephonyTimeZoneAlgorithmStatus telephonyTimeZoneAlgorithmStatus, + @NonNull LocationTimeZoneAlgorithmStatus locationTimeZoneAlgorithmStatus) { + mDetectorStatus = requireValidDetectorStatus(detectorStatus); + mTelephonyTimeZoneAlgorithmStatus = + Objects.requireNonNull(telephonyTimeZoneAlgorithmStatus); + mLocationTimeZoneAlgorithmStatus = Objects.requireNonNull(locationTimeZoneAlgorithmStatus); + } + + public @DetectorStatus int getDetectorStatus() { + return mDetectorStatus; + } + + @NonNull + public TelephonyTimeZoneAlgorithmStatus getTelephonyTimeZoneAlgorithmStatus() { + return mTelephonyTimeZoneAlgorithmStatus; + } + + @NonNull + public LocationTimeZoneAlgorithmStatus getLocationTimeZoneAlgorithmStatus() { + return mLocationTimeZoneAlgorithmStatus; + } + + @Override + public String toString() { + return "TimeZoneDetectorStatus{" + + "mDetectorStatus=" + DetectorStatusTypes.detectorStatusToString(mDetectorStatus) + + ", mTelephonyTimeZoneAlgorithmStatus=" + mTelephonyTimeZoneAlgorithmStatus + + ", mLocationTimeZoneAlgorithmStatus=" + mLocationTimeZoneAlgorithmStatus + + '}'; + } + + public static final @NonNull Creator<TimeZoneDetectorStatus> CREATOR = new Creator<>() { + @Override + public TimeZoneDetectorStatus createFromParcel(Parcel in) { + @DetectorStatus int detectorStatus = in.readInt(); + TelephonyTimeZoneAlgorithmStatus telephonyTimeZoneAlgorithmStatus = + in.readParcelable(getClass().getClassLoader(), + TelephonyTimeZoneAlgorithmStatus.class); + LocationTimeZoneAlgorithmStatus locationTimeZoneAlgorithmStatus = + in.readParcelable(getClass().getClassLoader(), + LocationTimeZoneAlgorithmStatus.class); + return new TimeZoneDetectorStatus(detectorStatus, + telephonyTimeZoneAlgorithmStatus, locationTimeZoneAlgorithmStatus); + } + + @Override + public TimeZoneDetectorStatus[] newArray(int size) { + return new TimeZoneDetectorStatus[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mDetectorStatus); + parcel.writeParcelable(mTelephonyTimeZoneAlgorithmStatus, flags); + parcel.writeParcelable(mLocationTimeZoneAlgorithmStatus, flags); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TimeZoneDetectorStatus that = (TimeZoneDetectorStatus) o; + return mDetectorStatus == that.mDetectorStatus + && mTelephonyTimeZoneAlgorithmStatus.equals(that.mTelephonyTimeZoneAlgorithmStatus) + && mLocationTimeZoneAlgorithmStatus.equals(that.mLocationTimeZoneAlgorithmStatus); + } + + @Override + public int hashCode() { + return Objects.hash(mDetectorStatus, mTelephonyTimeZoneAlgorithmStatus, + mLocationTimeZoneAlgorithmStatus); + } +} diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java index 0e9e28be8818..f357fb243fe1 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -81,11 +81,11 @@ public interface TimeZoneDetector { String SHELL_COMMAND_SET_GEO_DETECTION_ENABLED = "set_geo_detection_enabled"; /** - * A shell command that injects a geolocation time zone suggestion (as if from the + * A shell command that injects a location algorithm event (as if from the * location_time_zone_manager). * @hide */ - String SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE = "suggest_geo_location_time_zone"; + String SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT = "handle_location_algorithm_event"; /** * A shell command that injects a manual time zone suggestion (as if from the SettingsUI or diff --git a/core/proto/android/app/location_time_zone_manager.proto b/core/proto/android/app/location_time_zone_manager.proto index 5fdcfdf35a37..7037a6c4f68a 100644 --- a/core/proto/android/app/location_time_zone_manager.proto +++ b/core/proto/android/app/location_time_zone_manager.proto @@ -40,7 +40,7 @@ enum ControllerStateEnum { message LocationTimeZoneManagerServiceStateProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; - optional GeolocationTimeZoneSuggestionProto last_suggestion = 1; + optional LocationTimeZoneProviderEventProto last_event = 1; repeated TimeZoneProviderStateProto primary_provider_states = 2; repeated TimeZoneProviderStateProto secondary_provider_states = 3; repeated ControllerStateEnum controller_states = 4; diff --git a/core/proto/android/app/time_zone_detector.proto b/core/proto/android/app/time_zone_detector.proto index b52aa828bef9..cd4a36fafef0 100644 --- a/core/proto/android/app/time_zone_detector.proto +++ b/core/proto/android/app/time_zone_detector.proto @@ -22,13 +22,38 @@ import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; option java_outer_classname = "TimeZoneDetectorProto"; -// Represents a GeolocationTimeZoneSuggestion that can be / has been passed to the time zone +// Represents a LocationTimeZoneProviderEvent that can be / has been passed to the time zone // detector. +message LocationTimeZoneProviderEventProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional GeolocationTimeZoneSuggestionProto suggestion = 1; + repeated string debug_info = 2; + optional LocationTimeZoneAlgorithmStatusProto algorithm_status = 3; +} + +// Represents a LocationTimeZoneAlgorithmStatus that can be / has been passed to the time zone +// detector. +message LocationTimeZoneAlgorithmStatusProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional DetectionAlgorithmStatusEnum status = 1; +} + +// The state enum for detection algorithms. +enum DetectionAlgorithmStatusEnum { + DETECTION_ALGORITHM_STATUS_UNKNOWN = 0; + DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED = 1; + DETECTION_ALGORITHM_STATUS_NOT_RUNNING = 2; + DETECTION_ALGORITHM_STATUS_RUNNING = 3; +} + +// Represents a GeolocationTimeZoneSuggestion that can be contained in a +// LocationTimeZoneProviderEvent. message GeolocationTimeZoneSuggestionProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; repeated string zone_ids = 1; - repeated string debug_info = 2; } /* diff --git a/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java b/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java new file mode 100644 index 000000000000..f57ee43b76c8 --- /dev/null +++ b/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java @@ -0,0 +1,103 @@ +/* + * 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; + +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN; +import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_UNKNOWN; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus; +import android.app.time.DetectorStatusTypes.DetectorStatus; + +import org.junit.Test; + +public class DetectorStatusTypesTest { + + @Test + public void testRequireValidDetectionAlgorithmStatus() { + for (@DetectionAlgorithmStatus int status = DETECTION_ALGORITHM_STATUS_UNKNOWN; + status <= DETECTION_ALGORITHM_STATUS_RUNNING; status++) { + assertEquals(status, DetectorStatusTypes.requireValidDetectionAlgorithmStatus(status)); + } + + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.requireValidDetectionAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_UNKNOWN - 1)); + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.requireValidDetectionAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING + 1)); + } + + @Test + public void testFormatAndParseDetectionAlgorithmStatus() { + for (@DetectionAlgorithmStatus int status = DETECTION_ALGORITHM_STATUS_UNKNOWN; + status <= DETECTION_ALGORITHM_STATUS_RUNNING; status++) { + assertEquals(status, DetectorStatusTypes.detectionAlgorithmStatusFromString( + DetectorStatusTypes.detectionAlgorithmStatusToString(status))); + } + + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.detectorStatusToString( + DETECTION_ALGORITHM_STATUS_UNKNOWN - 1)); + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.detectorStatusToString( + DETECTION_ALGORITHM_STATUS_RUNNING + 1)); + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.detectorStatusFromString(null)); + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.detectorStatusFromString("")); + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.detectorStatusFromString("FOO")); + } + + @Test + public void testRequireValidDetectorStatus() { + for (@DetectorStatus int status = DETECTOR_STATUS_UNKNOWN; + status <= DETECTOR_STATUS_RUNNING; status++) { + assertEquals(status, DetectorStatusTypes.requireValidDetectorStatus(status)); + } + + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.requireValidDetectorStatus(DETECTOR_STATUS_UNKNOWN - 1)); + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.requireValidDetectorStatus(DETECTOR_STATUS_RUNNING + 1)); + } + + @Test + public void testFormatAndParseDetectorStatus() { + for (@DetectorStatus int status = DETECTOR_STATUS_UNKNOWN; + status <= DETECTOR_STATUS_RUNNING; status++) { + assertEquals(status, DetectorStatusTypes.detectorStatusFromString( + DetectorStatusTypes.detectorStatusToString(status))); + } + + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.detectorStatusToString(DETECTOR_STATUS_UNKNOWN - 1)); + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.detectorStatusToString(DETECTOR_STATUS_RUNNING + 1)); + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.detectorStatusFromString(null)); + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.detectorStatusFromString("")); + assertThrows(IllegalArgumentException.class, + () -> DetectorStatusTypes.detectorStatusFromString("FOO")); + } +} diff --git a/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java new file mode 100644 index 000000000000..a648a885aea2 --- /dev/null +++ b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java @@ -0,0 +1,210 @@ +/* + * 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; + +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY; +import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode; +import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK; +import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThrows; + +import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus; +import android.service.timezone.TimeZoneProviderStatus; + +import org.junit.Test; + +public class LocationTimeZoneAlgorithmStatusTest { + + private static final TimeZoneProviderStatus ARBITRARY_PROVIDER_RUNNING_STATUS = + new TimeZoneProviderStatus.Builder() + .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK) + .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK) + .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK) + .build(); + + @Test + public void testConstructorValidation() { + // Sample some invalid cases + + // There can't be a reported provider status if the algorithm isn't running. + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS, + PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS); + assertThrows(IllegalArgumentException.class, + () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING, + PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS, + PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS)); + + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS, + PROVIDER_STATUS_NOT_PRESENT, null); + assertThrows(IllegalArgumentException.class, + () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING, + PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS, + PROVIDER_STATUS_NOT_PRESENT, null)); + + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_NOT_PRESENT, null, + PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS); + assertThrows(IllegalArgumentException.class, + () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING, + PROVIDER_STATUS_NOT_PRESENT, null, + PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS)); + + // No reported provider status expected if the associated provider isn't ready / present. + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_NOT_PRESENT, null, + PROVIDER_STATUS_NOT_PRESENT, null); + assertThrows(IllegalArgumentException.class, + () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_NOT_PRESENT, ARBITRARY_PROVIDER_RUNNING_STATUS, + PROVIDER_STATUS_NOT_PRESENT, null)); + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_NOT_READY, null, + PROVIDER_STATUS_NOT_PRESENT, null); + assertThrows(IllegalArgumentException.class, + () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_NOT_READY, null, + PROVIDER_STATUS_NOT_PRESENT, ARBITRARY_PROVIDER_RUNNING_STATUS)); + } + + @Test + public void testEquals() { + LocationTimeZoneAlgorithmStatus one = new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS, + PROVIDER_STATUS_NOT_PRESENT, null); + assertEqualsAndHashCode(one, one); + + { + LocationTimeZoneAlgorithmStatus two = new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS, + PROVIDER_STATUS_NOT_PRESENT, null); + assertEqualsAndHashCode(one, two); + } + + { + LocationTimeZoneAlgorithmStatus three = new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING, + PROVIDER_STATUS_NOT_READY, null, + PROVIDER_STATUS_NOT_PRESENT, null); + assertNotEquals(one, three); + assertNotEquals(three, one); + } + } + + @Test + public void testParcelable() { + // Primary provider only. + { + LocationTimeZoneAlgorithmStatus locationAlgorithmStatus = + new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS, + PROVIDER_STATUS_NOT_PRESENT, null); + assertRoundTripParcelable(locationAlgorithmStatus); + } + + // Secondary provider only + { + LocationTimeZoneAlgorithmStatus locationAlgorithmStatus = + new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_NOT_PRESENT, null, + PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS); + assertRoundTripParcelable(locationAlgorithmStatus); + } + + // Algorithm not running. + { + LocationTimeZoneAlgorithmStatus locationAlgorithmStatus = + new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING, + PROVIDER_STATUS_NOT_PRESENT, null, + PROVIDER_STATUS_NOT_PRESENT, null); + assertRoundTripParcelable(locationAlgorithmStatus); + } + } + + @Test + public void testRequireValidProviderStatus() { + for (@ProviderStatus int status = PROVIDER_STATUS_NOT_PRESENT; + status <= PROVIDER_STATUS_IS_UNCERTAIN; status++) { + assertEquals(status, + LocationTimeZoneAlgorithmStatus.requireValidProviderStatus(status)); + } + + assertThrows(IllegalArgumentException.class, + () -> LocationTimeZoneAlgorithmStatus.requireValidProviderStatus( + PROVIDER_STATUS_NOT_PRESENT - 1)); + assertThrows(IllegalArgumentException.class, + () -> LocationTimeZoneAlgorithmStatus.requireValidProviderStatus( + PROVIDER_STATUS_IS_UNCERTAIN + 1)); + } + + @Test + public void testFormatAndParseProviderStatus() { + for (@ProviderStatus int status = PROVIDER_STATUS_NOT_PRESENT; + status <= PROVIDER_STATUS_IS_UNCERTAIN; status++) { + assertEquals(status, LocationTimeZoneAlgorithmStatus.providerStatusFromString( + LocationTimeZoneAlgorithmStatus.providerStatusToString(status))); + } + + assertThrows(IllegalArgumentException.class, + () -> LocationTimeZoneAlgorithmStatus.providerStatusToString( + PROVIDER_STATUS_NOT_PRESENT - 1)); + assertThrows(IllegalArgumentException.class, + () -> LocationTimeZoneAlgorithmStatus.providerStatusToString( + PROVIDER_STATUS_IS_UNCERTAIN + 1)); + assertThrows(IllegalArgumentException.class, + () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString(null)); + assertThrows(IllegalArgumentException.class, + () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString("")); + assertThrows(IllegalArgumentException.class, + () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString("FOO")); + } + + @Test + public void testParseCommandlineArg_noNullReportedStatuses() { + LocationTimeZoneAlgorithmStatus status = new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS, + PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS); + assertEquals(status, + LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString())); + } + + @Test + public void testParseCommandlineArg_withNullReportedStatuses() { + LocationTimeZoneAlgorithmStatus status = new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_CERTAIN, null, + PROVIDER_STATUS_IS_UNCERTAIN, null); + assertEquals(status, + LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString())); + } +} diff --git a/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java new file mode 100644 index 000000000000..b90c485bbbb6 --- /dev/null +++ b/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java @@ -0,0 +1,68 @@ +/* + * 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; + +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode; +import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable; + +import static org.junit.Assert.assertNotEquals; + +import org.junit.Test; + +public class TelephonyTimeZoneAlgorithmStatusTest { + + @Test + public void testEquals() { + TelephonyTimeZoneAlgorithmStatus one = new TelephonyTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING); + assertEqualsAndHashCode(one, one); + + { + TelephonyTimeZoneAlgorithmStatus two = new TelephonyTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING); + assertEqualsAndHashCode(one, two); + } + + { + TelephonyTimeZoneAlgorithmStatus three = new TelephonyTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING); + assertNotEquals(one, three); + assertNotEquals(three, one); + } + } + + @Test + public void testParcelable() { + // Algorithm running. + { + TelephonyTimeZoneAlgorithmStatus locationAlgorithmStatus = + new TelephonyTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING); + assertRoundTripParcelable(locationAlgorithmStatus); + } + + // Algorithm not running. + { + TelephonyTimeZoneAlgorithmStatus locationAlgorithmStatus = + new TelephonyTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING); + assertRoundTripParcelable(locationAlgorithmStatus); + } + } +} diff --git a/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java b/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java new file mode 100644 index 000000000000..dfff7ecdf989 --- /dev/null +++ b/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java @@ -0,0 +1,105 @@ +/* + * 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; + +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_NOT_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING; +import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode; +import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable; + +import static org.junit.Assert.assertNotEquals; + +import org.junit.Test; + +public class TimeZoneDetectorStatusTest { + + private static final TelephonyTimeZoneAlgorithmStatus ARBITRARY_TELEPHONY_ALGORITHM_STATUS = + new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING); + + private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_ALGORITHM_STATUS = + new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING, + LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null, + LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT, null); + + @Test + public void testEquals() { + TimeZoneDetectorStatus one = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING, + ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS); + assertEqualsAndHashCode(one, one); + + { + TimeZoneDetectorStatus two = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING, + ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS); + assertEqualsAndHashCode(one, two); + } + + { + TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING, + ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS); + assertNotEquals(one, three); + assertNotEquals(three, one); + } + + { + TelephonyTimeZoneAlgorithmStatus telephonyAlgorithmStatus = + new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING); + assertNotEquals(telephonyAlgorithmStatus, ARBITRARY_TELEPHONY_ALGORITHM_STATUS); + + TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING, + telephonyAlgorithmStatus, ARBITRARY_LOCATION_ALGORITHM_STATUS); + assertNotEquals(one, three); + assertNotEquals(three, one); + } + + { + LocationTimeZoneAlgorithmStatus locationAlgorithmStatus = + new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING, + LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null, + LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null); + assertNotEquals(locationAlgorithmStatus, ARBITRARY_LOCATION_ALGORITHM_STATUS); + + TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING, + ARBITRARY_TELEPHONY_ALGORITHM_STATUS, locationAlgorithmStatus); + assertNotEquals(one, three); + assertNotEquals(three, one); + } + } + + @Test + public void testParcelable() { + // Detector running. + { + TimeZoneDetectorStatus locationAlgorithmStatus = new TimeZoneDetectorStatus( + DETECTOR_STATUS_RUNNING, ARBITRARY_TELEPHONY_ALGORITHM_STATUS, + ARBITRARY_LOCATION_ALGORITHM_STATUS); + assertRoundTripParcelable(locationAlgorithmStatus); + } + + // Detector not running. + { + TimeZoneDetectorStatus locationAlgorithmStatus = + new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING, + ARBITRARY_TELEPHONY_ALGORITHM_STATUS, + ARBITRARY_LOCATION_ALGORITHM_STATUS); + assertRoundTripParcelable(locationAlgorithmStatus); + } + } +} diff --git a/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java b/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java index 8218fa5c4643..80d959977b0f 100644 --- a/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java +++ b/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java @@ -19,20 +19,15 @@ package com.android.server.timezonedetector; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; -import android.os.ShellCommand; -import android.os.SystemClock; -import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.StringTokenizer; /** - * A time zone suggestion from the location_time_zone_manager service to the time_zone_detector - * service. + * A time zone suggestion from the location_time_zone_manager service (AKA the location-based time + * zone detection algorithm). * * <p>Geolocation-based suggestions have the following properties: * @@ -63,24 +58,16 @@ import java.util.StringTokenizer; * location_time_zone_manager may become uncertain if components further downstream cannot * determine the device's location with sufficient accuracy, or if the location is known but no * time zone can be determined because no time zone mapping information is available.</li> - * <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is - * used to record why the suggestion exists and how it was obtained. This information exists - * only to aid in debugging and therefore is used by {@link #toString()}, but it is not for use - * in detection logic and is not considered in {@link #hashCode()} or {@link #equals(Object)}. * </li> * </ul> - * - * @hide */ public final class GeolocationTimeZoneSuggestion { @ElapsedRealtimeLong private final long mEffectiveFromElapsedMillis; @Nullable private final List<String> mZoneIds; - @Nullable private ArrayList<String> mDebugInfo; private GeolocationTimeZoneSuggestion( - @ElapsedRealtimeLong long effectiveFromElapsedMillis, - @Nullable List<String> zoneIds) { + @ElapsedRealtimeLong long effectiveFromElapsedMillis, @Nullable List<String> zoneIds) { mEffectiveFromElapsedMillis = effectiveFromElapsedMillis; if (zoneIds == null) { // Unopinionated @@ -104,8 +91,7 @@ public final class GeolocationTimeZoneSuggestion { */ @NonNull public static GeolocationTimeZoneSuggestion createCertainSuggestion( - @ElapsedRealtimeLong long effectiveFromElapsedMillis, - @NonNull List<String> zoneIds) { + @ElapsedRealtimeLong long effectiveFromElapsedMillis, @NonNull List<String> zoneIds) { return new GeolocationTimeZoneSuggestion(effectiveFromElapsedMillis, zoneIds); } @@ -126,25 +112,6 @@ public final class GeolocationTimeZoneSuggestion { return mZoneIds; } - /** Returns debug information. See {@link GeolocationTimeZoneSuggestion} for details. */ - @NonNull - public List<String> getDebugInfo() { - return mDebugInfo == null - ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo); - } - - /** - * Associates information with the instance that can be useful for debugging / logging. The - * information is present in {@link #toString()} but is not considered for - * {@link #equals(Object)} and {@link #hashCode()}. - */ - public void addDebugInfo(String... debugInfos) { - if (mDebugInfo == null) { - mDebugInfo = new ArrayList<>(); - } - mDebugInfo.addAll(Arrays.asList(debugInfos)); - } - @Override public boolean equals(Object o) { if (this == o) { @@ -169,59 +136,6 @@ public final class GeolocationTimeZoneSuggestion { return "GeolocationTimeZoneSuggestion{" + "mEffectiveFromElapsedMillis=" + mEffectiveFromElapsedMillis + ", mZoneIds=" + mZoneIds - + ", mDebugInfo=" + mDebugInfo + '}'; } - - /** @hide */ - public static GeolocationTimeZoneSuggestion parseCommandLineArg(@NonNull ShellCommand cmd) { - String zoneIdsString = null; - String opt; - while ((opt = cmd.getNextArg()) != null) { - switch (opt) { - case "--zone_ids": { - zoneIdsString = cmd.getNextArgRequired(); - break; - } - default: { - throw new IllegalArgumentException("Unknown option: " + opt); - } - } - } - - if (zoneIdsString == null) { - throw new IllegalArgumentException("Missing --zone_ids"); - } - - long elapsedRealtimeMillis = SystemClock.elapsedRealtime(); - List<String> zoneIds = parseZoneIdsArg(zoneIdsString); - GeolocationTimeZoneSuggestion suggestion = - new GeolocationTimeZoneSuggestion(elapsedRealtimeMillis, zoneIds); - suggestion.addDebugInfo("Command line injection"); - return suggestion; - } - - private static List<String> parseZoneIdsArg(String zoneIdsString) { - if ("UNCERTAIN".equals(zoneIdsString)) { - return null; - } else if ("EMPTY".equals(zoneIdsString)) { - return Collections.emptyList(); - } else { - ArrayList<String> zoneIds = new ArrayList<>(); - StringTokenizer tokenizer = new StringTokenizer(zoneIdsString, ","); - while (tokenizer.hasMoreTokens()) { - zoneIds.add(tokenizer.nextToken()); - } - return zoneIds; - } - } - - /** @hide */ - public static void printCommandLineOpts(@NonNull PrintWriter pw) { - pw.println("Geolocation suggestion options:"); - pw.println(" --zone_ids {UNCERTAIN|EMPTY|<Olson ID>+}"); - pw.println(); - pw.println("See " + GeolocationTimeZoneSuggestion.class.getName() - + " for more information"); - } } diff --git a/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java b/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java new file mode 100644 index 000000000000..1ffd9a11b300 --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java @@ -0,0 +1,194 @@ +/* + * 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 com.android.server.timezonedetector; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.time.LocationTimeZoneAlgorithmStatus; +import android.os.ShellCommand; +import android.os.SystemClock; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.StringTokenizer; + +/** + * An event from the location_time_zone_manager service (AKA the location-based time zone detection + * algorithm). An event can represent a new time zone recommendation, an algorithm status change, or + * both. + * + * <p>Events have the following properties: + * + * <ul> + * <li>{@code algorithmStatus}: The current status of the location-based time zone detection + * algorithm.</li> + * <li>{@code suggestion}: The latest time zone suggestion, if there is one.</li> + * <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is + * used to record why the event exists and how information contained within it was obtained. + * This information exists only to aid in debugging and therefore is used by + * {@link #toString()}, but it is not for use in detection logic and is not considered in + * {@link #hashCode()} or {@link #equals(Object)}. + * </li> + * </ul> + */ +public final class LocationAlgorithmEvent { + + @NonNull private final LocationTimeZoneAlgorithmStatus mAlgorithmStatus; + @Nullable private final GeolocationTimeZoneSuggestion mSuggestion; + @Nullable private ArrayList<String> mDebugInfo; + + /** Creates a new instance. */ + public LocationAlgorithmEvent( + @NonNull LocationTimeZoneAlgorithmStatus algorithmStatus, + @Nullable GeolocationTimeZoneSuggestion suggestion) { + mAlgorithmStatus = Objects.requireNonNull(algorithmStatus); + mSuggestion = suggestion; + } + + /** + * Returns the status of the location time zone detector algorithm. + */ + @NonNull + public LocationTimeZoneAlgorithmStatus getAlgorithmStatus() { + return mAlgorithmStatus; + } + + /** + * Returns the latest location algorithm suggestion. See {@link LocationAlgorithmEvent} for + * details. + */ + @Nullable + public GeolocationTimeZoneSuggestion getSuggestion() { + return mSuggestion; + } + + /** Returns debug information. See {@link LocationAlgorithmEvent} for details. */ + @NonNull + public List<String> getDebugInfo() { + return mDebugInfo == null + ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo); + } + + /** + * Associates information with the instance that can be useful for debugging / logging. The + * information is present in {@link #toString()} but is not considered for + * {@link #equals(Object)} and {@link #hashCode()}. + */ + public void addDebugInfo(String... debugInfos) { + if (mDebugInfo == null) { + mDebugInfo = new ArrayList<>(); + } + mDebugInfo.addAll(Arrays.asList(debugInfos)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LocationAlgorithmEvent that = (LocationAlgorithmEvent) o; + return mAlgorithmStatus.equals(that.mAlgorithmStatus) + && Objects.equals(mSuggestion, that.mSuggestion); + } + + @Override + public int hashCode() { + return Objects.hash(mAlgorithmStatus, mSuggestion); + } + + @Override + public String toString() { + return "LocationAlgorithmEvent{" + + "mAlgorithmStatus=" + mAlgorithmStatus + + ", mSuggestion=" + mSuggestion + + ", mDebugInfo=" + mDebugInfo + + '}'; + } + + static LocationAlgorithmEvent parseCommandLineArg(@NonNull ShellCommand cmd) { + String suggestionString = null; + LocationTimeZoneAlgorithmStatus algorithmStatus = null; + String opt; + while ((opt = cmd.getNextArg()) != null) { + switch (opt) { + case "--status": { + algorithmStatus = LocationTimeZoneAlgorithmStatus.parseCommandlineArg( + cmd.getNextArgRequired()); + break; + } + case "--suggestion": { + suggestionString = cmd.getNextArgRequired(); + break; + } + default: { + throw new IllegalArgumentException("Unknown option: " + opt); + } + } + } + + if (algorithmStatus == null) { + throw new IllegalArgumentException("Missing --status"); + } + + GeolocationTimeZoneSuggestion suggestion = null; + if (suggestionString != null) { + List<String> zoneIds = parseZoneIds(suggestionString); + long elapsedRealtimeMillis = SystemClock.elapsedRealtime(); + if (zoneIds == null) { + suggestion = GeolocationTimeZoneSuggestion.createUncertainSuggestion( + elapsedRealtimeMillis); + } else { + suggestion = GeolocationTimeZoneSuggestion.createCertainSuggestion( + elapsedRealtimeMillis, zoneIds); + } + } + + LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion); + event.addDebugInfo("Command line injection"); + return event; + } + + private static List<String> parseZoneIds(String zoneIdsString) { + if ("UNCERTAIN".equals(zoneIdsString)) { + return null; + } else if ("EMPTY".equals(zoneIdsString)) { + return Collections.emptyList(); + } else { + ArrayList<String> zoneIds = new ArrayList<>(); + StringTokenizer tokenizer = new StringTokenizer(zoneIdsString, ","); + while (tokenizer.hasMoreTokens()) { + zoneIds.add(tokenizer.nextToken()); + } + return zoneIds; + } + } + + static void printCommandLineOpts(@NonNull PrintWriter pw) { + pw.println("Location algorithm event options:"); + pw.println(" --status {LocationTimeZoneAlgorithmStatus toString() format}"); + pw.println(" [--suggestion {UNCERTAIN|EMPTY|<Olson ID>+}]"); + pw.println(); + pw.println("See " + LocationAlgorithmEvent.class.getName() + " for more information"); + } +} diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java index 6c36989320e8..aad53596fc19 100644 --- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java +++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java @@ -89,7 +89,7 @@ public final class MetricsTimeZoneDetectorState { @NonNull String deviceTimeZoneId, @Nullable ManualTimeZoneSuggestion latestManualSuggestion, @Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion, - @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) { + @Nullable LocationAlgorithmEvent latestLocationAlgorithmEvent) { boolean includeZoneIds = configurationInternal.isEnhancedMetricsCollectionEnabled(); String metricDeviceTimeZoneId = includeZoneIds ? deviceTimeZoneId : null; @@ -101,9 +101,13 @@ public final class MetricsTimeZoneDetectorState { MetricsTimeZoneSuggestion latestCanonicalTelephonySuggestion = createMetricsTimeZoneSuggestion( tzIdOrdinalGenerator, latestTelephonySuggestion, includeZoneIds); - MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion = - createMetricsTimeZoneSuggestion( - tzIdOrdinalGenerator, latestGeolocationSuggestion, includeZoneIds); + + MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion = null; + if (latestLocationAlgorithmEvent != null) { + GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion(); + latestCanonicalGeolocationSuggestion = createMetricsTimeZoneSuggestion( + tzIdOrdinalGenerator, suggestion, includeZoneIds); + } return new MetricsTimeZoneDetectorState( configurationInternal, deviceTimeZoneIdOrdinal, metricDeviceTimeZoneId, diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java index 80cf1d6b9031..74a518bf8382 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java @@ -59,11 +59,11 @@ public interface TimeZoneDetectorInternal { boolean setManualTimeZoneForDpm(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion); /** - * Suggests the current time zone, determined using geolocation, to the detector. The - * detector may ignore the signal based on system settings, whether better information is - * available, and so on. This method may be implemented asynchronously. + * Handles the supplied {@link LocationAlgorithmEvent}. The detector may ignore the event based + * on system settings, whether better information is available, and so on. This method may be + * implemented asynchronously. */ - void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion); + void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent locationAlgorithmEvent); /** Generates a state snapshot for metrics. */ @NonNull diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java index dfb44df7b993..07d04737c3e2 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java @@ -76,13 +76,14 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter } @Override - public void suggestGeolocationTimeZone( - @NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) { - Objects.requireNonNull(timeZoneSuggestion); + public void handleLocationAlgorithmEvent( + @NonNull LocationAlgorithmEvent locationAlgorithmEvent) { + Objects.requireNonNull(locationAlgorithmEvent); // This call can take place on the mHandler thread because there is no return value. mHandler.post( - () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion)); + () -> mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent( + locationAlgorithmEvent)); } @Override diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index f415cf03fdec..f8c1c9269ff3 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -300,12 +300,13 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub } /** Provided for command-line access. This is not exposed as a binder API. */ - void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) { + void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent locationAlgorithmEvent) { enforceSuggestGeolocationTimeZonePermission(); - Objects.requireNonNull(timeZoneSuggestion); + Objects.requireNonNull(locationAlgorithmEvent); mHandler.post( - () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion)); + () -> mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent( + locationAlgorithmEvent)); } @Override diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java index 1b9f8e6cd66f..69274dba7825 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java @@ -19,6 +19,7 @@ import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_CONFIR 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_HANDLE_LOCATION_ALGORITHM_EVENT; 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; @@ -27,7 +28,6 @@ import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SERVIC 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; import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; @@ -79,8 +79,8 @@ class TimeZoneDetectorShellCommand extends ShellCommand { return runIsGeoDetectionEnabled(); case SHELL_COMMAND_SET_GEO_DETECTION_ENABLED: return runSetGeoDetectionEnabled(); - case SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE: - return runSuggestGeolocationTimeZone(); + case SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT: + return runHandleLocationEvent(); case SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE: return runSuggestManualTimeZone(); case SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE: @@ -153,34 +153,34 @@ class TimeZoneDetectorShellCommand extends ShellCommand { return mInterface.updateConfiguration(userId, configuration) ? 0 : 1; } - private int runSuggestGeolocationTimeZone() { - return runSuggestTimeZone( - () -> GeolocationTimeZoneSuggestion.parseCommandLineArg(this), - mInterface::suggestGeolocationTimeZone); + private int runHandleLocationEvent() { + return runSingleArgMethod( + () -> LocationAlgorithmEvent.parseCommandLineArg(this), + mInterface::handleLocationAlgorithmEvent); } private int runSuggestManualTimeZone() { - return runSuggestTimeZone( + return runSingleArgMethod( () -> ManualTimeZoneSuggestion.parseCommandLineArg(this), mInterface::suggestManualTimeZone); } private int runSuggestTelephonyTimeZone() { - return runSuggestTimeZone( + return runSingleArgMethod( () -> TelephonyTimeZoneSuggestion.parseCommandLineArg(this), mInterface::suggestTelephonyTimeZone); } - private <T> int runSuggestTimeZone(Supplier<T> suggestionParser, Consumer<T> invoker) { + private <T> int runSingleArgMethod(Supplier<T> argParser, Consumer<T> invoker) { final PrintWriter pw = getOutPrintWriter(); try { - T suggestion = suggestionParser.get(); - if (suggestion == null) { - pw.println("Error: suggestion not specified"); + T arg = argParser.get(); + if (arg == null) { + pw.println("Error: arg not specified"); return 1; } - invoker.accept(suggestion); - pw.println("Suggestion " + suggestion + " injected."); + invoker.accept(arg); + pw.println("Arg " + arg + " injected."); return 0; } catch (RuntimeException e) { pw.println(e); @@ -263,18 +263,18 @@ class TimeZoneDetectorShellCommand extends ShellCommand { pw.printf(" Sets the geolocation time zone detection enabled setting.\n"); pw.printf(" %s\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK); pw.printf(" Signals that telephony time zone detection fall back can be used if" - + " geolocation detection is supported and enabled. This is a temporary state until" - + " geolocation detection becomes \"certain\". To have an effect this requires that" - + " the telephony fallback feature is supported on the device, see below for" - + " for device_config flags.\n"); - pw.println(); - pw.printf(" %s <geolocation suggestion opts>\n", - SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE); - pw.printf(" Suggests a time zone as if via the \"location\" origin.\n"); + + " geolocation detection is supported and enabled.\n)"); + pw.printf(" This is a temporary state until geolocation detection becomes \"certain\"." + + "\n"); + pw.printf(" To have an effect this requires that the telephony fallback feature is" + + " supported on the device, see below for device_config flags.\n"); + pw.printf(" %s <location event opts>\n", SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT); + pw.printf(" Simulates an event from the location time zone detection algorithm.\n"); pw.printf(" %s <manual suggestion opts>\n", SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE); - pw.printf(" Suggests a time zone as if via the \"manual\" origin.\n"); + pw.printf(" Suggests a time zone as if supplied by a user manually.\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(" Simulates a time zone suggestion from the telephony time zone detection" + + " algorithm.\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); @@ -284,7 +284,7 @@ class TimeZoneDetectorShellCommand extends ShellCommand { pw.printf(" %s\n", SHELL_COMMAND_DUMP_METRICS); pw.printf(" Dumps the service metrics to stdout for inspection.\n"); pw.println(); - GeolocationTimeZoneSuggestion.printCommandLineOpts(pw); + LocationAlgorithmEvent.printCommandLineOpts(pw); pw.println(); ManualTimeZoneSuggestion.printCommandLineOpts(pw); pw.println(); diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index 328cf72b262e..5768a6bfa0dc 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -157,10 +157,9 @@ public interface TimeZoneDetectorStrategy extends Dumpable { 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}. + * Handles an event from the location-based time zone detection algorithm. */ - void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion suggestion); + void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent event); /** * Suggests a time zone for the device using manually-entered (i.e. user sourced) information. @@ -183,7 +182,7 @@ public interface TimeZoneDetectorStrategy extends Dumpable { /** * Tells the strategy that it can fall back to telephony detection while geolocation detection - * remains uncertain. {@link #suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion)} can + * remains uncertain. {@link #handleLocationAlgorithmEvent(LocationAlgorithmEvent)} can * disable it again. See {@link TimeZoneDetectorStrategy} for details. */ void enableTelephonyTimeZoneFallback(); diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index ecf25e9c157c..eecf0f74bff6 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -29,9 +29,13 @@ import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.time.DetectorStatusTypes; +import android.app.time.LocationTimeZoneAlgorithmStatus; +import android.app.time.TelephonyTimeZoneAlgorithmStatus; import android.app.time.TimeZoneCapabilities; import android.app.time.TimeZoneCapabilitiesAndConfig; import android.app.time.TimeZoneConfiguration; +import android.app.time.TimeZoneDetectorStatus; import android.app.time.TimeZoneState; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; @@ -183,11 +187,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); /** - * The latest geolocation suggestion received. If the user disabled geolocation time zone - * detection then the latest suggestion is cleared. + * The latest location algorithm event received. */ @GuardedBy("this") - private final ReferenceWithHistory<GeolocationTimeZoneSuggestion> mLatestGeoLocationSuggestion = + private final ReferenceWithHistory<LocationAlgorithmEvent> mLatestLocationAlgorithmEvent = new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); /** @@ -208,6 +211,14 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat @NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>(); /** + * A snapshot of the current detector status. A local copy is cached because it is relatively + * heavyweight to obtain and is used more often than it is expected to change. + */ + @GuardedBy("this") + @NonNull + private TimeZoneDetectorStatus mDetectorStatus; + + /** * A snapshot of the current user's {@link ConfigurationInternal}. A local copy is cached * because it is relatively heavyweight to obtain and is used more often than it is expected to * change. Because many operations are asynchronous, this value may be out of date but should @@ -258,8 +269,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat // Listen for config and user changes and get an initial snapshot of configuration. StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged; mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener); - mCurrentConfigurationInternal = - mServiceConfigAccessor.getCurrentUserConfigurationInternal(); + + // Initialize mCurrentConfigurationInternal and mDetectorStatus with their starting + // values. + updateCurrentConfigurationInternalIfRequired("TimeZoneDetectorStrategyImpl:"); } } @@ -278,6 +291,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId); } return new TimeZoneCapabilitiesAndConfig( + mDetectorStatus, configurationInternal.asCapabilities(bypassUserPolicyChecks), configurationInternal.asConfiguration()); } @@ -295,28 +309,52 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat // but that could mean an immediate call to getCapabilitiesAndConfig() for the current user // wouldn't see the update. So, handle the cache update and notifications here. When the // async update listener triggers it will find everything already up to date and do nothing. - if (updateSuccessful && mCurrentConfigurationInternal.getUserId() == userId) { - ConfigurationInternal configurationInternal = - mServiceConfigAccessor.getConfigurationInternal(userId); - - // If the configuration actually changed, update the cached copy synchronously and do - // other necessary house-keeping / (async) listener notifications. - if (!configurationInternal.equals(mCurrentConfigurationInternal)) { - mCurrentConfigurationInternal = configurationInternal; - - String logMsg = "updateConfiguration:" - + " userId=" + userId - + ", configuration=" + configuration - + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks - + ", mCurrentConfigurationInternal=" + mCurrentConfigurationInternal; - logTimeZoneDebugInfo(logMsg); - - handleConfigurationInternalChanged(logMsg); - } + if (updateSuccessful) { + String logMsg = "updateConfiguration:" + + " userId=" + userId + + ", configuration=" + configuration + + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks; + updateCurrentConfigurationInternalIfRequired(logMsg); } return updateSuccessful; } + @GuardedBy("this") + private void updateCurrentConfigurationInternalIfRequired(@NonNull String logMsg) { + ConfigurationInternal newCurrentConfigurationInternal = + mServiceConfigAccessor.getCurrentUserConfigurationInternal(); + // mCurrentConfigurationInternal is null the first time this method is called. + ConfigurationInternal oldCurrentConfigurationInternal = mCurrentConfigurationInternal; + + // If the configuration actually changed, update the cached copy synchronously and do + // other necessary house-keeping / (async) listener notifications. + if (!newCurrentConfigurationInternal.equals(oldCurrentConfigurationInternal)) { + mCurrentConfigurationInternal = newCurrentConfigurationInternal; + + logMsg += " [oldConfiguration=" + oldCurrentConfigurationInternal + + ", newConfiguration=" + newCurrentConfigurationInternal + + "]"; + logTimeZoneDebugInfo(logMsg); + + // ConfigurationInternal changes can affect the detector's status. + updateDetectorStatus(); + + // The configuration and maybe the status changed so notify listeners. + notifyStateChangeListenersAsynchronously(); + + // The configuration change may have changed available suggestions or the way + // suggestions are used, so re-run detection. + doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg); + } + } + + @GuardedBy("this") + private void notifyStateChangeListenersAsynchronously() { + for (StateChangeListener listener : mStateChangeListeners) { + mStateChangeHandler.post(listener::onChange); + } + } + @Override public synchronized void addChangeListener(StateChangeListener listener) { mStateChangeListeners.add(listener); @@ -356,33 +394,39 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat } @Override - public synchronized void suggestGeolocationTimeZone( - @NonNull GeolocationTimeZoneSuggestion suggestion) { - + public synchronized void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent event) { ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal; if (DBG) { - Slog.d(LOG_TAG, "Geolocation suggestion received." + Slog.d(LOG_TAG, "Location algorithm event received." + " currentUserConfig=" + currentUserConfig - + " newSuggestion=" + suggestion); + + " event=" + event); } - Objects.requireNonNull(suggestion); + Objects.requireNonNull(event); - // Geolocation suggestions may be stored but not used during time zone detection if the + // Location algorithm events may be stored but not used during time zone detection if the // configuration doesn't have geo time zone detection enabled. The caller is expected to - // withdraw a previous suggestion (i.e. submit an "uncertain" suggestion, when geo time zone - // detection is disabled. - - // The suggestion's "effective from" time is ignored: we currently assume suggestions - // are made in a sensible order and the most recent is always the best one to use. - mLatestGeoLocationSuggestion.set(suggestion); + // withdraw a previous suggestion, i.e. submit an event containing an "uncertain" + // suggestion, when geo time zone detection is disabled. + + // We currently assume events are made in a sensible order and the most recent is always the + // best one to use. + mLatestLocationAlgorithmEvent.set(event); + + // The latest location algorithm event can affect the cached detector status, so update it + // and notify state change listeners as needed. + boolean statusChanged = updateDetectorStatus(); + if (statusChanged) { + notifyStateChangeListenersAsynchronously(); + } // Update the mTelephonyTimeZoneFallbackEnabled state if needed: a certain suggestion // will usually disable telephony fallback mode if it is currently enabled. + // TODO(b/236624675)Some provider status codes can be used to enable telephony fallback. disableTelephonyFallbackIfNeeded(); - // Now perform auto time zone detection. The new suggestion may be used to modify the - // time zone setting. - String reason = "New geolocation time zone suggested. suggestion=" + suggestion; + // Now perform auto time zone detection. The new event may be used to modify the time zone + // setting. + String reason = "New location algorithm event received. event=" + event; doAutoTimeZoneDetection(currentUserConfig, reason); } @@ -465,9 +509,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat + mTelephonyTimeZoneFallbackEnabled; logTimeZoneDebugInfo(logMsg); - // mTelephonyTimeZoneFallbackEnabled and mLatestGeoLocationSuggestion interact. - // If there is currently a certain geolocation suggestion, then the telephony fallback - // value needs to be considered after changing it. + // mTelephonyTimeZoneFallbackEnabled and mLatestLocationAlgorithmEvent interact. + // If the latest event contains a "certain" geolocation suggestion, then the telephony + // fallback value needs to be considered after changing it. // With the way that the mTelephonyTimeZoneFallbackEnabled time is currently chosen // above, and the fact that geolocation suggestions should never have a time in the // future, the following call will be a no-op, and telephony fallback will remain @@ -507,7 +551,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat mEnvironment.getDeviceTimeZone(), getLatestManualSuggestion(), telephonySuggestion, - getLatestGeolocationSuggestion()); + getLatestLocationAlgorithmEvent()); } @Override @@ -606,13 +650,15 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat */ @GuardedBy("this") private boolean doGeolocationTimeZoneDetection(@NonNull String detectionReason) { - GeolocationTimeZoneSuggestion latestGeolocationSuggestion = - mLatestGeoLocationSuggestion.get(); - if (latestGeolocationSuggestion == null) { + // Terminate early if there's nothing to do. + LocationAlgorithmEvent latestLocationAlgorithmEvent = mLatestLocationAlgorithmEvent.get(); + if (latestLocationAlgorithmEvent == null + || latestLocationAlgorithmEvent.getSuggestion() == null) { return false; } - List<String> zoneIds = latestGeolocationSuggestion.getZoneIds(); + GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion(); + List<String> zoneIds = suggestion.getZoneIds(); if (zoneIds == null) { // This means the originator of the suggestion is uncertain about the time zone. The // existing time zone setting must be left as it is but detection can go on looking for @@ -645,13 +691,18 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat } /** - * Sets the mTelephonyTimeZoneFallbackEnabled state to {@code false} if the latest geo - * suggestion is a "certain" suggestion that comes after the time when telephony fallback was - * enabled. + * Sets the mTelephonyTimeZoneFallbackEnabled state to {@code false} if the latest location + * algorithm event contains a "certain" suggestion that comes after the time when telephony + * fallback was enabled. */ @GuardedBy("this") private void disableTelephonyFallbackIfNeeded() { - GeolocationTimeZoneSuggestion suggestion = mLatestGeoLocationSuggestion.get(); + LocationAlgorithmEvent latestLocationAlgorithmEvent = mLatestLocationAlgorithmEvent.get(); + if (latestLocationAlgorithmEvent == null) { + return; + } + + GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion(); boolean isLatestSuggestionCertain = suggestion != null && suggestion.getZoneIds() != null; if (isLatestSuggestionCertain && mTelephonyTimeZoneFallbackEnabled.getValue()) { // This transition ONLY changes mTelephonyTimeZoneFallbackEnabled from @@ -809,33 +860,27 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * Handles a configuration change notification. */ private synchronized void handleConfigurationInternalMaybeChanged() { - ConfigurationInternal currentUserConfig = - mServiceConfigAccessor.getCurrentUserConfigurationInternal(); - - // The configuration may not actually have changed so check before doing anything. - if (!currentUserConfig.equals(mCurrentConfigurationInternal)) { - String logMsg = "handleConfigurationInternalMaybeChanged:" - + " oldConfiguration=" + mCurrentConfigurationInternal - + ", newConfiguration=" + currentUserConfig; - logTimeZoneDebugInfo(logMsg); - - mCurrentConfigurationInternal = currentUserConfig; - - handleConfigurationInternalChanged(logMsg); - } + String logMsg = "handleConfigurationInternalMaybeChanged:"; + updateCurrentConfigurationInternalIfRequired(logMsg); } - /** House-keeping that needs to be done when the mCurrentConfigurationInternal has changed. */ + /** + * Called whenever the information that contributes to {@link #mDetectorStatus} could have + * changed. Updates the cached status snapshot if required. + * + * @return true if the status had changed and has been updated + */ @GuardedBy("this") - private void handleConfigurationInternalChanged(@NonNull String logMsg) { - // Notify change listeners asynchronously. - for (StateChangeListener listener : mStateChangeListeners) { - mStateChangeHandler.post(listener::onChange); + private boolean updateDetectorStatus() { + TimeZoneDetectorStatus newDetectorStatus = createTimeZoneDetectorStatus( + mCurrentConfigurationInternal, mLatestLocationAlgorithmEvent.get()); + // mDetectorStatus is null the first time this method is called. + TimeZoneDetectorStatus oldDetectorStatus = mDetectorStatus; + boolean statusChanged = !newDetectorStatus.equals(oldDetectorStatus); + if (statusChanged) { + mDetectorStatus = newDetectorStatus; } - - // The configuration change may have changed available suggestions or the way - // suggestions are used, so re-run detection. - doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg); + return statusChanged; } /** @@ -847,6 +892,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat ipw.increaseIndent(); // level 1 ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal); + ipw.println("mDetectorStatus=" + mDetectorStatus); final boolean bypassUserPolicyChecks = false; ipw.println("[Capabilities=" + mCurrentConfigurationInternal.asCapabilities(bypassUserPolicyChecks) + "]"); @@ -870,9 +916,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat mLatestManualSuggestion.dump(ipw); ipw.decreaseIndent(); // level 2 - ipw.println("Geolocation suggestion history:"); + ipw.println("Location algorithm event history:"); ipw.increaseIndent(); // level 2 - mLatestGeoLocationSuggestion.dump(ipw); + mLatestLocationAlgorithmEvent.dump(ipw); ipw.decreaseIndent(); // level 2 ipw.println("Telephony suggestion history:"); @@ -886,6 +932,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * A method used to inspect strategy state during tests. Not intended for general use. */ @VisibleForTesting + @Nullable public synchronized ManualTimeZoneSuggestion getLatestManualSuggestion() { return mLatestManualSuggestion.get(); } @@ -894,6 +941,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * A method used to inspect strategy state during tests. Not intended for general use. */ @VisibleForTesting + @Nullable public synchronized QualifiedTelephonyTimeZoneSuggestion getLatestTelephonySuggestion( int slotIndex) { return mTelephonySuggestionsBySlotIndex.get(slotIndex); @@ -903,8 +951,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * A method used to inspect strategy state during tests. Not intended for general use. */ @VisibleForTesting - public synchronized GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() { - return mLatestGeoLocationSuggestion.get(); + @Nullable + public synchronized LocationAlgorithmEvent getLatestLocationAlgorithmEvent() { + return mLatestLocationAlgorithmEvent.get(); } @VisibleForTesting @@ -917,6 +966,11 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat return mCurrentConfigurationInternal; } + @VisibleForTesting + public synchronized TimeZoneDetectorStatus getCachedDetectorStatusForTests() { + return mDetectorStatus; + } + /** * A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata. */ @@ -970,4 +1024,42 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat private static String formatDebugString(TimestampedValue<?> value) { return value.getValue() + " @ " + Duration.ofMillis(value.getReferenceTimeMillis()); } + + @NonNull + private static TimeZoneDetectorStatus createTimeZoneDetectorStatus( + @NonNull ConfigurationInternal currentConfigurationInternal, + @Nullable LocationAlgorithmEvent latestLocationAlgorithmEvent) { + + int detectorStatus; + if (!currentConfigurationInternal.isAutoDetectionSupported()) { + detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_NOT_SUPPORTED; + } else if (currentConfigurationInternal.getAutoDetectionEnabledBehavior()) { + detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_RUNNING; + } else { + detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_NOT_RUNNING; + } + + TelephonyTimeZoneAlgorithmStatus telephonyAlgorithmStatus = + createTelephonyAlgorithmStatus(currentConfigurationInternal); + + LocationTimeZoneAlgorithmStatus locationAlgorithmStatus = + latestLocationAlgorithmEvent == null ? LocationTimeZoneAlgorithmStatus.UNKNOWN + : latestLocationAlgorithmEvent.getAlgorithmStatus(); + + return new TimeZoneDetectorStatus( + detectorStatus, telephonyAlgorithmStatus, locationAlgorithmStatus); + } + + @NonNull + private static TelephonyTimeZoneAlgorithmStatus createTelephonyAlgorithmStatus( + @NonNull ConfigurationInternal currentConfigurationInternal) { + int algorithmStatus; + if (!currentConfigurationInternal.isTelephonyDetectionSupported()) { + algorithmStatus = DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED; + } else { + // The telephony detector is passive, so we treat it as "running". + algorithmStatus = DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; + } + return new TelephonyTimeZoneAlgorithmStatus(algorithmStatus); + } } diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java index 1f752f45fd51..e90a1fe34798 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java @@ -19,7 +19,7 @@ package com.android.server.timezonedetector.location; import android.annotation.NonNull; import android.annotation.Nullable; -import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; +import com.android.server.timezonedetector.LocationAlgorithmEvent; import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState; import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State; @@ -32,14 +32,14 @@ import java.util.Objects; final class LocationTimeZoneManagerServiceState { private final @State String mControllerState; - @Nullable private final GeolocationTimeZoneSuggestion mLastSuggestion; + @Nullable private final LocationAlgorithmEvent mLastEvent; @NonNull private final List<@State String> mControllerStates; @NonNull private final List<ProviderState> mPrimaryProviderStates; @NonNull private final List<ProviderState> mSecondaryProviderStates; LocationTimeZoneManagerServiceState(@NonNull Builder builder) { mControllerState = builder.mControllerState; - mLastSuggestion = builder.mLastSuggestion; + mLastEvent = builder.mLastEvent; mControllerStates = Objects.requireNonNull(builder.mControllerStates); mPrimaryProviderStates = Objects.requireNonNull(builder.mPrimaryProviderStates); mSecondaryProviderStates = Objects.requireNonNull(builder.mSecondaryProviderStates); @@ -50,8 +50,8 @@ final class LocationTimeZoneManagerServiceState { } @Nullable - public GeolocationTimeZoneSuggestion getLastSuggestion() { - return mLastSuggestion; + public LocationAlgorithmEvent getLastEvent() { + return mLastEvent; } @NonNull @@ -73,7 +73,7 @@ final class LocationTimeZoneManagerServiceState { public String toString() { return "LocationTimeZoneManagerServiceState{" + "mControllerState=" + mControllerState - + ", mLastSuggestion=" + mLastSuggestion + + ", mLastEvent=" + mLastEvent + ", mControllerStates=" + mControllerStates + ", mPrimaryProviderStates=" + mPrimaryProviderStates + ", mSecondaryProviderStates=" + mSecondaryProviderStates @@ -83,7 +83,7 @@ final class LocationTimeZoneManagerServiceState { static final class Builder { private @State String mControllerState; - private GeolocationTimeZoneSuggestion mLastSuggestion; + private LocationAlgorithmEvent mLastEvent; private List<@State String> mControllerStates; private List<ProviderState> mPrimaryProviderStates; private List<ProviderState> mSecondaryProviderStates; @@ -95,8 +95,8 @@ final class LocationTimeZoneManagerServiceState { } @NonNull - Builder setLastSuggestion(@NonNull GeolocationTimeZoneSuggestion lastSuggestion) { - mLastSuggestion = Objects.requireNonNull(lastSuggestion); + Builder setLastEvent(@NonNull LocationAlgorithmEvent lastEvent) { + mLastEvent = Objects.requireNonNull(lastEvent); return this; } diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java index 60bbea77b636..cefd0b578df8 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java @@ -15,6 +15,10 @@ */ package com.android.server.timezonedetector.location; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN; import static android.app.time.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO; import static android.app.time.LocationTimeZoneManager.NULL_PACKAGE_NAME_TOKEN; import static android.app.time.LocationTimeZoneManager.SERVICE_NAME; @@ -51,9 +55,14 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus; import android.app.time.GeolocationTimeZoneSuggestionProto; +import android.app.time.LocationTimeZoneAlgorithmStatus; +import android.app.time.LocationTimeZoneAlgorithmStatusProto; import android.app.time.LocationTimeZoneManagerProto; import android.app.time.LocationTimeZoneManagerServiceStateProto; +import android.app.time.LocationTimeZoneProviderEventProto; +import android.app.time.TimeZoneDetectorProto; import android.app.time.TimeZoneProviderStateProto; import android.app.timezonedetector.TimeZoneDetector; import android.os.ShellCommand; @@ -62,6 +71,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; +import com.android.server.timezonedetector.LocationAlgorithmEvent; import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State; @@ -239,19 +249,39 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { outputStream = new DualDumpOutputStream( new IndentingPrintWriter(getOutPrintWriter(), " ")); } - if (state.getLastSuggestion() != null) { - GeolocationTimeZoneSuggestion lastSuggestion = state.getLastSuggestion(); - long lastSuggestionToken = outputStream.start( - "last_suggestion", LocationTimeZoneManagerServiceStateProto.LAST_SUGGESTION); - for (String zoneId : lastSuggestion.getZoneIds()) { - outputStream.write( - "zone_ids" , GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId); + + if (state.getLastEvent() != null) { + LocationAlgorithmEvent lastEvent = state.getLastEvent(); + long lastEventToken = outputStream.start( + "last_event", LocationTimeZoneManagerServiceStateProto.LAST_EVENT); + + // lastEvent.algorithmStatus + LocationTimeZoneAlgorithmStatus algorithmStatus = lastEvent.getAlgorithmStatus(); + long algorithmStatusToken = outputStream.start( + "algorithm_status", LocationTimeZoneProviderEventProto.ALGORITHM_STATUS); + outputStream.write("status", LocationTimeZoneAlgorithmStatusProto.STATUS, + convertDetectionAlgorithmStatusToEnumToProtoEnum(algorithmStatus.getStatus())); + outputStream.end(algorithmStatusToken); + + // lastEvent.suggestion + if (lastEvent.getSuggestion() != null) { + long suggestionToken = outputStream.start( + "suggestion", LocationTimeZoneProviderEventProto.SUGGESTION); + GeolocationTimeZoneSuggestion lastSuggestion = lastEvent.getSuggestion(); + for (String zoneId : lastSuggestion.getZoneIds()) { + outputStream.write( + "zone_ids", GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId); + } + outputStream.end(suggestionToken); } - for (String debugInfo : lastSuggestion.getDebugInfo()) { + + // lastEvent.debugInfo + for (String debugInfo : lastEvent.getDebugInfo()) { outputStream.write( - "debug_info", GeolocationTimeZoneSuggestionProto.DEBUG_INFO, debugInfo); + "debug_info", LocationTimeZoneProviderEventProto.DEBUG_INFO, debugInfo); } - outputStream.end(lastSuggestionToken); + + outputStream.end(lastEventToken); } writeControllerStates(outputStream, state.getControllerStates()); @@ -330,6 +360,22 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { } } + private static int convertDetectionAlgorithmStatusToEnumToProtoEnum( + @DetectionAlgorithmStatus int statusEnum) { + switch (statusEnum) { + case DETECTION_ALGORITHM_STATUS_UNKNOWN: + return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_UNKNOWN; + case DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED: + return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED; + case DETECTION_ALGORITHM_STATUS_NOT_RUNNING: + return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_NOT_RUNNING; + case DETECTION_ALGORITHM_STATUS_RUNNING: + return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_RUNNING; + default: + throw new IllegalArgumentException("Unknown statusEnum=" + statusEnum); + } + } + private void reportError(@NonNull Throwable e) { PrintWriter errPrintWriter = getErrPrintWriter(); errPrintWriter.println("Error: "); diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java index b1fc4f561033..15b57b1fbdfb 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java @@ -16,6 +16,10 @@ package com.android.server.timezonedetector.location; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY; import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE; import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION; import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN; @@ -33,6 +37,7 @@ import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus; import android.os.Handler; import android.os.SystemClock; import android.service.timezone.TimeZoneProviderEvent; @@ -295,6 +300,34 @@ abstract class LocationTimeZoneProvider implements Dumpable { || stateEnum == PROVIDER_STATE_DESTROYED; } + /** + * Maps the internal state enum value to one of the status values exposed to the layers + * above. + */ + public @ProviderStatus int getProviderStatus() { + switch (stateEnum) { + case PROVIDER_STATE_STARTED_INITIALIZING: + return PROVIDER_STATUS_NOT_READY; + case PROVIDER_STATE_STARTED_CERTAIN: + return PROVIDER_STATUS_IS_CERTAIN; + case PROVIDER_STATE_STARTED_UNCERTAIN: + return PROVIDER_STATUS_IS_UNCERTAIN; + case PROVIDER_STATE_PERM_FAILED: + // Perm failed means the providers wasn't configured, configured properly, + // or has removed itself for other reasons, e.g. turned-down server. + return PROVIDER_STATUS_NOT_PRESENT; + case PROVIDER_STATE_STOPPED: + case PROVIDER_STATE_DESTROYED: + // This is a "safe" default that best describes a provider that isn't in one of + // the more obviously mapped states. + return PROVIDER_STATUS_NOT_READY; + case PROVIDER_STATE_UNKNOWN: + default: + throw new IllegalStateException( + "Unknown state enum:" + prettyPrintStateEnum(stateEnum)); + } + } + /** Returns the status reported by the provider, if available. */ @Nullable TimeZoneProviderStatus getReportedStatus() { diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java index a9b9884e0074..ed7ea00ec8f5 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java @@ -35,6 +35,10 @@ import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; +import android.app.time.DetectorStatusTypes; +import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus; +import android.app.time.LocationTimeZoneAlgorithmStatus; +import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus; import android.service.timezone.TimeZoneProviderEvent; import android.service.timezone.TimeZoneProviderSuggestion; import android.util.IndentingPrintWriter; @@ -44,6 +48,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.Dumpable; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; +import com.android.server.timezonedetector.LocationAlgorithmEvent; import com.android.server.timezonedetector.ReferenceWithHistory; import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue; @@ -83,8 +88,7 @@ import java.util.Objects; * <p>All incoming calls except for {@link * LocationTimeZoneProviderController#dump(android.util.IndentingPrintWriter, String[])} must be * made on the {@link android.os.Handler} thread of the {@link ThreadingDomain} passed to {@link - * #LocationTimeZoneProviderController(ThreadingDomain, LocationTimeZoneProvider, - * LocationTimeZoneProvider)}. + * #LocationTimeZoneProviderController}. * * <p>Provider / controller integration notes: * @@ -172,10 +176,10 @@ class LocationTimeZoneProviderController implements Dumpable { @GuardedBy("mSharedLock") private final ReferenceWithHistory<@State String> mState = new ReferenceWithHistory<>(10); - /** Contains the last suggestion actually made, if there is one. */ + /** Contains the last event reported, if there is one. */ @GuardedBy("mSharedLock") @Nullable - private GeolocationTimeZoneSuggestion mLastSuggestion; + private LocationAlgorithmEvent mLastEvent; LocationTimeZoneProviderController(@NonNull ThreadingDomain threadingDomain, @NonNull MetricsLogger metricsLogger, @@ -213,7 +217,7 @@ class LocationTimeZoneProviderController implements Dumpable { setState(STATE_PROVIDERS_INITIALIZING); mPrimaryProvider.initialize(providerListener); mSecondaryProvider.initialize(providerListener); - setState(STATE_STOPPED); + setStateAndReportStatusOnlyEvent(STATE_STOPPED, "initialize()"); alterProvidersStartedStateIfRequired( null /* oldConfiguration */, mCurrentUserConfiguration); @@ -273,13 +277,51 @@ class LocationTimeZoneProviderController implements Dumpable { // Enter destroyed state. mPrimaryProvider.destroy(); mSecondaryProvider.destroy(); - setState(STATE_DESTROYED); + setStateAndReportStatusOnlyEvent(STATE_DESTROYED, "destroy()"); } } /** - * Updates {@link #mState} if needed, and performs all the record-keeping / callbacks associated - * with state changes. + * Sets the state and reports an event containing the algorithm status and a {@code null} + * suggestion. + */ + @GuardedBy("mSharedLock") + private void setStateAndReportStatusOnlyEvent(@State String state, @NonNull String reason) { + setState(state); + + final GeolocationTimeZoneSuggestion suggestion = null; + LocationAlgorithmEvent event = + new LocationAlgorithmEvent(generateCurrentAlgorithmStatus(), suggestion); + event.addDebugInfo(reason); + reportEvent(event); + } + + /** + * Reports an event containing the algorithm status and the supplied suggestion. + */ + @GuardedBy("mSharedLock") + private void reportSuggestionEvent( + @NonNull GeolocationTimeZoneSuggestion suggestion, @NonNull String reason) { + LocationTimeZoneAlgorithmStatus algorithmStatus = generateCurrentAlgorithmStatus(); + LocationAlgorithmEvent event = new LocationAlgorithmEvent( + algorithmStatus, suggestion); + event.addDebugInfo(reason); + reportEvent(event); + } + + /** + * Sends an event immediately. This method updates {@link #mLastEvent}. + */ + @GuardedBy("mSharedLock") + private void reportEvent(@NonNull LocationAlgorithmEvent event) { + debugLog("makeSuggestion: suggestion=" + event); + mCallback.sendEvent(event); + mLastEvent = event; + } + + /** + * Updates the state if needed. This includes setting {@link #mState} and performing all the + * record-keeping / callbacks associated with state changes. */ @GuardedBy("mSharedLock") private void setState(@State String state) { @@ -300,17 +342,7 @@ class LocationTimeZoneProviderController implements Dumpable { // By definition, if both providers are stopped, the controller is uncertain. cancelUncertaintyTimeout(); - // If a previous "certain" suggestion has been made, then a new "uncertain" - // suggestion must now be made to indicate the controller {does not / no longer has} - // an opinion and will not be sending further updates (until at least the providers are - // re-started). - if (Objects.equals(mState.get(), STATE_CERTAIN)) { - GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( - mEnvironment.elapsedRealtimeMillis(), - "Withdraw previous suggestion, providers are stopping: " + reason); - makeSuggestion(suggestion, STATE_UNCERTAIN); - } - setState(STATE_STOPPED); + setStateAndReportStatusOnlyEvent(STATE_STOPPED, "Providers stopped: " + reason); } @GuardedBy("mSharedLock") @@ -381,7 +413,7 @@ class LocationTimeZoneProviderController implements Dumpable { // timeout started when the primary entered {started uncertain} should be cancelled. if (newIsGeoDetectionExecutionEnabled) { - setState(STATE_INITIALIZING); + setStateAndReportStatusOnlyEvent(STATE_INITIALIZING, "initializing()"); // Try to start the primary provider. tryStartProvider(mPrimaryProvider, newConfiguration); @@ -397,13 +429,11 @@ class LocationTimeZoneProviderController implements Dumpable { ProviderState newSecondaryState = mSecondaryProvider.getCurrentState(); if (!newSecondaryState.isStarted()) { // If both providers are {perm failed} then the controller immediately - // reports uncertain. - GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( - mEnvironment.elapsedRealtimeMillis(), - "Providers are failed:" - + " primary=" + mPrimaryProvider.getCurrentState() - + " secondary=" + mPrimaryProvider.getCurrentState()); - makeSuggestion(suggestion, STATE_FAILED); + // reports the failure. + String reason = "Providers are failed:" + + " primary=" + mPrimaryProvider.getCurrentState() + + " secondary=" + mPrimaryProvider.getCurrentState(); + setStateAndReportStatusOnlyEvent(STATE_FAILED, reason); } } } else { @@ -537,12 +567,10 @@ class LocationTimeZoneProviderController implements Dumpable { // If both providers are now terminated, then a suggestion must be sent informing the // time zone detector that there are no further updates coming in the future. - GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( - mEnvironment.elapsedRealtimeMillis(), - "Both providers are terminated:" - + " primary=" + primaryCurrentState.provider - + ", secondary=" + secondaryCurrentState.provider); - makeSuggestion(suggestion, STATE_FAILED); + String reason = "Both providers are terminated:" + + " primary=" + primaryCurrentState.provider + + ", secondary=" + secondaryCurrentState.provider; + setStateAndReportStatusOnlyEvent(STATE_FAILED, reason); } } @@ -615,6 +643,9 @@ class LocationTimeZoneProviderController implements Dumpable { TimeZoneProviderSuggestion providerSuggestion = providerEvent.getSuggestion(); + // Set the current state so it is correct when the suggestion event is created. + setState(STATE_CERTAIN); + // For the suggestion's effectiveFromElapsedMillis, use the time embedded in the provider's // suggestion (which indicates the time when the provider detected the location used to // establish the time zone). @@ -623,15 +654,13 @@ class LocationTimeZoneProviderController implements Dumpable { // this would hinder the ability for the time_zone_detector to judge which suggestions are // based on newer information when comparing suggestions between different sources. long effectiveFromElapsedMillis = providerSuggestion.getElapsedRealtimeMillis(); - GeolocationTimeZoneSuggestion geoSuggestion = + GeolocationTimeZoneSuggestion suggestion = GeolocationTimeZoneSuggestion.createCertainSuggestion( effectiveFromElapsedMillis, providerSuggestion.getTimeZoneIds()); - - String debugInfo = "Event received provider=" + provider + String debugInfo = "Provider event received: provider=" + provider + ", providerEvent=" + providerEvent + ", suggestionCreationTime=" + mEnvironment.elapsedRealtimeMillis(); - geoSuggestion.addDebugInfo(debugInfo); - makeSuggestion(geoSuggestion, STATE_CERTAIN); + reportSuggestionEvent(suggestion, debugInfo); } @Override @@ -647,7 +676,7 @@ class LocationTimeZoneProviderController implements Dumpable { + mEnvironment.getProviderInitializationTimeoutFuzz()); ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay()); ipw.println("mState=" + mState.get()); - ipw.println("mLastSuggestion=" + mLastSuggestion); + ipw.println("mLastEvent=" + mLastEvent); ipw.println("State history:"); ipw.increaseIndent(); // level 2 @@ -668,19 +697,6 @@ class LocationTimeZoneProviderController implements Dumpable { } } - /** - * Sends an immediate suggestion and enters a new state if needed. This method updates - * mLastSuggestion and changes mStateEnum / reports the new state for metrics. - */ - @GuardedBy("mSharedLock") - private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion, - @State String newState) { - debugLog("makeSuggestion: suggestion=" + suggestion); - mCallback.suggest(suggestion); - mLastSuggestion = suggestion; - setState(newState); - } - /** Clears the uncertainty timeout. */ @GuardedBy("mSharedLock") private void cancelUncertaintyTimeout() { @@ -688,18 +704,16 @@ class LocationTimeZoneProviderController implements Dumpable { } /** - * Called when a provider has become "uncertain" about the time zone. + * Called when a provider has reported it is "uncertain" about the time zone. * * <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as * this enables the most flexibility for the controller to start other providers when there are - * multiple ones available. The controller is therefore responsible for deciding when to make a - * "uncertain" suggestion to the downstream time zone detector. + * multiple ones available. The controller is therefore responsible for deciding when to pass + * the "uncertain" suggestion to the downstream time zone detector. * * <p>This method schedules an "uncertainty" timeout (if one isn't already scheduled) to be * triggered later if nothing else preempts it. It can be preempted if the provider becomes - * certain (or does anything else that calls {@link - * #makeSuggestion(GeolocationTimeZoneSuggestion, String)}) within {@link - * Environment#getUncertaintyDelay()}. Preemption causes the scheduled + * certain within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled * "uncertainty" timeout to be cancelled. If the provider repeatedly sends uncertainty events * within the uncertainty delay period, those events are effectively ignored (i.e. the timeout * is not reset each time). @@ -741,6 +755,8 @@ class LocationTimeZoneProviderController implements Dumpable { synchronized (mSharedLock) { long afterUncertaintyTimeoutElapsedMillis = mEnvironment.elapsedRealtimeMillis(); + setState(STATE_UNCERTAIN); + // For the effectiveFromElapsedMillis suggestion property, use the // uncertaintyStartedElapsedMillis. This is the time when the provider first reported // uncertainty, i.e. before the uncertainty timeout. @@ -749,30 +765,65 @@ class LocationTimeZoneProviderController implements Dumpable { // the location_time_zone_manager finally confirms that the time zone was uncertain, // but the suggestion property allows the information to be back-dated, which should // help when comparing suggestions from different sources. - GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( - uncertaintyStartedElapsedMillis, - "Uncertainty timeout triggered for " + provider.getName() + ":" - + " primary=" + mPrimaryProvider - + ", secondary=" + mSecondaryProvider - + ", uncertaintyStarted=" - + Duration.ofMillis(uncertaintyStartedElapsedMillis) - + ", afterUncertaintyTimeout=" - + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis) - + ", uncertaintyDelay=" + uncertaintyDelay - ); - makeSuggestion(suggestion, STATE_UNCERTAIN); + GeolocationTimeZoneSuggestion suggestion = + GeolocationTimeZoneSuggestion.createUncertainSuggestion( + uncertaintyStartedElapsedMillis); + String debugInfo = "Uncertainty timeout triggered for " + provider.getName() + ":" + + " primary=" + mPrimaryProvider + + ", secondary=" + mSecondaryProvider + + ", uncertaintyStarted=" + + Duration.ofMillis(uncertaintyStartedElapsedMillis) + + ", afterUncertaintyTimeout=" + + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis) + + ", uncertaintyDelay=" + uncertaintyDelay; + reportSuggestionEvent(suggestion, debugInfo); } } + @GuardedBy("mSharedLock") @NonNull - private static GeolocationTimeZoneSuggestion createUncertainSuggestion( - @ElapsedRealtimeLong long effectiveFromElapsedMillis, - @NonNull String reason) { - GeolocationTimeZoneSuggestion suggestion = - GeolocationTimeZoneSuggestion.createUncertainSuggestion( - effectiveFromElapsedMillis); - suggestion.addDebugInfo(reason); - return suggestion; + private LocationTimeZoneAlgorithmStatus generateCurrentAlgorithmStatus() { + @State String controllerState = mState.get(); + ProviderState primaryProviderState = mPrimaryProvider.getCurrentState(); + ProviderState secondaryProviderState = mSecondaryProvider.getCurrentState(); + return createAlgorithmStatus(controllerState, primaryProviderState, secondaryProviderState); + } + + @NonNull + private static LocationTimeZoneAlgorithmStatus createAlgorithmStatus( + @NonNull @State String controllerState, + @NonNull ProviderState primaryProviderState, + @NonNull ProviderState secondaryProviderState) { + + @DetectionAlgorithmStatus int algorithmStatus = + mapControllerStateToDetectionAlgorithmStatus(controllerState); + @ProviderStatus int primaryProviderStatus = primaryProviderState.getProviderStatus(); + @ProviderStatus int secondaryProviderStatus = secondaryProviderState.getProviderStatus(); + + // Neither provider is running. The algorithm is not running. + return new LocationTimeZoneAlgorithmStatus(algorithmStatus, + primaryProviderStatus, primaryProviderState.getReportedStatus(), + secondaryProviderStatus, secondaryProviderState.getReportedStatus()); + } + + /** + * Maps the internal state enum value to one of the status values exposed to the layers above. + */ + private static @DetectionAlgorithmStatus int mapControllerStateToDetectionAlgorithmStatus( + @NonNull @State String controllerState) { + switch (controllerState) { + case STATE_INITIALIZING: + case STATE_PROVIDERS_INITIALIZING: + case STATE_CERTAIN: + case STATE_UNCERTAIN: + return DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; + case STATE_STOPPED: + case STATE_DESTROYED: + case STATE_FAILED: + case STATE_UNKNOWN: + default: + return DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING; + } } /** @@ -798,8 +849,8 @@ class LocationTimeZoneProviderController implements Dumpable { synchronized (mSharedLock) { LocationTimeZoneManagerServiceState.Builder builder = new LocationTimeZoneManagerServiceState.Builder(); - if (mLastSuggestion != null) { - builder.setLastSuggestion(mLastSuggestion); + if (mLastEvent != null) { + builder.setLastEvent(mLastEvent); } builder.setControllerState(mState.get()) .setStateChanges(mRecordedStates) @@ -867,17 +918,15 @@ class LocationTimeZoneProviderController implements Dumpable { abstract static class Callback { @NonNull protected final ThreadingDomain mThreadingDomain; - @NonNull protected final Object mSharedLock; Callback(@NonNull ThreadingDomain threadingDomain) { mThreadingDomain = Objects.requireNonNull(threadingDomain); - mSharedLock = threadingDomain.getLockObject(); } /** * Suggests the latest time zone state for the device. */ - abstract void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion); + abstract void sendEvent(@NonNull LocationAlgorithmEvent event); } /** diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java index 0c751aaa62c7..7eb7e01b539a 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java @@ -19,7 +19,7 @@ package com.android.server.timezonedetector.location; import android.annotation.NonNull; import com.android.server.LocalServices; -import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; +import com.android.server.timezonedetector.LocationAlgorithmEvent; import com.android.server.timezonedetector.TimeZoneDetectorInternal; /** @@ -34,11 +34,11 @@ class LocationTimeZoneProviderControllerCallbackImpl } @Override - void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion) { + void sendEvent(@NonNull LocationAlgorithmEvent event) { mThreadingDomain.assertCurrentThread(); TimeZoneDetectorInternal timeZoneDetector = LocalServices.getService(TimeZoneDetectorInternal.class); - timeZoneDetector.suggestGeolocationTimeZone(suggestion); + timeZoneDetector.handleLocationAlgorithmEvent(event); } } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java index fed8b4040aba..bcdc65c19330 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java @@ -21,12 +21,14 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.time.TimeZoneCapabilitiesAndConfig; import android.app.time.TimeZoneConfiguration; +import android.app.time.TimeZoneDetectorStatus; import android.app.time.TimeZoneState; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.util.IndentingPrintWriter; import java.util.ArrayList; +import java.util.Objects; public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { @@ -34,14 +36,17 @@ public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { new FakeServiceConfigAccessor(); private final ArrayList<StateChangeListener> mListeners = new ArrayList<>(); private TimeZoneState mTimeZoneState; + private TimeZoneDetectorStatus mStatus; public FakeTimeZoneDetectorStrategy() { mFakeServiceConfigAccessor.addConfigurationInternalChangeListener( this::notifyChangeListeners); } - public void initializeConfiguration(ConfigurationInternal configuration) { + public void initializeConfigurationAndStatus( + ConfigurationInternal configuration, TimeZoneDetectorStatus status) { mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration); + mStatus = Objects.requireNonNull(status); } @Override @@ -57,6 +62,7 @@ public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { assertEquals("Multi-user testing not supported", configurationInternal.getUserId(), userId); return new TimeZoneCapabilitiesAndConfig( + mStatus, configurationInternal.asCapabilities(bypassUserPolicyChecks), configurationInternal.asConfiguration()); } @@ -90,7 +96,7 @@ public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { } @Override - public void suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion timeZoneSuggestion) { + public void handleLocationAlgorithmEvent(LocationAlgorithmEvent locationAlgorithmEvent) { } @Override diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java index 0f667b3a690b..602842addff2 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java @@ -16,13 +16,8 @@ package com.android.server.timezonedetector; -import static com.android.server.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; - -import android.os.ShellCommand; import org.junit.Test; @@ -49,11 +44,6 @@ public class GeolocationTimeZoneSuggestionTest { assertEquals(certain1v1, certain1v2); assertEquals(certain1v2, certain1v1); - // DebugInfo must not be considered in equals(). - certain1v1.addDebugInfo("Debug info 1"); - certain1v2.addDebugInfo("Debug info 2"); - assertEquals(certain1v1, certain1v2); - long time2 = 2222L; GeolocationTimeZoneSuggestion certain2 = GeolocationTimeZoneSuggestion.createCertainSuggestion(time2, ARBITRARY_ZONE_IDS1); @@ -71,40 +61,4 @@ public class GeolocationTimeZoneSuggestionTest { assertNotEquals(certain1v1, certain3); assertNotEquals(certain3, certain1v1); } - - @Test(expected = IllegalArgumentException.class) - public void testParseCommandLineArg_noZoneIdsArg() { - ShellCommand testShellCommand = - createShellCommandWithArgsAndOptions(Collections.emptyList()); - GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand); - } - - @Test - public void testParseCommandLineArg_zoneIdsUncertain() { - ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( - "--zone_ids UNCERTAIN"); - assertNull(GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand) - .getZoneIds()); - } - - @Test - public void testParseCommandLineArg_zoneIdsEmpty() { - ShellCommand testShellCommand = createShellCommandWithArgsAndOptions("--zone_ids EMPTY"); - assertEquals(Collections.emptyList(), - GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand).getZoneIds()); - } - - @Test - public void testParseCommandLineArg_zoneIdsPresent() { - ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( - "--zone_ids Europe/London,Europe/Paris"); - assertEquals(Arrays.asList("Europe/London", "Europe/Paris"), - GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand).getZoneIds()); - } - - @Test(expected = IllegalArgumentException.class) - public void testParseCommandLineArg_unknownArgument() { - ShellCommand testShellCommand = createShellCommandWithArgsAndOptions("--bad_arg 0"); - GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand); - } } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java new file mode 100644 index 000000000000..4c14014405f4 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java @@ -0,0 +1,175 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.timezonedetector; + +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK; +import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK; + +import static com.android.server.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.app.time.LocationTimeZoneAlgorithmStatus; +import android.os.ShellCommand; +import android.service.timezone.TimeZoneProviderStatus; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +public class LocationAlgorithmEventTest { + + public static final TimeZoneProviderStatus ARBITRARY_PROVIDER_STATUS = + new TimeZoneProviderStatus.Builder() + .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK) + .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE) + .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK) + .build(); + + public static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_ALGORITHM_STATUS = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_STATUS, + PROVIDER_STATUS_NOT_PRESENT, null); + + @Test + public void testEquals() { + GeolocationTimeZoneSuggestion suggestion1 = + GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L); + LocationTimeZoneAlgorithmStatus status1 = new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_NOT_PRESENT, null, PROVIDER_STATUS_NOT_PRESENT, null); + LocationAlgorithmEvent event1v1 = new LocationAlgorithmEvent(status1, suggestion1); + assertEqualsAndHashCode(event1v1, event1v1); + + LocationAlgorithmEvent event1v2 = new LocationAlgorithmEvent(status1, suggestion1); + assertEqualsAndHashCode(event1v1, event1v2); + + GeolocationTimeZoneSuggestion suggestion2 = + GeolocationTimeZoneSuggestion.createUncertainSuggestion(2222L); + LocationAlgorithmEvent event2 = new LocationAlgorithmEvent(status1, suggestion2); + assertNotEquals(event1v1, event2); + + LocationTimeZoneAlgorithmStatus status2 = new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_NOT_PRESENT, null, PROVIDER_STATUS_NOT_READY, null); + LocationAlgorithmEvent event3 = new LocationAlgorithmEvent(status2, suggestion1); + assertNotEquals(event1v1, event3); + + // DebugInfo must not be considered in equals(). + event1v1.addDebugInfo("Debug info 1"); + event1v2.addDebugInfo("Debug info 2"); + assertEquals(event1v1, event1v2); + } + + @Test(expected = IllegalArgumentException.class) + public void testParseCommandLineArg_noStatus() { + GeolocationTimeZoneSuggestion suggestion = + GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L); + ShellCommand testShellCommand = + createShellCommandWithArgsAndOptions( + Arrays.asList("--suggestion", suggestion.toString())); + + LocationAlgorithmEvent.parseCommandLineArg(testShellCommand); + } + + @Test + public void testParseCommandLineArg_noSuggestion() { + GeolocationTimeZoneSuggestion suggestion = null; + LocationAlgorithmEvent event = new LocationAlgorithmEvent( + ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion); + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + Arrays.asList("--status", event.getAlgorithmStatus().toString())); + + assertEquals(event, LocationAlgorithmEvent.parseCommandLineArg(testShellCommand)); + } + + @Test + public void testParseCommandLineArg_suggestionUncertain() { + GeolocationTimeZoneSuggestion suggestion = + GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L); + LocationAlgorithmEvent event = new LocationAlgorithmEvent( + ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion); + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + Arrays.asList("--status", event.getAlgorithmStatus().toString(), + "--suggestion", "UNCERTAIN")); + + LocationAlgorithmEvent parsedEvent = + LocationAlgorithmEvent.parseCommandLineArg(testShellCommand); + assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus()); + assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds()); + } + + @Test + public void testParseCommandLineArg_suggestionEmpty() { + GeolocationTimeZoneSuggestion suggestion = + GeolocationTimeZoneSuggestion.createCertainSuggestion( + 1111L, Collections.emptyList()); + LocationAlgorithmEvent event = new LocationAlgorithmEvent( + ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion); + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + Arrays.asList("--status", event.getAlgorithmStatus().toString(), + "--suggestion", "EMPTY")); + + LocationAlgorithmEvent parsedEvent = + LocationAlgorithmEvent.parseCommandLineArg(testShellCommand); + assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus()); + assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds()); + } + + @Test + public void testParseCommandLineArg_suggestionPresent() { + GeolocationTimeZoneSuggestion suggestion = + GeolocationTimeZoneSuggestion.createCertainSuggestion( + 1111L, Arrays.asList("Europe/London", "Europe/Paris")); + LocationAlgorithmEvent event = new LocationAlgorithmEvent( + ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion); + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + Arrays.asList("--status", event.getAlgorithmStatus().toString(), + "--suggestion", "Europe/London,Europe/Paris")); + + LocationAlgorithmEvent parsedEvent = + LocationAlgorithmEvent.parseCommandLineArg(testShellCommand); + assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus()); + assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds()); + } + + @Test(expected = IllegalArgumentException.class) + public void testParseCommandLineArg_unknownArgument() { + GeolocationTimeZoneSuggestion suggestion = + GeolocationTimeZoneSuggestion.createCertainSuggestion( + 1111L, Arrays.asList("Europe/London", "Europe/Paris")); + LocationAlgorithmEvent event = new LocationAlgorithmEvent( + ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion); + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + Arrays.asList("--status", event.getAlgorithmStatus().toString(), + "--suggestion", "Europe/London,Europe/Paris", "--bad_arg")); + LocationAlgorithmEvent.parseCommandLineArg(testShellCommand); + } + + private static void assertEqualsAndHashCode(Object one, Object two) { + assertEquals(one, two); + assertEquals(two, one); + assertEquals(one.hashCode(), two.hashCode()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java index 223c53233065..ea801e887c4c 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java @@ -16,6 +16,10 @@ package com.android.server.timezonedetector; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT; + import static com.android.server.timezonedetector.MetricsTimeZoneDetectorState.DETECTION_MODE_GEO; import static org.junit.Assert.assertEquals; @@ -23,6 +27,7 @@ import static org.junit.Assert.assertNull; import android.annotation.ElapsedRealtimeLong; import android.annotation.UserIdInt; +import android.app.time.LocationTimeZoneAlgorithmStatus; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; @@ -31,6 +36,7 @@ import com.android.server.timezonedetector.MetricsTimeZoneDetectorState.MetricsT import org.junit.Test; import java.util.Arrays; +import java.util.List; import java.util.function.Function; /** Tests for {@link MetricsTimeZoneDetectorState}. */ @@ -38,6 +44,9 @@ public class MetricsTimeZoneDetectorStateTest { private static final @UserIdInt int ARBITRARY_USER_ID = 1; private static final @ElapsedRealtimeLong long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L; + private static final LocationTimeZoneAlgorithmStatus ARBITRARY_CERTAIN_STATUS = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null); private static final String DEVICE_TIME_ZONE_ID = "DeviceTimeZoneId"; private static final ManualTimeZoneSuggestion MANUAL_TIME_ZONE_SUGGESTION = @@ -50,11 +59,14 @@ public class MetricsTimeZoneDetectorStateTest { .setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE) .build(); - private static final GeolocationTimeZoneSuggestion GEOLOCATION_TIME_ZONE_SUGGESTION = + public static final GeolocationTimeZoneSuggestion GEOLOCATION_SUGGESTION_CERTAIN = GeolocationTimeZoneSuggestion.createCertainSuggestion( ARBITRARY_ELAPSED_REALTIME_MILLIS, Arrays.asList("GeoTimeZoneId1", "GeoTimeZoneId2")); + private static final LocationAlgorithmEvent LOCATION_ALGORITHM_EVENT = + new LocationAlgorithmEvent(ARBITRARY_CERTAIN_STATUS, GEOLOCATION_SUGGESTION_CERTAIN); + private final OrdinalGenerator<String> mOrdinalGenerator = new OrdinalGenerator<>(Function.identity()); @@ -68,7 +80,7 @@ public class MetricsTimeZoneDetectorStateTest { MetricsTimeZoneDetectorState metricsTimeZoneDetectorState = MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal, DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION, - TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION); + TELEPHONY_TIME_ZONE_SUGGESTION, LOCATION_ALGORITHM_EVENT); // Assert the content. assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState); @@ -88,9 +100,10 @@ public class MetricsTimeZoneDetectorStateTest { assertEquals(expectedTelephonySuggestion, metricsTimeZoneDetectorState.getLatestTelephonySuggestion()); + List<String> expectedZoneIds = LOCATION_ALGORITHM_EVENT.getSuggestion().getZoneIds(); MetricsTimeZoneSuggestion expectedGeoSuggestion = MetricsTimeZoneSuggestion.createCertain( - GEOLOCATION_TIME_ZONE_SUGGESTION.getZoneIds().toArray(new String[0]), + expectedZoneIds.toArray(new String[0]), new int[] { 3, 4 }); assertEquals(expectedGeoSuggestion, metricsTimeZoneDetectorState.getLatestGeolocationSuggestion()); @@ -106,7 +119,7 @@ public class MetricsTimeZoneDetectorStateTest { MetricsTimeZoneDetectorState metricsTimeZoneDetectorState = MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal, DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION, - TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION); + TELEPHONY_TIME_ZONE_SUGGESTION, LOCATION_ALGORITHM_EVENT); // Assert the content. assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState); diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java index 8909832391a4..a02c8ca001ce 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java @@ -16,14 +16,22 @@ package com.android.server.timezonedetector; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.app.time.LocationTimeZoneAlgorithmStatus; +import android.app.time.TelephonyTimeZoneAlgorithmStatus; import android.app.time.TimeZoneCapabilitiesAndConfig; import android.app.time.TimeZoneConfiguration; +import android.app.time.TimeZoneDetectorStatus; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.content.Context; import android.os.HandlerThread; @@ -41,6 +49,15 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public class TimeZoneDetectorInternalImplTest { + private static final TelephonyTimeZoneAlgorithmStatus ARBITRARY_TELEPHONY_STATUS = + new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING); + private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_CERTAIN_STATUS = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null); + private static final TimeZoneDetectorStatus ARBITRARY_DETECTOR_STATUS = + new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING, ARBITRARY_TELEPHONY_STATUS, + ARBITRARY_LOCATION_CERTAIN_STATUS); + private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L; private static final String ARBITRARY_ZONE_ID = "TestZoneId"; private static final List<String> ARBITRARY_ZONE_IDS = Arrays.asList(ARBITRARY_ZONE_ID); @@ -81,7 +98,8 @@ public class TimeZoneDetectorInternalImplTest { public void testGetCapabilitiesAndConfigForDpm() throws Exception { final boolean autoDetectionEnabled = true; ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled); - mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(testConfig); + TimeZoneDetectorStatus testStatus = ARBITRARY_DETECTOR_STATUS; + mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(testConfig, testStatus); TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig = mTimeZoneDetectorInternal.getCapabilitiesAndConfigForDpm(); @@ -93,6 +111,7 @@ public class TimeZoneDetectorInternalImplTest { TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig = new TimeZoneCapabilitiesAndConfig( + testStatus, testConfig.asCapabilities(expectedBypassUserPolicyChecks), testConfig.asConfiguration()); assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig); @@ -103,7 +122,9 @@ public class TimeZoneDetectorInternalImplTest { final boolean autoDetectionEnabled = false; ConfigurationInternal initialConfigurationInternal = createConfigurationInternal(autoDetectionEnabled); - mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfigurationInternal); + TimeZoneDetectorStatus testStatus = ARBITRARY_DETECTOR_STATUS; + mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus( + initialConfigurationInternal, testStatus); TimeZoneConfiguration timeConfiguration = new TimeZoneConfiguration.Builder() .setAutoDetectionEnabled(true) @@ -131,13 +152,15 @@ public class TimeZoneDetectorInternalImplTest { } @Test - public void testSuggestGeolocationTimeZone() throws Exception { + public void testHandleLocationAlgorithmEvent() throws Exception { GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion(); - mTimeZoneDetectorInternal.suggestGeolocationTimeZone(timeZoneSuggestion); + LocationAlgorithmEvent suggestionEvent = new LocationAlgorithmEvent( + ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion); + mTimeZoneDetectorInternal.handleLocationAlgorithmEvent(suggestionEvent); mTestHandler.assertTotalMessagesEnqueued(1); mTestHandler.waitForMessagesToBeProcessed(); - verify(mFakeTimeZoneDetectorStrategySpy).suggestGeolocationTimeZone(timeZoneSuggestion); + verify(mFakeTimeZoneDetectorStrategySpy).handleLocationAlgorithmEvent(suggestionEvent); } private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() { return new ManualTimeZoneSuggestion(ARBITRARY_ZONE_ID); diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java index d8346ee4355b..d9d8053e6220 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java @@ -16,6 +16,11 @@ package com.android.server.timezonedetector; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; @@ -34,8 +39,11 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.time.ITimeZoneDetectorListener; +import android.app.time.LocationTimeZoneAlgorithmStatus; +import android.app.time.TelephonyTimeZoneAlgorithmStatus; import android.app.time.TimeZoneCapabilitiesAndConfig; import android.app.time.TimeZoneConfiguration; +import android.app.time.TimeZoneDetectorStatus; import android.app.time.TimeZoneState; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; @@ -59,6 +67,13 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public class TimeZoneDetectorServiceTest { + private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_CERTAIN_STATUS = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null); + private static final TimeZoneDetectorStatus ARBITRARY_DETECTOR_STATUS = + new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING, + new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING), + ARBITRARY_LOCATION_CERTAIN_STATUS); private static final int ARBITRARY_USER_ID = 9999; private static final List<String> ARBITRARY_TIME_ZONE_IDS = Arrays.asList("TestZoneId"); private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L; @@ -113,7 +128,8 @@ public class TimeZoneDetectorServiceTest { ConfigurationInternal configuration = createConfigurationInternal(true /* autoDetectionEnabled*/); - mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(configuration); + mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(configuration, + ARBITRARY_DETECTOR_STATUS); TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig = mTimeZoneDetectorService.getCapabilitiesAndConfig(); @@ -128,6 +144,7 @@ public class TimeZoneDetectorServiceTest { TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig = new TimeZoneCapabilitiesAndConfig( + ARBITRARY_DETECTOR_STATUS, configuration.asCapabilities(expectedBypassUserPolicyChecks), configuration.asConfiguration()); assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig); @@ -161,7 +178,9 @@ public class TimeZoneDetectorServiceTest { public void testListenerRegistrationAndCallbacks() throws Exception { ConfigurationInternal initialConfiguration = createConfigurationInternal(false /* autoDetectionEnabled */); - mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfiguration); + + mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus( + initialConfiguration, ARBITRARY_DETECTOR_STATUS); IBinder mockListenerBinder = mock(IBinder.class); ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class); @@ -231,31 +250,35 @@ public class TimeZoneDetectorServiceTest { } @Test - public void testSuggestGeolocationTimeZone_withoutPermission() { + public void testHandleLocationAlgorithmEvent_withoutPermission() { doThrow(new SecurityException("Mock")) .when(mMockContext).enforceCallingPermission(anyString(), any()); GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion(); + LocationAlgorithmEvent event = new LocationAlgorithmEvent( + ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion); assertThrows(SecurityException.class, - () -> mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion)); + () -> mTimeZoneDetectorService.handleLocationAlgorithmEvent(event)); verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.SET_TIME_ZONE), anyString()); } @Test - public void testSuggestGeolocationTimeZone() throws Exception { + public void testHandleLocationAlgorithmEvent() throws Exception { doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion(); + LocationAlgorithmEvent event = new LocationAlgorithmEvent( + ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion); - mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion); + mTimeZoneDetectorService.handleLocationAlgorithmEvent(event); mTestHandler.assertTotalMessagesEnqueued(1); verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.SET_TIME_ZONE), anyString()); mTestHandler.waitForMessagesToBeProcessed(); - verify(mFakeTimeZoneDetectorStrategySpy).suggestGeolocationTimeZone(timeZoneSuggestion); + verify(mFakeTimeZoneDetectorStrategySpy).handleLocationAlgorithmEvent(event); } @Test diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index f50e7fbc76bb..b991c5a30415 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -16,6 +16,12 @@ package com.android.server.timezonedetector; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY; @@ -35,6 +41,7 @@ import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.T import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -47,8 +54,11 @@ import static org.mockito.Mockito.verify; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.time.LocationTimeZoneAlgorithmStatus; +import android.app.time.TelephonyTimeZoneAlgorithmStatus; import android.app.time.TimeZoneCapabilitiesAndConfig; import android.app.time.TimeZoneConfiguration; +import android.app.time.TimeZoneDetectorStatus; import android.app.time.TimeZoneState; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; @@ -189,6 +199,9 @@ public class TimeZoneDetectorStrategyImplTest { .setGeoDetectionEnabledSetting(true) .build(); + private static final TelephonyTimeZoneAlgorithmStatus TELEPHONY_ALGORITHM_RUNNING_STATUS = + new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING); + private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy; private FakeEnvironment mFakeEnvironment; private HandlerThread mHandlerThread; @@ -233,9 +246,7 @@ public class TimeZoneDetectorStrategyImplTest { { mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange( CONFIG_AUTO_DISABLED_GEO_DISABLED); - mTestHandler.waitForMessagesToBeProcessed(); - - stateChangeListener.assertNotificationsReceived(0); + assertStateChangeNotificationsSent(stateChangeListener, 0); assertEquals(CONFIG_AUTO_DISABLED_GEO_DISABLED, mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests()); } @@ -244,10 +255,7 @@ public class TimeZoneDetectorStrategyImplTest { { mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange( CONFIG_AUTO_ENABLED_GEO_ENABLED); - mTestHandler.waitForMessagesToBeProcessed(); - - stateChangeListener.assertNotificationsReceived(1); - stateChangeListener.resetNotificationsReceivedCount(); + assertStateChangeNotificationsSent(stateChangeListener, 1); assertEquals(CONFIG_AUTO_ENABLED_GEO_ENABLED, mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests()); } @@ -258,10 +266,7 @@ public class TimeZoneDetectorStrategyImplTest { new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build(); mTimeZoneDetectorStrategy.updateConfiguration( USER_ID, requestedChanges, bypassUserPolicyChecks); - mTestHandler.waitForMessagesToBeProcessed(); - - stateChangeListener.assertNotificationsReceived(1); - stateChangeListener.resetNotificationsReceivedCount(); + assertStateChangeNotificationsSent(stateChangeListener, 1); } } @@ -290,11 +295,9 @@ public class TimeZoneDetectorStrategyImplTest { new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build(); mTimeZoneDetectorStrategy.updateConfiguration( otherUserId, requestedChanges, bypassUserPolicyChecks); - mTestHandler.waitForMessagesToBeProcessed(); // Only changes to the current user's config are notified. - stateChangeListener.assertNotificationsReceived(0); - stateChangeListener.resetNotificationsReceivedCount(); + assertStateChangeNotificationsSent(stateChangeListener, 0); } // Current user behavior: the strategy caches and returns the latest configuration. @@ -426,9 +429,9 @@ public class TimeZoneDetectorStrategyImplTest { QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex1ScoredSuggestion = new QualifiedTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion, TELEPHONY_SCORE_NONE); - assertEquals(expectedSlotIndex1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); - assertNull(mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + script.verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion) + .verifyLatestQualifiedTelephonySuggestionReceived(SLOT_INDEX2, null); assertEquals(expectedSlotIndex1ScoredSuggestion, mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); @@ -439,10 +442,10 @@ public class TimeZoneDetectorStrategyImplTest { QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex2ScoredSuggestion = new QualifiedTelephonyTimeZoneSuggestion(slotIndex2TimeZoneSuggestion, TELEPHONY_SCORE_NONE); - assertEquals(expectedSlotIndex1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); - assertEquals(expectedSlotIndex2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + script.verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion) + .verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX2, expectedSlotIndex2ScoredSuggestion); // SlotIndex1 should always beat slotIndex2, all other things being equal. assertEquals(expectedSlotIndex1ScoredSuggestion, mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); @@ -477,8 +480,8 @@ public class TimeZoneDetectorStrategyImplTest { QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion = new QualifiedTelephonyTimeZoneSuggestion( lowQualitySuggestion, testCase.expectedScore); - assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + script.verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX1, expectedScoredSuggestion); assertEquals(expectedScoredSuggestion, mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); } @@ -494,8 +497,8 @@ public class TimeZoneDetectorStrategyImplTest { QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion = new QualifiedTelephonyTimeZoneSuggestion( goodQualitySuggestion, testCase2.expectedScore); - assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + script.verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX1, expectedScoredSuggestion); assertEquals(expectedScoredSuggestion, mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); } @@ -511,8 +514,8 @@ public class TimeZoneDetectorStrategyImplTest { QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion = new QualifiedTelephonyTimeZoneSuggestion( lowQualitySuggestion2, testCase.expectedScore); - assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + script.verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX1, expectedScoredSuggestion); assertEquals(expectedScoredSuggestion, mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); } @@ -543,8 +546,8 @@ public class TimeZoneDetectorStrategyImplTest { // Assert internal service state. QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion = new QualifiedTelephonyTimeZoneSuggestion(suggestion, testCase.expectedScore); - assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + script.verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX1, expectedScoredSuggestion); assertEquals(expectedScoredSuggestion, mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); @@ -560,8 +563,8 @@ public class TimeZoneDetectorStrategyImplTest { } // Assert internal service state. - assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + script.verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX1, expectedScoredSuggestion); assertEquals(expectedScoredSuggestion, mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); @@ -570,8 +573,8 @@ public class TimeZoneDetectorStrategyImplTest { .verifyTimeZoneNotChanged(); // Assert internal service state. - assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + script.verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX1, expectedScoredSuggestion); assertEquals(expectedScoredSuggestion, mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); } @@ -622,8 +625,8 @@ public class TimeZoneDetectorStrategyImplTest { } // Assert internal service state. - assertEquals(expectedZoneSlotIndex1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + script.verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion); assertEquals(expectedZoneSlotIndex1ScoredSuggestion, mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); } @@ -677,10 +680,10 @@ public class TimeZoneDetectorStrategyImplTest { } // Assert internal service state. - assertEquals(expectedZoneSlotIndex1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); - assertEquals(expectedEmptySlotIndex2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + script.verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion) + .verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX2, expectedEmptySlotIndex2ScoredSuggestion); assertEquals(expectedZoneSlotIndex1ScoredSuggestion, mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); @@ -690,10 +693,10 @@ public class TimeZoneDetectorStrategyImplTest { script.verifyTimeZoneNotChanged(); // Assert internal service state. - assertEquals(expectedZoneSlotIndex1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); - assertEquals(expectedZoneSlotIndex2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + script.verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion) + .verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX2, expectedZoneSlotIndex2ScoredSuggestion); // SlotIndex1 should always beat slotIndex2, all other things being equal. assertEquals(expectedZoneSlotIndex1ScoredSuggestion, mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); @@ -709,20 +712,20 @@ public class TimeZoneDetectorStrategyImplTest { } // Assert internal service state. - assertEquals(expectedEmptySlotIndex1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); - assertEquals(expectedZoneSlotIndex2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + script.verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX1, expectedEmptySlotIndex1ScoredSuggestion) + .verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX2, expectedZoneSlotIndex2ScoredSuggestion); assertEquals(expectedZoneSlotIndex2ScoredSuggestion, mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); // Reset the state for the next loop. script.simulateTelephonyTimeZoneSuggestion(emptySlotIndex2Suggestion) .verifyTimeZoneNotChanged(); - assertEquals(expectedEmptySlotIndex1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); - assertEquals(expectedEmptySlotIndex2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + script.verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX1, expectedEmptySlotIndex1ScoredSuggestion) + .verifyLatestQualifiedTelephonySuggestionReceived( + SLOT_INDEX2, expectedEmptySlotIndex2ScoredSuggestion); } } @@ -866,53 +869,185 @@ public class TimeZoneDetectorStrategyImplTest { } @Test - public void testGeoSuggestion_uncertain() { + public void testLocationAlgorithmEvent_statusChangesOnly() { + TestStateChangeListener stateChangeListener = new TestStateChangeListener(); Script script = new Script() .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED) - .resetConfigurationTracking(); + .resetConfigurationTracking() + .registerStateChangeListener(stateChangeListener); + + TimeZoneDetectorStatus expectedInitialDetectorStatus = new TimeZoneDetectorStatus( + DETECTOR_STATUS_RUNNING, + TELEPHONY_ALGORITHM_RUNNING_STATUS, + LocationTimeZoneAlgorithmStatus.UNKNOWN); + script.verifyCachedDetectorStatus(expectedInitialDetectorStatus); + + LocationTimeZoneAlgorithmStatus algorithmStatus1 = new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_NOT_READY, null, + PROVIDER_STATUS_NOT_PRESENT, null); + LocationTimeZoneAlgorithmStatus algorithmStatus2 = new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_NOT_PRESENT, null, + PROVIDER_STATUS_NOT_PRESENT, null); + assertNotEquals(algorithmStatus1, algorithmStatus2); + + { + LocationAlgorithmEvent locationAlgorithmEvent = + new LocationAlgorithmEvent(algorithmStatus1, null); + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneNotChanged(); - GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeolocationSuggestion(); + assertStateChangeNotificationsSent(stateChangeListener, 1); - script.simulateGeolocationTimeZoneSuggestion(uncertainSuggestion) + // Assert internal service state. + TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus( + DETECTOR_STATUS_RUNNING, + TELEPHONY_ALGORITHM_RUNNING_STATUS, + algorithmStatus1); + script.verifyCachedDetectorStatus(expectedDetectorStatus) + .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent); + + // Repeat the event to demonstrate the state change notifier is not triggered. + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneNotChanged(); + + assertStateChangeNotificationsSent(stateChangeListener, 0); + + // Assert internal service state. + script.verifyCachedDetectorStatus(expectedDetectorStatus) + .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent); + } + + { + LocationAlgorithmEvent locationAlgorithmEvent = + new LocationAlgorithmEvent(algorithmStatus2, null); + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneNotChanged(); + + assertStateChangeNotificationsSent(stateChangeListener, 1); + + // Assert internal service state. + TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus( + DETECTOR_STATUS_RUNNING, + TELEPHONY_ALGORITHM_RUNNING_STATUS, + algorithmStatus2); + script.verifyCachedDetectorStatus(expectedDetectorStatus) + .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent); + + // Repeat the event to demonstrate the state change notifier is not triggered. + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneNotChanged(); + + assertStateChangeNotificationsSent(stateChangeListener, 0); + + // Assert internal service state. + script.verifyCachedDetectorStatus(expectedDetectorStatus) + .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent); + } + } + + @Test + public void testLocationAlgorithmEvent_uncertain() { + TestStateChangeListener stateChangeListener = new TestStateChangeListener(); + Script script = new Script() + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED) + .resetConfigurationTracking() + .registerStateChangeListener(stateChangeListener); + + LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged(); + assertStateChangeNotificationsSent(stateChangeListener, 1); + // Assert internal service state. - assertEquals(uncertainSuggestion, - mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion()); + TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus( + DETECTOR_STATUS_RUNNING, + TELEPHONY_ALGORITHM_RUNNING_STATUS, + locationAlgorithmEvent.getAlgorithmStatus()); + script.verifyCachedDetectorStatus(expectedDetectorStatus) + .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent); + + // Repeat the event to demonstrate the state change notifier is not triggered. + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneNotChanged(); + + // Detector remains running and location algorithm is still uncertain so nothing to report. + assertStateChangeNotificationsSent(stateChangeListener, 0); + + // Assert internal service state. + script.verifyCachedDetectorStatus(expectedDetectorStatus) + .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent); } @Test - public void testGeoSuggestion_noZones() { + public void testLocationAlgorithmEvent_noZones() { + TestStateChangeListener stateChangeListener = new TestStateChangeListener(); Script script = new Script() .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED) - .resetConfigurationTracking(); + .resetConfigurationTracking() + .registerStateChangeListener(stateChangeListener); + + LocationAlgorithmEvent locationAlgorithmEvent = createCertainLocationAlgorithmEvent(); + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneNotChanged(); - GeolocationTimeZoneSuggestion noZonesSuggestion = createCertainGeolocationSuggestion(); + assertStateChangeNotificationsSent(stateChangeListener, 1); - script.simulateGeolocationTimeZoneSuggestion(noZonesSuggestion) + // Assert internal service state. + TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus( + DETECTOR_STATUS_RUNNING, + TELEPHONY_ALGORITHM_RUNNING_STATUS, + locationAlgorithmEvent.getAlgorithmStatus()); + script.verifyCachedDetectorStatus(expectedDetectorStatus) + .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent); + + // Repeat the event to demonstrate the state change notifier is not triggered. + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged(); + assertStateChangeNotificationsSent(stateChangeListener, 0); + // Assert internal service state. - assertEquals(noZonesSuggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion()); + script.verifyCachedDetectorStatus(expectedDetectorStatus) + .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent); } @Test - public void testGeoSuggestion_oneZone() { - GeolocationTimeZoneSuggestion suggestion = - createCertainGeolocationSuggestion("Europe/London"); - + public void testLocationAlgorithmEvent_oneZone() { + TestStateChangeListener stateChangeListener = new TestStateChangeListener(); Script script = new Script() .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED) - .resetConfigurationTracking(); + .resetConfigurationTracking() + .registerStateChangeListener(stateChangeListener); + + LocationAlgorithmEvent locationAlgorithmEvent = + createCertainLocationAlgorithmEvent("Europe/London"); + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneChangedAndReset(locationAlgorithmEvent); + + assertStateChangeNotificationsSent(stateChangeListener, 1); + + // Assert internal service state. + TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus( + DETECTOR_STATUS_RUNNING, + TELEPHONY_ALGORITHM_RUNNING_STATUS, + locationAlgorithmEvent.getAlgorithmStatus()); + script.verifyCachedDetectorStatus(expectedDetectorStatus) + .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent); + + // Repeat the event to demonstrate the state change notifier is not triggered. + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneNotChanged(); - script.simulateGeolocationTimeZoneSuggestion(suggestion) - .verifyTimeZoneChangedAndReset(suggestion); + assertStateChangeNotificationsSent(stateChangeListener, 0); // Assert internal service state. - assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion()); + script.verifyCachedDetectorStatus(expectedDetectorStatus) + .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent); } /** @@ -921,41 +1056,35 @@ public class TimeZoneDetectorStrategyImplTest { * set to until that unambiguously can't be correct. */ @Test - public void testGeoSuggestion_multiZone() { - GeolocationTimeZoneSuggestion londonOnlySuggestion = - createCertainGeolocationSuggestion("Europe/London"); - GeolocationTimeZoneSuggestion londonOrParisSuggestion = - createCertainGeolocationSuggestion("Europe/Paris", "Europe/London"); - GeolocationTimeZoneSuggestion parisOnlySuggestion = - createCertainGeolocationSuggestion("Europe/Paris"); - + public void testLocationAlgorithmEvent_multiZone() { Script script = new Script() .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED) .resetConfigurationTracking(); - script.simulateGeolocationTimeZoneSuggestion(londonOnlySuggestion) - .verifyTimeZoneChangedAndReset(londonOnlySuggestion); - assertEquals(londonOnlySuggestion, - mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion()); + LocationAlgorithmEvent londonOnlyEvent = + createCertainLocationAlgorithmEvent("Europe/London"); + script.simulateLocationAlgorithmEvent(londonOnlyEvent) + .verifyTimeZoneChangedAndReset(londonOnlyEvent) + .verifyLatestLocationAlgorithmEventReceived(londonOnlyEvent); // Confirm bias towards the current device zone when there's multiple zones to choose from. - script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion) - .verifyTimeZoneNotChanged(); - assertEquals(londonOrParisSuggestion, - mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion()); + LocationAlgorithmEvent londonOrParisEvent = + createCertainLocationAlgorithmEvent("Europe/Paris", "Europe/London"); + script.simulateLocationAlgorithmEvent(londonOrParisEvent) + .verifyTimeZoneNotChanged() + .verifyLatestLocationAlgorithmEventReceived(londonOrParisEvent); - script.simulateGeolocationTimeZoneSuggestion(parisOnlySuggestion) - .verifyTimeZoneChangedAndReset(parisOnlySuggestion); - assertEquals(parisOnlySuggestion, - mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion()); + LocationAlgorithmEvent parisOnlyEvent = createCertainLocationAlgorithmEvent("Europe/Paris"); + script.simulateLocationAlgorithmEvent(parisOnlyEvent) + .verifyTimeZoneChangedAndReset(parisOnlyEvent) + .verifyLatestLocationAlgorithmEventReceived(parisOnlyEvent); // Now the suggestion that previously left the device on Europe/London will leave the device // on Europe/Paris. - script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion) - .verifyTimeZoneNotChanged(); - assertEquals(londonOrParisSuggestion, - mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion()); + script.simulateLocationAlgorithmEvent(londonOrParisEvent) + .verifyTimeZoneNotChanged() + .verifyLatestLocationAlgorithmEventReceived(londonOrParisEvent); } /** @@ -964,8 +1093,9 @@ public class TimeZoneDetectorStrategyImplTest { */ @Test public void testChangingGeoDetectionEnabled() { - GeolocationTimeZoneSuggestion geolocationSuggestion = - createCertainGeolocationSuggestion("Europe/London"); + TestStateChangeListener stateChangeListener = new TestStateChangeListener(); + LocationAlgorithmEvent locationAlgorithmEvent = + createCertainLocationAlgorithmEvent("Europe/London"); TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion( SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, "Europe/Paris"); @@ -973,20 +1103,22 @@ public class TimeZoneDetectorStrategyImplTest { Script script = new Script() .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED) - .resetConfigurationTracking(); + .resetConfigurationTracking() + .registerStateChangeListener(stateChangeListener); // Add suggestions. Nothing should happen as time zone detection is disabled. - script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion) - .verifyTimeZoneNotChanged(); + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneNotChanged() + .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent); - assertEquals(geolocationSuggestion, - mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion()); + // A detector status change is considered a "state change". + assertStateChangeNotificationsSent(stateChangeListener, 1); script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion) - .verifyTimeZoneNotChanged(); + .verifyTimeZoneNotChanged() + .verifyLatestTelephonySuggestionReceived(SLOT_INDEX1, telephonySuggestion); - assertEquals(telephonySuggestion, - mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1).suggestion); + assertStateChangeNotificationsSent(stateChangeListener, 0); // Toggling the time zone detection enabled setting on should cause the device setting to be // set from the telephony signal, as we've started with geolocation time zone detection @@ -994,18 +1126,25 @@ public class TimeZoneDetectorStrategyImplTest { script.simulateSetAutoMode(true) .verifyTimeZoneChangedAndReset(telephonySuggestion); + // A configuration change is considered a "state change". + assertStateChangeNotificationsSent(stateChangeListener, 1); + // Changing the detection to enable geo detection will cause the device tz setting to // change to use the latest geolocation suggestion. script.simulateSetGeoDetectionEnabled(true) - .verifyTimeZoneChangedAndReset(geolocationSuggestion); + .verifyTimeZoneChangedAndReset(locationAlgorithmEvent); + + // A configuration change is considered a "state change". + assertStateChangeNotificationsSent(stateChangeListener, 1); // Changing the detection to disable geo detection should cause the device tz setting to // change to the telephony suggestion. script.simulateSetGeoDetectionEnabled(false) - .verifyTimeZoneChangedAndReset(telephonySuggestion); + .verifyTimeZoneChangedAndReset(telephonySuggestion) + .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent); - assertEquals(geolocationSuggestion, - mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion()); + // A configuration change is considered a "state change". + assertStateChangeNotificationsSent(stateChangeListener, 1); } @Test @@ -1039,21 +1178,20 @@ public class TimeZoneDetectorStrategyImplTest { // Receiving an "uncertain" geolocation suggestion should have no effect. { - GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = - createUncertainGeolocationSuggestion(); + LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); script.simulateIncrementClock() - .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(true); } // Receiving a "certain" geolocation suggestion should disable telephony fallback mode. { - GeolocationTimeZoneSuggestion geolocationSuggestion = - createCertainGeolocationSuggestion("Europe/London"); + LocationAlgorithmEvent locationAlgorithmEvent = + createCertainLocationAlgorithmEvent("Europe/London"); script.simulateIncrementClock() - .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion) - .verifyTimeZoneChangedAndReset(geolocationSuggestion) + .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneChangedAndReset(locationAlgorithmEvent) .verifyTelephonyFallbackIsEnabled(false); } @@ -1076,22 +1214,22 @@ public class TimeZoneDetectorStrategyImplTest { // Geolocation suggestions should continue to be used as normal (previous telephony // suggestions are not used, even when the geolocation suggestion is uncertain). { - GeolocationTimeZoneSuggestion geolocationSuggestion = - createCertainGeolocationSuggestion("Europe/Rome"); + LocationAlgorithmEvent certainLocationAlgorithmEvent = + createCertainLocationAlgorithmEvent("Europe/Rome"); script.simulateIncrementClock() - .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion) - .verifyTimeZoneChangedAndReset(geolocationSuggestion) + .simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent) + .verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent) .verifyTelephonyFallbackIsEnabled(false); - GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = - createUncertainGeolocationSuggestion(); + LocationAlgorithmEvent uncertainLocationAlgorithmEvent = + createUncertainLocationAlgorithmEvent(); script.simulateIncrementClock() - .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(false); script.simulateIncrementClock() - .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion) + .simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent) // No change needed, device will already be set to Europe/Rome. .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(false); @@ -1108,21 +1246,20 @@ public class TimeZoneDetectorStrategyImplTest { // Make the geolocation algorithm uncertain. { - GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = - createUncertainGeolocationSuggestion(); + LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); script.simulateIncrementClock() - .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneChangedAndReset(lastTelephonySuggestion) .verifyTelephonyFallbackIsEnabled(true); } // Make the geolocation algorithm certain, disabling telephony fallback. { - GeolocationTimeZoneSuggestion geolocationSuggestion = - createCertainGeolocationSuggestion("Europe/Lisbon"); + LocationAlgorithmEvent locationAlgorithmEvent = + createCertainLocationAlgorithmEvent("Europe/Lisbon"); script.simulateIncrementClock() - .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion) - .verifyTimeZoneChangedAndReset(geolocationSuggestion) + .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneChangedAndReset(locationAlgorithmEvent) .verifyTelephonyFallbackIsEnabled(false); } @@ -1130,10 +1267,9 @@ public class TimeZoneDetectorStrategyImplTest { // Demonstrate what happens when geolocation is uncertain when telephony fallback is // enabled. { - GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = - createUncertainGeolocationSuggestion(); + LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); script.simulateIncrementClock() - .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(false) .simulateEnableTelephonyFallback() @@ -1161,10 +1297,9 @@ public class TimeZoneDetectorStrategyImplTest { // Receiving an "uncertain" geolocation suggestion should have no effect. { - GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = - createUncertainGeolocationSuggestion(); + LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); script.simulateIncrementClock() - .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(true); } @@ -1172,10 +1307,9 @@ public class TimeZoneDetectorStrategyImplTest { // Make an uncertain geolocation suggestion, there is no telephony suggestion to fall back // to { - GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = - createUncertainGeolocationSuggestion(); + LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); script.simulateIncrementClock() - .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(true); } @@ -1185,17 +1319,16 @@ public class TimeZoneDetectorStrategyImplTest { // Geolocation suggestions should continue to be used as normal (previous telephony // suggestions are not used, even when the geolocation suggestion is uncertain). { - GeolocationTimeZoneSuggestion geolocationSuggestion = - createCertainGeolocationSuggestion("Europe/Rome"); + LocationAlgorithmEvent certainEvent = + createCertainLocationAlgorithmEvent("Europe/Rome"); script.simulateIncrementClock() - .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion) - .verifyTimeZoneChangedAndReset(geolocationSuggestion) + .simulateLocationAlgorithmEvent(certainEvent) + .verifyTimeZoneChangedAndReset(certainEvent) .verifyTelephonyFallbackIsEnabled(false); - GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = - createUncertainGeolocationSuggestion(); + LocationAlgorithmEvent uncertainEvent = createUncertainLocationAlgorithmEvent(); script.simulateIncrementClock() - .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .simulateLocationAlgorithmEvent(uncertainEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(false); @@ -1319,15 +1452,15 @@ public class TimeZoneDetectorStrategyImplTest { TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(0 /* slotIndex */, MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_SINGLE_ZONE, "Zone2"); - GeolocationTimeZoneSuggestion geolocationTimeZoneSuggestion = - createCertainGeolocationSuggestion("Zone3", "Zone2"); + LocationAlgorithmEvent locationAlgorithmEvent = + createCertainLocationAlgorithmEvent("Zone3", "Zone2"); script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion) .verifyTimeZoneNotChanged() - .simulateGeolocationTimeZoneSuggestion(geolocationTimeZoneSuggestion) + .simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged(); assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId, - manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion, + manualSuggestion, telephonySuggestion, locationAlgorithmEvent, MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL); // Update the config and confirm that the config metrics state updates also. @@ -1336,11 +1469,11 @@ public class TimeZoneDetectorStrategyImplTest { .setGeoDetectionEnabledSetting(true) .build(); - expectedDeviceTimeZoneId = geolocationTimeZoneSuggestion.getZoneIds().get(0); + expectedDeviceTimeZoneId = locationAlgorithmEvent.getSuggestion().getZoneIds().get(0); script.simulateConfigurationInternalChange(expectedInternalConfig) .verifyTimeZoneChangedAndReset(expectedDeviceTimeZoneId, TIME_ZONE_CONFIDENCE_HIGH); assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId, - manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion, + manualSuggestion, telephonySuggestion, locationAlgorithmEvent, MetricsTimeZoneDetectorState.DETECTION_MODE_GEO); } @@ -1352,7 +1485,7 @@ public class TimeZoneDetectorStrategyImplTest { ConfigurationInternal expectedInternalConfig, String expectedDeviceTimeZoneId, ManualTimeZoneSuggestion expectedManualSuggestion, TelephonyTimeZoneSuggestion expectedTelephonySuggestion, - GeolocationTimeZoneSuggestion expectedGeolocationTimeZoneSuggestion, + LocationAlgorithmEvent expectedLocationAlgorithmEvent, int expectedDetectionMode) { MetricsTimeZoneDetectorState actualState = mTimeZoneDetectorStrategy.generateMetricsState(); @@ -1365,7 +1498,7 @@ public class TimeZoneDetectorStrategyImplTest { MetricsTimeZoneDetectorState.create( tzIdOrdinalGenerator, expectedInternalConfig, expectedDeviceTimeZoneId, expectedManualSuggestion, expectedTelephonySuggestion, - expectedGeolocationTimeZoneSuggestion); + expectedLocationAlgorithmEvent); // Rely on MetricsTimeZoneDetectorState.equals() for time zone ID / ID ordinal comparisons. assertEquals(expectedState, actualState); } @@ -1405,20 +1538,37 @@ public class TimeZoneDetectorStrategyImplTest { return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX2).build(); } + private LocationAlgorithmEvent createCertainLocationAlgorithmEvent(@NonNull String... zoneIds) { + GeolocationTimeZoneSuggestion suggestion = createCertainGeolocationSuggestion(zoneIds); + LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_IS_CERTAIN, null, + PROVIDER_STATUS_NOT_PRESENT, null); + LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion); + event.addDebugInfo("Test certain event"); + return event; + } + + private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent() { + GeolocationTimeZoneSuggestion suggestion = createUncertainGeolocationSuggestion(); + LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus( + DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_IS_UNCERTAIN, null, + PROVIDER_STATUS_NOT_PRESENT, null); + LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion); + event.addDebugInfo("Test uncertain event"); + return event; + } + private GeolocationTimeZoneSuggestion createUncertainGeolocationSuggestion() { - return GeolocationTimeZoneSuggestion.createCertainSuggestion( - mFakeEnvironment.elapsedRealtimeMillis(), null); + return GeolocationTimeZoneSuggestion.createUncertainSuggestion( + mFakeEnvironment.elapsedRealtimeMillis()); } private GeolocationTimeZoneSuggestion createCertainGeolocationSuggestion( @NonNull String... zoneIds) { assertNotNull(zoneIds); - GeolocationTimeZoneSuggestion suggestion = - GeolocationTimeZoneSuggestion.createCertainSuggestion( - mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds)); - suggestion.addDebugInfo("Test suggestion"); - return suggestion; + return GeolocationTimeZoneSuggestion.createCertainSuggestion( + mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds)); } static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment { @@ -1499,6 +1649,14 @@ public class TimeZoneDetectorStrategyImplTest { } } + private void assertStateChangeNotificationsSent( + TestStateChangeListener stateChangeListener, int expectedCount) { + // State change notifications are asynchronous, so we have to wait. + mTestHandler.waitForMessagesToBeProcessed(); + + stateChangeListener.assertNotificationsReceivedAndReset(expectedCount); + } + /** * A "fluent" class allows reuse of code in tests: initialization, simulation and verification * logic. @@ -1516,6 +1674,11 @@ public class TimeZoneDetectorStrategyImplTest { return this; } + Script registerStateChangeListener(StateChangeListener stateChangeListener) { + mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener); + return this; + } + Script simulateIncrementClock() { mFakeEnvironment.incrementClock(); return this; @@ -1555,11 +1718,10 @@ public class TimeZoneDetectorStrategyImplTest { } /** - * Simulates the time zone detection strategy receiving a geolocation-originated - * suggestion. + * Simulates the time zone detection strategy receiving a location algorithm event. */ - Script simulateGeolocationTimeZoneSuggestion(GeolocationTimeZoneSuggestion suggestion) { - mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(suggestion); + Script simulateLocationAlgorithmEvent(LocationAlgorithmEvent event) { + mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent(event); return this; } @@ -1616,7 +1778,9 @@ public class TimeZoneDetectorStrategyImplTest { return this; } - Script verifyTimeZoneChangedAndReset(GeolocationTimeZoneSuggestion suggestion) { + Script verifyTimeZoneChangedAndReset(LocationAlgorithmEvent event) { + GeolocationTimeZoneSuggestion suggestion = event.getSuggestion(); + assertNotNull("Only events with suggestions can change the time zone", suggestion); assertEquals("Only use this method with unambiguous geo suggestions", 1, suggestion.getZoneIds().size()); verifyTimeZoneChangedAndReset( @@ -1631,6 +1795,32 @@ public class TimeZoneDetectorStrategyImplTest { return this; } + Script verifyCachedDetectorStatus(TimeZoneDetectorStatus expectedStatus) { + assertEquals(expectedStatus, + mTimeZoneDetectorStrategy.getCachedDetectorStatusForTests()); + return this; + } + + Script verifyLatestLocationAlgorithmEventReceived(LocationAlgorithmEvent expectedEvent) { + assertEquals(expectedEvent, + mTimeZoneDetectorStrategy.getLatestLocationAlgorithmEvent()); + return this; + } + + Script verifyLatestTelephonySuggestionReceived(int slotIndex, + TelephonyTimeZoneSuggestion expectedSuggestion) { + assertEquals(expectedSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(slotIndex).suggestion); + return this; + } + + Script verifyLatestQualifiedTelephonySuggestionReceived(int slotIndex, + QualifiedTelephonyTimeZoneSuggestion expectedQualifiedSuggestion) { + assertEquals(expectedQualifiedSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(slotIndex)); + return this; + } + Script resetConfigurationTracking() { mFakeEnvironment.commitAllChanges(); return this; @@ -1671,11 +1861,16 @@ public class TimeZoneDetectorStrategyImplTest { mNotificationsReceived++; } - public void resetNotificationsReceivedCount() { + public void assertNotificationsReceivedAndReset(int expectedCount) { + assertNotificationsReceived(expectedCount); + resetNotificationsReceivedCount(); + } + + private void resetNotificationsReceivedCount() { mNotificationsReceived = 0; } - public void assertNotificationsReceived(int expectedCount) { + private void assertNotificationsReceived(int expectedCount) { assertEquals(expectedCount, mNotificationsReceived); } } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java index c18acd20e96a..b08705be2eac 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java @@ -15,6 +15,8 @@ */ package com.android.server.timezonedetector.location; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE; import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK; import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE; @@ -42,6 +44,7 @@ import static com.android.server.timezonedetector.location.TestSupport.USER2_CON 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; import static org.junit.Assert.fail; @@ -51,6 +54,7 @@ import static java.util.Arrays.asList; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.service.timezone.TimeZoneProviderEvent; @@ -60,6 +64,7 @@ import android.util.IndentingPrintWriter; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; +import com.android.server.timezonedetector.LocationAlgorithmEvent; import com.android.server.timezonedetector.TestState; import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderMetricsLogger; import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; @@ -141,7 +146,7 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.setFailDuringInitialization(true); // Initialize. After initialization the providers must be initialized and one should be - // started. + // started. They should report their status change via the callback. controller.initialize(testEnvironment, mTestCallback); mTestPrimaryLocationTimeZoneProvider.assertInitialized(); @@ -154,7 +159,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -184,7 +190,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -211,7 +218,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING, STATE_FAILED); - mTestCallback.assertUncertainSuggestionMadeAndCommit(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -239,7 +247,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -262,7 +271,8 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -282,7 +292,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate time passing with no provider event being received from the primary. @@ -296,7 +307,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate time passing with no provider event being received from either the primary or @@ -311,7 +322,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); // Finally, the uncertainty timeout should cause the controller to make an uncertain @@ -324,7 +335,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN); - mTestCallback.assertUncertainSuggestionMadeAndCommit(); + mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit(); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -345,7 +356,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a location event being received from the primary provider. This should cause a @@ -358,7 +370,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -380,7 +392,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate time passing with no provider event being received from the primary. @@ -392,7 +405,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate a location event being received from the primary provider. This should cause a @@ -405,7 +418,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -427,7 +440,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate time passing with no provider event being received from the primary. @@ -439,7 +453,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate a location event being received from the secondary provider. This should cause a @@ -453,7 +467,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -475,7 +489,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a location event being received from the primary provider. This should cause a @@ -488,7 +503,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -501,7 +516,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertFalse(controller.isUncertaintyTimeoutSet()); // And a third, different event should cause another suggestion. @@ -513,7 +528,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -535,7 +550,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate time passing with no provider event being received from the primary. @@ -547,7 +563,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate a location event being received from the secondary provider. This should cause a @@ -561,7 +577,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -575,7 +591,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertFalse(controller.isUncertaintyTimeoutSet()); // And a third, different event should cause another suggestion. @@ -588,7 +604,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -610,7 +626,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a location event being received from the primary provider. This should cause a @@ -623,7 +640,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -639,7 +656,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate a location event being received from the secondary provider. This should cause a @@ -654,7 +671,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -670,7 +687,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate time passing. This means the uncertainty timeout should fire and the uncertain @@ -683,7 +700,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN); - mTestCallback.assertUncertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -705,7 +722,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a location event being received from the primary provider. This should cause a @@ -718,7 +736,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -733,7 +751,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); // And a success event from the primary provider should cause the controller to make another @@ -747,7 +765,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -767,7 +785,8 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is enabled. @@ -778,7 +797,8 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is disabled. @@ -788,7 +808,8 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -807,7 +828,8 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is enabled. @@ -818,7 +840,8 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a success event being received from the primary provider. @@ -830,7 +853,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -843,8 +866,9 @@ public class LocationTimeZoneProviderControllerTest { assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); - mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN, STATE_STOPPED); - mTestCallback.assertUncertainSuggestionMadeAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -865,7 +889,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate the primary provider suggesting a time zone. @@ -879,7 +904,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -897,9 +922,9 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig( PROVIDER_STATE_STARTED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); - mTestMetricsLogger.assertStateChangesAndCommit( - STATE_UNCERTAIN, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertUncertainSuggestionMadeAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED, STATE_INITIALIZING); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -920,7 +945,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a failure location event being received from the primary provider. This should @@ -933,7 +959,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate uncertainty from the secondary. @@ -945,7 +971,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); // And a success event from the secondary provider should cause the controller to make @@ -958,7 +984,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -971,7 +997,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); } @@ -992,7 +1018,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a failure location event being received from the primary provider. This should @@ -1005,7 +1032,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is disabled. @@ -1015,7 +1042,8 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is enabled. @@ -1026,7 +1054,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -1047,7 +1076,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate an uncertain event from the primary. This will start the secondary, which will @@ -1062,7 +1092,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate failure event from the secondary. This should just affect the secondary's state. @@ -1074,7 +1104,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); // And a success event from the primary provider should cause the controller to make @@ -1087,7 +1117,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -1100,7 +1130,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); } @@ -1121,7 +1151,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate an uncertain event from the primary. This will start the secondary, which will @@ -1136,7 +1167,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate failure event from the secondary. This should just affect the secondary's state. @@ -1148,7 +1179,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertUncertaintyTimeoutSet(testEnvironment, controller); // Now signal a config change so that geo detection is disabled. @@ -1158,7 +1189,8 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is enabled. Only the primary can be @@ -1170,7 +1202,8 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -1191,7 +1224,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a failure event from the primary. This will start the secondary. @@ -1203,7 +1237,7 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestMetricsLogger.assertStateChangesAndCommit(); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertNoEventReported(); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate failure event from the secondary. @@ -1214,7 +1248,8 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_FAILED); - mTestCallback.assertUncertainSuggestionMadeAndCommit(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -1233,7 +1268,7 @@ public class LocationTimeZoneProviderControllerTest { { LocationTimeZoneManagerServiceState state = controller.getStateForTests(); assertEquals(STATE_INITIALIZING, state.getControllerState()); - assertNull(state.getLastSuggestion()); + assertNull(state.getLastEvent().getSuggestion()); assertControllerRecordedStates(state, STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); assertProviderStates(state.getPrimaryProviderStates(), @@ -1251,7 +1286,7 @@ public class LocationTimeZoneProviderControllerTest { { LocationTimeZoneManagerServiceState state = controller.getStateForTests(); assertEquals(STATE_INITIALIZING, state.getControllerState()); - assertNull(state.getLastSuggestion()); + assertNull(state.getLastEvent().getSuggestion()); assertControllerRecordedStates(state); assertProviderStates( state.getPrimaryProviderStates(), PROVIDER_STATE_STARTED_UNCERTAIN); @@ -1268,7 +1303,7 @@ public class LocationTimeZoneProviderControllerTest { LocationTimeZoneManagerServiceState state = controller.getStateForTests(); assertEquals(STATE_CERTAIN, state.getControllerState()); assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(), - state.getLastSuggestion().getZoneIds()); + state.getLastEvent().getSuggestion().getZoneIds()); assertControllerRecordedStates(state, STATE_CERTAIN); assertProviderStates(state.getPrimaryProviderStates()); assertProviderStates( @@ -1280,7 +1315,7 @@ public class LocationTimeZoneProviderControllerTest { LocationTimeZoneManagerServiceState state = controller.getStateForTests(); assertEquals(STATE_CERTAIN, state.getControllerState()); assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(), - state.getLastSuggestion().getZoneIds()); + state.getLastEvent().getSuggestion().getZoneIds()); assertControllerRecordedStates(state); assertProviderStates(state.getPrimaryProviderStates()); assertProviderStates(state.getSecondaryProviderStates()); @@ -1313,7 +1348,8 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit( STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); - mTestCallback.assertNoSuggestionMade(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_RUNNING); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate the primary provider suggesting a time zone. @@ -1327,7 +1363,7 @@ public class LocationTimeZoneProviderControllerTest { PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); - mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( + mTestCallback.assertEventWithCertainSuggestionReportedAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -1335,11 +1371,11 @@ public class LocationTimeZoneProviderControllerTest { controller.destroy(); assertControllerState(controller, STATE_DESTROYED); - mTestMetricsLogger.assertStateChangesAndCommit( - STATE_UNCERTAIN, STATE_STOPPED, STATE_DESTROYED); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED, STATE_DESTROYED); // Confirm that the previous suggestion was overridden. - mTestCallback.assertUncertainSuggestionMadeAndCommit(); + mTestCallback.assertEventWithNoSuggestionReportedAndCommit( + DETECTION_ALGORITHM_STATUS_NOT_RUNNING); mTestPrimaryLocationTimeZoneProvider.assertStateChangesAndCommit( PROVIDER_STATE_STOPPED, PROVIDER_STATE_DESTROYED); @@ -1517,63 +1553,101 @@ public class LocationTimeZoneProviderControllerTest { private static class TestCallback extends LocationTimeZoneProviderController.Callback { - private TestState<GeolocationTimeZoneSuggestion> mLatestSuggestion = new TestState<>(); + private TestState<LocationAlgorithmEvent> mLatestEvent = new TestState<>(); TestCallback(ThreadingDomain threadingDomain) { super(threadingDomain); } @Override - void suggest(GeolocationTimeZoneSuggestion suggestion) { - mLatestSuggestion.set(suggestion); + void sendEvent(LocationAlgorithmEvent event) { + mLatestEvent.set(event); + } + + void assertNoEventReported() { + mLatestEvent.assertHasNotBeenSet(); + } + + /** + * Asserts one or more events have been reported, and the most recent does not contain a + * suggestion. + */ + void assertEventWithNoSuggestionReportedAndCommit( + @DetectionAlgorithmStatus int expectedAlgorithmStatus) { + mLatestEvent.assertHasBeenSet(); + + LocationAlgorithmEvent latest = mLatestEvent.getLatest(); + assertEquals(expectedAlgorithmStatus, latest.getAlgorithmStatus().getStatus()); + assertNull(latest.getSuggestion()); + mLatestEvent.commitLatest(); } - void assertCertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) { + void assertEventWithCertainSuggestionReportedAndCommit(TimeZoneProviderEvent event) { // Test coding error if this fails. assertEquals(TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION, event.getType()); + // By definition, the algorithm has to be running to report a suggestion. + @DetectionAlgorithmStatus int expectedAlgorithmStatus = + DETECTION_ALGORITHM_STATUS_RUNNING; TimeZoneProviderSuggestion suggestion = event.getSuggestion(); - assertSuggestionMadeAndCommit( + assertEventWithSuggestionReportedAndCommit( + expectedAlgorithmStatus, suggestion.getElapsedRealtimeMillis(), suggestion.getTimeZoneIds()); } - void assertNoSuggestionMade() { - mLatestSuggestion.assertHasNotBeenSet(); - } - - /** Asserts that an uncertain suggestion has been made from the supplied event. */ - void assertUncertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) { + /** + * Asserts that one or more events have been reported, and the most recent contains an + * uncertain suggestion matching select details from the supplied provider event. + */ + void assertEventWithUncertainSuggestionReportedAndCommit(TimeZoneProviderEvent event) { // Test coding error if this fails. assertEquals(TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN, event.getType()); - assertSuggestionMadeAndCommit(event.getCreationElapsedMillis(), null); + // By definition, the algorithm has to be running to report a suggestion. + @DetectionAlgorithmStatus int expectedAlgorithmStatus = + DETECTION_ALGORITHM_STATUS_RUNNING; + assertEventWithSuggestionReportedAndCommit( + expectedAlgorithmStatus, event.getCreationElapsedMillis(), null); } /** - * Asserts that an uncertain suggestion has been made. - * Ignores the suggestion's effectiveFromElapsedMillis. + * Asserts that one or more events have been reported, and the most recent contains an + * uncertain suggestion. Ignores the suggestion's effectiveFromElapsedMillis. */ - void assertUncertainSuggestionMadeAndCommit() { + void assertEventWithUncertainSuggestionReportedAndCommit() { + // By definition, the algorithm has to be running to report a suggestion. + @DetectionAlgorithmStatus int expectedAlgorithmStatus = + DETECTION_ALGORITHM_STATUS_RUNNING; + // An "uncertain" suggestion has null time zone IDs. - assertSuggestionMadeAndCommit(null, null); + assertEventWithSuggestionReportedAndCommit(expectedAlgorithmStatus, null, null); } /** - * Asserts that a suggestion has been made and some properties of that suggestion. - * When expectedEffectiveFromElapsedMillis is null then its value isn't checked. + * Asserts that an event has been reported containing a suggestion and some properties of + * that suggestion. When expectedEffectiveFromElapsedMillis is null then its value isn't + * checked. */ - private void assertSuggestionMadeAndCommit( + private void assertEventWithSuggestionReportedAndCommit( + @DetectionAlgorithmStatus int expectedAlgorithmStatus, @Nullable @ElapsedRealtimeLong Long expectedEffectiveFromElapsedMillis, @Nullable List<String> expectedZoneIds) { - mLatestSuggestion.assertHasBeenSet(); + mLatestEvent.assertHasBeenSet(); + + LocationAlgorithmEvent latestEvent = mLatestEvent.getLatest(); + assertEquals(expectedAlgorithmStatus, latestEvent.getAlgorithmStatus().getStatus()); + + GeolocationTimeZoneSuggestion suggestion = latestEvent.getSuggestion(); + assertNotNull("Latest event doesn't contain a suggestion: event=" + latestEvent, + suggestion); + if (expectedEffectiveFromElapsedMillis != null) { - assertEquals( - expectedEffectiveFromElapsedMillis.longValue(), - mLatestSuggestion.getLatest().getEffectiveFromElapsedMillis()); + assertEquals(expectedEffectiveFromElapsedMillis.longValue(), + suggestion.getEffectiveFromElapsedMillis()); } - assertEquals(expectedZoneIds, mLatestSuggestion.getLatest().getZoneIds()); - mLatestSuggestion.commitLatest(); + assertEquals(expectedZoneIds, suggestion.getZoneIds()); + mLatestEvent.commitLatest(); } } |