/* * 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.service.timezone; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; 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 a {@link TimeZoneProviderService}. * *

Not all status properties or status values will apply to all provider implementations. * {@code _NOT_APPLICABLE} status can be used to indicate properties that have no meaning for a * given implementation. * *

Time zone providers are expected to work in one of two ways: *

    *
  1. Location: Providers will determine location and then map that location to one or more * time zone IDs.
  2. *
  3. External signals: Providers could use indirect signals like country code * and/or local offset / DST information provided to the device to infer a time zone, e.g. * signals like MCC and NITZ for telephony devices, IP geo location, or DHCP information * (RFC4833). The time zone ID could also be fed directly to the device by an external service. *
  4. *
* *

The status properties are: *

* * @hide */ @SystemApi public final class TimeZoneProviderStatus implements Parcelable { /** * A status code related to a dependency a provider may have. * * @hide */ @IntDef(prefix = "DEPENDENCY_STATUS_", value = { DEPENDENCY_STATUS_UNKNOWN, DEPENDENCY_STATUS_NOT_APPLICABLE, DEPENDENCY_STATUS_OK, DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE, DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT, DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS, DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS, }) @Target(ElementType.TYPE_USE) @Retention(RetentionPolicy.SOURCE) public @interface DependencyStatus {} /** * The dependency's status is unknown. * * @hide */ public static final @DependencyStatus int DEPENDENCY_STATUS_UNKNOWN = 0; /** The dependency is not used by the provider's implementation. */ public static final @DependencyStatus int DEPENDENCY_STATUS_NOT_APPLICABLE = 1; /** The dependency is applicable and there are no known problems. */ public static final @DependencyStatus int DEPENDENCY_STATUS_OK = 2; /** * The dependency is used but is temporarily unavailable, e.g. connectivity has been lost for an * unpredictable amount of time. * *

This status is considered normal is may be entered many times a day. */ public static final @DependencyStatus int DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE = 3; /** * The dependency is used by the provider but is blocked by the environment in a way that the * provider has detected and is considered likely to persist for some time, e.g. connectivity * has been lost due to boarding a plane. * *

This status is considered unusual and could be used by the system as a trigger to try * other time zone providers / time zone detection mechanisms. The bar for using this status * should therefore be set fairly high to avoid a device bringing up other providers or * switching to a different detection mechanism that may provide a different suggestion. */ public static final @DependencyStatus int DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT = 4; /** * The dependency is used by the provider but is running in a degraded mode due to the user's * settings. A user can take action to improve this, e.g. by changing a setting. * *

This status could be used by the system as a trigger to try other time zone * providers / time zone detection mechanisms. The user may be informed. */ public static final @DependencyStatus int DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS = 5; /** * The dependency is used by the provider but is completely blocked by the user's settings. * A user can take action to correct this, e.g. by changing a setting. * *

This status could be used by the system as a trigger to try other time zone providers / * time zone detection mechanisms. The user may be informed. */ public static final @DependencyStatus int DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS = 6; /** * A status code related to an operation in a provider's detection algorithm. * * @hide */ @IntDef(prefix = "OPERATION_STATUS_", value = { OPERATION_STATUS_UNKNOWN, OPERATION_STATUS_NOT_APPLICABLE, OPERATION_STATUS_OK, OPERATION_STATUS_FAILED, }) @Target(ElementType.TYPE_USE) @Retention(RetentionPolicy.SOURCE) public @interface OperationStatus {} /** * The operation's status is unknown. * * @hide */ public static final @OperationStatus int OPERATION_STATUS_UNKNOWN = 0; /** The operation is not used by the provider's implementation. */ public static final @OperationStatus int OPERATION_STATUS_NOT_APPLICABLE = 1; /** The operation is applicable and there are no known problems. */ public static final @OperationStatus int OPERATION_STATUS_OK = 2; /** The operation is applicable and it recently failed. */ public static final @OperationStatus int OPERATION_STATUS_FAILED = 3; private final @DependencyStatus int mLocationDetectionDependencyStatus; private final @DependencyStatus int mConnectivityDependencyStatus; private final @OperationStatus int mTimeZoneResolutionOperationStatus; private TimeZoneProviderStatus( @DependencyStatus int locationDetectionStatus, @DependencyStatus int connectivityStatus, @OperationStatus int timeZoneResolutionStatus) { mLocationDetectionDependencyStatus = locationDetectionStatus; mConnectivityDependencyStatus = connectivityStatus; mTimeZoneResolutionOperationStatus = timeZoneResolutionStatus; } /** * Returns the status of the location detection dependencies used by the provider (where * applicable). */ public @DependencyStatus int getLocationDetectionDependencyStatus() { return mLocationDetectionDependencyStatus; } /** * Returns the status of the connectivity dependencies used by the provider (where applicable). */ public @DependencyStatus int getConnectivityDependencyStatus() { return mConnectivityDependencyStatus; } /** * Returns the status of the time zone resolution operation used by the provider. */ public @OperationStatus int getTimeZoneResolutionOperationStatus() { return mTimeZoneResolutionOperationStatus; } @Override public String toString() { return "TimeZoneProviderStatus{" + "mLocationDetectionDependencyStatus=" + dependencyStatusToString(mLocationDetectionDependencyStatus) + ", mConnectivityDependencyStatus=" + dependencyStatusToString(mConnectivityDependencyStatus) + ", mTimeZoneResolutionOperationStatus=" + operationStatusToString(mTimeZoneResolutionOperationStatus) + '}'; } /** * Parses a {@link TimeZoneProviderStatus} from a toString() string for manual command-line * testing. * * @hide */ @NonNull public static TimeZoneProviderStatus parseProviderStatus(@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("TimeZoneProviderStatus\\{" + "mLocationDetectionDependencyStatus=([^,]+)" + ", mConnectivityDependencyStatus=([^,]+)" + ", mTimeZoneResolutionOperationStatus=([^\\}]+)" + "\\}"); Matcher matcher = pattern.matcher(arg); if (!matcher.matches()) { throw new IllegalArgumentException("Unable to parse provider status: " + arg); } @DependencyStatus int locationDependencyStatus = dependencyStatusFromString(matcher.group(1)); @DependencyStatus int connectivityDependencyStatus = dependencyStatusFromString(matcher.group(2)); @OperationStatus int timeZoneResolutionOperationStatus = operationStatusFromString(matcher.group(3)); return new TimeZoneProviderStatus(locationDependencyStatus, connectivityDependencyStatus, timeZoneResolutionOperationStatus); } public static final @NonNull Creator CREATOR = new Creator<>() { @Override public TimeZoneProviderStatus createFromParcel(Parcel in) { @DependencyStatus int locationDetectionStatus = in.readInt(); @DependencyStatus int connectivityStatus = in.readInt(); @OperationStatus int timeZoneResolutionStatus = in.readInt(); return new TimeZoneProviderStatus( locationDetectionStatus, connectivityStatus, timeZoneResolutionStatus); } @Override public TimeZoneProviderStatus[] newArray(int size) { return new TimeZoneProviderStatus[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { parcel.writeInt(mLocationDetectionDependencyStatus); parcel.writeInt(mConnectivityDependencyStatus); parcel.writeInt(mTimeZoneResolutionOperationStatus); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } TimeZoneProviderStatus that = (TimeZoneProviderStatus) o; return mLocationDetectionDependencyStatus == that.mLocationDetectionDependencyStatus && mConnectivityDependencyStatus == that.mConnectivityDependencyStatus && mTimeZoneResolutionOperationStatus == that.mTimeZoneResolutionOperationStatus; } @Override public int hashCode() { return Objects.hash( mLocationDetectionDependencyStatus, mConnectivityDependencyStatus, mTimeZoneResolutionOperationStatus); } /** @hide */ public boolean couldEnableTelephonyFallback() { return mLocationDetectionDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT || mLocationDetectionDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS || mConnectivityDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT || mConnectivityDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS; } /** A builder for {@link TimeZoneProviderStatus}. */ public static final class Builder { private @DependencyStatus int mLocationDetectionDependencyStatus = DEPENDENCY_STATUS_UNKNOWN; private @DependencyStatus int mConnectivityDependencyStatus = DEPENDENCY_STATUS_UNKNOWN; private @OperationStatus int mTimeZoneResolutionOperationStatus = OPERATION_STATUS_UNKNOWN; /** * Creates a new builder instance. At creation time all status properties are set to * their "UNKNOWN" value. */ public Builder() { } /** * @hide */ public Builder(TimeZoneProviderStatus toCopy) { mLocationDetectionDependencyStatus = toCopy.mLocationDetectionDependencyStatus; mConnectivityDependencyStatus = toCopy.mConnectivityDependencyStatus; mTimeZoneResolutionOperationStatus = toCopy.mTimeZoneResolutionOperationStatus; } /** * Sets the status of the provider's location detection dependency (where applicable). * See the {@code DEPENDENCY_STATUS_} constants for more information. */ @NonNull public Builder setLocationDetectionDependencyStatus( @DependencyStatus int locationDetectionStatus) { mLocationDetectionDependencyStatus = locationDetectionStatus; return this; } /** * Sets the status of the provider's connectivity dependency (where applicable). * See the {@code DEPENDENCY_STATUS_} constants for more information. */ @NonNull public Builder setConnectivityDependencyStatus(@DependencyStatus int connectivityStatus) { mConnectivityDependencyStatus = connectivityStatus; return this; } /** * Sets the status of the provider's time zone resolution operation. * See the {@code OPERATION_STATUS_} constants for more information. */ @NonNull public Builder setTimeZoneResolutionOperationStatus( @OperationStatus int timeZoneResolutionStatus) { mTimeZoneResolutionOperationStatus = timeZoneResolutionStatus; return this; } /** * Builds a {@link TimeZoneProviderStatus} instance. */ @NonNull public TimeZoneProviderStatus build() { return new TimeZoneProviderStatus( requireValidDependencyStatus(mLocationDetectionDependencyStatus), requireValidDependencyStatus(mConnectivityDependencyStatus), requireValidOperationStatus(mTimeZoneResolutionOperationStatus)); } } private static @OperationStatus int requireValidOperationStatus( @OperationStatus int operationStatus) { if (operationStatus < OPERATION_STATUS_UNKNOWN || operationStatus > OPERATION_STATUS_FAILED) { throw new IllegalArgumentException(Integer.toString(operationStatus)); } return operationStatus; } /** @hide */ @NonNull public static String operationStatusToString(@OperationStatus int operationStatus) { switch (operationStatus) { case OPERATION_STATUS_UNKNOWN: return "UNKNOWN"; case OPERATION_STATUS_NOT_APPLICABLE: return "NOT_APPLICABLE"; case OPERATION_STATUS_OK: return "OK"; case OPERATION_STATUS_FAILED: return "FAILED"; default: throw new IllegalArgumentException("Unknown status: " + operationStatus); } } /** @hide */ public static @OperationStatus int operationStatusFromString( @Nullable String operationStatusString) { if (TextUtils.isEmpty(operationStatusString)) { throw new IllegalArgumentException("Empty status: " + operationStatusString); } switch (operationStatusString) { case "UNKNOWN": return OPERATION_STATUS_UNKNOWN; case "NOT_APPLICABLE": return OPERATION_STATUS_NOT_APPLICABLE; case "OK": return OPERATION_STATUS_OK; case "FAILED": return OPERATION_STATUS_FAILED; default: throw new IllegalArgumentException("Unknown status: " + operationStatusString); } } private static @DependencyStatus int requireValidDependencyStatus( @DependencyStatus int dependencyStatus) { if (dependencyStatus < DEPENDENCY_STATUS_UNKNOWN || dependencyStatus > DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS) { throw new IllegalArgumentException(Integer.toString(dependencyStatus)); } return dependencyStatus; } /** @hide */ @NonNull public static String dependencyStatusToString(@DependencyStatus int dependencyStatus) { switch (dependencyStatus) { case DEPENDENCY_STATUS_UNKNOWN: return "UNKNOWN"; case DEPENDENCY_STATUS_NOT_APPLICABLE: return "NOT_APPLICABLE"; case DEPENDENCY_STATUS_OK: return "OK"; case DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE: return "TEMPORARILY_UNAVAILABLE"; case DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT: return "BLOCKED_BY_ENVIRONMENT"; case DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS: return "DEGRADED_BY_SETTINGS"; case DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS: return "BLOCKED_BY_SETTINGS"; default: throw new IllegalArgumentException("Unknown status: " + dependencyStatus); } } /** @hide */ public static @DependencyStatus int dependencyStatusFromString( @Nullable String dependencyStatusString) { if (TextUtils.isEmpty(dependencyStatusString)) { throw new IllegalArgumentException("Empty status: " + dependencyStatusString); } switch (dependencyStatusString) { case "UNKNOWN": return DEPENDENCY_STATUS_UNKNOWN; case "NOT_APPLICABLE": return DEPENDENCY_STATUS_NOT_APPLICABLE; case "OK": return DEPENDENCY_STATUS_OK; case "TEMPORARILY_UNAVAILABLE": return DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE; case "BLOCKED_BY_ENVIRONMENT": return DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT; case "DEGRADED_BY_SETTINGS": return DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS; case "BLOCKED_BY_SETTINGS": return DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS; default: throw new IllegalArgumentException( "Unknown status: " + dependencyStatusString); } } }