Merge "Tidy up the "unbundled" provider API"

This commit is contained in:
Neil Fuller
2020-09-24 18:51:00 +00:00
committed by Android (Google) Code Review
16 changed files with 414 additions and 80 deletions

View File

@@ -23,6 +23,8 @@ import android.os.Parcel;
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 @@ public final class LocationTimeZoneEvent implements Parcelable {
@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 final class LocationTimeZoneEvent implements Parcelable {
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 @@ public final class LocationTimeZoneEvent implements Parcelable {
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 @@ public final class LocationTimeZoneEvent implements Parcelable {
*
* <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.
*/

View File

@@ -30,7 +30,9 @@ import java.util.Objects;
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 @@ public final class LocationTimeZoneProviderRequest implements Parcelable {
}
};
/** 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 @@ public final class LocationTimeZoneProviderRequest implements Parcelable {
@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 @@ public final class LocationTimeZoneProviderRequest implements Parcelable {
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 @@ public final class LocationTimeZoneProviderRequest implements Parcelable {
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);
}
}
}

View File

@@ -20,5 +20,8 @@ java_sdk_library {
libs: [
"androidx.annotation_annotation",
],
api_packages: ["com.android.location.provider"],
api_packages: [
"com.android.location.provider",
"com.android.location.timezone.provider",
],
}

View File

@@ -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;
}
}

View File

@@ -18,7 +18,6 @@ package com.android.location.timezone.provider;
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 com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
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 @@ public abstract class LocationTimeZoneProviderBase {
/**
* 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);
}

View File

@@ -34,15 +34,30 @@ public final class LocationTimeZoneProviderRequestUnbundled {
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) {

View File

@@ -29,6 +29,7 @@ import android.util.Slog;
import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
import java.time.Duration;
import java.util.Objects;
/**
@@ -142,14 +143,13 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
}
@Override
void onEnable() {
void onEnable(@NonNull Duration initializationTimeout) {
// Set a request on the proxy - it will be sent immediately if the service is bound,
// or will be sent as soon as the service becomes bound.
// TODO(b/152744911): Decide whether to send a timeout so the provider knows how long
// it has to generate the first event before it could be bypassed.
LocationTimeZoneProviderRequest request =
new LocationTimeZoneProviderRequest.Builder()
.setReportLocationTimeZone(true)
.setInitializationTimeoutMillis(initializationTimeout.toMillis())
.build();
mProxy.setRequest(request);
}

View File

@@ -22,6 +22,7 @@ import com.android.server.LocalServices;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.TimeZoneDetectorInternal;
import java.time.Duration;
import java.util.Objects;
/**
@@ -30,6 +31,10 @@ import java.util.Objects;
*/
class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment {
private static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
private static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
private static final Duration PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
@NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal;
@NonNull private final LocationTimeZoneProviderController mController;
@@ -45,7 +50,26 @@ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Envir
}
@Override
@NonNull
ConfigurationInternal getCurrentUserConfigurationInternal() {
return mTimeZoneDetectorInternal.getCurrentUserConfigurationInternal();
}
@Override
@NonNull
Duration getProviderInitializationTimeout() {
return PROVIDER_INITIALIZATION_TIMEOUT;
}
@Override
@NonNull
Duration getProviderInitializationTimeoutFuzz() {
return PROVIDER_INITIALIZATION_TIMEOUT_FUZZ;
}
@Override
@NonNull
Duration getUncertaintyDelay() {
return PROVIDER_UNCERTAINTY_DELAY;
}
}

View File

@@ -33,7 +33,6 @@ import android.location.timezone.LocationTimeZoneEvent;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
@@ -50,9 +49,6 @@ import java.util.Objects;
*/
class ControllerImpl extends LocationTimeZoneProviderController {
@VisibleForTesting
static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(5);
@NonNull private final LocationTimeZoneProvider mProvider;
@NonNull private final SingleRunnableQueue mDelayedSuggestionQueue;
@@ -140,7 +136,8 @@ class ControllerImpl extends LocationTimeZoneProviderController {
switch (providerState.stateEnum) {
case PROVIDER_STATE_DISABLED: {
debugLog("Enabling " + mProvider);
mProvider.enable(configuration);
mProvider.enable(
configuration, mEnvironment.getProviderInitializationTimeout());
break;
}
case PROVIDER_STATE_ENABLED: {
@@ -183,13 +180,16 @@ class ControllerImpl extends LocationTimeZoneProviderController {
if (isProviderEnabled) {
if (!providerWasEnabled) {
// When a provider has first been enabled, we allow it some time for it to
// initialize.
// initialize before sending its first event.
Duration initializationTimeout = mEnvironment.getProviderInitializationTimeout()
.plus(mEnvironment.getProviderInitializationTimeoutFuzz());
// This sets up an empty suggestion to trigger if no explicit "certain" or
// "uncertain" suggestion preempts it within UNCERTAINTY_DELAY. If, for some reason,
// the provider does provide any events then this scheduled suggestion will ensure
// the controller makes at least an uncertain suggestion.
suggestDelayed(createEmptySuggestion(
"No event received in delay=" + UNCERTAINTY_DELAY), UNCERTAINTY_DELAY);
// "uncertain" suggestion preempts it within initializationTimeout. If, for some
// reason, the provider does not produce any events then this scheduled suggestion
// will ensure the controller makes at least an "uncertain" suggestion.
suggestDelayed(createEmptySuggestion("No event received from provider in"
+ " initializationTimeout=" + initializationTimeout),
initializationTimeout);
}
} else {
// Clear any queued suggestions.
@@ -199,7 +199,8 @@ class ControllerImpl extends LocationTimeZoneProviderController {
// made, then a new "uncertain" suggestion must be made to indicate the provider no
// longer has an opinion and will not be sending updates.
if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) {
suggestImmediate(createEmptySuggestion(""));
suggestImmediate(createEmptySuggestion(
"Provider disabled, clearing previous suggestion"));
}
}
}
@@ -309,21 +310,23 @@ class ControllerImpl extends LocationTimeZoneProviderController {
*
* <p>Providers are expected to report their uncertainty as soon as they become uncertain, as
* this enables the most flexibility for the controller to enable other providers when there are
* multiple ones. The controller is therefore responsible for deciding when to make a
* multiple ones available. The controller is therefore responsible for deciding when to make a
* "uncertain" suggestion.
*
* <p>This method schedules an "uncertain" suggestion (if one isn't already scheduled) to be
* made later if nothing else preempts it. It can be preempted if the provider becomes certain
* (or does anything else that calls {@link #suggestImmediate(GeolocationTimeZoneSuggestion)})
* within UNCERTAINTY_DELAY. Preemption causes the scheduled "uncertain" event to be cancelled.
* If the provider repeatedly sends uncertainty events within UNCERTAINTY_DELAY, those events
* are effectively ignored (i.e. the timer is not reset each time).
* within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled
* "uncertain" event to be cancelled. If the provider repeatedly sends uncertainty events within
* the uncertainty delay period, those events are effectively ignored (i.e. the timer is not
* reset each time).
*/
private void scheduleUncertainSuggestionIfNeeded(@Nullable LocationTimeZoneEvent event) {
if (mPendingSuggestion == null || mPendingSuggestion.getZoneIds() != null) {
GeolocationTimeZoneSuggestion suggestion = createEmptySuggestion(
"provider=" + mProvider + " became uncertain, event=" + event);
suggestDelayed(suggestion, UNCERTAINTY_DELAY);
// Only send the empty suggestion after the uncertainty delay.
suggestDelayed(suggestion, mEnvironment.getUncertaintyDelay());
}
}
@@ -334,6 +337,11 @@ class ControllerImpl extends LocationTimeZoneProviderController {
ipw.increaseIndent(); // level 1
ipw.println("mCurrentUserConfiguration=" + mCurrentUserConfiguration);
ipw.println("providerInitializationTimeout="
+ mEnvironment.getProviderInitializationTimeout());
ipw.println("providerInitializationTimeoutFuzz="
+ mEnvironment.getProviderInitializationTimeoutFuzz());
ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay());
ipw.println("mPendingSuggestion=" + mPendingSuggestion);
ipw.println("mLastSuggestion=" + mLastSuggestion);

View File

@@ -37,6 +37,7 @@ import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.Dumpable;
import com.android.server.timezonedetector.ReferenceWithHistory;
import java.time.Duration;
import java.util.Objects;
/**
@@ -368,7 +369,8 @@ abstract class LocationTimeZoneProvider implements Dumpable {
* #getCurrentState()} is at {@link ProviderState#PROVIDER_STATE_DISABLED}. This method must be
* called using the handler thread from the {@link ThreadingDomain}.
*/
final void enable(@NonNull ConfigurationInternal currentUserConfiguration) {
final void enable(@NonNull ConfigurationInternal currentUserConfiguration,
@NonNull Duration initializationTimeout) {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
@@ -378,14 +380,14 @@ abstract class LocationTimeZoneProvider implements Dumpable {
ProviderState newState = currentState.newState(
PROVIDER_STATE_ENABLED, null, currentUserConfiguration, "enable() called");
setCurrentState(newState, false);
onEnable();
onEnable(initializationTimeout);
}
}
/**
* Implemented by subclasses to do work during {@link #enable}.
*/
abstract void onEnable();
abstract void onEnable(@NonNull Duration initializationTimeout);
/**
* Disables the provider. It is an error* to call this method except when the {@link

View File

@@ -24,6 +24,7 @@ import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.Dumpable;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
import java.time.Duration;
import java.util.Objects;
/**
@@ -100,6 +101,24 @@ abstract class LocationTimeZoneProviderController implements Dumpable {
/** Returns the {@link ConfigurationInternal} for the current user of the device. */
abstract ConfigurationInternal getCurrentUserConfigurationInternal();
/**
* Returns the value passed to LocationTimeZoneProviders informing them of how long they
* have to return their first time zone suggestion.
*/
abstract Duration getProviderInitializationTimeout();
/**
* Returns the extra time granted on top of {@link #getProviderInitializationTimeout()} to
* allow for slop like communication delays.
*/
abstract Duration getProviderInitializationTimeoutFuzz();
/**
* Returns the delay allowed after receiving uncertainty from a provider before it should be
* passed on.
*/
abstract Duration getUncertaintyDelay();
}
/**

View File

@@ -23,6 +23,8 @@ import android.annotation.Nullable;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import java.time.Duration;
/**
* A {@link LocationTimeZoneProvider} that provides minimal responses needed for the {@link
* LocationTimeZoneProviderController} to operate correctly when there is no "real" provider
@@ -55,7 +57,7 @@ class NullLocationTimeZoneProvider extends LocationTimeZoneProvider {
}
@Override
void onEnable() {
void onEnable(@NonNull Duration initializationTimeout) {
// Report a failure (asynchronously using the mThreadingDomain thread to avoid recursion).
mThreadingDomain.post(()-> {
// Enter the perm-failed state.

View File

@@ -20,16 +20,20 @@ import static android.location.timezone.ParcelableTestSupport.assertRoundTripPar
import org.junit.Test;
import java.time.Duration;
public class LocationTimeZoneProviderRequestTest {
@Test
public void testParcelable() {
LocationTimeZoneProviderRequest.Builder builder =
new LocationTimeZoneProviderRequest.Builder()
.setReportLocationTimeZone(true);
.setReportLocationTimeZone(false);
assertRoundTripParcelable(builder.build());
builder.setReportLocationTimeZone(false);
builder.setReportLocationTimeZone(true)
.setInitializationTimeoutMillis(Duration.ofMinutes(5).toMillis());
assertRoundTripParcelable(builder.build());
}
}

View File

@@ -18,7 +18,6 @@ package com.android.server.location.timezone;
import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_SUCCESS;
import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN;
import static com.android.server.location.timezone.ControllerImpl.UNCERTAINTY_DELAY;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_DISABLED;
@@ -47,6 +46,7 @@ import com.android.server.timezonedetector.TestState;
import org.junit.Before;
import org.junit.Test;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -92,7 +92,8 @@ public class ControllerImplTest {
mTestLocationTimeZoneProvider.assertInitialized();
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestThreadingDomain.assertNextQueueItemIsDelayed(UNCERTAINTY_DELAY);
Duration expectedTimeout = expectedProviderInitializationTimeout();
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
mTestCallback.assertNoSuggestionMade();
}
@@ -121,7 +122,8 @@ public class ControllerImplTest {
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
Duration expectedTimeout = expectedProviderInitializationTimeout();
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
// Simulate time passing with no event being received.
mTestThreadingDomain.executeNext();
@@ -140,7 +142,8 @@ public class ControllerImplTest {
controllerImpl.initialize(testEnvironment, mTestCallback);
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
Duration expectedTimeout = expectedProviderInitializationTimeout();
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
mTestCallback.assertNoSuggestionMade();
// Simulate a location event being received by the provider. This should cause a suggestion
@@ -163,7 +166,8 @@ public class ControllerImplTest {
controllerImpl.initialize(testEnvironment, mTestCallback);
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
Duration expectedTimeout = expectedProviderInitializationTimeout();
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
mTestCallback.assertNoSuggestionMade();
// Simulate a location event being received by the provider. This should cause a suggestion
@@ -203,7 +207,8 @@ public class ControllerImplTest {
controllerImpl.initialize(testEnvironment, mTestCallback);
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
Duration expectedTimeout = expectedProviderInitializationTimeout();
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
mTestCallback.assertNoSuggestionMade();
// Simulate a location event being received by the provider. This should cause a suggestion
@@ -216,15 +221,16 @@ public class ControllerImplTest {
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
// Uncertainty should cause
// Uncertainty should cause a suggestion to (only) be queued.
mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
mTestThreadingDomain.assertSingleDelayedQueueItem(testEnvironment.getUncertaintyDelay());
mTestCallback.assertNoSuggestionMade();
// And a third event should cause yet another suggestion.
// And a third event should cause yet another suggestion and for the queued item to be
// removed.
mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
@@ -250,7 +256,8 @@ public class ControllerImplTest {
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestThreadingDomain.assertNextQueueItemIsDelayed(UNCERTAINTY_DELAY);
Duration expectedTimeout = expectedProviderInitializationTimeout();
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
mTestCallback.assertNoSuggestionMade();
// Now signal a config change so that geo detection is disabled.
@@ -270,7 +277,8 @@ public class ControllerImplTest {
controllerImpl.initialize(testEnvironment, mTestCallback);
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
Duration expectedTimeout = expectedProviderInitializationTimeout();
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
mTestCallback.assertNoSuggestionMade();
// Simulate a location event being received by the provider. This should cause a suggestion
@@ -304,7 +312,8 @@ public class ControllerImplTest {
// There should be a runnable scheduled to suggest uncertainty if no event is received.
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
Duration expectedTimeout = expectedProviderInitializationTimeout();
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
mTestCallback.assertNoSuggestionMade();
// Have the provider suggest a time zone.
@@ -328,7 +337,8 @@ public class ControllerImplTest {
int[] expectedStateTransitions = { PROVIDER_STATE_DISABLED, PROVIDER_STATE_ENABLED };
mTestLocationTimeZoneProvider.assertStateChangesAndCommit(expectedStateTransitions);
mTestLocationTimeZoneProvider.assertConfig(USER2_CONFIG_GEO_DETECTION_ENABLED);
mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
expectedTimeout = expectedProviderInitializationTimeout();
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
mTestCallback.assertNoSuggestionMade();
// Simulate no event being received, and time passing.
@@ -351,8 +361,18 @@ public class ControllerImplTest {
return builder.build();
}
private Duration expectedProviderInitializationTimeout() {
return TestEnvironment.PROVIDER_INITIALIZATION_TIMEOUT
.plus(TestEnvironment.PROVIDER_INITIALIZATION_TIMEOUT_FUZZ);
}
private static class TestEnvironment extends LocationTimeZoneProviderController.Environment {
static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
private static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(3);
private final LocationTimeZoneProviderController mController;
private ConfigurationInternal mConfigurationInternal;
@@ -369,6 +389,21 @@ public class ControllerImplTest {
return mConfigurationInternal;
}
@Override
Duration getProviderInitializationTimeout() {
return PROVIDER_INITIALIZATION_TIMEOUT;
}
@Override
Duration getProviderInitializationTimeoutFuzz() {
return PROVIDER_INITIALIZATION_TIMEOUT_FUZZ;
}
@Override
Duration getUncertaintyDelay() {
return UNCERTAINTY_DELAY;
}
void simulateConfigChange(ConfigurationInternal newConfig) {
ConfigurationInternal oldConfig = mConfigurationInternal;
mConfigurationInternal = Objects.requireNonNull(newConfig);
@@ -432,7 +467,7 @@ public class ControllerImplTest {
}
@Override
void onEnable() {
void onEnable(Duration initializationTimeout) {
// Nothing needed for tests.
}
@@ -464,7 +499,7 @@ public class ControllerImplTest {
/**
* Asserts the provider's config matches the expected, and the current state is set
* accordinly. Commits the latest changes to the state.
* accordingly. Commits the latest changes to the state.
*/
void assertIsEnabled(@NonNull ConfigurationInternal expectedConfig) {
assertConfig(expectedConfig);

View File

@@ -34,6 +34,8 @@ import com.android.server.timezonedetector.TestState;
import org.junit.Before;
import org.junit.Test;
import java.time.Duration;
/**
* Tests for {@link NullLocationTimeZoneProvider} and, indirectly, the class it extends
* {@link LocationTimeZoneProvider}.
@@ -73,7 +75,8 @@ public class NullLocationTimeZoneProviderTest {
provider.initialize(providerState -> mTestController.onProviderStateChange(providerState));
ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
provider.enable(config);
Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
provider.enable(config, arbitraryInitializationTimeout);
// The StubbedProvider should enters enabled state, but immediately schedule a runnable to
// switch to perm failure.

View File

@@ -105,8 +105,8 @@ class TestThreadingDomain extends ThreadingDomain {
assertTrue(getNextQueueItemDelayMillis() == 0);
}
void assertNextQueueItemIsDelayed(Duration expectedDelay) {
assertTrue(getNextQueueItemDelayMillis() == expectedDelay.toMillis());
private void assertNextQueueItemIsDelayed(Duration expectedDelay) {
assertEquals(getNextQueueItemDelayMillis(), expectedDelay.toMillis());
}
void assertQueueEmpty() {