From b94da68e104c869303cab748d5d35b9b80a674a2 Mon Sep 17 00:00:00 2001 From: Neil Fuller Date: Mon, 14 Dec 2020 20:59:39 +0000 Subject: [PATCH] Add System APIs for TimeZoneProvider First step of moving LocationTimeZoneProvider from the com.android.location.provider API surface to system API. This copies / renames various APIs, generally dropping "Location" from the name to try to generalize them. The com/android/server/location/timezone classes have been updated to use the new classes but the names have been left for now as they are an internal detail and it's not clear which would be "location specific" and which might be general. Bug: 175633818 Test: build / treehugger Change-Id: Ied2b43336ef80d2b4ed2eba8849ea1976145df8d --- core/api/system-current.txt | 32 ++ .../service/timezone/ITimeZoneProvider.aidl | 28 ++ .../timezone/ITimeZoneProviderManager.aidl | 28 ++ .../timezone/TimeZoneProviderService.java | 225 ++++++++++ .../timezone/TimeZoneProviderSuggestion.aidl | 22 + .../timezone/TimeZoneProviderSuggestion.java | 185 ++++++++ core/res/AndroidManifest.xml | 7 +- .../timezone/ParcelableTestSupport.java | 53 +++ .../TimeZoneProviderSuggestionTest.java | 107 +++++ .../BinderLocationTimeZoneProvider.java | 59 ++- .../location/timezone/ControllerImpl.java | 195 ++++---- .../timezone/HandlerThreadingDomain.java | 5 +- .../LocationTimeZoneManagerService.java | 7 +- .../timezone/LocationTimeZoneProvider.java | 215 ++++----- .../LocationTimeZoneProviderController.java | 4 +- .../LocationTimeZoneProviderProxy.java | 27 +- .../NullLocationTimeZoneProvider.java | 12 +- .../RealLocationTimeZoneProviderProxy.java | 67 ++- .../SimulatedBinderProviderEvent.java | 59 ++- ...imulatedLocationTimeZoneProviderProxy.java | 15 +- .../location/timezone/ThreadingDomain.java | 9 +- .../timezone/TimeZoneProviderEvent.java | 143 ++++++ .../timezone/TimeZoneProviderRequest.java | 105 +++++ .../location/timezone/ControllerImplTest.java | 419 +++++++++--------- .../NullLocationTimeZoneProviderTest.java | 13 +- 25 files changed, 1489 insertions(+), 552 deletions(-) create mode 100644 core/java/android/service/timezone/ITimeZoneProvider.aidl create mode 100644 core/java/android/service/timezone/ITimeZoneProviderManager.aidl create mode 100644 core/java/android/service/timezone/TimeZoneProviderService.java create mode 100644 core/java/android/service/timezone/TimeZoneProviderSuggestion.aidl create mode 100644 core/java/android/service/timezone/TimeZoneProviderSuggestion.java create mode 100644 core/tests/coretests/src/android/service/timezone/ParcelableTestSupport.java create mode 100644 core/tests/coretests/src/android/service/timezone/TimeZoneProviderSuggestionTest.java create mode 100644 services/core/java/com/android/server/location/timezone/TimeZoneProviderEvent.java create mode 100644 services/core/java/com/android/server/location/timezone/TimeZoneProviderRequest.java diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 821d3f147282d..cfc3288821596 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9640,6 +9640,38 @@ package android.service.textclassifier { } +package android.service.timezone { + + public abstract class TimeZoneProviderService extends android.app.Service { + ctor public TimeZoneProviderService(); + method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent); + method public abstract void onStartUpdates(long); + method public abstract void onStopUpdates(); + method public final void reportPermanentFailure(@NonNull Throwable); + method public final void reportSuggestion(@NonNull android.service.timezone.TimeZoneProviderSuggestion); + method public final void reportUncertain(); + field public static final String BIND_PERMISSION = "android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER"; + field public static final String PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.PrimaryLocationTimeZoneProviderService"; + field public static final String SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.SecondaryLocationTimeZoneProviderService"; + } + + public final class TimeZoneProviderSuggestion implements android.os.Parcelable { + method public int describeContents(); + method public long getElapsedRealtimeMillis(); + method @NonNull public java.util.List getTimeZoneIds(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class TimeZoneProviderSuggestion.Builder { + ctor public TimeZoneProviderSuggestion.Builder(); + method @NonNull public android.service.timezone.TimeZoneProviderSuggestion build(); + method @NonNull public android.service.timezone.TimeZoneProviderSuggestion.Builder setElapsedRealtimeMillis(long); + method @NonNull public android.service.timezone.TimeZoneProviderSuggestion.Builder setTimeZoneIds(@NonNull java.util.List); + } + +} + package android.service.trust { public class TrustAgentService extends android.app.Service { diff --git a/core/java/android/service/timezone/ITimeZoneProvider.aidl b/core/java/android/service/timezone/ITimeZoneProvider.aidl new file mode 100644 index 0000000000000..62fa15757857b --- /dev/null +++ b/core/java/android/service/timezone/ITimeZoneProvider.aidl @@ -0,0 +1,28 @@ +/* + * 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 android.service.timezone; + +import android.service.timezone.ITimeZoneProviderManager; + +/** + * @hide + */ +oneway interface ITimeZoneProvider { + void setTimeZoneProviderManager(in @nullable ITimeZoneProviderManager manager); + void startUpdates(in long initializationTimeoutMillis); + void stopUpdates(); +} diff --git a/core/java/android/service/timezone/ITimeZoneProviderManager.aidl b/core/java/android/service/timezone/ITimeZoneProviderManager.aidl new file mode 100644 index 0000000000000..bf4fe0a7ffa68 --- /dev/null +++ b/core/java/android/service/timezone/ITimeZoneProviderManager.aidl @@ -0,0 +1,28 @@ +/* + * 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 android.service.timezone; + +import android.service.timezone.TimeZoneProviderSuggestion; + +/** + * @hide + */ +oneway interface ITimeZoneProviderManager { + void onTimeZoneProviderSuggestion(in TimeZoneProviderSuggestion timeZoneProviderSuggestion); + void onTimeZoneProviderUncertain(); + void onTimeZoneProviderPermanentFailure(in String failureReason); +} diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java new file mode 100644 index 0000000000000..9533a8f9f13ba --- /dev/null +++ b/core/java/android/service/timezone/TimeZoneProviderService.java @@ -0,0 +1,225 @@ +/* + * 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 android.service.timezone; + +import android.annotation.DurationMillisLong; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.os.BackgroundThread; + +import java.util.Objects; + +/** + * A service to generate time zone callbacks to the platform. Developers must extend this class. + * + *

Provider implementations are started via a call to {@link #onStartUpdates(long)} and stopped + * via a call to {@link #onStopUpdates()}. + * + *

Once started, providers are expected to detect the time zone if possible, and report the + * result via {@link #reportSuggestion(TimeZoneProviderSuggestion)} or {@link + * #reportUncertain()}. Providers may also report that they have permanently failed + * by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each + * method for details. + * + *

After starting, providers are expected to issue their first callback within the timeout + * duration specified in {@link #onStartUpdates(long)}, or they will be implicitly considered to be + * uncertain. + * + *

Once stopped or failed, providers are required to stop generating callbacks. + * + *

Provider discovery: + * + *

You must declare the service in your manifest file with the + * {@link android.Manifest.permission#INSTALL_LOCATION_TIME_ZONE_PROVIDER} permission, + * and include an intent filter with the necessary action indicating what type of provider it is. + * + *

Device configuration can influence how {@link TimeZoneProviderService}s are discovered. + * In one mode, there can be multiple {@link TimeZoneProviderService}s configured with the same + * action, and the one with the highest "serviceVersion" metadata will be used. + * + *

{@link TimeZoneProviderService}s may be deployed into processes that run once-per-user + * or once-per-device (i.e. they service multiple users). The "serviceIsMultiuser" metadata must + * be set accordingly. + * + *

Provider types: + * + *

Android currently supports up to two location-derived time zone providers. These are called + * the "primary" and "secondary" location time zone provider, configured using {@link + * #PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE} and {@link + * #SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE} respectively. The primary location time + * zone provider is started first and will be used until becomes uncertain or fails, at which point + * the secondary provider will be started. + * + * For example: + *

+ *     <service android:name=".FooTimeZoneProviderService"
+ *             android:exported="true"
+ *             android:permission="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER">
+ *         <intent-filter>
+ *             <action
+ *             android:name="android.service.timezone.SecondaryLocationTimeZoneProviderService"
+ *             />
+ *         </intent-filter>
+ *         <meta-data android:name="serviceVersion" android:value="1" />
+ *         <meta-data android:name="serviceIsMultiuser" android:value="true" />
+ *     </service>
+ * 
+ * + * + *

Threading: + * + *

Calls to {@code report} methods can be made on on any thread and will be passed asynchronously + * to the system server. Calls to {@link #onStartUpdates(long)} and {@link #onStopUpdates()} will + * occur on a single thread. + * + * @hide + */ +@SystemApi +public abstract class TimeZoneProviderService extends Service { + + private static final String TAG = "TimeZoneProviderService"; + + private final Handler mHandler = BackgroundThread.getHandler(); + + /** + * The Intent action that the primary location-derived time zone provider service must respond + * to. Add it to the intent filter of the service in its manifest. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = + "android.service.timezone.PrimaryLocationTimeZoneProviderService"; + + /** + * The Intent action that the secondary location-based time zone provider service must respond + * to. Add it to the intent filter of the service in its manifest. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = + "android.service.timezone.SecondaryLocationTimeZoneProviderService"; + + /** + * The permission that a service must require to ensure that only Android system can bind to it. + * If this permission is not enforced in the AndroidManifest of the service, the system will + * skip that service. + */ + public static final String BIND_PERMISSION = + "android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER"; + + private final TimeZoneProviderServiceWrapper mWrapper = new TimeZoneProviderServiceWrapper(); + + /** Set by {@link #mHandler} thread. */ + @Nullable + private ITimeZoneProviderManager mManager; + + @Override + @NonNull + public final IBinder onBind(@NonNull Intent intent) { + return mWrapper; + } + + /** + * Indicates a successful time zone detection. See {@link TimeZoneProviderSuggestion} for + * details. + */ + public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion) { + Objects.requireNonNull(suggestion); + + mHandler.post(() -> { + ITimeZoneProviderManager manager = mManager; + if (manager != null) { + try { + manager.onTimeZoneProviderSuggestion(suggestion); + } catch (RemoteException | RuntimeException e) { + Log.w(TAG, e); + } + } + }); + } + + /** + * 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 final void reportUncertain() { + mHandler.post(() -> { + ITimeZoneProviderManager manager = mManager; + if (manager != null) { + try { + manager.onTimeZoneProviderUncertain(); + } catch (RemoteException | RuntimeException e) { + Log.w(TAG, e); + } + } + }); + } + + /** + * 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 final void reportPermanentFailure(@NonNull Throwable cause) { + Objects.requireNonNull(cause); + + mHandler.post(() -> { + ITimeZoneProviderManager manager = mManager; + if (manager != null) { + try { + manager.onTimeZoneProviderPermanentFailure(cause.getMessage()); + } catch (RemoteException | RuntimeException e) { + Log.w(TAG, e); + } + } + }); + } + + /** + * Starts the provider sending updates. + */ + public abstract void onStartUpdates(@DurationMillisLong long initializationTimeoutMillis); + + /** + * Stops the provider sending updates. + */ + public abstract void onStopUpdates(); + + private class TimeZoneProviderServiceWrapper extends ITimeZoneProvider.Stub { + + @Override + public void setTimeZoneProviderManager(ITimeZoneProviderManager manager) { + Objects.requireNonNull(manager); + mHandler.post(() -> TimeZoneProviderService.this.mManager = manager); + } + + public void startUpdates(@DurationMillisLong long initializationTimeoutMillis) { + mHandler.post(() -> onStartUpdates(initializationTimeoutMillis)); + } + + public void stopUpdates() { + mHandler.post(TimeZoneProviderService.this::onStopUpdates); + } + } +} diff --git a/core/java/android/service/timezone/TimeZoneProviderSuggestion.aidl b/core/java/android/service/timezone/TimeZoneProviderSuggestion.aidl new file mode 100644 index 0000000000000..d0cb73bd23860 --- /dev/null +++ b/core/java/android/service/timezone/TimeZoneProviderSuggestion.aidl @@ -0,0 +1,22 @@ +/* + * 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 android.service.timezone; + +/** + * @hide + */ +parcelable TimeZoneProviderSuggestion; diff --git a/core/java/android/service/timezone/TimeZoneProviderSuggestion.java b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java new file mode 100644 index 0000000000000..cf299a738fbc9 --- /dev/null +++ b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java @@ -0,0 +1,185 @@ +/* + * 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 android.service.timezone; + +import android.annotation.ElapsedRealtimeLong; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * A suggestion from a {@link TimeZoneProviderService} containing zero or more time zones. + * + * @hide + */ +@SystemApi +public final class TimeZoneProviderSuggestion implements Parcelable { + + @NonNull + private final List mTimeZoneIds; + + @ElapsedRealtimeLong + private final long mElapsedRealtimeMillis; + + private TimeZoneProviderSuggestion(@NonNull List timeZoneIds, + @ElapsedRealtimeLong long elapsedRealtimeMillis) { + mTimeZoneIds = immutableList(timeZoneIds); + mElapsedRealtimeMillis = elapsedRealtimeMillis; + } + + /** + * Returns the time of the suggestion in elapsed real-time since system boot. Where possible, + * the time should be based on the time of the data used when determining time zone. For + * example, if it was based on a {@link android.location.Location} then it should be the time + * associated with that location. + * + *

This value is compared to {@link + * android.os.SystemClock#elapsedRealtime()}, to calculate the age of a fix and to compare + * {@link TimeZoneProviderSuggestion} instances. + * + * @return elapsed real-time of fix, in milliseconds + */ + @ElapsedRealtimeLong + public long getElapsedRealtimeMillis() { + return mElapsedRealtimeMillis; + } + + /** + * Returns the zero or more time zone IDs for this suggestion. + * + *

Time zone IDs are TZDB IDs like "America/Los_Angeles" that would be accepted by {@link + * java.util.TimeZone#getTimeZone(String)}. + * + *

Most often a suggestion will contain a single time zone ID but other possibilities are + * valid. A suggestion with zero time zone IDs means the provider is certain there are no time + * zones for the current location, e.g. for oceans, boundaries or disputed areas. A suggestion + * with multiple IDs can occur on boundaries or disputed areas. The ordering should be in order + * of likelihood if possible, but the time zone detection service may choose from any of the + * zones suggested if it has other supporting information available. + */ + @NonNull + public List getTimeZoneIds() { + return mTimeZoneIds; + } + + @Override + public String toString() { + return "TimeZoneProviderSuggestion{" + + "mTimeZoneIds=" + mTimeZoneIds + + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis + + "(" + Duration.ofMillis(mElapsedRealtimeMillis) + ")" + + '}'; + } + + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public TimeZoneProviderSuggestion createFromParcel(Parcel in) { + @SuppressWarnings("unchecked") + ArrayList timeZoneIds = + (ArrayList) in.readArrayList(null /* classLoader */); + long elapsedRealtimeMillis = in.readLong(); + return new TimeZoneProviderSuggestion(timeZoneIds, elapsedRealtimeMillis); + } + + @Override + public TimeZoneProviderSuggestion[] newArray(int size) { + return new TimeZoneProviderSuggestion[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeList(mTimeZoneIds); + parcel.writeLong(mElapsedRealtimeMillis); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TimeZoneProviderSuggestion that = (TimeZoneProviderSuggestion) o; + return mElapsedRealtimeMillis == that.mElapsedRealtimeMillis + && mTimeZoneIds.equals(that.mTimeZoneIds); + } + + @Override + public int hashCode() { + return Objects.hash(mTimeZoneIds, mElapsedRealtimeMillis); + } + + /** A builder for {@link TimeZoneProviderSuggestion}. */ + public static final class Builder { + + private @NonNull List mTimeZoneIds = Collections.emptyList(); + @ElapsedRealtimeLong + private long mElapsedRealtimeMillis = SystemClock.elapsedRealtime(); + + /** + * Sets the time zone IDs of this suggestion. + */ + @NonNull + public Builder setTimeZoneIds(@NonNull List timeZoneIds) { + mTimeZoneIds = Objects.requireNonNull(timeZoneIds); + return this; + } + + /** + * Sets the time of this suggestion, in elapsed real-time since system boot. + */ + @NonNull + public Builder setElapsedRealtimeMillis(@ElapsedRealtimeLong long time) { + mElapsedRealtimeMillis = time; + return this; + } + + /** + * Builds a {@link TimeZoneProviderSuggestion} instance. + */ + @NonNull + public TimeZoneProviderSuggestion build() { + return new TimeZoneProviderSuggestion(mTimeZoneIds, mElapsedRealtimeMillis); + } + } + + @NonNull + private static List immutableList(@NonNull List list) { + Objects.requireNonNull(list); + if (list.isEmpty()) { + return Collections.emptyList(); + } else { + return Collections.unmodifiableList(new ArrayList<>(list)); + } + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ef7edc2f2dd21..7ec2ee23470ad 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5299,7 +5299,7 @@ - - - - + diff --git a/core/tests/coretests/src/android/service/timezone/ParcelableTestSupport.java b/core/tests/coretests/src/android/service/timezone/ParcelableTestSupport.java new file mode 100644 index 0000000000000..777bda9fe1ffc --- /dev/null +++ b/core/tests/coretests/src/android/service/timezone/ParcelableTestSupport.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 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 static org.junit.Assert.assertEquals; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.reflect.Field; + +/** Utility methods related to {@link Parcelable} objects used in several tests. */ +final class ParcelableTestSupport { + + private ParcelableTestSupport() {} + + /** Returns the result of parceling and unparceling the argument. */ + @SuppressWarnings("unchecked") + public static T roundTripParcelable(T parcelable) { + Parcel parcel = Parcel.obtain(); + parcel.writeTypedObject(parcelable, 0); + parcel.setDataPosition(0); + + Parcelable.Creator creator; + try { + Field creatorField = parcelable.getClass().getField("CREATOR"); + creator = (Parcelable.Creator) creatorField.get(null); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new AssertionError(e); + } + T toReturn = parcel.readTypedObject(creator); + parcel.recycle(); + return toReturn; + } + + public static void assertRoundTripParcelable(T instance) { + assertEquals(instance, roundTripParcelable(instance)); + } +} diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderSuggestionTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderSuggestionTest.java new file mode 100644 index 0000000000000..f805555145f63 --- /dev/null +++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderSuggestionTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 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 android.service.timezone; + +import static android.service.timezone.ParcelableTestSupport.assertRoundTripParcelable; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import static java.util.Collections.singletonList; + +import org.junit.Test; + +import java.util.List; + +public class TimeZoneProviderSuggestionTest { + + private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 9999; + + private static final List ARBITRARY_TIME_ZONE_IDS = singletonList("Europe/London"); + + @Test(expected = RuntimeException.class) + public void testInvalidTimeZoneIds() { + new TimeZoneProviderSuggestion.Builder() + .setTimeZoneIds(null); + } + + @Test + public void testEquals() { + TimeZoneProviderSuggestion.Builder builder1 = new TimeZoneProviderSuggestion.Builder() + .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS); + { + TimeZoneProviderSuggestion one = builder1.build(); + assertEquals(one, one); + } + + TimeZoneProviderSuggestion.Builder builder2 = new TimeZoneProviderSuggestion.Builder() + .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS); + { + TimeZoneProviderSuggestion one = builder1.build(); + TimeZoneProviderSuggestion two = builder2.build(); + assertEquals(one, two); + assertEquals(two, one); + } + + builder1.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS + 1); + { + TimeZoneProviderSuggestion one = builder1.build(); + TimeZoneProviderSuggestion two = builder2.build(); + assertNotEquals(one, two); + assertNotEquals(two, one); + } + + builder2.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS + 1); + { + TimeZoneProviderSuggestion one = builder1.build(); + TimeZoneProviderSuggestion two = builder2.build(); + assertEquals(one, two); + assertEquals(two, one); + } + + builder2.setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS); + { + TimeZoneProviderSuggestion one = builder1.build(); + TimeZoneProviderSuggestion two = builder2.build(); + assertNotEquals(one, two); + assertNotEquals(two, one); + } + + builder1.setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS); + { + TimeZoneProviderSuggestion one = builder1.build(); + TimeZoneProviderSuggestion two = builder2.build(); + assertEquals(one, two); + assertEquals(two, one); + } + } + + @Test + public void testParcelable_noTimeZoneIds() { + TimeZoneProviderSuggestion.Builder builder = new TimeZoneProviderSuggestion.Builder() + .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS); + assertRoundTripParcelable(builder.build()); + } + + @Test + public void testParcelable_withTimeZoneIds() { + TimeZoneProviderSuggestion.Builder builder = new TimeZoneProviderSuggestion.Builder() + .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS); + assertRoundTripParcelable(builder.build()); + } +} diff --git a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java index fd8cf707e6978..b9c23b73062a9 100644 --- a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java @@ -17,20 +17,17 @@ package com.android.server.location.timezone; import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.IndentingPrintWriter; import android.util.Slog; -import com.android.internal.location.timezone.LocationTimeZoneEvent; -import com.android.internal.location.timezone.LocationTimeZoneProviderRequest; - import java.time.Duration; import java.util.Objects; @@ -57,9 +54,9 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider { void onInitialize() { mProxy.initialize(new LocationTimeZoneProviderProxy.Listener() { @Override - public void onReportLocationTimeZoneEvent( - @NonNull LocationTimeZoneEvent locationTimeZoneEvent) { - handleLocationTimeZoneEvent(locationTimeZoneEvent); + public void onReportTimeZoneProviderEvent( + @NonNull TimeZoneProviderEvent timeZoneProviderEvent) { + handleTimeZoneProviderEvent(timeZoneProviderEvent); } @Override @@ -80,27 +77,27 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider { synchronized (mSharedLock) { ProviderState currentState = mCurrentState.get(); switch (currentState.stateEnum) { - case PROVIDER_STATE_ENABLED_INITIALIZING: - case PROVIDER_STATE_ENABLED_UNCERTAIN: - case PROVIDER_STATE_ENABLED_CERTAIN: { + case PROVIDER_STATE_STARTED_INITIALIZING: + case PROVIDER_STATE_STARTED_UNCERTAIN: + case PROVIDER_STATE_STARTED_CERTAIN: { // Losing a remote provider is treated as becoming uncertain. String msg = "handleProviderLost reason=" + reason + ", mProviderName=" + mProviderName + ", currentState=" + currentState; debugLog(msg); - // This is an unusual PROVIDER_STATE_ENABLED_UNCERTAIN state because + // This is an unusual PROVIDER_STATE_STARTED_UNCERTAIN state because // event == null ProviderState newState = currentState.newState( - PROVIDER_STATE_ENABLED_UNCERTAIN, null, + PROVIDER_STATE_STARTED_UNCERTAIN, null, currentState.currentUserConfiguration, msg); setCurrentState(newState, true); break; } - case PROVIDER_STATE_DISABLED: { + case PROVIDER_STATE_STOPPED: { debugLog("handleProviderLost reason=" + reason + ", mProviderName=" + mProviderName + ", currentState=" + currentState - + ": No state change required, provider is disabled."); + + ": No state change required, provider is stopped."); break; } case PROVIDER_STATE_PERM_FAILED: { @@ -123,16 +120,16 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider { synchronized (mSharedLock) { ProviderState currentState = mCurrentState.get(); switch (currentState.stateEnum) { - case PROVIDER_STATE_ENABLED_INITIALIZING: - case PROVIDER_STATE_ENABLED_CERTAIN: - case PROVIDER_STATE_ENABLED_UNCERTAIN: { + case PROVIDER_STATE_STARTED_INITIALIZING: + case PROVIDER_STATE_STARTED_CERTAIN: + case PROVIDER_STATE_STARTED_UNCERTAIN: { debugLog("handleOnProviderBound mProviderName=" + mProviderName - + ", currentState=" + currentState + ": Provider is enabled."); + + ", currentState=" + currentState + ": Provider is started."); break; } - case PROVIDER_STATE_DISABLED: { + case PROVIDER_STATE_STOPPED: { debugLog("handleOnProviderBound mProviderName=" + mProviderName - + ", currentState=" + currentState + ": Provider is disabled."); + + ", currentState=" + currentState + ": Provider is stopped."); break; } case PROVIDER_STATE_PERM_FAILED: { @@ -150,23 +147,17 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider { } @Override - void onEnable(@NonNull Duration initializationTimeout) { + void onStartUpdates(@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. - LocationTimeZoneProviderRequest request = - new LocationTimeZoneProviderRequest.Builder() - .setReportLocationTimeZone(true) - .setInitializationTimeoutMillis(initializationTimeout.toMillis()) - .build(); + TimeZoneProviderRequest request = + TimeZoneProviderRequest.createStartUpdatesRequest(initializationTimeout); mProxy.setRequest(request); } @Override - void onDisable() { - LocationTimeZoneProviderRequest request = - new LocationTimeZoneProviderRequest.Builder() - .setReportLocationTimeZone(false) - .build(); + void onStopUpdates() { + TimeZoneProviderRequest request = TimeZoneProviderRequest.createStopUpdatesRequest(); mProxy.setRequest(request); } diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/location/timezone/ControllerImpl.java index 07615ff580750..7496168499020 100644 --- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java +++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java @@ -16,24 +16,24 @@ package com.android.server.location.timezone; -import static com.android.internal.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE; -import static com.android.internal.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_SUCCESS; -import static com.android.internal.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN; import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog; import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; +import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE; +import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION; +import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN; +import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; -import com.android.internal.location.timezone.LocationTimeZoneEvent; import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; @@ -46,9 +46,9 @@ import java.util.Objects; * A real implementation of {@link LocationTimeZoneProviderController} that supports a primary and a * secondary {@link LocationTimeZoneProvider}. * - *

The primary is used until it fails or becomes uncertain. The secondary will then be enabled. + *

The primary is used until it fails or becomes uncertain. The secondary will then be started. * The controller will immediately make suggestions based on "certain" {@link - * LocationTimeZoneEvent}s, i.e. events that demonstrate the provider is certain what the time zone + * TimeZoneProviderEvent}s, i.e. events that demonstrate the provider is certain what the time zone * is. The controller will not make immediate suggestions based on "uncertain" events, giving * providers time to change their mind. This also gives the secondary provider time to initialize * when the primary becomes uncertain. @@ -107,7 +107,7 @@ class ControllerImpl extends LocationTimeZoneProviderController { mPrimaryProvider.initialize(providerListener); mSecondaryProvider.initialize(providerListener); - alterProvidersEnabledStateIfRequired( + alterProvidersStartedStateIfRequired( null /* oldConfiguration */, mCurrentUserConfiguration); } } @@ -125,15 +125,15 @@ class ControllerImpl extends LocationTimeZoneProviderController { if (!newConfig.equals(oldConfig)) { if (newConfig.getUserId() != oldConfig.getUserId()) { - // If the user changed, disable the providers if needed. They may be re-enabled + // If the user changed, stop the providers if needed. They may be re-started // for the new user immediately afterwards if their settings allow. debugLog("User changed. old=" + oldConfig.getUserId() - + ", new=" + newConfig.getUserId() + ": Disabling providers"); - disableProviders(); + + ", new=" + newConfig.getUserId() + ": Stopping providers"); + stopProviders(); - alterProvidersEnabledStateIfRequired(null /* oldConfiguration */, newConfig); + alterProvidersStartedStateIfRequired(null /* oldConfiguration */, newConfig); } else { - alterProvidersEnabledStateIfRequired(oldConfig, newConfig); + alterProvidersStartedStateIfRequired(oldConfig, newConfig); } } } @@ -145,43 +145,44 @@ class ControllerImpl extends LocationTimeZoneProviderController { } @Override + @DurationMillisLong long getUncertaintyTimeoutDelayMillis() { return mUncertaintyTimeoutQueue.getQueuedDelayMillis(); } @GuardedBy("mSharedLock") - private void disableProviders() { - disableProviderIfEnabled(mPrimaryProvider); - disableProviderIfEnabled(mSecondaryProvider); + private void stopProviders() { + stopProviderIfStarted(mPrimaryProvider); + stopProviderIfStarted(mSecondaryProvider); - // By definition, if both providers are disabled, the controller is uncertain. + // By definition, if both providers are stopped, the controller is uncertain. cancelUncertaintyTimeout(); } @GuardedBy("mSharedLock") - private void disableProviderIfEnabled(@NonNull LocationTimeZoneProvider provider) { - if (provider.getCurrentState().isEnabled()) { - disableProvider(provider); + private void stopProviderIfStarted(@NonNull LocationTimeZoneProvider provider) { + if (provider.getCurrentState().isStarted()) { + stopProvider(provider); } } @GuardedBy("mSharedLock") - private void disableProvider(@NonNull LocationTimeZoneProvider provider) { + private void stopProvider(@NonNull LocationTimeZoneProvider provider) { ProviderState providerState = provider.getCurrentState(); switch (providerState.stateEnum) { - case PROVIDER_STATE_DISABLED: { - debugLog("No need to disable " + provider + ": already disabled"); + case PROVIDER_STATE_STOPPED: { + debugLog("No need to stop " + provider + ": already stopped"); break; } - case PROVIDER_STATE_ENABLED_INITIALIZING: - case PROVIDER_STATE_ENABLED_CERTAIN: - case PROVIDER_STATE_ENABLED_UNCERTAIN: { - debugLog("Disabling " + provider); - provider.disable(); + case PROVIDER_STATE_STARTED_INITIALIZING: + case PROVIDER_STATE_STARTED_CERTAIN: + case PROVIDER_STATE_STARTED_UNCERTAIN: { + debugLog("Stopping " + provider); + provider.stopUpdates(); break; } case PROVIDER_STATE_PERM_FAILED: { - debugLog("Unable to disable " + provider + ": it is perm failed"); + debugLog("Unable to stop " + provider + ": it is perm failed"); break; } default: { @@ -192,20 +193,20 @@ class ControllerImpl extends LocationTimeZoneProviderController { } /** - * Sets the providers into the correct enabled/disabled state for the {@code newConfiguration} + * Sets the providers into the correct started/stopped state for the {@code newConfiguration} * and, if there is a provider state change, makes any suggestions required to inform the * downstream time zone detection code. * *

This is a utility method that exists to avoid duplicated logic for the various cases when - * provider enabled / disabled state may need to be set or changed, e.g. during initialization + * provider started / stopped state may need to be set or changed, e.g. during initialization * or when a new configuration has been received. */ @GuardedBy("mSharedLock") - private void alterProvidersEnabledStateIfRequired( + private void alterProvidersStartedStateIfRequired( @Nullable ConfigurationInternal oldConfiguration, @NonNull ConfigurationInternal newConfiguration) { - // Provider enabled / disabled states only need to be changed if geoDetectionEnabled has + // Provider started / stopped states only need to be changed if geoDetectionEnabled has // changed. boolean oldGeoDetectionEnabled = oldConfiguration != null && oldConfiguration.getGeoDetectionEnabledBehavior(); @@ -215,28 +216,28 @@ class ControllerImpl extends LocationTimeZoneProviderController { } // The check above ensures that the logic below only executes if providers are going from - // {enabled *} -> {disabled}, or {disabled} -> {enabled initializing}. If this changes in - // future and there could be {enabled *} -> {enabled *} cases, or cases where the provider - // can't be assumed to go straight to the {enabled initializing} state, then the logic below + // {started *} -> {stopped}, or {stopped} -> {started initializing}. If this changes in + // future and there could be {started *} -> {started *} cases, or cases where the provider + // can't be assumed to go straight to the {started initializing} state, then the logic below // would need to cover extra conditions, for example: - // 1) If the primary is in {enabled uncertain}, the secondary should be enabled. + // 1) If the primary is in {started uncertain}, the secondary should be started. // 2) If (1), and the secondary instantly enters the {perm failed} state, the uncertainty - // timeout started when the primary entered {enabled uncertain} should be cancelled. + // timeout started when the primary entered {started uncertain} should be cancelled. if (newGeoDetectionEnabled) { - // Try to enable the primary provider. - tryEnableProvider(mPrimaryProvider, newConfiguration); + // Try to start the primary provider. + tryStartProvider(mPrimaryProvider, newConfiguration); - // The secondary should only ever be enabled if the primary now isn't enabled (i.e. it - // couldn't become {enabled initializing} because it is {perm failed}). + // The secondary should only ever be started if the primary now isn't started (i.e. it + // couldn't become {started initializing} because it is {perm failed}). ProviderState newPrimaryState = mPrimaryProvider.getCurrentState(); - if (!newPrimaryState.isEnabled()) { - // If the primary provider is {perm failed} then the controller must try to enable + if (!newPrimaryState.isStarted()) { + // If the primary provider is {perm failed} then the controller must try to start // the secondary. - tryEnableProvider(mSecondaryProvider, newConfiguration); + tryStartProvider(mSecondaryProvider, newConfiguration); ProviderState newSecondaryState = mSecondaryProvider.getCurrentState(); - if (!newSecondaryState.isEnabled()) { + if (!newSecondaryState.isStarted()) { // If both providers are {perm failed} then the controller immediately // becomes uncertain. GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( @@ -247,7 +248,7 @@ class ControllerImpl extends LocationTimeZoneProviderController { } } } else { - disableProviders(); + stopProviders(); // There can be an uncertainty timeout set if the controller most recently received // an uncertain event. This is a no-op if there isn't a timeout set. @@ -256,34 +257,35 @@ class ControllerImpl extends LocationTimeZoneProviderController { // If a previous "certain" suggestion has been made, then a new "uncertain" // suggestion must now be made to indicate the controller {does not / no longer has} // an opinion and will not be sending further updates (until at least the config - // changes again and providers are re-enabled). + // changes again and providers are re-started). if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) { GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( - "Provider is disabled:" + "Provider is stopped:" + " primary=" + mPrimaryProvider.getCurrentState()); makeSuggestion(suggestion); } } } - private void tryEnableProvider(@NonNull LocationTimeZoneProvider provider, + private void tryStartProvider(@NonNull LocationTimeZoneProvider provider, @NonNull ConfigurationInternal configuration) { ProviderState providerState = provider.getCurrentState(); switch (providerState.stateEnum) { - case PROVIDER_STATE_DISABLED: { + case PROVIDER_STATE_STOPPED: { debugLog("Enabling " + provider); - provider.enable(configuration, mEnvironment.getProviderInitializationTimeout(), + provider.startUpdates(configuration, + mEnvironment.getProviderInitializationTimeout(), mEnvironment.getProviderInitializationTimeoutFuzz()); break; } - case PROVIDER_STATE_ENABLED_INITIALIZING: - case PROVIDER_STATE_ENABLED_CERTAIN: - case PROVIDER_STATE_ENABLED_UNCERTAIN: { - debugLog("No need to enable " + provider + ": already enabled"); + case PROVIDER_STATE_STARTED_INITIALIZING: + case PROVIDER_STATE_STARTED_CERTAIN: + case PROVIDER_STATE_STARTED_UNCERTAIN: { + debugLog("No need to start " + provider + ": already started"); break; } case PROVIDER_STATE_PERM_FAILED: { - debugLog("Unable to enable " + provider + ": it is perm failed"); + debugLog("Unable to start " + provider + ": it is perm failed"); break; } default: { @@ -300,20 +302,20 @@ class ControllerImpl extends LocationTimeZoneProviderController { synchronized (mSharedLock) { switch (providerState.stateEnum) { - case PROVIDER_STATE_DISABLED: { - // This should never happen: entering disabled does not trigger a state change. - warnLog("onProviderStateChange: Unexpected state change for disabled provider," + case PROVIDER_STATE_STOPPED: { + // This should never happen: entering stopped does not trigger a state change. + warnLog("onProviderStateChange: Unexpected state change for stopped provider," + " provider=" + provider); break; } - case PROVIDER_STATE_ENABLED_INITIALIZING: - case PROVIDER_STATE_ENABLED_CERTAIN: - case PROVIDER_STATE_ENABLED_UNCERTAIN: { - // Entering enabled does not trigger a state change, so this only happens if an - // event is received while the provider is enabled. + case PROVIDER_STATE_STARTED_INITIALIZING: + case PROVIDER_STATE_STARTED_CERTAIN: + case PROVIDER_STATE_STARTED_UNCERTAIN: { + // Entering started does not trigger a state change, so this only happens if an + // event is received while the provider is started. debugLog("onProviderStateChange: Received notification of a state change while" - + " enabled, provider=" + provider); - handleProviderEnabledStateChange(providerState); + + " started, provider=" + provider); + handleProviderStartedStateChange(providerState); break; } case PROVIDER_STATE_PERM_FAILED: { @@ -344,18 +346,18 @@ class ControllerImpl extends LocationTimeZoneProviderController { ProviderState primaryCurrentState = mPrimaryProvider.getCurrentState(); ProviderState secondaryCurrentState = mSecondaryProvider.getCurrentState(); - // If a provider has failed, the other may need to be enabled. + // If a provider has failed, the other may need to be started. if (failedProvider == mPrimaryProvider) { if (secondaryCurrentState.stateEnum != PROVIDER_STATE_PERM_FAILED) { - // The primary must have failed. Try to enable the secondary. This does nothing if - // the provider is already enabled, and will leave the provider in - // {enabled initializing} if the provider is disabled. - tryEnableProvider(mSecondaryProvider, mCurrentUserConfiguration); + // The primary must have failed. Try to start the secondary. This does nothing if + // the provider is already started, and will leave the provider in + // {started initializing} if the provider is stopped. + tryStartProvider(mSecondaryProvider, mCurrentUserConfiguration); } } else if (failedProvider == mSecondaryProvider) { // No-op: The secondary will only be active if the primary is uncertain or is failed. - // So, there the primary should not need to be enabled when the secondary fails. - if (primaryCurrentState.stateEnum != PROVIDER_STATE_ENABLED_UNCERTAIN + // So, there the primary should not need to be started when the secondary fails. + if (primaryCurrentState.stateEnum != PROVIDER_STATE_STARTED_UNCERTAIN && primaryCurrentState.stateEnum != PROVIDER_STATE_PERM_FAILED) { warnLog("Secondary provider unexpected reported a failure:" + " failed provider=" + failedProvider.getName() @@ -384,16 +386,16 @@ class ControllerImpl extends LocationTimeZoneProviderController { } /** - * Called when a provider has changed state but just moved from one enabled state to another - * enabled state, usually as a result of a new {@link LocationTimeZoneEvent} being received. + * Called when a provider has changed state but just moved from one started state to another + * started state, usually as a result of a new {@link TimeZoneProviderEvent} being received. * However, there are rare cases where the event can also be null. */ @GuardedBy("mSharedLock") - private void handleProviderEnabledStateChange(@NonNull ProviderState providerState) { + private void handleProviderStartedStateChange(@NonNull ProviderState providerState) { LocationTimeZoneProvider provider = providerState.provider; - LocationTimeZoneEvent event = providerState.event; + TimeZoneProviderEvent event = providerState.event; if (event == null) { - // Implicit uncertainty, i.e. where the provider is enabled, but a problem has been + // Implicit uncertainty, i.e. where the provider is started, but a problem has been // detected without having received an event. For example, if the process has detected // the loss of a binder-based provider, or initialization took too long. This is treated // the same as explicit uncertainty, i.e. where the provider has explicitly told this @@ -404,18 +406,18 @@ class ControllerImpl extends LocationTimeZoneProviderController { } if (!mCurrentUserConfiguration.getGeoDetectionEnabledBehavior()) { - // This should not happen: the provider should not be in an enabled state if the user + // This should not happen: the provider should not be in an started state if the user // does not have geodetection enabled. - warnLog("Provider=" + provider + " is enabled, but" + warnLog("Provider=" + provider + " is started, but" + " currentUserConfiguration=" + mCurrentUserConfiguration + " suggests it shouldn't be."); } - switch (event.getEventType()) { + switch (event.getType()) { case EVENT_TYPE_PERMANENT_FAILURE: { - // This shouldn't happen. A provider cannot be enabled and have this event type. + // This shouldn't happen. A provider cannot be started and have this event type. warnLog("Provider=" + provider - + " is enabled, but event suggests it shouldn't be"); + + " is started, but event suggests it shouldn't be"); break; } case EVENT_TYPE_UNCERTAIN: { @@ -423,13 +425,13 @@ class ControllerImpl extends LocationTimeZoneProviderController { + ", explicit uncertainty. event=" + event); break; } - case EVENT_TYPE_SUCCESS: { - handleProviderCertainty(provider, event.getTimeZoneIds(), + case EVENT_TYPE_SUGGESTION: { + handleProviderSuggestion(provider, event.getSuggestion().getTimeZoneIds(), "Event received provider=" + provider + ", event=" + event); break; } default: { - warnLog("Unknown eventType=" + event.getEventType()); + warnLog("Unknown eventType=" + event.getType()); break; } } @@ -439,7 +441,7 @@ class ControllerImpl extends LocationTimeZoneProviderController { * Called when a provider has become "certain" about the time zone(s). */ @GuardedBy("mSharedLock") - private void handleProviderCertainty( + private void handleProviderSuggestion( @NonNull LocationTimeZoneProvider provider, @Nullable List timeZoneIds, @NonNull String reason) { @@ -447,11 +449,10 @@ class ControllerImpl extends LocationTimeZoneProviderController { cancelUncertaintyTimeout(); if (provider == mPrimaryProvider) { - disableProviderIfEnabled(mSecondaryProvider); + stopProviderIfStarted(mSecondaryProvider); } - GeolocationTimeZoneSuggestion suggestion = - new GeolocationTimeZoneSuggestion(timeZoneIds); + GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(timeZoneIds); suggestion.addDebugInfo(reason); // Rely on the receiver to dedupe suggestions. It is better to over-communicate. makeSuggestion(suggestion); @@ -503,7 +504,7 @@ class ControllerImpl extends LocationTimeZoneProviderController { * Called when a provider has become "uncertain" about the time zone. * *

A provider is expected to report its uncertainty as soon as it becomes uncertain, as - * this enables the most flexibility for the controller to enable other providers when there are + * this enables the most flexibility for the controller to start other providers when there are * multiple ones available. The controller is therefore responsible for deciding when to make a * "uncertain" suggestion to the downstream time zone detector. * @@ -532,11 +533,11 @@ class ControllerImpl extends LocationTimeZoneProviderController { } if (provider == mPrimaryProvider) { - // (Try to) enable the secondary. It could already be enabled, or enabling might not + // (Try to) start the secondary. It could already be started, or enabling might not // succeed if the provider has previously reported it is perm failed. The uncertainty // timeout (set above) is used to ensure that an uncertain suggestion will be made if // the secondary cannot generate a success event in time. - tryEnableProvider(mSecondaryProvider, mCurrentUserConfiguration); + tryStartProvider(mSecondaryProvider, mCurrentUserConfiguration); } } diff --git a/services/core/java/com/android/server/location/timezone/HandlerThreadingDomain.java b/services/core/java/com/android/server/location/timezone/HandlerThreadingDomain.java index 17e719ed2cb09..b59898adc340b 100644 --- a/services/core/java/com/android/server/location/timezone/HandlerThreadingDomain.java +++ b/services/core/java/com/android/server/location/timezone/HandlerThreadingDomain.java @@ -16,6 +16,7 @@ package com.android.server.location.timezone; +import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.os.Handler; @@ -57,12 +58,12 @@ final class HandlerThreadingDomain extends ThreadingDomain { } @Override - void postDelayed(@NonNull Runnable r, long delayMillis) { + void postDelayed(@NonNull Runnable r, @DurationMillisLong long delayMillis) { getHandler().postDelayed(r, delayMillis); } @Override - void postDelayed(Runnable r, Object token, long delayMillis) { + void postDelayed(Runnable r, Object token, @DurationMillisLong long delayMillis) { getHandler().postDelayed(r, token, delayMillis); } diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java index c8a1db6681ffe..98ebec2cbe1c2 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java @@ -24,6 +24,7 @@ import android.os.Binder; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.SystemProperties; +import android.service.timezone.TimeZoneProviderService; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; @@ -44,7 +45,7 @@ import java.util.Objects; * A service class that acts as a container for the {@link LocationTimeZoneProviderController}, * which determines what {@link com.android.server.timezonedetector.GeolocationTimeZoneSuggestion} * are made to the {@link TimeZoneDetectorInternal}, and the {@link LocationTimeZoneProvider}s that - * (indirectly) generate {@link com.android.internal.location.timezone.LocationTimeZoneEvent}s. + * (indirectly) generate {@link TimeZoneProviderEvent}s. * *

For details of the time zone suggestion behavior, see {@link * LocationTimeZoneProviderController}. @@ -120,9 +121,9 @@ public class LocationTimeZoneManagerService extends Binder { private static final String ATTRIBUTION_TAG = "LocationTimeZoneService"; private static final String PRIMARY_LOCATION_TIME_ZONE_SERVICE_ACTION = - "com.android.location.timezone.service.v1.PrimaryLocationTimeZoneProvider"; + TimeZoneProviderService.PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE; private static final String SECONDARY_LOCATION_TIME_ZONE_SERVICE_ACTION = - "com.android.location.timezone.service.v1.SecondaryLocationTimeZoneProvider"; + TimeZoneProviderService.SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE; @NonNull private final Context mContext; diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java index dc56238e2f488..8b51ab4522c99 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java @@ -16,16 +16,17 @@ package com.android.server.location.timezone; -import static com.android.internal.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE; -import static com.android.internal.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_SUCCESS; -import static com.android.internal.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN; import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; +import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE; +import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION; +import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN; +import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -34,7 +35,6 @@ import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.location.timezone.LocationTimeZoneEvent; import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue; import com.android.server.timezonedetector.ConfigurationInternal; @@ -50,7 +50,7 @@ import java.util.Objects; * process, or could be a stubbed instance when no real provider is registered. * *

The provider is supplied with a {@link ProviderListener} via {@link - * #initialize(ProviderListener)}. This enables it to communicates asynchronous detection / error + * #initialize(ProviderListener)}. This starts communication of asynchronous detection / error * events back to the {@link LocationTimeZoneProviderController} via the {@link * ProviderListener#onProviderStateChange} method. This call must be made on the * {@link Handler} thread from the {@link ThreadingDomain} passed to the constructor. @@ -77,9 +77,10 @@ abstract class LocationTimeZoneProvider implements Dumpable { */ static class ProviderState { - @IntDef({ PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_ENABLED_INITIALIZING, - PROVIDER_STATE_ENABLED_CERTAIN, PROVIDER_STATE_ENABLED_UNCERTAIN, - PROVIDER_STATE_DISABLED, PROVIDER_STATE_PERM_FAILED }) + @IntDef(prefix = "PROVIDER_STATE_", + value = { PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_STARTED_INITIALIZING, + PROVIDER_STATE_STARTED_CERTAIN, PROVIDER_STATE_STARTED_UNCERTAIN, + PROVIDER_STATE_STOPPED, PROVIDER_STATE_PERM_FAILED }) @interface ProviderStateEnum {} /** @@ -88,31 +89,31 @@ abstract class LocationTimeZoneProvider implements Dumpable { static final int PROVIDER_STATE_UNKNOWN = 0; /** - * The provider is enabled and has not reported its first event. + * The provider is started and has not reported its first event. */ - static final int PROVIDER_STATE_ENABLED_INITIALIZING = 1; + static final int PROVIDER_STATE_STARTED_INITIALIZING = 1; /** - * The provider is enabled and most recently reported a "success" event. + * The provider is started and most recently reported a "suggestion" event. */ - static final int PROVIDER_STATE_ENABLED_CERTAIN = 2; + static final int PROVIDER_STATE_STARTED_CERTAIN = 2; /** - * The provider is enabled and most recently reported an "uncertain" event. + * The provider is started and most recently reported an "uncertain" event. */ - static final int PROVIDER_STATE_ENABLED_UNCERTAIN = 3; + static final int PROVIDER_STATE_STARTED_UNCERTAIN = 3; /** - * The provider is disabled. + * The provider is stopped. * * This is the state after {@link #initialize} is called. */ - static final int PROVIDER_STATE_DISABLED = 4; + static final int PROVIDER_STATE_STOPPED = 4; /** - * The provider has failed and cannot be re-enabled. + * The provider has failed and cannot be re-started. * - * Providers may enter this state after a provider is enabled. + * Providers may enter this state after a provider is started. */ static final int PROVIDER_STATE_PERM_FAILED = 5; @@ -123,16 +124,16 @@ abstract class LocationTimeZoneProvider implements Dumpable { public final @ProviderStateEnum int stateEnum; /** - * The last {@link LocationTimeZoneEvent} received. Only populated when {@link #stateEnum} - * is either {@link #PROVIDER_STATE_ENABLED_CERTAIN} or {@link - * #PROVIDER_STATE_ENABLED_UNCERTAIN}, but it can be {@code null} then too if no event has + * The last {@link TimeZoneProviderEvent} received. Only populated when {@link #stateEnum} + * is either {@link #PROVIDER_STATE_STARTED_CERTAIN} or {@link + * #PROVIDER_STATE_STARTED_UNCERTAIN}, but it can be {@code null} then too if no event has * yet been received. */ - @Nullable public final LocationTimeZoneEvent event; + @Nullable public final TimeZoneProviderEvent event; /** * The user configuration associated with the current state. Only and always present when - * {@link #stateEnum} is one of the enabled states. + * {@link #stateEnum} is one of the started states. */ @Nullable public final ConfigurationInternal currentUserConfiguration; @@ -140,6 +141,7 @@ abstract class LocationTimeZoneProvider implements Dumpable { * The time according to the elapsed realtime clock when the provider entered the current * state. Included for debugging, not used for equality. */ + @ElapsedRealtimeLong private final long mStateEntryTimeMillis; /** @@ -151,7 +153,7 @@ abstract class LocationTimeZoneProvider implements Dumpable { private ProviderState(@NonNull LocationTimeZoneProvider provider, @ProviderStateEnum int stateEnum, - @Nullable LocationTimeZoneEvent event, + @Nullable TimeZoneProviderEvent event, @Nullable ConfigurationInternal currentUserConfiguration, @Nullable String debugInfo) { this.provider = Objects.requireNonNull(provider); @@ -174,25 +176,25 @@ abstract class LocationTimeZoneProvider implements Dumpable { * and that the required parameters for the new state are present / absent. */ ProviderState newState(@ProviderStateEnum int newStateEnum, - @Nullable LocationTimeZoneEvent event, + @Nullable TimeZoneProviderEvent event, @Nullable ConfigurationInternal currentUserConfig, @Nullable String debugInfo) { // Check valid "from" transitions. switch (this.stateEnum) { case PROVIDER_STATE_UNKNOWN: { - if (newStateEnum != PROVIDER_STATE_DISABLED) { + if (newStateEnum != PROVIDER_STATE_STOPPED) { throw new IllegalArgumentException( "Must transition from " + prettyPrintStateEnum( PROVIDER_STATE_UNKNOWN) - + " to " + prettyPrintStateEnum(PROVIDER_STATE_DISABLED)); + + " to " + prettyPrintStateEnum(PROVIDER_STATE_STOPPED)); } break; } - case PROVIDER_STATE_DISABLED: - case PROVIDER_STATE_ENABLED_INITIALIZING: - case PROVIDER_STATE_ENABLED_CERTAIN: - case PROVIDER_STATE_ENABLED_UNCERTAIN: { + case PROVIDER_STATE_STOPPED: + case PROVIDER_STATE_STARTED_INITIALIZING: + case PROVIDER_STATE_STARTED_CERTAIN: + case PROVIDER_STATE_STARTED_UNCERTAIN: { // These can go to each other or PROVIDER_STATE_PERM_FAILED. break; } @@ -211,21 +213,21 @@ abstract class LocationTimeZoneProvider implements Dumpable { throw new IllegalArgumentException("Cannot transition to " + prettyPrintStateEnum(PROVIDER_STATE_UNKNOWN)); } - case PROVIDER_STATE_DISABLED: { + case PROVIDER_STATE_STOPPED: { if (event != null || currentUserConfig != null) { throw new IllegalArgumentException( - "Disabled state: event and currentUserConfig must be null" + "Stopped state: event and currentUserConfig must be null" + ", event=" + event + ", currentUserConfig=" + currentUserConfig); } break; } - case PROVIDER_STATE_ENABLED_INITIALIZING: - case PROVIDER_STATE_ENABLED_CERTAIN: - case PROVIDER_STATE_ENABLED_UNCERTAIN: { + case PROVIDER_STATE_STARTED_INITIALIZING: + case PROVIDER_STATE_STARTED_CERTAIN: + case PROVIDER_STATE_STARTED_UNCERTAIN: { if (currentUserConfig == null) { throw new IllegalArgumentException( - "Enabled state: currentUserConfig must not be null"); + "Started state: currentUserConfig must not be null"); } break; } @@ -245,11 +247,11 @@ abstract class LocationTimeZoneProvider implements Dumpable { return new ProviderState(provider, newStateEnum, event, currentUserConfig, debugInfo); } - /** Returns {@code true} if {@link #stateEnum} is one of the enabled states. */ - boolean isEnabled() { - return stateEnum == PROVIDER_STATE_ENABLED_INITIALIZING - || stateEnum == PROVIDER_STATE_ENABLED_CERTAIN - || stateEnum == PROVIDER_STATE_ENABLED_UNCERTAIN; + /** Returns {@code true} if {@link #stateEnum} is one of the started states. */ + boolean isStarted() { + return stateEnum == PROVIDER_STATE_STARTED_INITIALIZING + || stateEnum == PROVIDER_STATE_STARTED_CERTAIN + || stateEnum == PROVIDER_STATE_STARTED_UNCERTAIN; } @Override @@ -286,14 +288,14 @@ abstract class LocationTimeZoneProvider implements Dumpable { private static String prettyPrintStateEnum(@ProviderStateEnum int state) { switch (state) { - case PROVIDER_STATE_DISABLED: - return "Disabled (" + PROVIDER_STATE_DISABLED + ")"; - case PROVIDER_STATE_ENABLED_INITIALIZING: - return "Enabled initializing (" + PROVIDER_STATE_ENABLED_INITIALIZING + ")"; - case PROVIDER_STATE_ENABLED_CERTAIN: - return "Enabled certain (" + PROVIDER_STATE_ENABLED_CERTAIN + ")"; - case PROVIDER_STATE_ENABLED_UNCERTAIN: - return "Enabled uncertain (" + PROVIDER_STATE_ENABLED_UNCERTAIN + ")"; + case PROVIDER_STATE_STOPPED: + return "Stopped (" + PROVIDER_STATE_STOPPED + ")"; + case PROVIDER_STATE_STARTED_INITIALIZING: + return "Started initializing (" + PROVIDER_STATE_STARTED_INITIALIZING + ")"; + case PROVIDER_STATE_STARTED_CERTAIN: + return "Started certain (" + PROVIDER_STATE_STARTED_CERTAIN + ")"; + case PROVIDER_STATE_STARTED_UNCERTAIN: + return "Started uncertain (" + PROVIDER_STATE_STARTED_UNCERTAIN + ")"; case PROVIDER_STATE_PERM_FAILED: return "Perm failure (" + PROVIDER_STATE_PERM_FAILED + ")"; case PROVIDER_STATE_UNKNOWN: @@ -315,7 +317,7 @@ abstract class LocationTimeZoneProvider implements Dumpable { new ReferenceWithHistory<>(10); /** - * Used for scheduling initialization timeouts, i.e. for providers that have just been enabled. + * Used for scheduling initialization timeouts, i.e. for providers that have just been started. */ @NonNull private final SingleRunnableQueue mInitializationTimeoutQueue; @@ -344,7 +346,7 @@ abstract class LocationTimeZoneProvider implements Dumpable { mProviderListener = Objects.requireNonNull(providerListener); ProviderState currentState = ProviderState.createStartingState(this); ProviderState newState = currentState.newState( - PROVIDER_STATE_DISABLED, null, null, + PROVIDER_STATE_STOPPED, null, null, "initialize() called"); setCurrentState(newState, false); @@ -407,28 +409,28 @@ abstract class LocationTimeZoneProvider implements Dumpable { } /** - * Enables the provider. It is an error to call this method except when the {@link - * #getCurrentState()} is at {@link ProviderState#PROVIDER_STATE_DISABLED}. This method must be + * Starts the provider. It is an error to call this method except when the {@link + * #getCurrentState()} is at {@link ProviderState#PROVIDER_STATE_STOPPED}. This method must be * called using the handler thread from the {@link ThreadingDomain}. */ - final void enable(@NonNull ConfigurationInternal currentUserConfiguration, + final void startUpdates(@NonNull ConfigurationInternal currentUserConfiguration, @NonNull Duration initializationTimeout, @NonNull Duration initializationTimeoutFuzz) { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { - assertCurrentState(PROVIDER_STATE_DISABLED); + assertCurrentState(PROVIDER_STATE_STOPPED); ProviderState currentState = mCurrentState.get(); ProviderState newState = currentState.newState( - PROVIDER_STATE_ENABLED_INITIALIZING, null /* event */, - currentUserConfiguration, "enable() called"); + PROVIDER_STATE_STARTED_INITIALIZING, null /* event */, + currentUserConfiguration, "startUpdates() called"); setCurrentState(newState, false); Duration delay = initializationTimeout.plus(initializationTimeoutFuzz); mInitializationTimeoutQueue.runDelayed( this::handleInitializationTimeout, delay.toMillis()); - onEnable(initializationTimeout); + onStartUpdates(initializationTimeout); } } @@ -437,10 +439,10 @@ abstract class LocationTimeZoneProvider implements Dumpable { synchronized (mSharedLock) { ProviderState currentState = mCurrentState.get(); - if (currentState.stateEnum == PROVIDER_STATE_ENABLED_INITIALIZING) { + if (currentState.stateEnum == PROVIDER_STATE_STARTED_INITIALIZING) { // On initialization timeout the provider becomes uncertain. ProviderState newState = currentState.newState( - PROVIDER_STATE_ENABLED_UNCERTAIN, null /* event */, + PROVIDER_STATE_STARTED_UNCERTAIN, null /* event */, currentState.currentUserConfiguration, "initialization timeout"); setCurrentState(newState, true); } @@ -448,65 +450,64 @@ abstract class LocationTimeZoneProvider implements Dumpable { } /** - * Implemented by subclasses to do work during {@link #enable}. + * Implemented by subclasses to do work during {@link #startUpdates}. */ - abstract void onEnable(@NonNull Duration initializationTimeout); + abstract void onStartUpdates(@NonNull Duration initializationTimeout); /** - * Disables the provider. It is an error* to call this method except when the {@link - * #getCurrentState()} is one of the enabled states. This method must be + * Stops the provider. It is an error* to call this method except when the {@link + * #getCurrentState()} is one of the started states. This method must be * called using the handler thread from the {@link ThreadingDomain}. */ - final void disable() { + final void stopUpdates() { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { - assertIsEnabled(); + assertIsStarted(); ProviderState currentState = mCurrentState.get(); ProviderState newState = currentState.newState( - PROVIDER_STATE_DISABLED, null, null, "disable() called"); + PROVIDER_STATE_STOPPED, null, null, "stopUpdates() called"); setCurrentState(newState, false); if (mInitializationTimeoutQueue.hasQueued()) { mInitializationTimeoutQueue.cancel(); } - onDisable(); + onStopUpdates(); } } /** - * Implemented by subclasses to do work during {@link #disable}. + * Implemented by subclasses to do work during {@link #stopUpdates}. */ - abstract void onDisable(); + abstract void onStopUpdates(); - /** For subclasses to invoke when a {@link LocationTimeZoneEvent} has been received. */ - final void handleLocationTimeZoneEvent( - @NonNull LocationTimeZoneEvent locationTimeZoneEvent) { + /** For subclasses to invoke when a {@link TimeZoneProviderEvent} has been received. */ + final void handleTimeZoneProviderEvent(@NonNull TimeZoneProviderEvent timeZoneProviderEvent) { mThreadingDomain.assertCurrentThread(); - Objects.requireNonNull(locationTimeZoneEvent); + Objects.requireNonNull(timeZoneProviderEvent); synchronized (mSharedLock) { - debugLog("handleLocationTimeZoneEvent: mProviderName=" + mProviderName - + ", locationTimeZoneEvent=" + locationTimeZoneEvent); + debugLog("handleTimeZoneProviderEvent: mProviderName=" + mProviderName + + ", timeZoneProviderEvent=" + timeZoneProviderEvent); ProviderState currentState = mCurrentState.get(); - int eventType = locationTimeZoneEvent.getEventType(); + int eventType = timeZoneProviderEvent.getType(); switch (currentState.stateEnum) { case PROVIDER_STATE_PERM_FAILED: { // After entering perm failed, there is nothing to do. The remote peer is // supposed to stop sending events after it has reported perm failure. - logWarn("handleLocationTimeZoneEvent: Event=" + locationTimeZoneEvent + logWarn("handleTimeZoneProviderEvent: Event=" + timeZoneProviderEvent + " received for provider=" + this + " when in failed state"); return; } - case PROVIDER_STATE_DISABLED: { + case PROVIDER_STATE_STOPPED: { switch (eventType) { case EVENT_TYPE_PERMANENT_FAILURE: { - String msg = "handleLocationTimeZoneEvent:" - + " Failure event=" + locationTimeZoneEvent - + " received for disabled provider=" + this + String msg = "handleTimeZoneProviderEvent:" + + " Failure event=" + timeZoneProviderEvent + + " received for stopped provider=" + this + ", entering permanently failed state"; logWarn(msg); ProviderState newState = currentState.newState( @@ -517,30 +518,30 @@ abstract class LocationTimeZoneProvider implements Dumpable { } return; } - case EVENT_TYPE_SUCCESS: + case EVENT_TYPE_SUGGESTION: case EVENT_TYPE_UNCERTAIN: { - // Any geolocation-related events received for a disabled provider are + // Any geolocation-related events received for a stopped provider are // ignored: they should not happen. - logWarn("handleLocationTimeZoneEvent:" - + " event=" + locationTimeZoneEvent - + " received for disabled provider=" + this + logWarn("handleTimeZoneProviderEvent:" + + " event=" + timeZoneProviderEvent + + " received for stopped provider=" + this + ", ignoring"); return; } default: { throw new IllegalStateException( - "Unknown eventType=" + locationTimeZoneEvent); + "Unknown eventType=" + timeZoneProviderEvent); } } } - case PROVIDER_STATE_ENABLED_INITIALIZING: - case PROVIDER_STATE_ENABLED_CERTAIN: - case PROVIDER_STATE_ENABLED_UNCERTAIN: { + case PROVIDER_STATE_STARTED_INITIALIZING: + case PROVIDER_STATE_STARTED_CERTAIN: + case PROVIDER_STATE_STARTED_UNCERTAIN: { switch (eventType) { case EVENT_TYPE_PERMANENT_FAILURE: { - String msg = "handleLocationTimeZoneEvent:" - + " Failure event=" + locationTimeZoneEvent + String msg = "handleTimeZoneProviderEvent:" + + " Failure event=" + timeZoneProviderEvent + " received for provider=" + this + ", entering permanently failed state"; logWarn(msg); @@ -554,16 +555,16 @@ abstract class LocationTimeZoneProvider implements Dumpable { return; } case EVENT_TYPE_UNCERTAIN: - case EVENT_TYPE_SUCCESS: { + case EVENT_TYPE_SUGGESTION: { @ProviderStateEnum int providerStateEnum; if (eventType == EVENT_TYPE_UNCERTAIN) { - providerStateEnum = PROVIDER_STATE_ENABLED_UNCERTAIN; + providerStateEnum = PROVIDER_STATE_STARTED_UNCERTAIN; } else { - providerStateEnum = PROVIDER_STATE_ENABLED_CERTAIN; + providerStateEnum = PROVIDER_STATE_STARTED_CERTAIN; } ProviderState newState = currentState.newState(providerStateEnum, - locationTimeZoneEvent, currentState.currentUserConfiguration, - "handleLocationTimeZoneEvent() when enabled"); + timeZoneProviderEvent, currentState.currentUserConfiguration, + "handleTimeZoneProviderEvent() when started"); setCurrentState(newState, true); if (mInitializationTimeoutQueue.hasQueued()) { mInitializationTimeoutQueue.cancel(); @@ -572,7 +573,7 @@ abstract class LocationTimeZoneProvider implements Dumpable { } default: { throw new IllegalStateException( - "Unknown eventType=" + locationTimeZoneEvent); + "Unknown eventType=" + timeZoneProviderEvent); } } } @@ -589,10 +590,10 @@ abstract class LocationTimeZoneProvider implements Dumpable { abstract void logWarn(String msg); @GuardedBy("mSharedLock") - private void assertIsEnabled() { + private void assertIsStarted() { ProviderState currentState = mCurrentState.get(); - if (!currentState.isEnabled()) { - throw new IllegalStateException("Required an enabled state, but was " + currentState); + if (!currentState.isStarted()) { + throw new IllegalStateException("Required a started state, but was " + currentState); } } diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java index ace066e12d3f3..45ec400aa4002 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java @@ -16,6 +16,7 @@ package com.android.server.location.timezone; +import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.os.Handler; @@ -59,7 +60,7 @@ import java.util.Objects; * disputed areas and oceans. Distinguishing uncertainty allows the controller to try other * providers (or give up), where as certainty means it should not. * - *

A provider can fail permanently. A permanent failure will disable the provider until next + *

A provider can fail permanently. A permanent failure will stop the provider until next * boot. */ abstract class LocationTimeZoneProviderController implements Dumpable { @@ -90,6 +91,7 @@ abstract class LocationTimeZoneProviderController implements Dumpable { abstract boolean isUncertaintyTimeoutSet(); @VisibleForTesting + @DurationMillisLong abstract long getUncertaintyTimeoutDelayMillis(); /** diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java index 8a0259d12a6c2..91b52f14d9301 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java @@ -23,17 +23,15 @@ import android.os.Handler; import android.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; -import com.android.internal.location.timezone.LocationTimeZoneEvent; -import com.android.internal.location.timezone.LocationTimeZoneProviderRequest; import com.android.server.timezonedetector.Dumpable; import java.util.Objects; /** - * System server-side proxy for ILocationTimeZoneProvider implementations, i.e. this provides the - * system server object used to communicate with a remote LocationTimeZoneProvider over Binder, - * which could be running in a different process. As LocationTimeZoneProviders are bound / unbound - * this proxy will rebind to the "best" available remote process. + * System server-side proxy for ITimeZoneProvider implementations, i.e. this provides the system + * server object used to communicate with a remote TimeZoneProvider over Binder, which could be + * running in a different process. As TimeZoneProviders are bound / unbound this proxy will rebind + * to the "best" available remote process. * *

Threading guarantees provided / required by this interface: *

    @@ -41,8 +39,8 @@ import java.util.Objects; * from the {@link ThreadingDomain} passed to the constructor, excluding * {@link #dump(IndentingPrintWriter, String[])} *
  • Non-static public methods that make binder calls to remote processes (e.g. - * {@link #setRequest(LocationTimeZoneProviderRequest)}) are executed asynchronously and will - * return immediately.
  • + * {@link #setRequest(TimeZoneProviderRequest)}) are executed asynchronously and will return + * immediately. *
  • Callbacks received via binder are delivered via {@link Listener} are delivered on the * {@link Handler} thread from the {@link ThreadingDomain} passed to the constructor. *
@@ -93,16 +91,15 @@ abstract class LocationTimeZoneProviderProxy implements Dumpable { /** * Sets a new request for the provider. */ - abstract void setRequest(@NonNull LocationTimeZoneProviderRequest request); + abstract void setRequest(@NonNull TimeZoneProviderRequest request); /** - * Handles a {@link LocationTimeZoneEvent} from a remote process. + * Handles a {@link TimeZoneProviderEvent} from a remote process. */ - final void handleLocationTimeZoneEvent( - @NonNull LocationTimeZoneEvent locationTimeZoneEvent) { + final void handleTimeZoneProviderEvent(@NonNull TimeZoneProviderEvent timeZoneProviderEvent) { // These calls are invoked on a binder thread. Move to the mThreadingDomain thread as // required by the guarantees for this class. - mThreadingDomain.post(() -> mListener.onReportLocationTimeZoneEvent(locationTimeZoneEvent)); + mThreadingDomain.post(() -> mListener.onReportTimeZoneProviderEvent(timeZoneProviderEvent)); } /** @@ -112,9 +109,9 @@ abstract class LocationTimeZoneProviderProxy implements Dumpable { interface Listener { /** - * Called when a provider receives a {@link LocationTimeZoneEvent}. + * Called when a provider receives a {@link TimeZoneProviderEvent}. */ - void onReportLocationTimeZoneEvent(@NonNull LocationTimeZoneEvent locationTimeZoneEvent); + void onReportTimeZoneProviderEvent(@NonNull TimeZoneProviderEvent timeZoneProviderEvent); /** * Called when a provider is (re)bound. diff --git a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java index fbcc71fad20a7..e11a7cee43db9 100644 --- a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java @@ -35,11 +35,11 @@ import java.time.Duration; * and a secondary {@link LocationTimeZoneProvider}, but only a primary is configured, the secondary * config will be left null and the {@link LocationTimeZoneProvider} implementation will be * defaulted to a {@link NullLocationTimeZoneProvider}. The {@link NullLocationTimeZoneProvider} - * enters a {@link ProviderState#PROVIDER_STATE_PERM_FAILED} state immediately after being enabled + * enters a {@link ProviderState#PROVIDER_STATE_PERM_FAILED} state immediately after being started * for the first time and sends the appropriate event, which ensures the {@link * LocationTimeZoneProviderController} won't expect any further {@link - * com.android.internal.location.timezone.LocationTimeZoneEvent}s to come from it, and won't attempt - * to use it again. + * TimeZoneProviderEvent}s to come from it, and won't attempt to use it + * again. */ class NullLocationTimeZoneProvider extends LocationTimeZoneProvider { @@ -57,7 +57,7 @@ class NullLocationTimeZoneProvider extends LocationTimeZoneProvider { } @Override - void onEnable(@NonNull Duration initializationTimeout) { + void onStartUpdates(@NonNull Duration initializationTimeout) { // Report a failure (asynchronously using the mThreadingDomain thread to avoid recursion). mThreadingDomain.post(()-> { // Enter the perm-failed state. @@ -69,8 +69,8 @@ class NullLocationTimeZoneProvider extends LocationTimeZoneProvider { } @Override - void onDisable() { - // Ignored - NullLocationTimeZoneProvider is always permanently failed. + void onStopUpdates() { + // Ignored - this implementation is always permanently failed. } @Override diff --git a/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java index 1a012882305f2..1bb5ceca76555 100644 --- a/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java @@ -22,22 +22,22 @@ import android.content.ComponentName; import android.content.Context; import android.os.IBinder; import android.os.RemoteException; +import android.service.timezone.ITimeZoneProvider; +import android.service.timezone.ITimeZoneProviderManager; +import android.service.timezone.TimeZoneProviderSuggestion; import android.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; -import com.android.internal.location.timezone.ILocationTimeZoneProvider; -import com.android.internal.location.timezone.ILocationTimeZoneProviderManager; -import com.android.internal.location.timezone.LocationTimeZoneEvent; -import com.android.internal.location.timezone.LocationTimeZoneProviderRequest; import com.android.server.ServiceWatcher; import java.util.Objects; /** - * System server-side proxy for ILocationTimeZoneProvider implementations, i.e. this provides the - * system server object used to communicate with a remote LocationTimeZoneProvider over Binder, - * which could be running in a different process. As "remote" LocationTimeZoneProviders are bound / - * unbound this proxy will rebind to the "best" available remote process. + * System server-side proxy for ITimeZoneProvider implementations, i.e. this provides the + * system server object used to communicate with a remote {@link + * android.service.timezone.TimeZoneProviderService} over Binder, which could be running in a + * different process. As "remote" providers are bound / unbound this proxy will rebind to the "best" + * available remote process. */ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { @@ -47,7 +47,7 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { @Nullable private ManagerProxy mManagerProxy; @GuardedBy("mProxyLock") - @NonNull private LocationTimeZoneProviderRequest mRequest; + @NonNull private TimeZoneProviderRequest mRequest; RealLocationTimeZoneProviderProxy( @NonNull Context context, @NonNull ThreadingDomain threadingDomain, @@ -55,7 +55,7 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { int nonOverlayPackageResId) { super(context, threadingDomain); mManagerProxy = null; - mRequest = LocationTimeZoneProviderRequest.EMPTY_REQUEST; + mRequest = TimeZoneProviderRequest.createStopUpdatesRequest(); mServiceWatcher = new ServiceWatcher(context, action, this::onBind, this::onUnbind, enableOverlayResId, nonOverlayPackageResId); } @@ -93,12 +93,12 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { private void onBindOnHandlerThread(@NonNull IBinder binder) { mThreadingDomain.assertCurrentThread(); - ILocationTimeZoneProvider provider = ILocationTimeZoneProvider.Stub.asInterface(binder); + ITimeZoneProvider provider = ITimeZoneProvider.Stub.asInterface(binder); synchronized (mSharedLock) { try { mManagerProxy = new ManagerProxy(); - provider.setLocationTimeZoneProviderManager(mManagerProxy); + provider.setTimeZoneProviderManager(mManagerProxy); trySendCurrentRequest(); mListener.onProviderBound(); } catch (RemoteException e) { @@ -118,7 +118,7 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { } @Override - final void setRequest(@NonNull LocationTimeZoneProviderRequest request) { + final void setRequest(@NonNull TimeZoneProviderRequest request) { mThreadingDomain.assertCurrentThread(); Objects.requireNonNull(request); @@ -131,11 +131,14 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { @GuardedBy("mProxyLock") private void trySendCurrentRequest() { - LocationTimeZoneProviderRequest request = mRequest; + TimeZoneProviderRequest request = mRequest; mServiceWatcher.runOnBinder(binder -> { - ILocationTimeZoneProvider service = - ILocationTimeZoneProvider.Stub.asInterface(binder); - service.setRequest(request); + ITimeZoneProvider service = ITimeZoneProvider.Stub.asInterface(binder); + if (request.sendUpdates()) { + service.startUpdates(request.getInitializationTimeout().toMillis()); + } else { + service.stopUpdates(); + } }); } @@ -148,21 +151,39 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { } /** - * A system Server-side proxy for the ILocationTimeZoneProviderManager, i.e. this is a local - * binder stub. Each "remote" LocationTimeZoneProvider is passed a binder instance that it - * then uses to communicate back with the system server, invoking the logic here. + * A system Server-side proxy for the ITimeZoneProviderManager, i.e. this is a local binder stub + * Each "remote" TimeZoneProvider is passed a binder instance that it then uses to communicate + * back with the system server, invoking the logic here. */ - private class ManagerProxy extends ILocationTimeZoneProviderManager.Stub { + private class ManagerProxy extends ITimeZoneProviderManager.Stub { // executed on binder thread @Override - public void onLocationTimeZoneEvent(LocationTimeZoneEvent locationTimeZoneEvent) { + public void onTimeZoneProviderSuggestion(TimeZoneProviderSuggestion suggestion) { + onTimeZoneProviderEvent(TimeZoneProviderEvent.createSuggestionEvent(suggestion)); + } + + // executed on binder thread + @Override + public void onTimeZoneProviderUncertain() { + onTimeZoneProviderEvent(TimeZoneProviderEvent.createUncertainEvent()); + + } + + // executed on binder thread + @Override + public void onTimeZoneProviderPermanentFailure(String failureReason) { + onTimeZoneProviderEvent( + TimeZoneProviderEvent.createPermanentFailureEvent(failureReason)); + } + + private void onTimeZoneProviderEvent(TimeZoneProviderEvent event) { synchronized (mSharedLock) { if (mManagerProxy != this) { return; } } - handleLocationTimeZoneEvent(locationTimeZoneEvent); + handleTimeZoneProviderEvent(event); } } } diff --git a/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java b/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java index 77253099d54f1..0987ee5c17bc2 100644 --- a/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java +++ b/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java @@ -16,9 +16,6 @@ package com.android.server.location.timezone; -import static com.android.internal.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE; -import static com.android.internal.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_SUCCESS; -import static com.android.internal.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN; import static com.android.server.location.timezone.LocationTimeZoneManagerService.PRIMARY_PROVIDER_NAME; import static com.android.server.location.timezone.LocationTimeZoneManagerService.SECONDARY_PROVIDER_NAME; @@ -26,8 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.ShellCommand; import android.os.SystemClock; - -import com.android.internal.location.timezone.LocationTimeZoneEvent; +import android.service.timezone.TimeZoneProviderSuggestion; import java.io.PrintWriter; import java.util.ArrayList; @@ -50,14 +46,14 @@ final class SimulatedBinderProviderEvent { @NonNull private final String mProviderName; - private final int mEventType; - @Nullable private final LocationTimeZoneEvent mLocationTimeZoneEvent; + private final int mType; + @Nullable private final TimeZoneProviderEvent mTimeZoneProviderEvent; private SimulatedBinderProviderEvent(@NonNull String providerName, int eventType, - @Nullable LocationTimeZoneEvent locationTimeZoneEvent) { + @Nullable TimeZoneProviderEvent timeZoneProviderEvent) { this.mProviderName = Objects.requireNonNull(providerName); - this.mEventType = eventType; - this.mLocationTimeZoneEvent = locationTimeZoneEvent; + this.mType = eventType; + this.mTimeZoneProviderEvent = timeZoneProviderEvent; } @NonNull @@ -66,24 +62,24 @@ final class SimulatedBinderProviderEvent { } @Nullable - LocationTimeZoneEvent getLocationTimeZoneEvent() { - return mLocationTimeZoneEvent; + TimeZoneProviderEvent getTimeZoneProviderEvent() { + return mTimeZoneProviderEvent; } - int getEventType() { - return mEventType; + int getType() { + return mType; } /** Prints the command line options that {@link #createFromArgs(ShellCommand)} understands. */ static void printCommandLineOpts(PrintWriter pw) { pw.println("Simulated provider binder event:"); pw.println(); - pw.println(" [onBind|onUnbind|locationTimeZoneEvent" + pw.println(" [onBind|onUnbind|timeZoneProviderEvent" + " ]"); pw.println(); pw.println(" = " + VALID_PROVIDER_NAMES); - pw.println(" =" - + " [PERMANENT_FAILURE|UNCERTAIN|SUCCESS