diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java index ceab02f510629..aac23d88fa7bc 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -102,6 +102,14 @@ public interface TimeZoneDetector { */ String SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK = "enable_telephony_fallback"; + /** + * A shell command that dumps a {@link + * com.android.server.timezonedetector.MetricsTimeZoneDetectorState} object to stdout for + * debugging. + * @hide + */ + String SHELL_COMMAND_DUMP_METRICS = "dump_metrics"; + /** * A shared utility method to create a {@link ManualTimeZoneSuggestion}. * diff --git a/core/proto/android/app/time_zone_detector.proto b/core/proto/android/app/time_zone_detector.proto index b33ca1d4f476b..b52aa828bef98 100644 --- a/core/proto/android/app/time_zone_detector.proto +++ b/core/proto/android/app/time_zone_detector.proto @@ -32,16 +32,8 @@ message GeolocationTimeZoneSuggestionProto { } /* - * An obfuscated and simplified time zone suggestion for metrics use. - * - * The suggestion's time zone IDs (which relate to location) are obfuscated by - * mapping them to an ordinal. When the ordinal is assigned consistently across - * several objects (i.e. so the same time zone ID is always mapped to the same - * ordinal), this allows comparisons between those objects. For example, we can - * answer "did these two suggestions agree?", "does the suggestion match the - * device's current time zone?", without leaking knowledge of location. Ordinals - * are also significantly more compact than full IANA TZDB IDs, albeit highly - * unstable and of limited use. + * A generic-form time zone suggestion for metrics use. Required to be a superset of the + * MetricsTimeZoneSuggestion proto defined in atoms.proto to ensure binary compatibility. */ message MetricsTimeZoneSuggestion { option (android.msg_privacy).dest = DEST_AUTOMATIC; @@ -55,5 +47,24 @@ message MetricsTimeZoneSuggestion { // The ordinals for time zone(s) in the suggestion. Always empty for // UNCERTAIN, and can be empty for CERTAIN, for example when the device is in // a disputed area / on an ocean. - repeated uint32 time_zone_ordinals = 2; + // + // The suggestion's time zone IDs (which relate to location) are obfuscated by + // mapping them to an ordinal. When the ordinal is assigned consistently across + // several objects (i.e. so the same time zone ID is always mapped to the same + // ordinal), this allows comparisons between those objects. For example, we can + // answer "did these two suggestions agree?", "does the suggestion match the + // device's current time zone?", without leaking knowledge of location. Ordinals + // are also significantly more compact than full IANA TZDB IDs, albeit unstable + // and of limited use. + repeated int32 time_zone_ordinals = 2; + + // The actual time zone ID(s) in the suggestion. Similar to time_zone_ordinals + // but contains the actual string IDs. + // + // This information is only captured / reported for some devices based on the + // value of a server side flag, i.e. it could be enabled for internal testers. + // Therefore the list can be empty even when time_zone_ordinals is populated. + // + // When enabled, see time_zone_ordinals for the expected number of values. + repeated string time_zone_ids = 3; } diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java index d24a3df56420d..cf0e350d7f8ea 100644 --- a/services/core/java/com/android/server/timedetector/ServerFlags.java +++ b/services/core/java/com/android/server/timedetector/ServerFlags.java @@ -66,6 +66,7 @@ public final class ServerFlags { KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE, KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE, KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED, + KEY_ENHANCED_METRICS_COLLECTION_ENABLED, }) @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) @Retention(RetentionPolicy.SOURCE) @@ -156,12 +157,18 @@ public final class ServerFlags { "time_detector_origin_priorities_override"; /** - * The key to override the time detector lower bound configuration. The values is the number of + * The key to override the time detector lower bound configuration. The value is the number of * milliseconds since the beginning of the Unix epoch. */ public static final @DeviceConfigKey String KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE = "time_detector_lower_bound_millis_override"; + /** + * The key to allow extra metrics / telemetry information to be collected from internal testers. + */ + public static final @DeviceConfigKey String KEY_ENHANCED_METRICS_COLLECTION_ENABLED = + "enhanced_metrics_collection_enabled"; + @GuardedBy("mListeners") private final ArrayMap> mListeners = new ArrayMap<>(); diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java index 65f077ea60e0a..2291777651dd8 100644 --- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java +++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java @@ -40,6 +40,7 @@ public final class ConfigurationInternal { private final boolean mTelephonyDetectionSupported; private final boolean mGeoDetectionSupported; private final boolean mTelephonyFallbackSupported; + private final boolean mEnhancedMetricsCollectionEnabled; private final boolean mAutoDetectionEnabledSetting; private final @UserIdInt int mUserId; private final boolean mUserConfigAllowed; @@ -50,6 +51,7 @@ public final class ConfigurationInternal { mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported; mGeoDetectionSupported = builder.mGeoDetectionSupported; mTelephonyFallbackSupported = builder.mTelephonyFallbackSupported; + mEnhancedMetricsCollectionEnabled = builder.mEnhancedMetricsCollectionEnabled; mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting; mUserId = builder.mUserId; @@ -81,6 +83,15 @@ public final class ConfigurationInternal { return mTelephonyFallbackSupported; } + /** + * Returns {@code true} if the device can collect / report extra metrics information for QA + * / testers. These metrics might involve logging more expensive or more revealing data that + * would not be collected from the set of public users. + */ + public boolean isEnhancedMetricsCollectionEnabled() { + return mEnhancedMetricsCollectionEnabled; + } + /** Returns the value of the auto time zone detection enabled setting. */ public boolean getAutoDetectionEnabledSetting() { return mAutoDetectionEnabledSetting; @@ -227,6 +238,7 @@ public final class ConfigurationInternal { && mTelephonyDetectionSupported == that.mTelephonyDetectionSupported && mGeoDetectionSupported == that.mGeoDetectionSupported && mTelephonyFallbackSupported == that.mTelephonyFallbackSupported + && mEnhancedMetricsCollectionEnabled == that.mEnhancedMetricsCollectionEnabled && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting && mLocationEnabledSetting == that.mLocationEnabledSetting && mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting; @@ -235,7 +247,8 @@ public final class ConfigurationInternal { @Override public int hashCode() { return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported, - mGeoDetectionSupported, mTelephonyFallbackSupported, mAutoDetectionEnabledSetting, + mGeoDetectionSupported, mTelephonyFallbackSupported, + mEnhancedMetricsCollectionEnabled, mAutoDetectionEnabledSetting, mLocationEnabledSetting, mGeoDetectionEnabledSetting); } @@ -247,6 +260,7 @@ public final class ConfigurationInternal { + ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported + ", mGeoDetectionSupported=" + mGeoDetectionSupported + ", mTelephonyFallbackSupported=" + mTelephonyFallbackSupported + + ", mEnhancedMetricsCollectionEnabled=" + mEnhancedMetricsCollectionEnabled + ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting + ", mLocationEnabledSetting=" + mLocationEnabledSetting + ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting @@ -264,6 +278,7 @@ public final class ConfigurationInternal { private boolean mTelephonyDetectionSupported; private boolean mGeoDetectionSupported; private boolean mTelephonyFallbackSupported; + private boolean mEnhancedMetricsCollectionEnabled; private boolean mAutoDetectionEnabledSetting; private boolean mLocationEnabledSetting; private boolean mGeoDetectionEnabledSetting; @@ -284,6 +299,7 @@ public final class ConfigurationInternal { this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported; this.mTelephonyFallbackSupported = toCopy.mTelephonyFallbackSupported; this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported; + this.mEnhancedMetricsCollectionEnabled = toCopy.mEnhancedMetricsCollectionEnabled; this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting; this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting; this.mGeoDetectionEnabledSetting = toCopy.mGeoDetectionEnabledSetting; @@ -322,6 +338,14 @@ public final class ConfigurationInternal { return this; } + /** + * Sets the value for enhanced metrics collection. + */ + public Builder setEnhancedMetricsCollectionEnabled(boolean enabled) { + mEnhancedMetricsCollectionEnabled = enabled; + return this; + } + /** * Sets the value of the automatic time zone detection enabled setting for this device. */ diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java index f156f8c8e59de..ecac267ff4e91 100644 --- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java +++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java @@ -34,11 +34,13 @@ import java.util.Objects; * A class that provides time zone detector state information for metrics. * *

- * Regarding time zone ID ordinals: + * Regarding the use of time zone ID ordinals in metrics / telemetry: *

- * 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. + * For general metrics, 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 (ordinal). This allows comparison of IDs without revealing what those IDs are. + * See {@link #isEnhancedMetricsCollectionEnabled()} for the setting that enables actual IDs to be + * collected. */ public final class MetricsTimeZoneDetectorState { @@ -54,6 +56,7 @@ public final class MetricsTimeZoneDetectorState { @NonNull private final ConfigurationInternal mConfigurationInternal; private final int mDeviceTimeZoneIdOrdinal; + @Nullable private final String mDeviceTimeZoneId; @Nullable private final MetricsTimeZoneSuggestion mLatestManualSuggestion; @Nullable private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion; @Nullable private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion; @@ -61,11 +64,13 @@ public final class MetricsTimeZoneDetectorState { private MetricsTimeZoneDetectorState( @NonNull ConfigurationInternal configurationInternal, int deviceTimeZoneIdOrdinal, + @Nullable String deviceTimeZoneId, @Nullable MetricsTimeZoneSuggestion latestManualSuggestion, @Nullable MetricsTimeZoneSuggestion latestTelephonySuggestion, @Nullable MetricsTimeZoneSuggestion latestGeolocationSuggestion) { mConfigurationInternal = Objects.requireNonNull(configurationInternal); mDeviceTimeZoneIdOrdinal = deviceTimeZoneIdOrdinal; + mDeviceTimeZoneId = deviceTimeZoneId; mLatestManualSuggestion = latestManualSuggestion; mLatestTelephonySuggestion = latestTelephonySuggestion; mLatestGeolocationSuggestion = latestGeolocationSuggestion; @@ -83,18 +88,24 @@ public final class MetricsTimeZoneDetectorState { @Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion, @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) { + boolean includeZoneIds = configurationInternal.isEnhancedMetricsCollectionEnabled(); + String metricDeviceTimeZoneId = includeZoneIds ? deviceTimeZoneId : null; int deviceTimeZoneIdOrdinal = tzIdOrdinalGenerator.ordinal(Objects.requireNonNull(deviceTimeZoneId)); MetricsTimeZoneSuggestion latestCanonicalManualSuggestion = - createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestManualSuggestion); + createMetricsTimeZoneSuggestion( + tzIdOrdinalGenerator, latestManualSuggestion, includeZoneIds); MetricsTimeZoneSuggestion latestCanonicalTelephonySuggestion = - createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestTelephonySuggestion); + createMetricsTimeZoneSuggestion( + tzIdOrdinalGenerator, latestTelephonySuggestion, includeZoneIds); MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion = - createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestGeolocationSuggestion); + createMetricsTimeZoneSuggestion( + tzIdOrdinalGenerator, latestGeolocationSuggestion, includeZoneIds); return new MetricsTimeZoneDetectorState( - configurationInternal, deviceTimeZoneIdOrdinal, latestCanonicalManualSuggestion, - latestCanonicalTelephonySuggestion, latestCanonicalGeolocationSuggestion); + configurationInternal, deviceTimeZoneIdOrdinal, metricDeviceTimeZoneId, + latestCanonicalManualSuggestion, latestCanonicalTelephonySuggestion, + latestCanonicalGeolocationSuggestion); } /** Returns true if the device supports telephony time zone detection. */ @@ -112,6 +123,11 @@ public final class MetricsTimeZoneDetectorState { return mConfigurationInternal.isTelephonyFallbackSupported(); } + /** Returns true if enhanced metric collection is enabled. */ + public boolean isEnhancedMetricsCollectionEnabled() { + return mConfigurationInternal.isEnhancedMetricsCollectionEnabled(); + } + /** Returns true if user's location can be used generally. */ public boolean getUserLocationEnabledSetting() { return mConfigurationInternal.getLocationEnabledSetting(); @@ -142,13 +158,23 @@ public final class MetricsTimeZoneDetectorState { } /** - * Returns the ordinal for the device's currently set time zone ID. + * Returns the ordinal for the device's current time zone ID. * See {@link MetricsTimeZoneDetectorState} for information about ordinals. */ public int getDeviceTimeZoneIdOrdinal() { return mDeviceTimeZoneIdOrdinal; } + /** + * Returns the device's current time zone ID. This will only be populated if {@link + * #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link + * MetricsTimeZoneDetectorState} for details. + */ + @Nullable + public String getDeviceTimeZoneId() { + return mDeviceTimeZoneId; + } + /** * Returns a canonical form of the last manual suggestion received. */ @@ -183,6 +209,7 @@ public final class MetricsTimeZoneDetectorState { } MetricsTimeZoneDetectorState that = (MetricsTimeZoneDetectorState) o; return mDeviceTimeZoneIdOrdinal == that.mDeviceTimeZoneIdOrdinal + && Objects.equals(mDeviceTimeZoneId, that.mDeviceTimeZoneId) && mConfigurationInternal.equals(that.mConfigurationInternal) && Objects.equals(mLatestManualSuggestion, that.mLatestManualSuggestion) && Objects.equals(mLatestTelephonySuggestion, that.mLatestTelephonySuggestion) @@ -191,7 +218,7 @@ public final class MetricsTimeZoneDetectorState { @Override public int hashCode() { - return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal, + return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal, mDeviceTimeZoneId, mLatestManualSuggestion, mLatestTelephonySuggestion, mLatestGeolocationSuggestion); } @@ -200,6 +227,7 @@ public final class MetricsTimeZoneDetectorState { return "MetricsTimeZoneDetectorState{" + "mConfigurationInternal=" + mConfigurationInternal + ", mDeviceTimeZoneIdOrdinal=" + mDeviceTimeZoneIdOrdinal + + ", mDeviceTimeZoneId=" + mDeviceTimeZoneId + ", mLatestManualSuggestion=" + mLatestManualSuggestion + ", mLatestTelephonySuggestion=" + mLatestTelephonySuggestion + ", mLatestGeolocationSuggestion=" + mLatestGeolocationSuggestion @@ -209,34 +237,40 @@ public final class MetricsTimeZoneDetectorState { @Nullable private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion( @NonNull OrdinalGenerator zoneIdOrdinalGenerator, - @NonNull ManualTimeZoneSuggestion manualSuggestion) { + @NonNull ManualTimeZoneSuggestion manualSuggestion, + boolean includeFullZoneIds) { if (manualSuggestion == null) { return null; } - int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(manualSuggestion.getZoneId()); - return MetricsTimeZoneSuggestion.createCertain( - new int[] { zoneIdOrdinal }); + String suggestionZoneId = manualSuggestion.getZoneId(); + String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null; + int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) }; + return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals); } @Nullable private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion( @NonNull OrdinalGenerator zoneIdOrdinalGenerator, - @NonNull TelephonyTimeZoneSuggestion telephonySuggestion) { + @NonNull TelephonyTimeZoneSuggestion telephonySuggestion, + boolean includeFullZoneIds) { if (telephonySuggestion == null) { return null; } - if (telephonySuggestion.getZoneId() == null) { + String suggestionZoneId = telephonySuggestion.getZoneId(); + if (suggestionZoneId == null) { return MetricsTimeZoneSuggestion.createUncertain(); } - int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(telephonySuggestion.getZoneId()); - return MetricsTimeZoneSuggestion.createCertain(new int[] { zoneIdOrdinal }); + String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null; + int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) }; + return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals); } @Nullable private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion( @NonNull OrdinalGenerator zoneIdOrdinalGenerator, - @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion) { + @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion, + boolean includeFullZoneIds) { if (geolocationSuggestion == null) { return null; } @@ -245,7 +279,9 @@ public final class MetricsTimeZoneDetectorState { if (zoneIds == null) { return MetricsTimeZoneSuggestion.createUncertain(); } - return MetricsTimeZoneSuggestion.createCertain(zoneIdOrdinalGenerator.ordinals(zoneIds)); + String[] metricZoneIds = includeFullZoneIds ? zoneIds.toArray(new String[0]) : null; + int[] zoneIdOrdinals = zoneIdOrdinalGenerator.ordinals(zoneIds); + return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals); } /** @@ -254,33 +290,49 @@ public final class MetricsTimeZoneDetectorState { * MetricsTimeZoneSuggestion proto definition. */ public static final class MetricsTimeZoneSuggestion { - @Nullable - private final int[] mZoneIdOrdinals; + @Nullable private final String[] mZoneIds; + @Nullable private final int[] mZoneIdOrdinals; - MetricsTimeZoneSuggestion(@Nullable int[] zoneIdOrdinals) { + private MetricsTimeZoneSuggestion( + @Nullable String[] zoneIds, @Nullable int[] zoneIdOrdinals) { + mZoneIds = zoneIds; mZoneIdOrdinals = zoneIdOrdinals; } @NonNull static MetricsTimeZoneSuggestion createUncertain() { - return new MetricsTimeZoneSuggestion(null); + return new MetricsTimeZoneSuggestion(null, null); } @NonNull static MetricsTimeZoneSuggestion createCertain( - @NonNull int[] zoneIdOrdinals) { - return new MetricsTimeZoneSuggestion(zoneIdOrdinals); + @Nullable String[] zoneIds, @NonNull int[] zoneIdOrdinals) { + return new MetricsTimeZoneSuggestion(zoneIds, zoneIdOrdinals); } public boolean isCertain() { return mZoneIdOrdinals != null; } + /** + * Returns ordinals for the time zone IDs contained in the suggestion. + * See {@link MetricsTimeZoneDetectorState} for information about ordinals. + */ @Nullable public int[] getZoneIdOrdinals() { return mZoneIdOrdinals; } + /** + * Returns the time zone IDs contained in the suggestion. This will only be populated if + * {@link #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link + * MetricsTimeZoneDetectorState} for details. + */ + @Nullable + public String[] getZoneIds() { + return mZoneIds; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -290,18 +342,22 @@ public final class MetricsTimeZoneDetectorState { return false; } MetricsTimeZoneSuggestion that = (MetricsTimeZoneSuggestion) o; - return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals); + return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals) + && Arrays.equals(mZoneIds, that.mZoneIds); } @Override public int hashCode() { - return Arrays.hashCode(mZoneIdOrdinals); + int result = Arrays.hashCode(mZoneIds); + result = 31 * result + Arrays.hashCode(mZoneIdOrdinals); + return result; } @Override public String toString() { return "MetricsTimeZoneSuggestion{" + "mZoneIdOrdinals=" + Arrays.toString(mZoneIdOrdinals) + + ", mZoneIds=" + Arrays.toString(mZoneIds) + '}'; } } diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java index 02ea43341beff..4612f659d51a9 100644 --- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java +++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java @@ -62,6 +62,7 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { private static final Set CONFIGURATION_INTERNAL_SERVER_FLAGS_KEYS_TO_WATCH = Collections.unmodifiableSet(new ArraySet<>(new String[] { ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, + ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED, ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE, ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED, @@ -296,6 +297,7 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { isTelephonyTimeZoneDetectionFeatureSupported()) .setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported()) .setTelephonyFallbackSupported(isTelephonyFallbackSupported()) + .setEnhancedMetricsCollectionEnabled(isEnhancedMetricsCollectionEnabled()) .setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting()) .setUserConfigAllowed(isUserConfigAllowed(userId)) .setLocationEnabledSetting(getLocationEnabledSetting(userId)) @@ -400,6 +402,17 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { defaultEnabled); } + /** + * Returns {@code true} if extra metrics / telemetry information can be collected. Used for + * internal testers. + */ + private boolean isEnhancedMetricsCollectionEnabled() { + final boolean defaultEnabled = false; + return mServerFlags.getBoolean( + ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED, + defaultEnabled); + } + @Override @NonNull public synchronized String getPrimaryLocationTimeZoneProviderPackageName() { diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index 14784cf0d5501..f75608e145395 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -364,6 +364,13 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub } } + @NonNull + MetricsTimeZoneDetectorState generateMetricsState() { + enforceManageTimeZoneDetectorPermission(); + + return mTimeZoneDetectorStrategy.generateMetricsState(); + } + @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) { diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java index 2b912ad7ba601..8535b3d485c4d 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java @@ -15,6 +15,7 @@ */ package com.android.server.timezonedetector; +import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_DUMP_METRICS; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED; @@ -28,6 +29,7 @@ import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGES import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE; import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; +import static com.android.server.timedetector.ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED; import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED; import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT; import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE; @@ -80,6 +82,8 @@ class TimeZoneDetectorShellCommand extends ShellCommand { return runSuggestTelephonyTimeZone(); case SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK: return runEnableTelephonyFallback(); + case SHELL_COMMAND_DUMP_METRICS: + return runDumpMetrics(); default: { return handleDefaultCommands(cmd); } @@ -168,14 +172,22 @@ class TimeZoneDetectorShellCommand extends ShellCommand { pw.println("Suggestion " + suggestion + " injected."); return 0; } catch (RuntimeException e) { - pw.println(e.toString()); + pw.println(e); return 1; } } private int runEnableTelephonyFallback() { mInterface.enableTelephonyFallback(); - return 1; + return 0; + } + + private int runDumpMetrics() { + final PrintWriter pw = getOutPrintWriter(); + MetricsTimeZoneDetectorState metricsState = mInterface.generateMetricsState(); + pw.println("MetricsTimeZoneDetectorState:"); + pw.println(metricsState.toString()); + return 0; } @Override @@ -208,10 +220,10 @@ class TimeZoneDetectorShellCommand extends ShellCommand { pw.println(); pw.printf(" %s \n", SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE); - pw.printf(" %s \n", - SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE); - pw.printf(" %s \n", - SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE); + pw.printf(" %s \n", SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE); + pw.printf(" %s \n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE); + pw.printf(" %s\n", SHELL_COMMAND_DUMP_METRICS); + pw.printf(" Dumps the service metrics to stdout for inspection.\n"); pw.println(); GeolocationTimeZoneSuggestion.printCommandLineOpts(pw); pw.println(); @@ -235,6 +247,8 @@ class TimeZoneDetectorShellCommand extends ShellCommand { pw.printf(" %s\n", KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED); pw.printf(" Used to enable / disable support for telephony detection fallback. Also see" + " the %s command.\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK); + pw.printf(" %s\n", KEY_ENHANCED_METRICS_COLLECTION_ENABLED); + pw.printf(" Used to increase the detail of metrics collected / reported.\n"); pw.println(); pw.printf("[*] To be enabled, the user must still have location = on / auto time zone" + " detection = on.\n"); diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java index 6ee6020c79852..a963785c25e72 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java @@ -49,6 +49,7 @@ public class ConfigurationInternalTest { .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setTelephonyFallbackSupported(false) + .setEnhancedMetricsCollectionEnabled(false) .setAutoDetectionEnabledSetting(true) .setLocationEnabledSetting(true) .setGeoDetectionEnabledSetting(true) @@ -112,6 +113,7 @@ public class ConfigurationInternalTest { .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setTelephonyFallbackSupported(false) + .setEnhancedMetricsCollectionEnabled(false) .setAutoDetectionEnabledSetting(true) .setLocationEnabledSetting(true) .setGeoDetectionEnabledSetting(true) @@ -177,6 +179,7 @@ public class ConfigurationInternalTest { .setTelephonyDetectionFeatureSupported(false) .setGeoDetectionFeatureSupported(false) .setTelephonyFallbackSupported(false) + .setEnhancedMetricsCollectionEnabled(false) .setAutoDetectionEnabledSetting(true) .setLocationEnabledSetting(true) .setGeoDetectionEnabledSetting(true) @@ -240,6 +243,7 @@ public class ConfigurationInternalTest { .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(false) .setTelephonyFallbackSupported(false) + .setEnhancedMetricsCollectionEnabled(false) .setAutoDetectionEnabledSetting(true) .setLocationEnabledSetting(true) .setGeoDetectionEnabledSetting(true) diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java new file mode 100644 index 0000000000000..9029ac57d31e0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java @@ -0,0 +1,170 @@ +/* + * 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 com.android.server.timezonedetector.MetricsTimeZoneDetectorState.DETECTION_MODE_GEO; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import android.annotation.ElapsedRealtimeLong; +import android.annotation.UserIdInt; +import android.app.timezonedetector.ManualTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; + +import com.android.server.timezonedetector.MetricsTimeZoneDetectorState.MetricsTimeZoneSuggestion; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.function.Function; + +/** Tests for {@link MetricsTimeZoneDetectorState}. */ +public class MetricsTimeZoneDetectorStateTest { + + private static final @UserIdInt int ARBITRARY_USER_ID = 1; + private static final @ElapsedRealtimeLong long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L; + private static final String DEVICE_TIME_ZONE_ID = "DeviceTimeZoneId"; + + private static final ManualTimeZoneSuggestion MANUAL_TIME_ZONE_SUGGESTION = + new ManualTimeZoneSuggestion("ManualTimeZoneId"); + + private static final TelephonyTimeZoneSuggestion TELEPHONY_TIME_ZONE_SUGGESTION = + new TelephonyTimeZoneSuggestion.Builder(0) + .setZoneId("TelephonyZoneId") + .setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY) + .setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE) + .build(); + + private static final GeolocationTimeZoneSuggestion GEOLOCATION_TIME_ZONE_SUGGESTION = + GeolocationTimeZoneSuggestion.createCertainSuggestion( + ARBITRARY_ELAPSED_REALTIME_MILLIS, + Arrays.asList("GeoTimeZoneId1", "GeoTimeZoneId2")); + + private final OrdinalGenerator mOrdinalGenerator = + new OrdinalGenerator<>(Function.identity()); + + @Test + public void enhancedMetricsCollectionEnabled() { + final boolean enhancedMetricsCollectionEnabled = true; + ConfigurationInternal configurationInternal = + createConfigurationInternal(enhancedMetricsCollectionEnabled); + + // Create the object. + MetricsTimeZoneDetectorState metricsTimeZoneDetectorState = + MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal, + DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION, + TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION); + + // Assert the content. + assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState); + + assertEquals(DEVICE_TIME_ZONE_ID, metricsTimeZoneDetectorState.getDeviceTimeZoneId()); + MetricsTimeZoneSuggestion expectedManualSuggestion = + MetricsTimeZoneSuggestion.createCertain( + new String[] { MANUAL_TIME_ZONE_SUGGESTION.getZoneId() }, + new int[] { 1 }); + assertEquals(expectedManualSuggestion, + metricsTimeZoneDetectorState.getLatestManualSuggestion()); + + MetricsTimeZoneSuggestion expectedTelephonySuggestion = + MetricsTimeZoneSuggestion.createCertain( + new String[] { TELEPHONY_TIME_ZONE_SUGGESTION.getZoneId() }, + new int[] { 2 }); + assertEquals(expectedTelephonySuggestion, + metricsTimeZoneDetectorState.getLatestTelephonySuggestion()); + + MetricsTimeZoneSuggestion expectedGeoSuggestion = + MetricsTimeZoneSuggestion.createCertain( + GEOLOCATION_TIME_ZONE_SUGGESTION.getZoneIds().toArray(new String[0]), + new int[] { 3, 4 }); + assertEquals(expectedGeoSuggestion, + metricsTimeZoneDetectorState.getLatestGeolocationSuggestion()); + } + + @Test + public void enhancedMetricsCollectionDisabled() { + final boolean enhancedMetricsCollectionEnabled = false; + ConfigurationInternal configurationInternal = + createConfigurationInternal(enhancedMetricsCollectionEnabled); + + // Create the object. + MetricsTimeZoneDetectorState metricsTimeZoneDetectorState = + MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal, + DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION, + TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION); + + // Assert the content. + assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState); + + // When enhancedMetricsCollectionEnabled == false, no time zone IDs should be included. + assertNull(metricsTimeZoneDetectorState.getDeviceTimeZoneId()); + final String[] omittedZoneIds = null; + + MetricsTimeZoneSuggestion expectedManualSuggestion = + MetricsTimeZoneSuggestion.createCertain( + omittedZoneIds, + new int[] { 1 }); + assertEquals(expectedManualSuggestion, + metricsTimeZoneDetectorState.getLatestManualSuggestion()); + + MetricsTimeZoneSuggestion expectedTelephonySuggestion = + MetricsTimeZoneSuggestion.createCertain( + omittedZoneIds, + new int[] { 2 }); + assertEquals(expectedTelephonySuggestion, + metricsTimeZoneDetectorState.getLatestTelephonySuggestion()); + + MetricsTimeZoneSuggestion expectedGeoSuggestion = + MetricsTimeZoneSuggestion.createCertain( + omittedZoneIds, + new int[] { 3, 4 }); + assertEquals(expectedGeoSuggestion, + metricsTimeZoneDetectorState.getLatestGeolocationSuggestion()); + } + + private static void assertCommonConfiguration(ConfigurationInternal configurationInternal, + MetricsTimeZoneDetectorState metricsTimeZoneDetectorState) { + assertEquals(configurationInternal.isTelephonyDetectionSupported(), + metricsTimeZoneDetectorState.isTelephonyDetectionSupported()); + assertEquals(configurationInternal.isGeoDetectionSupported(), + metricsTimeZoneDetectorState.isGeoDetectionSupported()); + assertEquals(configurationInternal.getAutoDetectionEnabledSetting(), + metricsTimeZoneDetectorState.getAutoDetectionEnabledSetting()); + assertEquals(configurationInternal.getLocationEnabledSetting(), + metricsTimeZoneDetectorState.getUserLocationEnabledSetting()); + assertEquals(configurationInternal.getGeoDetectionEnabledSetting(), + metricsTimeZoneDetectorState.getGeoDetectionEnabledSetting()); + assertEquals(configurationInternal.isEnhancedMetricsCollectionEnabled(), + metricsTimeZoneDetectorState.isEnhancedMetricsCollectionEnabled()); + assertEquals(0, metricsTimeZoneDetectorState.getDeviceTimeZoneIdOrdinal()); + assertEquals(DETECTION_MODE_GEO, metricsTimeZoneDetectorState.getDetectionMode()); + } + + private static ConfigurationInternal createConfigurationInternal( + boolean enhancedMetricsCollectionEnabled) { + return new ConfigurationInternal.Builder(ARBITRARY_USER_ID) + .setUserConfigAllowed(true) + .setTelephonyDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) + .setEnhancedMetricsCollectionEnabled(enhancedMetricsCollectionEnabled) + .setAutoDetectionEnabledSetting(true) + .setLocationEnabledSetting(true) + .setGeoDetectionEnabledSetting(true) + .build(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java index 193b2e3d07669..e0e5ba0560483 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java @@ -379,6 +379,7 @@ public class TimeZoneDetectorServiceTest { .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setTelephonyFallbackSupported(false) + .setEnhancedMetricsCollectionEnabled(false) .setUserConfigAllowed(true) .setAutoDetectionEnabledSetting(autoDetectionEnabled) .setLocationEnabledSetting(geoDetectionEnabled) diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index ef1b4f58dd769..27f7814c5d237 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -87,10 +87,11 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED = new ConfigurationInternal.Builder(USER_ID) - .setUserConfigAllowed(false) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setTelephonyFallbackSupported(false) + .setEnhancedMetricsCollectionEnabled(false) + .setUserConfigAllowed(false) .setAutoDetectionEnabledSetting(false) .setLocationEnabledSetting(true) .setGeoDetectionEnabledSetting(false) @@ -98,10 +99,11 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED = new ConfigurationInternal.Builder(USER_ID) - .setUserConfigAllowed(false) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setTelephonyFallbackSupported(false) + .setEnhancedMetricsCollectionEnabled(false) + .setUserConfigAllowed(false) .setAutoDetectionEnabledSetting(true) .setLocationEnabledSetting(true) .setGeoDetectionEnabledSetting(true) @@ -109,10 +111,11 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED = new ConfigurationInternal.Builder(USER_ID) - .setUserConfigAllowed(true) .setTelephonyDetectionFeatureSupported(false) .setGeoDetectionFeatureSupported(false) .setTelephonyFallbackSupported(false) + .setEnhancedMetricsCollectionEnabled(false) + .setUserConfigAllowed(true) .setAutoDetectionEnabledSetting(false) .setLocationEnabledSetting(true) .setGeoDetectionEnabledSetting(false) @@ -120,10 +123,11 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED = new ConfigurationInternal.Builder(USER_ID) - .setUserConfigAllowed(true) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setTelephonyFallbackSupported(false) + .setEnhancedMetricsCollectionEnabled(false) + .setUserConfigAllowed(true) .setAutoDetectionEnabledSetting(false) .setLocationEnabledSetting(true) .setGeoDetectionEnabledSetting(false) @@ -134,6 +138,7 @@ public class TimeZoneDetectorStrategyImplTest { .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setTelephonyFallbackSupported(false) + .setEnhancedMetricsCollectionEnabled(false) .setUserConfigAllowed(true) .setAutoDetectionEnabledSetting(true) .setLocationEnabledSetting(true) @@ -145,6 +150,7 @@ public class TimeZoneDetectorStrategyImplTest { .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setTelephonyFallbackSupported(false) + .setEnhancedMetricsCollectionEnabled(false) .setUserConfigAllowed(true) .setAutoDetectionEnabledSetting(true) .setLocationEnabledSetting(true) @@ -955,8 +961,20 @@ public class TimeZoneDetectorStrategyImplTest { } @Test - public void testGenerateMetricsState() { - ConfigurationInternal expectedInternalConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED; + public void testGenerateMetricsState_enhancedMetricsCollection() { + testGenerateMetricsState(true); + } + + @Test + public void testGenerateMetricsState_notEnhancedMetricsCollection() { + testGenerateMetricsState(false); + } + + private void testGenerateMetricsState(boolean enhancedMetricsCollection) { + ConfigurationInternal expectedInternalConfig = + new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED_GEO_DISABLED) + .setEnhancedMetricsCollectionEnabled(enhancedMetricsCollection) + .build(); String expectedDeviceTimeZoneId = "InitialZoneId"; Script script = new Script() @@ -1028,7 +1046,7 @@ public class TimeZoneDetectorStrategyImplTest { tzIdOrdinalGenerator, expectedInternalConfig, expectedDeviceTimeZoneId, expectedManualSuggestion, expectedTelephonySuggestion, expectedGeolocationTimeZoneSuggestion); - // Rely on MetricsTimeZoneDetectorState.equals() for time zone ID ordinal comparisons. + // Rely on MetricsTimeZoneDetectorState.equals() for time zone ID / ID ordinal comparisons. assertEquals(expectedState, actualState); } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java index a2df31305c2f7..9d013101827c2 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java @@ -47,6 +47,7 @@ final class TestSupport { .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setTelephonyFallbackSupported(false) + .setEnhancedMetricsCollectionEnabled(false) .setAutoDetectionEnabledSetting(true) .setLocationEnabledSetting(true) .setGeoDetectionEnabledSetting(geoDetectionEnabledSetting)