482 lines
19 KiB
Java
482 lines
19 KiB
Java
/*
|
|
* 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);
|
|
}
|
|
}
|
|
}
|