Tidy up the "unbundled" provider API
This commit introduces a LocationTimeZoneEventUnbundled, as
LocationTimeZoneEvent will not be on another API surface so cannot be
reused. (The equivalent from LocationProvider is Location, which is
public API so there is no LocationUnbundled.)
Add an initialization timeout to LocationTimeZoneProviderRequest so that
providers can make intelligent choices about (for example) how long to
spend waiting for geolocation to happen passively.
Test: atest services/tests/servicestests/src/com/android/internal/location/timezone/
Test: atest services/tests/servicestests/src/com/android/server/location/timezone/
Bug: 152744911
Change-Id: Id3e9e6916e8c3a132d8fc892338578ab9d2ff574
diff --git a/location/java/android/location/timezone/LocationTimeZoneEvent.java b/location/java/android/location/timezone/LocationTimeZoneEvent.java
index 55bc507..d3fd5c3 100644
--- a/location/java/android/location/timezone/LocationTimeZoneEvent.java
+++ b/location/java/android/location/timezone/LocationTimeZoneEvent.java
@@ -23,6 +23,8 @@
import android.os.Parcelable;
import android.os.UserHandle;
+import com.android.internal.util.Preconditions;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -37,7 +39,7 @@
@IntDef({ EVENT_TYPE_UNKNOWN, EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUCCESS,
EVENT_TYPE_UNCERTAIN })
- @interface EventType {}
+ public @interface EventType {}
/** Uninitialized value for {@link #mEventType} - must not be used for real events. */
private static final int EVENT_TYPE_UNKNOWN = 0;
@@ -49,8 +51,8 @@
public static final int EVENT_TYPE_PERMANENT_FAILURE = 1;
/**
- * Indicates a successful geolocation time zone detection event. {@link #mTimeZoneIds} will be
- * non-null but can legitimately be empty, e.g. for disputed areas, oceans.
+ * Indicates a successful geolocation time zone detection event. {@link #getTimeZoneIds()} will
+ * be non-null but can legitimately be empty, e.g. for disputed areas, oceans.
*/
public static final int EVENT_TYPE_SUCCESS = 2;
@@ -81,10 +83,7 @@
mTimeZoneIds = immutableList(timeZoneIds);
boolean emptyTimeZoneIdListExpected = eventType != EVENT_TYPE_SUCCESS;
- if (emptyTimeZoneIdListExpected && !timeZoneIds.isEmpty()) {
- throw new IllegalStateException(
- "timeZoneIds must only have values when eventType is success");
- }
+ Preconditions.checkState(!emptyTimeZoneIdListExpected || timeZoneIds.isEmpty());
mElapsedRealtimeNanos = elapsedRealtimeNanos;
}
@@ -102,9 +101,7 @@
*
* <p>This value can be reliably compared to {@link
* android.os.SystemClock#elapsedRealtimeNanos}, to calculate the age of a fix and to compare
- * {@link LocationTimeZoneEvent} fixes. This is reliable because elapsed real-time is guaranteed
- * monotonic for each system boot and continues to increment even when the system is in deep
- * sleep.
+ * {@link LocationTimeZoneEvent} instances.
*
* @return elapsed real-time of fix, in nanoseconds since system boot.
*/
diff --git a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java b/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java
index 2a37ef8..5c9d290 100644
--- a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java
+++ b/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java
@@ -30,7 +30,9 @@
public final class LocationTimeZoneProviderRequest implements Parcelable {
public static final LocationTimeZoneProviderRequest EMPTY_REQUEST =
- new LocationTimeZoneProviderRequest(false);
+ new LocationTimeZoneProviderRequest(
+ false /* reportLocationTimeZone */,
+ 0 /* initializationTimeoutMillis */);
public static final Creator<LocationTimeZoneProviderRequest> CREATOR =
new Creator<LocationTimeZoneProviderRequest>() {
@@ -45,17 +47,36 @@
}
};
- /** Location time zone reporting is requested (true) */
private final boolean mReportLocationTimeZone;
- private LocationTimeZoneProviderRequest(boolean reportLocationTimeZone) {
+ private final long mInitializationTimeoutMillis;
+
+ private LocationTimeZoneProviderRequest(
+ boolean reportLocationTimeZone, long initializationTimeoutMillis) {
mReportLocationTimeZone = reportLocationTimeZone;
+ mInitializationTimeoutMillis = initializationTimeoutMillis;
}
+ /**
+ * Returns {@code true} if the provider should report events related to the device's current
+ * time zone, {@code false} otherwise.
+ */
public boolean getReportLocationTimeZone() {
return mReportLocationTimeZone;
}
+ // TODO(b/152744911) - once there are a couple of implementations, decide whether this needs to
+ // be passed to the LocationTimeZoneProvider and remove if it is not useful.
+ /**
+ * Returns the maximum time that the provider is allowed to initialize before it is expected to
+ * send an event of any sort. Only valid when {@link #getReportLocationTimeZone()} is {@code
+ * true}. Failure to send an event in this time (with some fuzz) may be interpreted as if the
+ * provider is uncertain of the time zone, and/or it could lead to the provider being disabled.
+ */
+ public long getInitializationTimeoutMillis() {
+ return mInitializationTimeoutMillis;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -63,14 +84,15 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeInt(mReportLocationTimeZone ? 1 : 0);
+ parcel.writeBoolean(mReportLocationTimeZone);
+ parcel.writeLong(mInitializationTimeoutMillis);
}
static LocationTimeZoneProviderRequest createFromParcel(Parcel in) {
- ClassLoader classLoader = LocationTimeZoneProviderRequest.class.getClassLoader();
- return new Builder()
- .setReportLocationTimeZone(in.readInt() == 1)
- .build();
+ boolean reportLocationTimeZone = in.readBoolean();
+ long initializationTimeoutMillis = in.readLong();
+ return new LocationTimeZoneProviderRequest(
+ reportLocationTimeZone, initializationTimeoutMillis);
}
@Override
@@ -82,31 +104,28 @@
return false;
}
LocationTimeZoneProviderRequest that = (LocationTimeZoneProviderRequest) o;
- return mReportLocationTimeZone == that.mReportLocationTimeZone;
+ return mReportLocationTimeZone == that.mReportLocationTimeZone
+ && mInitializationTimeoutMillis == that.mInitializationTimeoutMillis;
}
@Override
public int hashCode() {
- return Objects.hash(mReportLocationTimeZone);
+ return Objects.hash(mReportLocationTimeZone, mInitializationTimeoutMillis);
}
@Override
public String toString() {
- StringBuilder s = new StringBuilder();
- s.append("TimeZoneProviderRequest[");
- if (mReportLocationTimeZone) {
- s.append("ON");
- } else {
- s.append("OFF");
- }
- s.append(']');
- return s.toString();
+ return "LocationTimeZoneProviderRequest{"
+ + "mReportLocationTimeZone=" + mReportLocationTimeZone
+ + ", mInitializationTimeoutMillis=" + mInitializationTimeoutMillis
+ + "}";
}
/** @hide */
public static final class Builder {
private boolean mReportLocationTimeZone;
+ private long mInitializationTimeoutMillis;
/**
* Sets the property that enables / disables the provider. This is set to {@code false} by
@@ -117,10 +136,20 @@
return this;
}
+ /**
+ * Sets the initialization timeout. See {@link
+ * LocationTimeZoneProviderRequest#getInitializationTimeoutMillis()} for details.
+ */
+ public Builder setInitializationTimeoutMillis(long timeoutMillis) {
+ mInitializationTimeoutMillis = timeoutMillis;
+ return this;
+ }
+
/** Builds the {@link LocationTimeZoneProviderRequest} instance. */
@NonNull
public LocationTimeZoneProviderRequest build() {
- return new LocationTimeZoneProviderRequest(this.mReportLocationTimeZone);
+ return new LocationTimeZoneProviderRequest(
+ mReportLocationTimeZone, mInitializationTimeoutMillis);
}
}
}
diff --git a/location/lib/Android.bp b/location/lib/Android.bp
index cd45e8e..c0188c0 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -20,5 +20,8 @@
libs: [
"androidx.annotation_annotation",
],
- api_packages: ["com.android.location.provider"],
+ api_packages: [
+ "com.android.location.provider",
+ "com.android.location.timezone.provider",
+ ],
}
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java
new file mode 100644
index 0000000..3675574
--- /dev/null
+++ b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 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.location.timezone.provider;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.location.timezone.LocationTimeZoneEvent;
+import android.os.SystemClock;
+import android.os.UserHandle;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * An event from a {@link LocationTimeZoneProviderBase} sent while determining a device's time zone
+ * using its location.
+ *
+ * @hide
+ */
+public final class LocationTimeZoneEventUnbundled {
+
+ @IntDef({ EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUCCESS, EVENT_TYPE_UNCERTAIN })
+ @interface EventType {}
+
+ /**
+ * Indicates there was a permanent failure. This is not generally expected, and probably means a
+ * required backend service has been turned down, or the client is unreasonably old.
+ */
+ public static final int EVENT_TYPE_PERMANENT_FAILURE =
+ LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE;
+
+ /**
+ * Indicates a successful geolocation time zone detection event. {@link #getTimeZoneIds()} will
+ * be non-null but can legitimately be empty, e.g. for disputed areas, oceans.
+ */
+ public static final int EVENT_TYPE_SUCCESS = LocationTimeZoneEvent.EVENT_TYPE_SUCCESS;
+
+ /**
+ * Indicates the time zone is not known because of an expected runtime state or error, e.g. when
+ * the provider is unable to detect location, or there was a problem when resolving the location
+ * to a time zone.
+ */
+ public static final int EVENT_TYPE_UNCERTAIN = LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN;
+
+ @NonNull
+ private final LocationTimeZoneEvent mDelegate;
+
+ private LocationTimeZoneEventUnbundled(@NonNull LocationTimeZoneEvent delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns the event type.
+ */
+ @Nullable
+ public @EventType int getEventType() {
+ return mDelegate.getEventType();
+ }
+
+ /**
+ * Gets the time zone IDs of this event. Contains zero or more IDs for a successful lookup.
+ * The value is undefined for an unsuccessful lookup. See also {@link #getEventType()}.
+ */
+ @NonNull
+ public List<String> getTimeZoneIds() {
+ return mDelegate.getTimeZoneIds();
+ }
+
+ /**
+ * Returns the information from this as a {@link LocationTimeZoneEvent}.
+ * @hide
+ */
+ @NonNull
+ public LocationTimeZoneEvent getInternalLocationTimeZoneEvent() {
+ return mDelegate;
+ }
+
+ @Override
+ public String toString() {
+ return "LocationTimeZoneEventUnbundled{"
+ + "mDelegate=" + mDelegate
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ LocationTimeZoneEventUnbundled that = (LocationTimeZoneEventUnbundled) o;
+ return mDelegate.equals(that.mDelegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return mDelegate.hashCode();
+ }
+
+ /**
+ * A builder of {@link LocationTimeZoneEventUnbundled} instances.
+ *
+ * @hide
+ */
+ public static final class Builder {
+
+ private @EventType int mEventType;
+ private @NonNull List<String> mTimeZoneIds = Collections.emptyList();
+
+ /**
+ * Set the time zone ID of this event.
+ */
+ @NonNull
+ public Builder setEventType(@EventType int eventType) {
+ checkValidEventType(eventType);
+ mEventType = eventType;
+ return this;
+ }
+
+ /**
+ * Sets the time zone IDs of this event.
+ */
+ @NonNull
+ public Builder setTimeZoneIds(@NonNull List<String> timeZoneIds) {
+ mTimeZoneIds = Objects.requireNonNull(timeZoneIds);
+ return this;
+ }
+
+ /**
+ * Builds a {@link LocationTimeZoneEventUnbundled} instance.
+ */
+ @NonNull
+ public LocationTimeZoneEventUnbundled build() {
+ final int internalEventType = this.mEventType;
+ LocationTimeZoneEvent event = new LocationTimeZoneEvent.Builder()
+ .setUserHandle(UserHandle.of(ActivityManager.getCurrentUser()))
+ .setEventType(internalEventType)
+ .setTimeZoneIds(mTimeZoneIds)
+ .setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos())
+ .build();
+ return new LocationTimeZoneEventUnbundled(event);
+ }
+ }
+
+ private static int checkValidEventType(int eventType) {
+ if (eventType != EVENT_TYPE_SUCCESS
+ && eventType != EVENT_TYPE_UNCERTAIN
+ && eventType != EVENT_TYPE_PERMANENT_FAILURE) {
+ throw new IllegalStateException("eventType=" + eventType);
+ }
+ return eventType;
+ }
+}
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
index c533c20..9df7166 100644
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
+++ b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
@@ -18,7 +18,6 @@
import android.annotation.Nullable;
import android.content.Context;
-import android.location.timezone.LocationTimeZoneEvent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -30,12 +29,34 @@
import java.util.Objects;
/**
- * Base class for location time zone providers implemented as unbundled services.
+ * A base class for location time zone providers implemented as unbundled services.
*
- * TODO(b/152744911): Provide details of the expected service actions and threading.
+ * <p>Provider implementations are enabled / disabled via a call to {@link
+ * #onSetRequest(LocationTimeZoneProviderRequestUnbundled)}.
+ *
+ * <p>Once enabled, providers are expected to detect the time zone if possible, and report the
+ * result via {@link #reportLocationTimeZoneEvent(LocationTimeZoneEventUnbundled)} with a type of
+ * either {@link LocationTimeZoneEventUnbundled#EVENT_TYPE_UNCERTAIN} or {@link
+ * LocationTimeZoneEventUnbundled#EVENT_TYPE_SUCCESS}. Providers may also report that they have
+ * permanently failed by sending an event of type {@link
+ * LocationTimeZoneEventUnbundled#EVENT_TYPE_PERMANENT_FAILURE}. See the javadocs for each event
+ * type for details.
+ *
+ * <p>Providers are expected to issue their first event within {@link
+ * LocationTimeZoneProviderRequest#getInitializationTimeoutMillis()}.
+ *
+ * <p>Once disabled or have failed, providers are required to stop producing events.
+ *
+ * <p>Threading:
+ *
+ * <p>Calls to {@link #reportLocationTimeZoneEvent(LocationTimeZoneEventUnbundled)} can be made on
+ * on any thread, but may be processed asynchronously by the system server. Similarly, calls to
+ * {@link #onSetRequest(LocationTimeZoneProviderRequestUnbundled)} may occur on any thread.
*
* <p>IMPORTANT: This class is effectively a public API for unbundled applications, and must remain
* API stable.
+ *
+ * @hide
*/
public abstract class LocationTimeZoneProviderBase {
@@ -64,11 +85,11 @@
/**
* Reports a new location time zone event from this provider.
*/
- public void reportLocationTimeZoneEvent(LocationTimeZoneEvent locationTimeZoneEvent) {
+ protected void reportLocationTimeZoneEvent(LocationTimeZoneEventUnbundled event) {
ILocationTimeZoneProviderManager manager = mManager;
if (manager != null) {
try {
- manager.onLocationTimeZoneEvent(locationTimeZoneEvent);
+ manager.onLocationTimeZoneEvent(event.getInternalLocationTimeZoneEvent());
} catch (RemoteException | RuntimeException e) {
Log.w(mTag, e);
}
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
index e898bbf..ab50dc3 100644
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
+++ b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
@@ -34,15 +34,30 @@
private final LocationTimeZoneProviderRequest mRequest;
+ /** @hide */
public LocationTimeZoneProviderRequestUnbundled(
@NonNull LocationTimeZoneProviderRequest request) {
mRequest = Objects.requireNonNull(request);
}
+ /**
+ * Returns {@code true} if the provider should report events related to the device's current
+ * time zone, {@code false} otherwise.
+ */
public boolean getReportLocationTimeZone() {
return mRequest.getReportLocationTimeZone();
}
+ /**
+ * Returns the maximum time that the provider is allowed to initialize before it is expected to
+ * send an event of any sort. Only valid when {@link #getReportLocationTimeZone()} is {@code
+ * true}. Failure to send an event in this time (with some fuzz) may be interpreted as if the
+ * provider is uncertain of the time zone, and/or it could lead to the provider being disabled.
+ */
+ public long getInitializationTimeoutMillis() {
+ return mRequest.getInitializationTimeoutMillis();
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {