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

Commit bae46650 authored by Neil Fuller's avatar Neil Fuller
Browse files

Add "enhanced metrics collection" support

Add "enhanced metrics collection" support to the time zone detector service.

This commit doesn't make any actual changes to the metrics code, that
will be done separately. This just adds the infrastructure to support
the server flag to turn the behavior on.

Details:

"Enhanced metric collection" is for deployment to QA and other internal
users only. This initially means the actual time zone IDs will
optionally be recorded in the metrics collected by the device in
addition to the original "time zone ID ordinal" approach used for
general / public users.

Manual testing:

adb shell cmd time_zone_detector dump_metrics
adb shell cmd device_config put system_time enhanced_metrics_collection_enabled true
adb shell cmd time_zone_detector dump_metrics

Bug: 200279201
Test: atest services/tests/servicestests/src/com/android/server/timezonedetector/
Change-Id: Ibb480b748fb951f9781b3d6ccc9a14b682ff4a1a
parent 0d3682ba
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -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}.
     *
+22 −11
Original line number Diff line number Diff line
@@ -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;
}
+8 −1
Original line number Diff line number Diff line
@@ -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<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>();

+25 −1
Original line number Diff line number Diff line
@@ -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.
         */
+85 −29
Original line number Diff line number Diff line
@@ -34,11 +34,13 @@ import java.util.Objects;
 * A class that provides time zone detector state information for metrics.
 *
 * <p>
 * Regarding time zone ID ordinals:
 * Regarding the use of time zone ID ordinals in metrics / telemetry:
 * <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.
 * 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<String> 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<String> 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<String> 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)
                    + '}';
        }
    }
Loading