Loading services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java 0 → 100644 +344 −0 Original line number Diff line number Diff line /* * 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) + '}'; } } } services/core/java/com/android/server/timezonedetector/OrdinalGenerator.java 0 → 100644 +49 −0 Original line number Diff line number Diff line /* * 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; } } services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java +4 −0 Original line number Diff line number Diff line Loading @@ -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(); } services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java +6 −0 Original line number Diff line number Diff line Loading @@ -90,4 +90,10 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter mHandler.post( () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion)); } @Override @NonNull public MetricsTimeZoneDetectorState generateMetricsState() { return mTimeZoneDetectorStrategy.generateMetricsState(); } } services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +8 −3 Original line number Diff line number Diff line Loading @@ -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 */ Loading Loading @@ -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(); } Loading
services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java 0 → 100644 +344 −0 Original line number Diff line number Diff line /* * 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) + '}'; } } }
services/core/java/com/android/server/timezonedetector/OrdinalGenerator.java 0 → 100644 +49 −0 Original line number Diff line number Diff line /* * 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; } }
services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java +4 −0 Original line number Diff line number Diff line Loading @@ -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(); }
services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java +6 −0 Original line number Diff line number Diff line Loading @@ -90,4 +90,10 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter mHandler.post( () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion)); } @Override @NonNull public MetricsTimeZoneDetectorState generateMetricsState() { return mTimeZoneDetectorStrategy.generateMetricsState(); } }
services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +8 −3 Original line number Diff line number Diff line Loading @@ -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 */ Loading Loading @@ -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(); }