blob: e0b78e9ae008e74784a91980a1b29feba0c7437d [file] [log] [blame]
/*
* 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}.
*
* <p>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.
*
* <p>Time zone providers are expected to work in one of two ways:
* <ol>
* <li>Location: Providers will determine location and then map that location to one or more
* time zone IDs.</li>
* <li>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.
* </li>
* </ol>
*
* <p>The status properties are:
* <ul>
* <li>location detection - for location-based providers, the status of the location detection
* mechanism</li>
* <li>connectivity - connectivity can influence providers directly, for example if they use
* a networked service to map location to time zone ID, or use geo IP, or indirectly for
* location detection (e.g. for the network location provider.</li>
* <li>time zone resolution - the status related to determining a time zone ID or using a
* detected time zone ID. For example, a networked service may be reachable (i.e. connectivity
* is working) but the service could return errors, a time zone ID detected may not be usable
* for a device because of TZDB version skew, or external indirect signals may available but
* do not match the properties of a known time zone ID.</li>
* </ul>
*
* @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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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<TimeZoneProviderStatus> 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);
}
}
}