diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 58606d2038792..1552b9f977156 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9814,6 +9814,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 4f9a8de367b00..4e85af030c6ec 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5304,7 +5304,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