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 android.service.timezone;
18 
19 import android.annotation.ElapsedRealtimeLong;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SystemApi;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.os.SystemClock;
26 
27 import java.time.Duration;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.Objects;
32 
33 /**
34  * A suggestion from a {@link TimeZoneProviderService} containing zero or more time zones.
35  *
36  * @hide
37  */
38 @SystemApi
39 public final class TimeZoneProviderSuggestion implements Parcelable {
40 
41     @NonNull
42     private final List<String> mTimeZoneIds;
43 
44     @ElapsedRealtimeLong
45     private final long mElapsedRealtimeMillis;
46 
TimeZoneProviderSuggestion(@onNull List<String> timeZoneIds, @ElapsedRealtimeLong long elapsedRealtimeMillis)47     private TimeZoneProviderSuggestion(@NonNull List<String> timeZoneIds,
48             @ElapsedRealtimeLong long elapsedRealtimeMillis) {
49         mTimeZoneIds = immutableList(timeZoneIds);
50         mElapsedRealtimeMillis = elapsedRealtimeMillis;
51     }
52 
53     /**
54      * Returns the time of the suggestion in elapsed real-time since system boot. Where possible,
55      * the time should be based on the time of the data used when determining time zone. For
56      * example, if it was based on a {@link android.location.Location} then it should be the time
57      * associated with that location.
58      *
59      * <p>This value is compared to {@link
60      * android.os.SystemClock#elapsedRealtime()}, to calculate the age of a fix and to compare
61      * {@link TimeZoneProviderSuggestion} instances.
62      *
63      * @return elapsed real-time of fix, in milliseconds
64      */
65     @ElapsedRealtimeLong
getElapsedRealtimeMillis()66     public long getElapsedRealtimeMillis() {
67         return mElapsedRealtimeMillis;
68     }
69 
70     /**
71      * Returns the zero or more time zone IDs for this suggestion.
72      *
73      * <p>Time zone IDs are TZDB IDs like "America/Los_Angeles" that would be accepted by {@link
74      * java.util.TimeZone#getTimeZone(String)}.
75      *
76      * <p>Most often a suggestion will contain a single time zone ID but other possibilities are
77      * valid. A suggestion with zero time zone IDs means the provider is certain there are no time
78      * zones for the current location, e.g. for oceans, boundaries or disputed areas. A suggestion
79      * with multiple IDs can occur on boundaries or disputed areas. The ordering should be in order
80      * of likelihood if possible, but the time zone detection service may choose from any of the
81      * zones suggested if it has other supporting information available.
82      */
83     @NonNull
getTimeZoneIds()84     public List<String> getTimeZoneIds() {
85         return mTimeZoneIds;
86     }
87 
88     @Override
toString()89     public String toString() {
90         return "TimeZoneProviderSuggestion{"
91                 + "mTimeZoneIds=" + mTimeZoneIds
92                 + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
93                 + "(" + Duration.ofMillis(mElapsedRealtimeMillis) + ")"
94                 + '}';
95     }
96 
97     public static final @NonNull Creator<TimeZoneProviderSuggestion> CREATOR =
98             new Creator<TimeZoneProviderSuggestion>() {
99                 @Override
100                 public TimeZoneProviderSuggestion createFromParcel(Parcel in) {
101                     @SuppressWarnings("unchecked")
102                     ArrayList<String> timeZoneIds =
103                             (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
104                     long elapsedRealtimeMillis = in.readLong();
105                     return new TimeZoneProviderSuggestion(timeZoneIds, elapsedRealtimeMillis);
106                 }
107 
108                 @Override
109                 public TimeZoneProviderSuggestion[] newArray(int size) {
110                     return new TimeZoneProviderSuggestion[size];
111                 }
112             };
113 
114     @Override
describeContents()115     public int describeContents() {
116         return 0;
117     }
118 
119     @Override
writeToParcel(@onNull Parcel parcel, int flags)120     public void writeToParcel(@NonNull Parcel parcel, int flags) {
121         parcel.writeList(mTimeZoneIds);
122         parcel.writeLong(mElapsedRealtimeMillis);
123     }
124 
125     /**
126      * Similar to {@link #equals} except this methods checks for equivalence, not equality.
127      * i.e. two suggestions are equivalent if they suggest the same time zones.
128      *
129      * @hide
130      */
131     @SuppressWarnings("ReferenceEquality")
isEquivalentTo(@ullable TimeZoneProviderSuggestion other)132     public boolean isEquivalentTo(@Nullable TimeZoneProviderSuggestion other) {
133         if (this == other) {
134             return true;
135         }
136         if (other == null) {
137             return false;
138         }
139         // Only check the time zone IDs. The times can be different, but we don't mind.
140         return mTimeZoneIds.equals(other.mTimeZoneIds);
141     }
142 
143     @Override
equals(Object o)144     public boolean equals(Object o) {
145         if (this == o) {
146             return true;
147         }
148         if (o == null || getClass() != o.getClass()) {
149             return false;
150         }
151         TimeZoneProviderSuggestion that = (TimeZoneProviderSuggestion) o;
152         return mElapsedRealtimeMillis == that.mElapsedRealtimeMillis
153                 && mTimeZoneIds.equals(that.mTimeZoneIds);
154     }
155 
156     @Override
hashCode()157     public int hashCode() {
158         return Objects.hash(mTimeZoneIds, mElapsedRealtimeMillis);
159     }
160 
161     /** A builder for {@link TimeZoneProviderSuggestion}. */
162     public static final class Builder {
163 
164         private @NonNull List<String> mTimeZoneIds = Collections.emptyList();
165         @ElapsedRealtimeLong
166         private long mElapsedRealtimeMillis = SystemClock.elapsedRealtime();
167 
168         /**
169          * Sets the time zone IDs of this suggestion.
170          */
171         @NonNull
setTimeZoneIds(@onNull List<String> timeZoneIds)172         public Builder setTimeZoneIds(@NonNull List<String> timeZoneIds) {
173             mTimeZoneIds = Objects.requireNonNull(timeZoneIds);
174             return this;
175         }
176 
177         /**
178          * Sets the time of this suggestion, in elapsed real-time since system boot.
179          */
180         @NonNull
setElapsedRealtimeMillis(@lapsedRealtimeLong long time)181         public Builder setElapsedRealtimeMillis(@ElapsedRealtimeLong long time) {
182             mElapsedRealtimeMillis = time;
183             return this;
184         }
185 
186         /**
187          * Builds a {@link TimeZoneProviderSuggestion} instance.
188          */
189         @NonNull
build()190         public TimeZoneProviderSuggestion build() {
191             return new TimeZoneProviderSuggestion(mTimeZoneIds, mElapsedRealtimeMillis);
192         }
193     }
194 
195     @NonNull
immutableList(@onNull List<String> list)196     private static List<String> immutableList(@NonNull List<String> list) {
197         Objects.requireNonNull(list);
198         if (list.isEmpty()) {
199             return Collections.emptyList();
200         } else {
201             return Collections.unmodifiableList(new ArrayList<>(list));
202         }
203     }
204 }
205