From a4aee0d52031a243707b2a9015994a0abf7acfeb Mon Sep 17 00:00:00 2001 From: Neil Fuller Date: Tue, 25 Oct 2022 13:19:55 +0100 Subject: [PATCH] Add status to TimeZoneCapabilitiesAndConfig Update LocationTimeZoneProviderController to report newly available status information. After this change, the location_time_zone_manager now informs the time_zone_detector of status changes, not just suggestion changes. To reflect this, the path from location_time_zone_manager to time_zone_detector involves a new "LocationAlgorithmEvent" (which can contain an optional suggestion) and not the suggestion directly. In fact, location_time_zone_manager may over-notify (i.e. when neither status nor suggestion have changed) to reflect internal state changes in LocationTimeZoneProviderController, but the time_zone_detector can easily recognize dupes and discard them while keeping the latest event received for logging / debugging. The reason for this change is so that time zone detector status information can be added to TimeZoneCapabilitiesAndConfig for use by SettingsUI in upcoming UI changes. The full structure is represented (Overall detector -> Detection Algorithms -> Sub-components like time zone providers) so that the UI folks can use what they want today. The initial use is so that SettingsUI can show user alerts / warnings about "bad" location settings that are blocking / degrading location time zone provider behavior. This change is also included in this commit so the whole end-to-end path is implemented. When the detector status changes, the UI (TimeZoneListener) listeners will now be informed. Manual testing: Various commands tried like: adb shell cmd time_zone_detector handle_location_algorithm_event \ --status '"LocationTimeZoneAlgorithmStatus{mAlgorithmStatus=RUNNING, mPrimaryProviderStatus=NOT_READY, mPrimaryProviderReportedStatus=null, mSecondaryProviderStatus=NOT_READY, mSecondaryProviderReportedStatus=null}"' Bug: 236624675 Test: See manual test instructions Test: atest services/tests/servicestests/src/com/android/server/timedetector/ Test: atest services/tests/servicestests/src/com/android/server/timezonedetector/ Test: atest ./tests/tests/time/src/android/app/time/cts/ Test: atest core/tests/coretests/src/android/service/timezone/ Test: atest core/tests/coretests/src/android/service/time/ Change-Id: I50636bf75ad2f57ddeb1dce829678cf354b2a05a --- .../android/app/time/DetectorStatusTypes.java | 227 ++++++++ .../time/LocationTimeZoneAlgorithmStatus.aidl | 19 + .../time/LocationTimeZoneAlgorithmStatus.java | 363 ++++++++++++ .../TelephonyTimeZoneAlgorithmStatus.aidl | 19 + .../TelephonyTimeZoneAlgorithmStatus.java | 96 ++++ .../time/TimeZoneCapabilitiesAndConfig.java | 66 ++- .../app/time/TimeZoneDetectorStatus.aidl | 19 + .../app/time/TimeZoneDetectorStatus.java | 124 ++++ .../timezonedetector/TimeZoneDetector.java | 4 +- .../app/location_time_zone_manager.proto | 2 +- .../android/app/time_zone_detector.proto | 29 +- .../app/time/DetectorStatusTypesTest.java | 103 ++++ .../LocationTimeZoneAlgorithmStatusTest.java | 210 +++++++ .../TelephonyTimeZoneAlgorithmStatusTest.java | 68 +++ .../app/time/TimeZoneDetectorStatusTest.java | 105 ++++ .../GeolocationTimeZoneSuggestion.java | 94 +-- .../LocationAlgorithmEvent.java | 194 +++++++ .../MetricsTimeZoneDetectorState.java | 12 +- .../TimeZoneDetectorInternal.java | 8 +- .../TimeZoneDetectorInternalImpl.java | 9 +- .../TimeZoneDetectorService.java | 7 +- .../TimeZoneDetectorShellCommand.java | 52 +- .../TimeZoneDetectorStrategy.java | 7 +- .../TimeZoneDetectorStrategyImpl.java | 246 +++++--- .../LocationTimeZoneManagerServiceState.java | 18 +- .../LocationTimeZoneManagerShellCommand.java | 66 ++- .../location/LocationTimeZoneProvider.java | 33 ++ .../LocationTimeZoneProviderController.java | 215 ++++--- ...imeZoneProviderControllerCallbackImpl.java | 6 +- .../FakeTimeZoneDetectorStrategy.java | 10 +- .../GeolocationTimeZoneSuggestionTest.java | 46 -- .../LocationAlgorithmEventTest.java | 175 ++++++ .../MetricsTimeZoneDetectorStateTest.java | 21 +- .../TimeZoneDetectorInternalImplTest.java | 33 +- .../TimeZoneDetectorServiceTest.java | 37 +- .../TimeZoneDetectorStrategyImplTest.java | 537 ++++++++++++------ ...ocationTimeZoneProviderControllerTest.java | 286 ++++++---- 37 files changed, 2885 insertions(+), 681 deletions(-) create mode 100644 core/java/android/app/time/DetectorStatusTypes.java create mode 100644 core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl create mode 100644 core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java create mode 100644 core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl create mode 100644 core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java create mode 100644 core/java/android/app/time/TimeZoneDetectorStatus.aidl create mode 100644 core/java/android/app/time/TimeZoneDetectorStatus.java create mode 100644 core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java create mode 100644 core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java create mode 100644 core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java create mode 100644 core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java create mode 100644 services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java create mode 100644 services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java diff --git a/core/java/android/app/time/DetectorStatusTypes.java b/core/java/android/app/time/DetectorStatusTypes.java new file mode 100644 index 0000000000000..3643fc9a7d86e --- /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. + * + * + * + * @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 0000000000000..7184b123af1ce --- /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 0000000000000..710b8c40cefe8 --- /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 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 0000000000000..0eb5b63b7ffb3 --- /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 0000000000000..95240c00fa3fa --- /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 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 cd91b0431b28e..4684c6ad811c8 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 CREATOR = - new Creator() { - public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) { - return TimeZoneCapabilitiesAndConfig.createFromParcel(in); - } - - public TimeZoneCapabilitiesAndConfig[] newArray(int size) { - return new TimeZoneCapabilitiesAndConfig[size]; - } - }; + public static final @NonNull Creator 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,25 +66,40 @@ 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. */ @@ -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 0000000000000..32204df6d698c --- /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 0000000000000..16374639b77cd --- /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 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 0e9e28be88188..f357fb243fe18 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 5fdcfdf35a37a..7037a6c4f68a9 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 b52aa828bef98..cd4a36fafef04 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 0000000000000..f57ee43b76c89 --- /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 0000000000000..a648a885aea2b --- /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 0000000000000..b90c485bbbb61 --- /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 0000000000000..dfff7ecdf9891 --- /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 8218fa5c4643b..80d959977b0f0 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). * *

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. - *

  • {@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)}. *
  • * - * - * @hide */ public final class GeolocationTimeZoneSuggestion { @ElapsedRealtimeLong private final long mEffectiveFromElapsedMillis; @Nullable private final List mZoneIds; - @Nullable private ArrayList mDebugInfo; private GeolocationTimeZoneSuggestion( - @ElapsedRealtimeLong long effectiveFromElapsedMillis, - @Nullable List zoneIds) { + @ElapsedRealtimeLong long effectiveFromElapsedMillis, @Nullable List 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 zoneIds) { + @ElapsedRealtimeLong long effectiveFromElapsedMillis, @NonNull List 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 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 zoneIds = parseZoneIdsArg(zoneIdsString); - GeolocationTimeZoneSuggestion suggestion = - new GeolocationTimeZoneSuggestion(elapsedRealtimeMillis, zoneIds); - suggestion.addDebugInfo("Command line injection"); - return suggestion; - } - - private static List parseZoneIdsArg(String zoneIdsString) { - if ("UNCERTAIN".equals(zoneIdsString)) { - return null; - } else if ("EMPTY".equals(zoneIdsString)) { - return Collections.emptyList(); - } else { - ArrayList 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|+}"); - 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 0000000000000..1ffd9a11b300f --- /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. + * + *

    Events have the following properties: + * + *

      + *
    • {@code algorithmStatus}: The current status of the location-based time zone detection + * algorithm.
    • + *
    • {@code suggestion}: The latest time zone suggestion, if there is one.
    • + *
    • {@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)}. + *
    • + *
    + */ +public final class LocationAlgorithmEvent { + + @NonNull private final LocationTimeZoneAlgorithmStatus mAlgorithmStatus; + @Nullable private final GeolocationTimeZoneSuggestion mSuggestion; + @Nullable private ArrayList 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 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 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 parseZoneIds(String zoneIdsString) { + if ("UNCERTAIN".equals(zoneIdsString)) { + return null; + } else if ("EMPTY".equals(zoneIdsString)) { + return Collections.emptyList(); + } else { + ArrayList 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|+}]"); + 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 6c36989320e8f..aad53596fc190 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 80cf1d6b9031b..74a518bf83821 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 dfb44df7b993f..07d04737c3e25 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 f415cf03fdecc..f8c1c9269ff39 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 1b9f8e6cd66f6..69274dba78250 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 int runSuggestTimeZone(Supplier suggestionParser, Consumer invoker) { + private int runSingleArgMethod(Supplier argParser, Consumer 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 \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 \n", SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT); + pw.printf(" Simulates an event from the location time zone detection algorithm.\n"); pw.printf(" %s \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 \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