1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.timezonedetector; 18 19 import static libcore.io.IoUtils.closeQuietly; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.app.timezonedetector.ManualTimeZoneSuggestion; 25 import android.app.timezonedetector.TelephonyTimeZoneSuggestion; 26 import android.util.proto.ProtoOutputStream; 27 28 import java.io.ByteArrayOutputStream; 29 import java.util.Arrays; 30 import java.util.List; 31 import java.util.Objects; 32 33 /** 34 * A class that provides time zone detector state information for metrics. 35 * 36 * <p> 37 * Regarding time zone ID ordinals: 38 * <p> 39 * We don't want to leak user location information by reporting time zone IDs. Instead, time zone 40 * IDs are consistently identified within a given instance of this class by a numeric ID. This 41 * allows comparison of IDs without revealing what those IDs are. 42 */ 43 public final class MetricsTimeZoneDetectorState { 44 45 @IntDef(prefix = "DETECTION_MODE_", 46 value = { DETECTION_MODE_MANUAL, DETECTION_MODE_GEO, DETECTION_MODE_TELEPHONY}) 47 @interface DetectionMode {}; 48 49 @DetectionMode 50 public static final int DETECTION_MODE_MANUAL = 0; 51 @DetectionMode 52 public static final int DETECTION_MODE_GEO = 1; 53 @DetectionMode 54 public static final int DETECTION_MODE_TELEPHONY = 2; 55 56 @NonNull 57 private final ConfigurationInternal mConfigurationInternal; 58 @NonNull 59 private final int mDeviceTimeZoneIdOrdinal; 60 @Nullable 61 private final MetricsTimeZoneSuggestion mLatestManualSuggestion; 62 @Nullable 63 private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion; 64 @Nullable 65 private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion; 66 MetricsTimeZoneDetectorState( @onNull ConfigurationInternal configurationInternal, int deviceTimeZoneIdOrdinal, @Nullable MetricsTimeZoneSuggestion latestManualSuggestion, @Nullable MetricsTimeZoneSuggestion latestTelephonySuggestion, @Nullable MetricsTimeZoneSuggestion latestGeolocationSuggestion)67 private MetricsTimeZoneDetectorState( 68 @NonNull ConfigurationInternal configurationInternal, 69 int deviceTimeZoneIdOrdinal, 70 @Nullable MetricsTimeZoneSuggestion latestManualSuggestion, 71 @Nullable MetricsTimeZoneSuggestion latestTelephonySuggestion, 72 @Nullable MetricsTimeZoneSuggestion latestGeolocationSuggestion) { 73 mConfigurationInternal = Objects.requireNonNull(configurationInternal); 74 mDeviceTimeZoneIdOrdinal = deviceTimeZoneIdOrdinal; 75 mLatestManualSuggestion = latestManualSuggestion; 76 mLatestTelephonySuggestion = latestTelephonySuggestion; 77 mLatestGeolocationSuggestion = latestGeolocationSuggestion; 78 } 79 80 /** 81 * Creates {@link MetricsTimeZoneDetectorState} from the supplied parameters, using the {@link 82 * OrdinalGenerator} to generate time zone ID ordinals. 83 */ create( @onNull OrdinalGenerator<String> tzIdOrdinalGenerator, @NonNull ConfigurationInternal configurationInternal, @NonNull String deviceTimeZoneId, @Nullable ManualTimeZoneSuggestion latestManualSuggestion, @Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion, @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion)84 public static MetricsTimeZoneDetectorState create( 85 @NonNull OrdinalGenerator<String> tzIdOrdinalGenerator, 86 @NonNull ConfigurationInternal configurationInternal, 87 @NonNull String deviceTimeZoneId, 88 @Nullable ManualTimeZoneSuggestion latestManualSuggestion, 89 @Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion, 90 @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) { 91 92 int deviceTimeZoneIdOrdinal = 93 tzIdOrdinalGenerator.ordinal(Objects.requireNonNull(deviceTimeZoneId)); 94 MetricsTimeZoneSuggestion latestObfuscatedManualSuggestion = 95 createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestManualSuggestion); 96 MetricsTimeZoneSuggestion latestObfuscatedTelephonySuggestion = 97 createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestTelephonySuggestion); 98 MetricsTimeZoneSuggestion latestObfuscatedGeolocationSuggestion = 99 createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestGeolocationSuggestion); 100 101 return new MetricsTimeZoneDetectorState( 102 configurationInternal, deviceTimeZoneIdOrdinal, latestObfuscatedManualSuggestion, 103 latestObfuscatedTelephonySuggestion, latestObfuscatedGeolocationSuggestion); 104 } 105 106 /** Returns true if the device supports telephony time zone detection. */ isTelephonyDetectionSupported()107 public boolean isTelephonyDetectionSupported() { 108 return mConfigurationInternal.isTelephonyDetectionSupported(); 109 } 110 111 /** Returns true if the device supports geolocation time zone detection. */ isGeoDetectionSupported()112 public boolean isGeoDetectionSupported() { 113 return mConfigurationInternal.isGeoDetectionSupported(); 114 } 115 116 /** Returns true if user's location can be used generally. */ isUserLocationEnabled()117 public boolean isUserLocationEnabled() { 118 return mConfigurationInternal.isLocationEnabled(); 119 } 120 121 /** Returns the value of the geolocation time zone detection enabled setting. */ getGeoDetectionEnabledSetting()122 public boolean getGeoDetectionEnabledSetting() { 123 return mConfigurationInternal.getGeoDetectionEnabledSetting(); 124 } 125 126 /** Returns the value of the auto time zone detection enabled setting. */ getAutoDetectionEnabledSetting()127 public boolean getAutoDetectionEnabledSetting() { 128 return mConfigurationInternal.getAutoDetectionEnabledSetting(); 129 } 130 131 /** 132 * Returns the detection mode the device is currently using, which can be influenced by various 133 * things besides the user's setting. 134 */ 135 @DetectionMode getDetectionMode()136 public int getDetectionMode() { 137 if (!mConfigurationInternal.getAutoDetectionEnabledBehavior()) { 138 return DETECTION_MODE_MANUAL; 139 } else if (mConfigurationInternal.getGeoDetectionEnabledBehavior()) { 140 return DETECTION_MODE_GEO; 141 } else { 142 return DETECTION_MODE_TELEPHONY; 143 } 144 } 145 146 /** 147 * Returns the ordinal for the device's currently set time zone ID. 148 * See {@link MetricsTimeZoneDetectorState} for information about ordinals. 149 */ 150 @NonNull getDeviceTimeZoneIdOrdinal()151 public int getDeviceTimeZoneIdOrdinal() { 152 return mDeviceTimeZoneIdOrdinal; 153 } 154 155 /** 156 * Returns bytes[] for a {@link MetricsTimeZoneSuggestion} for the last manual 157 * suggestion received. 158 */ 159 @Nullable getLatestManualSuggestionProtoBytes()160 public byte[] getLatestManualSuggestionProtoBytes() { 161 return suggestionProtoBytes(mLatestManualSuggestion); 162 } 163 164 /** 165 * Returns bytes[] for a {@link MetricsTimeZoneSuggestion} for the last, best 166 * telephony suggestion received. 167 */ 168 @Nullable getLatestTelephonySuggestionProtoBytes()169 public byte[] getLatestTelephonySuggestionProtoBytes() { 170 return suggestionProtoBytes(mLatestTelephonySuggestion); 171 } 172 173 /** 174 * Returns bytes[] for a {@link MetricsTimeZoneSuggestion} for the last geolocation 175 * suggestion received. 176 */ 177 @Nullable getLatestGeolocationSuggestionProtoBytes()178 public byte[] getLatestGeolocationSuggestionProtoBytes() { 179 return suggestionProtoBytes(mLatestGeolocationSuggestion); 180 } 181 182 @Override equals(Object o)183 public boolean equals(Object o) { 184 if (this == o) { 185 return true; 186 } 187 if (o == null || getClass() != o.getClass()) { 188 return false; 189 } 190 MetricsTimeZoneDetectorState that = (MetricsTimeZoneDetectorState) o; 191 return mDeviceTimeZoneIdOrdinal == that.mDeviceTimeZoneIdOrdinal 192 && mConfigurationInternal.equals(that.mConfigurationInternal) 193 && Objects.equals(mLatestManualSuggestion, that.mLatestManualSuggestion) 194 && Objects.equals(mLatestTelephonySuggestion, that.mLatestTelephonySuggestion) 195 && Objects.equals(mLatestGeolocationSuggestion, that.mLatestGeolocationSuggestion); 196 } 197 198 @Override hashCode()199 public int hashCode() { 200 return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal, 201 mLatestManualSuggestion, mLatestTelephonySuggestion, mLatestGeolocationSuggestion); 202 } 203 204 @Override toString()205 public String toString() { 206 return "MetricsTimeZoneDetectorState{" 207 + "mConfigurationInternal=" + mConfigurationInternal 208 + ", mDeviceTimeZoneIdOrdinal=" + mDeviceTimeZoneIdOrdinal 209 + ", mLatestManualSuggestion=" + mLatestManualSuggestion 210 + ", mLatestTelephonySuggestion=" + mLatestTelephonySuggestion 211 + ", mLatestGeolocationSuggestion=" + mLatestGeolocationSuggestion 212 + '}'; 213 } 214 suggestionProtoBytes( @ullable MetricsTimeZoneSuggestion suggestion)215 private static byte[] suggestionProtoBytes( 216 @Nullable MetricsTimeZoneSuggestion suggestion) { 217 if (suggestion == null) { 218 return null; 219 } 220 return suggestion.toBytes(); 221 } 222 223 @Nullable createMetricsTimeZoneSuggestion( @onNull OrdinalGenerator<String> zoneIdOrdinalGenerator, @NonNull ManualTimeZoneSuggestion manualSuggestion)224 private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion( 225 @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator, 226 @NonNull ManualTimeZoneSuggestion manualSuggestion) { 227 if (manualSuggestion == null) { 228 return null; 229 } 230 231 int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(manualSuggestion.getZoneId()); 232 return MetricsTimeZoneSuggestion.createCertain( 233 new int[] { zoneIdOrdinal }); 234 } 235 236 @Nullable createMetricsTimeZoneSuggestion( @onNull OrdinalGenerator<String> zoneIdOrdinalGenerator, @NonNull TelephonyTimeZoneSuggestion telephonySuggestion)237 private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion( 238 @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator, 239 @NonNull TelephonyTimeZoneSuggestion telephonySuggestion) { 240 if (telephonySuggestion == null) { 241 return null; 242 } 243 if (telephonySuggestion.getZoneId() == null) { 244 return MetricsTimeZoneSuggestion.createUncertain(); 245 } 246 int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(telephonySuggestion.getZoneId()); 247 return MetricsTimeZoneSuggestion.createCertain(new int[] { zoneIdOrdinal }); 248 } 249 250 @Nullable createMetricsTimeZoneSuggestion( @onNull OrdinalGenerator<String> zoneIdOrdinalGenerator, @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion)251 private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion( 252 @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator, 253 @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion) { 254 if (geolocationSuggestion == null) { 255 return null; 256 } 257 258 List<String> zoneIds = geolocationSuggestion.getZoneIds(); 259 if (zoneIds == null) { 260 return MetricsTimeZoneSuggestion.createUncertain(); 261 } 262 return MetricsTimeZoneSuggestion.createCertain(zoneIdOrdinalGenerator.ordinals(zoneIds)); 263 } 264 265 /** 266 * A Java class that closely matches the android.app.time.MetricsTimeZoneSuggestion 267 * proto definition. 268 */ 269 private static final class MetricsTimeZoneSuggestion { 270 @Nullable 271 private final int[] mZoneIdOrdinals; 272 MetricsTimeZoneSuggestion(@ullable int[] zoneIdOrdinals)273 MetricsTimeZoneSuggestion(@Nullable int[] zoneIdOrdinals) { 274 mZoneIdOrdinals = zoneIdOrdinals; 275 } 276 277 @NonNull createUncertain()278 static MetricsTimeZoneSuggestion createUncertain() { 279 return new MetricsTimeZoneSuggestion(null); 280 } 281 createCertain( @onNull int[] zoneIdOrdinals)282 public static MetricsTimeZoneSuggestion createCertain( 283 @NonNull int[] zoneIdOrdinals) { 284 return new MetricsTimeZoneSuggestion(zoneIdOrdinals); 285 } 286 isCertain()287 boolean isCertain() { 288 return mZoneIdOrdinals != null; 289 } 290 291 @Nullable getZoneIdOrdinals()292 int[] getZoneIdOrdinals() { 293 return mZoneIdOrdinals; 294 } 295 toBytes()296 byte[] toBytes() { 297 // We don't get access to the atoms.proto definition for nested proto fields, so we use 298 // an identically specified proto. 299 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 300 ProtoOutputStream protoOutputStream = new ProtoOutputStream(byteArrayOutputStream); 301 int typeProtoValue = isCertain() 302 ? android.app.time.MetricsTimeZoneSuggestion.CERTAIN 303 : android.app.time.MetricsTimeZoneSuggestion.UNCERTAIN; 304 protoOutputStream.write(android.app.time.MetricsTimeZoneSuggestion.TYPE, 305 typeProtoValue); 306 if (isCertain()) { 307 for (int zoneIdOrdinal : getZoneIdOrdinals()) { 308 protoOutputStream.write( 309 android.app.time.MetricsTimeZoneSuggestion.TIME_ZONE_ORDINALS, 310 zoneIdOrdinal); 311 } 312 } 313 protoOutputStream.flush(); 314 closeQuietly(byteArrayOutputStream); 315 return byteArrayOutputStream.toByteArray(); 316 } 317 318 @Override equals(Object o)319 public boolean equals(Object o) { 320 if (this == o) { 321 return true; 322 } 323 if (o == null || getClass() != o.getClass()) { 324 return false; 325 } 326 MetricsTimeZoneSuggestion that = (MetricsTimeZoneSuggestion) o; 327 return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals); 328 } 329 330 @Override hashCode()331 public int hashCode() { 332 return Arrays.hashCode(mZoneIdOrdinals); 333 } 334 335 @Override toString()336 public String toString() { 337 return "MetricsTimeZoneSuggestion{" 338 + "mZoneIdOrdinals=" + Arrays.toString(mZoneIdOrdinals) 339 + '}'; 340 } 341 } 342 } 343