• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.ShellCommand;
22 
23 import java.io.PrintWriter;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.List;
28 import java.util.Objects;
29 import java.util.StringTokenizer;
30 
31 /**
32  * A time zone suggestion from a geolocation source.
33  *
34  * <p> Geolocation-based suggestions have the following properties:
35  *
36  * <ul>
37  *     <li>{@code zoneIds}. When not {@code null}, {@code zoneIds} contains a list of suggested time
38  *     zone IDs, e.g. ["America/Phoenix", "America/Denver"]. Usually there will be a single zoneId.
39  *     When there are multiple, this indicates multiple answers are possible for the current
40  *     location / accuracy, i.e. if there is a nearby time zone border. The detection logic
41  *     receiving the suggestion is expected to use the first element in the absence of other
42  *     information, but one of the others may be used if there is supporting evidence / preferences
43  *     such as a device setting or corroborating signals from another source.
44  *     <br />{@code zoneIds} can be empty if the current location has been determined to have no
45  *     time zone. For example, oceans or disputed areas. This is considered a strong signal and the
46  *     received need not look for time zone from other sources.
47  *     <br />{@code zoneIds} can be {@code null} to indicate that the geolocation source has entered
48  *     an "un-opinionated" state and any previous suggestion is being withdrawn. This indicates the
49  *     source cannot provide a valid suggestion due to technical limitations. For example, a
50  *     geolocation source may become un-opinionated if the device's location is no longer known with
51  *     sufficient accuracy, or if the location is known but no time zone can be determined because
52  *     no time zone mapping information is available.</li>
53  *     <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is
54  *     used to record why the suggestion exists and how it was obtained. This information exists
55  *     only to aid in debugging and therefore is used by {@link #toString()}, but it is not for use
56  *     in detection logic and is not considered in {@link #hashCode()} or {@link #equals(Object)}.
57  *     </li>
58  * </ul>
59  *
60  * @hide
61  */
62 public final class GeolocationTimeZoneSuggestion {
63 
64     @Nullable private final List<String> mZoneIds;
65     @Nullable private ArrayList<String> mDebugInfo;
66 
GeolocationTimeZoneSuggestion(@ullable List<String> zoneIds)67     public GeolocationTimeZoneSuggestion(@Nullable List<String> zoneIds) {
68         if (zoneIds == null) {
69             // Unopinionated
70             mZoneIds = null;
71         } else {
72             mZoneIds = Collections.unmodifiableList(new ArrayList<>(zoneIds));
73         }
74     }
75 
76     /**
77      * Returns the zone Ids being suggested. See {@link GeolocationTimeZoneSuggestion} for details.
78      */
79     @Nullable
getZoneIds()80     public List<String> getZoneIds() {
81         return mZoneIds;
82     }
83 
84     /** Returns debug information. See {@link GeolocationTimeZoneSuggestion} for details. */
85     @NonNull
getDebugInfo()86     public List<String> getDebugInfo() {
87         return mDebugInfo == null
88                 ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
89     }
90 
91     /**
92      * Associates information with the instance that can be useful for debugging / logging. The
93      * information is present in {@link #toString()} but is not considered for
94      * {@link #equals(Object)} and {@link #hashCode()}.
95      */
addDebugInfo(String... debugInfos)96     public void addDebugInfo(String... debugInfos) {
97         if (mDebugInfo == null) {
98             mDebugInfo = new ArrayList<>();
99         }
100         mDebugInfo.addAll(Arrays.asList(debugInfos));
101     }
102 
103     @Override
equals(Object o)104     public boolean equals(Object o) {
105         if (this == o) {
106             return true;
107         }
108         if (o == null || getClass() != o.getClass()) {
109             return false;
110         }
111         GeolocationTimeZoneSuggestion
112                 that = (GeolocationTimeZoneSuggestion) o;
113         return Objects.equals(mZoneIds, that.mZoneIds);
114     }
115 
116     @Override
hashCode()117     public int hashCode() {
118         return Objects.hash(mZoneIds);
119     }
120 
121     @Override
toString()122     public String toString() {
123         return "GeolocationTimeZoneSuggestion{"
124                 + "mZoneIds=" + mZoneIds
125                 + ", mDebugInfo=" + mDebugInfo
126                 + '}';
127     }
128 
129     /** @hide */
parseCommandLineArg(@onNull ShellCommand cmd)130     public static GeolocationTimeZoneSuggestion parseCommandLineArg(@NonNull ShellCommand cmd) {
131         String zoneIdsString = null;
132         String opt;
133         while ((opt = cmd.getNextArg()) != null) {
134             switch (opt) {
135                 case "--zone_ids": {
136                     zoneIdsString  = cmd.getNextArgRequired();
137                     break;
138                 }
139                 default: {
140                     throw new IllegalArgumentException("Unknown option: " + opt);
141                 }
142             }
143         }
144         List<String> zoneIds = parseZoneIdsArg(zoneIdsString);
145         GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(zoneIds);
146         suggestion.addDebugInfo("Command line injection");
147         return suggestion;
148     }
149 
parseZoneIdsArg(String zoneIdsString)150     private static List<String> parseZoneIdsArg(String zoneIdsString) {
151         if ("UNCERTAIN".equals(zoneIdsString)) {
152             return null;
153         } else if ("EMPTY".equals(zoneIdsString)) {
154             return Collections.emptyList();
155         } else {
156             ArrayList<String> zoneIds = new ArrayList<>();
157             StringTokenizer tokenizer = new StringTokenizer(zoneIdsString, ",");
158             while (tokenizer.hasMoreTokens()) {
159                 zoneIds.add(tokenizer.nextToken());
160             }
161             return zoneIds;
162         }
163     }
164 
165     /** @hide */
printCommandLineOpts(@onNull PrintWriter pw)166     public static void printCommandLineOpts(@NonNull PrintWriter pw) {
167         pw.println("Geolocation suggestion options:");
168         pw.println("  --zone_ids {UNCERTAIN|EMPTY|<Olson ID>+}");
169         pw.println();
170         pw.println("See " + GeolocationTimeZoneSuggestion.class.getName()
171                 + " for more information");
172     }
173 }
174