Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3299f091 authored by Neil Fuller's avatar Neil Fuller
Browse files

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
parent 8e8eaa86
Loading
Loading
Loading
Loading
+7 −10
Original line number Diff line number Diff line
@@ -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.
     */
+49 −20
Original line number Diff line number Diff line
@@ -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);
        }
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -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",
    ],
}
+172 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+26 −5
Original line number Diff line number Diff line
@@ -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);
            }
Loading