Merge "Add infrastructure for metrics" into sc-dev
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.timezonedetector;
|
||||
|
||||
import static libcore.io.IoUtils.closeQuietly;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.timezonedetector.ManualTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A class that provides time zone detector state information for metrics.
|
||||
*
|
||||
* <p>
|
||||
* Regarding time zone ID ordinals:
|
||||
* <p>
|
||||
* We don't want to leak user location information by reporting time zone IDs. Instead, time zone
|
||||
* IDs are consistently identified within a given instance of this class by a numeric ID. This
|
||||
* allows comparison of IDs without revealing what those IDs are.
|
||||
*/
|
||||
public final class MetricsTimeZoneDetectorState {
|
||||
|
||||
@IntDef(prefix = "DETECTION_MODE_",
|
||||
value = { DETECTION_MODE_MANUAL, DETECTION_MODE_GEO, DETECTION_MODE_TELEPHONY})
|
||||
@interface DetectionMode {};
|
||||
|
||||
@DetectionMode
|
||||
public static final int DETECTION_MODE_MANUAL = 0;
|
||||
@DetectionMode
|
||||
public static final int DETECTION_MODE_GEO = 1;
|
||||
@DetectionMode
|
||||
public static final int DETECTION_MODE_TELEPHONY = 2;
|
||||
|
||||
@NonNull
|
||||
private final ConfigurationInternal mConfigurationInternal;
|
||||
@NonNull
|
||||
private final int mDeviceTimeZoneIdOrdinal;
|
||||
@Nullable
|
||||
private final MetricsTimeZoneSuggestion mLatestManualSuggestion;
|
||||
@Nullable
|
||||
private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion;
|
||||
@Nullable
|
||||
private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion;
|
||||
|
||||
private MetricsTimeZoneDetectorState(
|
||||
@NonNull ConfigurationInternal configurationInternal,
|
||||
int deviceTimeZoneIdOrdinal,
|
||||
@Nullable MetricsTimeZoneSuggestion latestManualSuggestion,
|
||||
@Nullable MetricsTimeZoneSuggestion latestTelephonySuggestion,
|
||||
@Nullable MetricsTimeZoneSuggestion latestGeolocationSuggestion) {
|
||||
mConfigurationInternal = Objects.requireNonNull(configurationInternal);
|
||||
mDeviceTimeZoneIdOrdinal = deviceTimeZoneIdOrdinal;
|
||||
mLatestManualSuggestion = latestManualSuggestion;
|
||||
mLatestTelephonySuggestion = latestTelephonySuggestion;
|
||||
mLatestGeolocationSuggestion = latestGeolocationSuggestion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link MetricsTimeZoneDetectorState} from the supplied parameters, using the {@link
|
||||
* OrdinalGenerator} to generate time zone ID ordinals.
|
||||
*/
|
||||
public static MetricsTimeZoneDetectorState create(
|
||||
@NonNull OrdinalGenerator<String> tzIdOrdinalGenerator,
|
||||
@NonNull ConfigurationInternal configurationInternal,
|
||||
@NonNull String deviceTimeZoneId,
|
||||
@Nullable ManualTimeZoneSuggestion latestManualSuggestion,
|
||||
@Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion,
|
||||
@Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) {
|
||||
|
||||
// TODO(b/172934905) Add logic to canonicalize the time zone IDs to Android's preferred IDs
|
||||
// so that the ordinals will match even when the ID is not identical, just equivalent.
|
||||
int deviceTimeZoneIdOrdinal =
|
||||
tzIdOrdinalGenerator.ordinal(Objects.requireNonNull(deviceTimeZoneId));
|
||||
MetricsTimeZoneSuggestion latestObfuscatedManualSuggestion =
|
||||
createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestManualSuggestion);
|
||||
MetricsTimeZoneSuggestion latestObfuscatedTelephonySuggestion =
|
||||
createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestTelephonySuggestion);
|
||||
MetricsTimeZoneSuggestion latestObfuscatedGeolocationSuggestion =
|
||||
createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestGeolocationSuggestion);
|
||||
|
||||
return new MetricsTimeZoneDetectorState(
|
||||
configurationInternal, deviceTimeZoneIdOrdinal, latestObfuscatedManualSuggestion,
|
||||
latestObfuscatedTelephonySuggestion, latestObfuscatedGeolocationSuggestion);
|
||||
}
|
||||
|
||||
/** Returns true if the device supports telephony time zone detection. */
|
||||
public boolean isTelephonyDetectionSupported() {
|
||||
return mConfigurationInternal.isTelephonyDetectionSupported();
|
||||
}
|
||||
|
||||
/** Returns true if the device supports geolocation time zone detection. */
|
||||
public boolean isGeoDetectionSupported() {
|
||||
return mConfigurationInternal.isGeoDetectionSupported();
|
||||
}
|
||||
|
||||
/** Returns true if user's location can be used generally. */
|
||||
public boolean isUserLocationEnabled() {
|
||||
return mConfigurationInternal.isLocationEnabled();
|
||||
}
|
||||
|
||||
/** Returns the value of the geolocation time zone detection enabled setting. */
|
||||
public boolean getGeoDetectionEnabledSetting() {
|
||||
return mConfigurationInternal.getGeoDetectionEnabledSetting();
|
||||
}
|
||||
|
||||
/** Returns the value of the auto time zone detection enabled setting. */
|
||||
public boolean getAutoDetectionEnabledSetting() {
|
||||
return mConfigurationInternal.getAutoDetectionEnabledSetting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the detection mode the device is currently using, which can be influenced by various
|
||||
* things besides the user's setting.
|
||||
*/
|
||||
@DetectionMode
|
||||
public int getDetectionMode() {
|
||||
if (!mConfigurationInternal.getAutoDetectionEnabledBehavior()) {
|
||||
return DETECTION_MODE_MANUAL;
|
||||
} else if (mConfigurationInternal.getGeoDetectionEnabledBehavior()) {
|
||||
return DETECTION_MODE_GEO;
|
||||
} else {
|
||||
return DETECTION_MODE_TELEPHONY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ordinal for the device's currently set time zone ID.
|
||||
* See {@link MetricsTimeZoneDetectorState} for information about ordinals.
|
||||
*/
|
||||
@NonNull
|
||||
public int getDeviceTimeZoneIdOrdinal() {
|
||||
return mDeviceTimeZoneIdOrdinal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bytes[] for a {@link MetricsTimeZoneSuggestion} for the last manual
|
||||
* suggestion received.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] getLatestManualSuggestionProtoBytes() {
|
||||
return suggestionProtoBytes(mLatestManualSuggestion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bytes[] for a {@link MetricsTimeZoneSuggestion} for the last, best
|
||||
* telephony suggestion received.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] getLatestTelephonySuggestionProtoBytes() {
|
||||
return suggestionProtoBytes(mLatestTelephonySuggestion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bytes[] for a {@link MetricsTimeZoneSuggestion} for the last geolocation
|
||||
* suggestion received.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] getLatestGeolocationSuggestionProtoBytes() {
|
||||
return suggestionProtoBytes(mLatestGeolocationSuggestion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
MetricsTimeZoneDetectorState that = (MetricsTimeZoneDetectorState) o;
|
||||
return mDeviceTimeZoneIdOrdinal == that.mDeviceTimeZoneIdOrdinal
|
||||
&& mConfigurationInternal.equals(that.mConfigurationInternal)
|
||||
&& Objects.equals(mLatestManualSuggestion, that.mLatestManualSuggestion)
|
||||
&& Objects.equals(mLatestTelephonySuggestion, that.mLatestTelephonySuggestion)
|
||||
&& Objects.equals(mLatestGeolocationSuggestion, that.mLatestGeolocationSuggestion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal,
|
||||
mLatestManualSuggestion, mLatestTelephonySuggestion, mLatestGeolocationSuggestion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MetricsTimeZoneDetectorState{"
|
||||
+ "mConfigurationInternal=" + mConfigurationInternal
|
||||
+ ", mDeviceTimeZoneIdOrdinal=" + mDeviceTimeZoneIdOrdinal
|
||||
+ ", mLatestManualSuggestion=" + mLatestManualSuggestion
|
||||
+ ", mLatestTelephonySuggestion=" + mLatestTelephonySuggestion
|
||||
+ ", mLatestGeolocationSuggestion=" + mLatestGeolocationSuggestion
|
||||
+ '}';
|
||||
}
|
||||
|
||||
private static byte[] suggestionProtoBytes(
|
||||
@Nullable MetricsTimeZoneSuggestion suggestion) {
|
||||
if (suggestion == null) {
|
||||
return null;
|
||||
}
|
||||
return suggestion.toBytes();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
|
||||
@NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
|
||||
@NonNull ManualTimeZoneSuggestion manualSuggestion) {
|
||||
if (manualSuggestion == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(manualSuggestion.getZoneId());
|
||||
return MetricsTimeZoneSuggestion.createCertain(
|
||||
new int[] { zoneIdOrdinal });
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
|
||||
@NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
|
||||
@NonNull TelephonyTimeZoneSuggestion telephonySuggestion) {
|
||||
if (telephonySuggestion == null) {
|
||||
return null;
|
||||
}
|
||||
if (telephonySuggestion.getZoneId() == null) {
|
||||
return MetricsTimeZoneSuggestion.createUncertain();
|
||||
}
|
||||
int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(telephonySuggestion.getZoneId());
|
||||
return MetricsTimeZoneSuggestion.createCertain(new int[] { zoneIdOrdinal });
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
|
||||
@NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
|
||||
@Nullable GeolocationTimeZoneSuggestion geolocationSuggestion) {
|
||||
if (geolocationSuggestion == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> zoneIds = geolocationSuggestion.getZoneIds();
|
||||
if (zoneIds == null) {
|
||||
return MetricsTimeZoneSuggestion.createUncertain();
|
||||
}
|
||||
return MetricsTimeZoneSuggestion.createCertain(zoneIdOrdinalGenerator.ordinals(zoneIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* A Java class that closely matches the android.app.time.MetricsTimeZoneSuggestion
|
||||
* proto definition.
|
||||
*/
|
||||
private static final class MetricsTimeZoneSuggestion {
|
||||
@Nullable
|
||||
private final int[] mZoneIdOrdinals;
|
||||
|
||||
MetricsTimeZoneSuggestion(@Nullable int[] zoneIdOrdinals) {
|
||||
mZoneIdOrdinals = zoneIdOrdinals;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static MetricsTimeZoneSuggestion createUncertain() {
|
||||
return new MetricsTimeZoneSuggestion(null);
|
||||
}
|
||||
|
||||
public static MetricsTimeZoneSuggestion createCertain(
|
||||
@NonNull int[] zoneIdOrdinals) {
|
||||
return new MetricsTimeZoneSuggestion(zoneIdOrdinals);
|
||||
}
|
||||
|
||||
boolean isCertain() {
|
||||
return mZoneIdOrdinals != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
int[] getZoneIdOrdinals() {
|
||||
return mZoneIdOrdinals;
|
||||
}
|
||||
|
||||
byte[] toBytes() {
|
||||
// We don't get access to the atoms.proto definition for nested proto fields, so we use
|
||||
// an identically specified proto.
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ProtoOutputStream protoOutputStream = new ProtoOutputStream(byteArrayOutputStream);
|
||||
int typeProtoValue = isCertain()
|
||||
? android.app.time.MetricsTimeZoneSuggestion.CERTAIN
|
||||
: android.app.time.MetricsTimeZoneSuggestion.UNCERTAIN;
|
||||
protoOutputStream.write(android.app.time.MetricsTimeZoneSuggestion.TYPE,
|
||||
typeProtoValue);
|
||||
if (isCertain()) {
|
||||
for (int zoneIdOrdinal : getZoneIdOrdinals()) {
|
||||
protoOutputStream.write(
|
||||
android.app.time.MetricsTimeZoneSuggestion.TIME_ZONE_ORDINALS,
|
||||
zoneIdOrdinal);
|
||||
}
|
||||
}
|
||||
protoOutputStream.flush();
|
||||
closeQuietly(byteArrayOutputStream);
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
MetricsTimeZoneSuggestion that = (MetricsTimeZoneSuggestion) o;
|
||||
return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(mZoneIdOrdinals);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MetricsTimeZoneSuggestion{"
|
||||
+ "mZoneIdOrdinals=" + Arrays.toString(mZoneIdOrdinals)
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.server.timezonedetector;
|
||||
|
||||
import android.util.ArraySet;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A helper class that turns a set of objects into ordinal values, i.e. each object is offered
|
||||
* up via {@link #ordinal(Object)} or similar method, and a number will be returned. If the
|
||||
* object has been seen before by the instance then the same number will be returned. Intended
|
||||
* for situations where it is useful to know if values from some finite set are the same or
|
||||
* different, but the value is either large or may reveal PII. This class relies on {@link
|
||||
* Object#equals(Object)} and {@link Object#hashCode()}.
|
||||
*/
|
||||
class OrdinalGenerator<T> {
|
||||
private final ArraySet<T> mKnownIds = new ArraySet<>();
|
||||
|
||||
int ordinal(T object) {
|
||||
int ordinal = mKnownIds.indexOf(object);
|
||||
if (ordinal < 0) {
|
||||
ordinal = mKnownIds.size();
|
||||
mKnownIds.add(object);
|
||||
}
|
||||
return ordinal;
|
||||
}
|
||||
|
||||
int[] ordinals(List<T> objects) {
|
||||
int[] ordinals = new int[objects.size()];
|
||||
for (int i = 0; i < ordinals.length; i++) {
|
||||
ordinals[i] = ordinal(objects.get(i));
|
||||
}
|
||||
return ordinals;
|
||||
}
|
||||
}
|
||||
@@ -50,4 +50,8 @@ public interface TimeZoneDetectorInternal extends Dumpable.Container {
|
||||
* available, and so on. This method may be implemented asynchronously.
|
||||
*/
|
||||
void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion);
|
||||
|
||||
/** Generates a state snapshot for metrics. */
|
||||
@NonNull
|
||||
MetricsTimeZoneDetectorState generateMetricsState();
|
||||
}
|
||||
|
||||
@@ -90,4 +90,10 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter
|
||||
mHandler.post(
|
||||
() -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public MetricsTimeZoneDetectorState generateMetricsState() {
|
||||
return mTimeZoneDetectorStrategy.generateMetricsState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,9 +66,10 @@ import android.util.IndentingPrintWriter;
|
||||
* <p>Threading:
|
||||
*
|
||||
* <p>Suggestion calls with a void return type may be handed off to a separate thread and handled
|
||||
* asynchronously. Synchronous calls like {@link #getCurrentUserConfigurationInternal()}, and debug
|
||||
* calls like {@link #dump(IndentingPrintWriter, String[])}, may be called on a different thread
|
||||
* concurrently with other operations.
|
||||
* asynchronously. Synchronous calls like {@link #getCurrentUserConfigurationInternal()},
|
||||
* {@link #generateMetricsState()} and debug calls like {@link
|
||||
* #dump(IndentingPrintWriter, String[])}, may be called on a different thread concurrently with
|
||||
* other operations.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -123,4 +124,8 @@ public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container {
|
||||
* suggestion.
|
||||
*/
|
||||
void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion);
|
||||
|
||||
/** Generates a state snapshot for metrics. */
|
||||
@NonNull
|
||||
MetricsTimeZoneDetectorState generateMetricsState();
|
||||
}
|
||||
|
||||
@@ -371,6 +371,28 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public synchronized MetricsTimeZoneDetectorState generateMetricsState() {
|
||||
int currentUserId = mEnvironment.getCurrentUserId();
|
||||
// Just capture one telephony suggestion: the one that would be used right now if telephony
|
||||
// detection is in use.
|
||||
QualifiedTelephonyTimeZoneSuggestion bestQualifiedTelephonySuggestion =
|
||||
findBestTelephonySuggestion();
|
||||
TelephonyTimeZoneSuggestion telephonySuggestion =
|
||||
bestQualifiedTelephonySuggestion == null
|
||||
? null : bestQualifiedTelephonySuggestion.suggestion;
|
||||
// A new generator is created each time: we don't want / require consistency.
|
||||
OrdinalGenerator<String> tzIdOrdinalGenerator = new OrdinalGenerator<>();
|
||||
return MetricsTimeZoneDetectorState.create(
|
||||
tzIdOrdinalGenerator,
|
||||
getConfigurationInternal(currentUserId),
|
||||
mEnvironment.getDeviceTimeZone(),
|
||||
getLatestManualSuggestion(),
|
||||
telephonySuggestion,
|
||||
getLatestGeolocationSuggestion());
|
||||
}
|
||||
|
||||
private static int scoreTelephonySuggestion(@NonNull TelephonyTimeZoneSuggestion suggestion) {
|
||||
int score;
|
||||
if (suggestion.getZoneId() == null) {
|
||||
|
||||
@@ -42,10 +42,12 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
|
||||
@NonNull private final LocationTimeZoneProviderProxy mProxy;
|
||||
|
||||
BinderLocationTimeZoneProvider(
|
||||
@NonNull ProviderMetricsLogger providerMetricsLogger,
|
||||
@NonNull ThreadingDomain threadingDomain,
|
||||
@NonNull String providerName,
|
||||
@NonNull LocationTimeZoneProviderProxy proxy) {
|
||||
super(threadingDomain, providerName, new ZoneInfoDbTimeZoneIdValidator());
|
||||
super(providerMetricsLogger, threadingDomain, providerName,
|
||||
new ZoneInfoDbTimeZoneIdValidator());
|
||||
mProxy = Objects.requireNonNull(proxy);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.android.server.FgThread;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.timezonedetector.ServiceConfigAccessor;
|
||||
import com.android.server.timezonedetector.TimeZoneDetectorInternal;
|
||||
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderMetricsLogger;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
@@ -297,7 +298,9 @@ public class LocationTimeZoneManagerService extends Binder {
|
||||
R.string.config_primaryLocationTimeZoneProviderPackageName
|
||||
);
|
||||
}
|
||||
return new BinderLocationTimeZoneProvider(mThreadingDomain, PRIMARY_PROVIDER_NAME, proxy);
|
||||
ProviderMetricsLogger providerMetricsLogger = new RealProviderMetricsLogger(0);
|
||||
return new BinderLocationTimeZoneProvider(
|
||||
providerMetricsLogger, mThreadingDomain, PRIMARY_PROVIDER_NAME, proxy);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -317,7 +320,9 @@ public class LocationTimeZoneManagerService extends Binder {
|
||||
R.string.config_secondaryLocationTimeZoneProviderPackageName
|
||||
);
|
||||
}
|
||||
return new BinderLocationTimeZoneProvider(mThreadingDomain, SECONDARY_PROVIDER_NAME, proxy);
|
||||
ProviderMetricsLogger providerMetricsLogger = new RealProviderMetricsLogger(1);
|
||||
return new BinderLocationTimeZoneProvider(
|
||||
providerMetricsLogger, mThreadingDomain, SECONDARY_PROVIDER_NAME, proxy);
|
||||
}
|
||||
|
||||
/** Used for bug triage and in tests to simulate provider events. */
|
||||
|
||||
@@ -97,6 +97,14 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
boolean isValid(@NonNull String timeZoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener interface used to log provider events for metrics.
|
||||
*/
|
||||
interface ProviderMetricsLogger {
|
||||
/** Logs that a provider changed state. */
|
||||
void onProviderStateChanged(@ProviderStateEnum int stateEnum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about the provider's current state.
|
||||
*/
|
||||
@@ -349,6 +357,7 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull private final ProviderMetricsLogger mProviderMetricsLogger;
|
||||
@NonNull final ThreadingDomain mThreadingDomain;
|
||||
@NonNull final Object mSharedLock;
|
||||
@NonNull final String mProviderName;
|
||||
@@ -380,10 +389,12 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
@NonNull private TimeZoneIdValidator mTimeZoneIdValidator;
|
||||
|
||||
/** Creates the instance. */
|
||||
LocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain,
|
||||
LocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger,
|
||||
@NonNull ThreadingDomain threadingDomain,
|
||||
@NonNull String providerName,
|
||||
@NonNull TimeZoneIdValidator timeZoneIdValidator) {
|
||||
mThreadingDomain = Objects.requireNonNull(threadingDomain);
|
||||
mProviderMetricsLogger = Objects.requireNonNull(providerMetricsLogger);
|
||||
mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue();
|
||||
mSharedLock = threadingDomain.getLockObject();
|
||||
mProviderName = Objects.requireNonNull(providerName);
|
||||
@@ -485,6 +496,7 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
mCurrentState.set(newState);
|
||||
onSetCurrentState(newState);
|
||||
if (!Objects.equals(newState, oldState)) {
|
||||
mProviderMetricsLogger.onProviderStateChanged(newState.stateEnum);
|
||||
if (mStateChangeRecording) {
|
||||
mRecordedStates.add(newState);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.timezonedetector.location;
|
||||
|
||||
import android.annotation.IntRange;
|
||||
|
||||
import com.android.internal.util.FrameworkStatsLog;
|
||||
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderMetricsLogger;
|
||||
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
|
||||
|
||||
/**
|
||||
* The real implementation of {@link ProviderMetricsLogger} which logs using
|
||||
* {@link FrameworkStatsLog}.
|
||||
*/
|
||||
public class RealProviderMetricsLogger implements ProviderMetricsLogger {
|
||||
|
||||
@IntRange(from = 0, to = 1)
|
||||
private final int mProviderIndex;
|
||||
|
||||
public RealProviderMetricsLogger(@IntRange(from = 0, to = 1) int providerIndex) {
|
||||
mProviderIndex = providerIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderStateChanged(@ProviderStateEnum int stateEnum) {
|
||||
// TODO(b/172934905): Implement once the atom has landed.
|
||||
}
|
||||
}
|
||||
@@ -117,6 +117,11 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
|
||||
mLastTelephonySuggestion = timeZoneSuggestion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricsTimeZoneDetectorState generateMetricsState() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDumpable(Dumpable dumpable) {
|
||||
mDumpables.add(dumpable);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.timezonedetector;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class OrdinalGeneratorTest {
|
||||
|
||||
@Test
|
||||
public void testOrdinal() {
|
||||
OrdinalGenerator<String> ordinalGenerator = new OrdinalGenerator<>();
|
||||
int oneOrd = ordinalGenerator.ordinal("One");
|
||||
int twoOrd = ordinalGenerator.ordinal("Two");
|
||||
assertNotEquals(oneOrd, twoOrd);
|
||||
|
||||
assertEquals(oneOrd, ordinalGenerator.ordinal("One"));
|
||||
assertEquals(twoOrd, ordinalGenerator.ordinal("Two"));
|
||||
|
||||
int threeOrd = ordinalGenerator.ordinal("Three");
|
||||
assertNotEquals(oneOrd, threeOrd);
|
||||
assertNotEquals(twoOrd, threeOrd);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrdinals() {
|
||||
OrdinalGenerator<String> ordinalGenerator = new OrdinalGenerator<>();
|
||||
int[] oneTwoOrds = ordinalGenerator.ordinals(Arrays.asList("One", "Two"));
|
||||
int[] twoThreeOrds = ordinalGenerator.ordinals(Arrays.asList("Two", "Three"));
|
||||
assertEquals(oneTwoOrds[0], ordinalGenerator.ordinal("One"));
|
||||
assertEquals(oneTwoOrds[1], ordinalGenerator.ordinal("Two"));
|
||||
assertEquals(twoThreeOrds[0], ordinalGenerator.ordinal("Two"));
|
||||
assertEquals(twoThreeOrds[1], ordinalGenerator.ordinal("Three"));
|
||||
}
|
||||
}
|
||||
@@ -925,6 +925,106 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
assertTrue(dumpCalled.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateMetricsState() {
|
||||
ConfigurationInternal expectedInternalConfig = CONFIG_INT_AUTO_DISABLED_GEO_DISABLED;
|
||||
String expectedDeviceTimeZoneId = "InitialZoneId";
|
||||
|
||||
Script script = new Script()
|
||||
.initializeConfig(expectedInternalConfig)
|
||||
.initializeTimeZoneSetting(expectedDeviceTimeZoneId);
|
||||
|
||||
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId, null, null,
|
||||
null, MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL);
|
||||
|
||||
// Make sure the manual suggestion is recorded.
|
||||
ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Zone1");
|
||||
script.simulateManualTimeZoneSuggestion(USER_ID, manualSuggestion,
|
||||
true /* expectedResult */)
|
||||
.verifyTimeZoneChangedAndReset(manualSuggestion);
|
||||
expectedDeviceTimeZoneId = manualSuggestion.getZoneId();
|
||||
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
|
||||
manualSuggestion, null, null,
|
||||
MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL);
|
||||
|
||||
// With time zone auto detection off, telephony suggestions will be recorded, but geo
|
||||
// suggestions won't out of an abundance of caution around respecting user privacy when
|
||||
// geo detection is off.
|
||||
TelephonyTimeZoneSuggestion telephonySuggestion =
|
||||
createTelephonySuggestion(0 /* slotIndex */, MATCH_TYPE_NETWORK_COUNTRY_ONLY,
|
||||
QUALITY_SINGLE_ZONE, "Zone2");
|
||||
GeolocationTimeZoneSuggestion geolocationTimeZoneSuggestion =
|
||||
createGeoLocationSuggestion(Arrays.asList("Zone3", "Zone2"));
|
||||
script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
|
||||
.verifyTimeZoneNotChanged()
|
||||
.simulateGeolocationTimeZoneSuggestion(geolocationTimeZoneSuggestion)
|
||||
.verifyTimeZoneNotChanged();
|
||||
|
||||
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
|
||||
manualSuggestion, telephonySuggestion, null /* expectedGeoSuggestion */,
|
||||
MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL);
|
||||
|
||||
// Update the config and confirm that the config metrics state updates also.
|
||||
TimeZoneConfiguration configUpdate =
|
||||
createConfig(true /* autoDetection */, true /* geoDetection */);
|
||||
expectedInternalConfig = new ConfigurationInternal.Builder(expectedInternalConfig)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.setGeoDetectionEnabled(true)
|
||||
.build();
|
||||
script.simulateUpdateConfiguration(USER_ID, configUpdate, true /* expectedResult */)
|
||||
.verifyConfigurationChangedAndReset(expectedInternalConfig)
|
||||
.verifyTimeZoneNotChanged();
|
||||
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
|
||||
manualSuggestion, telephonySuggestion, null /* expectedGeoSuggestion */,
|
||||
MetricsTimeZoneDetectorState.DETECTION_MODE_GEO);
|
||||
|
||||
// Now simulate a geo suggestion and confirm it is used and reported in the metrics too.
|
||||
expectedDeviceTimeZoneId = geolocationTimeZoneSuggestion.getZoneIds().get(0);
|
||||
script.simulateGeolocationTimeZoneSuggestion(geolocationTimeZoneSuggestion)
|
||||
.verifyTimeZoneChangedAndReset(expectedDeviceTimeZoneId);
|
||||
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
|
||||
manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion,
|
||||
MetricsTimeZoneDetectorState.DETECTION_MODE_GEO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the information returned by {@link
|
||||
* TimeZoneDetectorStrategy#generateMetricsState()} matches expectations.
|
||||
*/
|
||||
private void assertMetricsState(
|
||||
ConfigurationInternal expectedInternalConfig,
|
||||
String expectedDeviceTimeZoneId, ManualTimeZoneSuggestion expectedManualSuggestion,
|
||||
TelephonyTimeZoneSuggestion expectedTelephonySuggestion,
|
||||
GeolocationTimeZoneSuggestion expectedGeolocationTimeZoneSuggestion,
|
||||
int expectedDetectionMode) {
|
||||
|
||||
MetricsTimeZoneDetectorState actualState = mTimeZoneDetectorStrategy.generateMetricsState();
|
||||
|
||||
// Check the various feature state values are what we expect.
|
||||
assertFeatureStateMatchesConfig(expectedInternalConfig, actualState, expectedDetectionMode);
|
||||
|
||||
OrdinalGenerator<String> tzIdOrdinalGenerator = new OrdinalGenerator<>();
|
||||
MetricsTimeZoneDetectorState expectedState =
|
||||
MetricsTimeZoneDetectorState.create(
|
||||
tzIdOrdinalGenerator, expectedInternalConfig, expectedDeviceTimeZoneId,
|
||||
expectedManualSuggestion, expectedTelephonySuggestion,
|
||||
expectedGeolocationTimeZoneSuggestion);
|
||||
// Rely on MetricsTimeZoneDetectorState.equals() for time zone ID ordinal comparisons.
|
||||
assertEquals(expectedState, actualState);
|
||||
}
|
||||
|
||||
private static void assertFeatureStateMatchesConfig(ConfigurationInternal config,
|
||||
MetricsTimeZoneDetectorState actualState, int expectedDetectionMode) {
|
||||
assertEquals(config.isTelephonyDetectionSupported(),
|
||||
actualState.isTelephonyDetectionSupported());
|
||||
assertEquals(config.isGeoDetectionSupported(), actualState.isGeoDetectionSupported());
|
||||
assertEquals(config.getAutoDetectionEnabledSetting(),
|
||||
actualState.getAutoDetectionEnabledSetting());
|
||||
assertEquals(config.getGeoDetectionEnabledSetting(),
|
||||
actualState.getGeoDetectionEnabledSetting());
|
||||
assertEquals(expectedDetectionMode, actualState.getDetectionMode());
|
||||
}
|
||||
|
||||
private static ManualTimeZoneSuggestion createManualSuggestion(String zoneId) {
|
||||
return new ManualTimeZoneSuggestion(zoneId);
|
||||
}
|
||||
|
||||
@@ -79,15 +79,18 @@ public class ControllerImplTest {
|
||||
// For simplicity, the TestThreadingDomain uses the test's main thread. To execute posted
|
||||
// runnables, the test must call methods on mTestThreadingDomain otherwise those runnables
|
||||
// will never get a chance to execute.
|
||||
LocationTimeZoneProvider.ProviderMetricsLogger stubbedProviderMetricsLogger = stateEnum -> {
|
||||
// Stubbed.
|
||||
};
|
||||
mTestThreadingDomain = new TestThreadingDomain();
|
||||
mTestCallback = new TestCallback(mTestThreadingDomain);
|
||||
mTimeZoneAvailabilityChecker = new FakeTimeZoneIdValidator();
|
||||
mTestPrimaryLocationTimeZoneProvider =
|
||||
new TestLocationTimeZoneProvider(
|
||||
mTestThreadingDomain, "primary", mTimeZoneAvailabilityChecker);
|
||||
mTestSecondaryLocationTimeZoneProvider =
|
||||
new TestLocationTimeZoneProvider(
|
||||
mTestThreadingDomain, "secondary", mTimeZoneAvailabilityChecker);
|
||||
mTestPrimaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider(
|
||||
stubbedProviderMetricsLogger, mTestThreadingDomain, "primary",
|
||||
mTimeZoneAvailabilityChecker);
|
||||
mTestSecondaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider(
|
||||
stubbedProviderMetricsLogger, mTestThreadingDomain, "secondary",
|
||||
mTimeZoneAvailabilityChecker);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1181,10 +1184,11 @@ public class ControllerImplTest {
|
||||
/**
|
||||
* Creates the instance.
|
||||
*/
|
||||
TestLocationTimeZoneProvider(ThreadingDomain threadingDomain,
|
||||
String providerName,
|
||||
TestLocationTimeZoneProvider(ProviderMetricsLogger providerMetricsLogger,
|
||||
ThreadingDomain threadingDomain, String providerName,
|
||||
TimeZoneIdValidator timeZoneIdValidator) {
|
||||
super(threadingDomain, providerName, timeZoneIdValidator);
|
||||
super(providerMetricsLogger, threadingDomain, providerName,
|
||||
timeZoneIdValidator);
|
||||
}
|
||||
|
||||
public void setFailDuringInitialization(boolean failInitialization) {
|
||||
|
||||
@@ -53,6 +53,7 @@ import org.junit.Test;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -79,19 +80,18 @@ public class LocationTimeZoneProviderTest {
|
||||
@Test
|
||||
public void lifecycle() {
|
||||
String providerName = "arbitrary";
|
||||
TestLocationTimeZoneProvider provider =
|
||||
new TestLocationTimeZoneProvider(
|
||||
mTestThreadingDomain,
|
||||
providerName,
|
||||
mTimeZoneAvailabilityChecker);
|
||||
RecordingProviderMetricsLogger providerMetricsLogger = new RecordingProviderMetricsLogger();
|
||||
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
|
||||
providerMetricsLogger, mTestThreadingDomain, providerName,
|
||||
mTimeZoneAvailabilityChecker);
|
||||
mTimeZoneAvailabilityChecker.validIds("Europe/London");
|
||||
|
||||
// initialize()
|
||||
provider.initialize(mProviderListener);
|
||||
provider.assertOnInitializeCalled();
|
||||
|
||||
ProviderState currentState = provider.getCurrentState();
|
||||
assertEquals(PROVIDER_STATE_STOPPED, currentState.stateEnum);
|
||||
ProviderState currentState = assertAndReturnProviderState(
|
||||
provider, providerMetricsLogger, PROVIDER_STATE_STOPPED);
|
||||
assertNull(currentState.currentUserConfiguration);
|
||||
assertSame(provider, currentState.provider);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
@@ -105,9 +105,9 @@ public class LocationTimeZoneProviderTest {
|
||||
|
||||
provider.assertOnStartCalled(arbitraryInitializationTimeout);
|
||||
|
||||
currentState = provider.getCurrentState();
|
||||
currentState = assertAndReturnProviderState(
|
||||
provider, providerMetricsLogger, PROVIDER_STATE_STARTED_INITIALIZING);
|
||||
assertSame(provider, currentState.provider);
|
||||
assertEquals(PROVIDER_STATE_STARTED_INITIALIZING, currentState.stateEnum);
|
||||
assertEquals(config, currentState.currentUserConfiguration);
|
||||
assertNull(currentState.event);
|
||||
// The initialization timeout should be queued.
|
||||
@@ -129,9 +129,9 @@ public class LocationTimeZoneProviderTest {
|
||||
TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion);
|
||||
provider.simulateProviderEventReceived(event);
|
||||
|
||||
currentState = provider.getCurrentState();
|
||||
currentState = assertAndReturnProviderState(
|
||||
provider, providerMetricsLogger, PROVIDER_STATE_STARTED_CERTAIN);
|
||||
assertSame(provider, currentState.provider);
|
||||
assertEquals(PROVIDER_STATE_STARTED_CERTAIN, currentState.stateEnum);
|
||||
assertEquals(event, currentState.event);
|
||||
assertEquals(config, currentState.currentUserConfiguration);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
@@ -141,9 +141,9 @@ public class LocationTimeZoneProviderTest {
|
||||
event = TimeZoneProviderEvent.createUncertainEvent();
|
||||
provider.simulateProviderEventReceived(event);
|
||||
|
||||
currentState = provider.getCurrentState();
|
||||
currentState = assertAndReturnProviderState(
|
||||
provider, providerMetricsLogger, PROVIDER_STATE_STARTED_UNCERTAIN);
|
||||
assertSame(provider, currentState.provider);
|
||||
assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN, currentState.stateEnum);
|
||||
assertEquals(event, currentState.event);
|
||||
assertEquals(config, currentState.currentUserConfiguration);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
@@ -153,7 +153,8 @@ public class LocationTimeZoneProviderTest {
|
||||
provider.stopUpdates();
|
||||
provider.assertOnStopUpdatesCalled();
|
||||
|
||||
currentState = provider.getCurrentState();
|
||||
currentState = assertAndReturnProviderState(
|
||||
provider, providerMetricsLogger, PROVIDER_STATE_STOPPED);
|
||||
assertSame(provider, currentState.provider);
|
||||
assertEquals(PROVIDER_STATE_STOPPED, currentState.stateEnum);
|
||||
assertNull(currentState.event);
|
||||
@@ -171,11 +172,10 @@ public class LocationTimeZoneProviderTest {
|
||||
@Test
|
||||
public void defaultHandleTestCommandImpl() {
|
||||
String providerName = "primary";
|
||||
TestLocationTimeZoneProvider provider =
|
||||
new TestLocationTimeZoneProvider(
|
||||
mTestThreadingDomain,
|
||||
providerName,
|
||||
mTimeZoneAvailabilityChecker);
|
||||
StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
|
||||
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
|
||||
providerMetricsLogger, mTestThreadingDomain, providerName,
|
||||
mTimeZoneAvailabilityChecker);
|
||||
|
||||
TestCommand testCommand = TestCommand.createForTests("test", new Bundle());
|
||||
AtomicReference<Bundle> resultReference = new AtomicReference<>();
|
||||
@@ -191,11 +191,10 @@ public class LocationTimeZoneProviderTest {
|
||||
@Test
|
||||
public void stateRecording() {
|
||||
String providerName = "primary";
|
||||
TestLocationTimeZoneProvider provider =
|
||||
new TestLocationTimeZoneProvider(
|
||||
mTestThreadingDomain,
|
||||
providerName,
|
||||
mTimeZoneAvailabilityChecker);
|
||||
StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
|
||||
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
|
||||
providerMetricsLogger, mTestThreadingDomain, providerName,
|
||||
mTimeZoneAvailabilityChecker);
|
||||
provider.setStateChangeRecordingEnabled(true);
|
||||
mTimeZoneAvailabilityChecker.validIds("Europe/London");
|
||||
|
||||
@@ -237,11 +236,10 @@ public class LocationTimeZoneProviderTest {
|
||||
@Test
|
||||
public void considerSuggestionWithInvalidTimeZoneIdsAsUncertain() {
|
||||
String providerName = "primary";
|
||||
TestLocationTimeZoneProvider provider =
|
||||
new TestLocationTimeZoneProvider(
|
||||
mTestThreadingDomain,
|
||||
providerName,
|
||||
mTimeZoneAvailabilityChecker);
|
||||
StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
|
||||
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
|
||||
providerMetricsLogger, mTestThreadingDomain, providerName,
|
||||
mTimeZoneAvailabilityChecker);
|
||||
provider.setStateChangeRecordingEnabled(true);
|
||||
provider.initialize(mProviderListener);
|
||||
|
||||
@@ -285,6 +283,20 @@ public class LocationTimeZoneProviderTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the provider's state after asserting that the current state matches what is expected.
|
||||
* This also asserts that the metrics logger was informed of the state change.
|
||||
*/
|
||||
private static ProviderState assertAndReturnProviderState(
|
||||
TestLocationTimeZoneProvider provider,
|
||||
RecordingProviderMetricsLogger providerMetricsLogger, int expectedStateEnum) {
|
||||
ProviderState currentState = provider.getCurrentState();
|
||||
assertEquals(expectedStateEnum, currentState.stateEnum);
|
||||
providerMetricsLogger.assertChangeLoggedAndRemove(expectedStateEnum);
|
||||
providerMetricsLogger.assertNoMoreLogEntries();
|
||||
return currentState;
|
||||
}
|
||||
|
||||
private static class TestLocationTimeZoneProvider extends LocationTimeZoneProvider {
|
||||
|
||||
private boolean mOnInitializeCalled;
|
||||
@@ -294,10 +306,11 @@ public class LocationTimeZoneProviderTest {
|
||||
private boolean mOnStopUpdatesCalled;
|
||||
|
||||
/** Creates the instance. */
|
||||
TestLocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain,
|
||||
TestLocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger,
|
||||
@NonNull ThreadingDomain threadingDomain,
|
||||
@NonNull String providerName,
|
||||
@NonNull TimeZoneIdValidator timeZoneIdValidator) {
|
||||
super(threadingDomain, providerName, timeZoneIdValidator);
|
||||
super(providerMetricsLogger, threadingDomain, providerName, timeZoneIdValidator);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -366,6 +379,36 @@ public class LocationTimeZoneProviderTest {
|
||||
public void validIds(String... timeZoneIdss) {
|
||||
mValidTimeZoneIds.addAll(asList(timeZoneIdss));
|
||||
}
|
||||
}
|
||||
|
||||
private static class StubbedProviderMetricsLogger implements
|
||||
LocationTimeZoneProvider.ProviderMetricsLogger {
|
||||
|
||||
@Override
|
||||
public void onProviderStateChanged(int stateEnum) {
|
||||
// Stubbed
|
||||
}
|
||||
}
|
||||
|
||||
private static class RecordingProviderMetricsLogger implements
|
||||
LocationTimeZoneProvider.ProviderMetricsLogger {
|
||||
|
||||
private LinkedList<Integer> mStates = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public void onProviderStateChanged(int stateEnum) {
|
||||
mStates.add(stateEnum);
|
||||
}
|
||||
|
||||
public void assertChangeLoggedAndRemove(int expectedLoggedState) {
|
||||
assertEquals("expected loggedState=" + expectedLoggedState
|
||||
+ " but states logged were=" + mStates,
|
||||
(Integer) expectedLoggedState, mStates.peekFirst());
|
||||
mStates.removeFirst();
|
||||
}
|
||||
|
||||
public void assertNoMoreLogEntries() {
|
||||
assertTrue(mStates.isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user