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.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 
26 import java.lang.annotation.ElementType;
27 import java.lang.annotation.Retention;
28 import java.lang.annotation.RetentionPolicy;
29 import java.lang.annotation.Target;
30 import java.time.Duration;
31 import java.util.Objects;
32 
33 /**
34  * Encapsulates a reported event from a {@link TimeZoneProviderService}.
35  *
36  * @hide
37  */
38 public final class TimeZoneProviderEvent implements Parcelable {
39 
40     @IntDef(prefix = "EVENT_TYPE_",
41             value = { EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUGGESTION, EVENT_TYPE_UNCERTAIN })
42     @Retention(RetentionPolicy.SOURCE)
43     @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
44     public @interface EventType {}
45 
46     /**
47      * The provider failed permanently. See {@link
48      * TimeZoneProviderService#reportPermanentFailure(Throwable)}
49      */
50     public static final @EventType int EVENT_TYPE_PERMANENT_FAILURE = 1;
51 
52     /**
53      * The provider made a suggestion. See {@link
54      * TimeZoneProviderService#reportSuggestion(TimeZoneProviderSuggestion)}
55      */
56     public static final @EventType int EVENT_TYPE_SUGGESTION = 2;
57 
58     /**
59      * The provider was uncertain about the time zone. See {@link
60      * TimeZoneProviderService#reportUncertain(TimeZoneProviderStatus)}
61      */
62     public static final @EventType int EVENT_TYPE_UNCERTAIN = 3;
63 
64     private final @EventType int mType;
65 
66     @ElapsedRealtimeLong
67     private final long mCreationElapsedMillis;
68 
69     // Populated when mType == EVENT_TYPE_SUGGESTION
70     @Nullable
71     private final TimeZoneProviderSuggestion mSuggestion;
72 
73     // Populated when mType == EVENT_TYPE_PERMANENT_FAILURE
74     @Nullable
75     private final String mFailureCause;
76 
77     // May be populated when EVENT_TYPE_SUGGESTION or EVENT_TYPE_UNCERTAIN
78     @Nullable
79     private final TimeZoneProviderStatus mTimeZoneProviderStatus;
80 
TimeZoneProviderEvent(@ventType int type, @ElapsedRealtimeLong long creationElapsedMillis, @Nullable TimeZoneProviderSuggestion suggestion, @Nullable String failureCause, @Nullable TimeZoneProviderStatus timeZoneProviderStatus)81     private TimeZoneProviderEvent(@EventType int type,
82             @ElapsedRealtimeLong long creationElapsedMillis,
83             @Nullable TimeZoneProviderSuggestion suggestion,
84             @Nullable String failureCause,
85             @Nullable TimeZoneProviderStatus timeZoneProviderStatus) {
86         mType = validateEventType(type);
87         mCreationElapsedMillis = creationElapsedMillis;
88         mSuggestion = suggestion;
89         mFailureCause = failureCause;
90         mTimeZoneProviderStatus = timeZoneProviderStatus;
91 
92         // Confirm the type and the provider status agree.
93         if (mType == EVENT_TYPE_PERMANENT_FAILURE && mTimeZoneProviderStatus != null) {
94             throw new IllegalArgumentException(
95                     "Unexpected status: mType=" + mType
96                             + ", mTimeZoneProviderStatus=" + mTimeZoneProviderStatus);
97         }
98     }
99 
validateEventType(@ventType int eventType)100     private static @EventType int validateEventType(@EventType int eventType) {
101         if (eventType < EVENT_TYPE_PERMANENT_FAILURE || eventType > EVENT_TYPE_UNCERTAIN) {
102             throw new IllegalArgumentException(Integer.toString(eventType));
103         }
104         return eventType;
105     }
106 
107     /** Returns an event of type {@link #EVENT_TYPE_SUGGESTION}. */
createSuggestionEvent( @lapsedRealtimeLong long creationElapsedMillis, @NonNull TimeZoneProviderSuggestion suggestion, @Nullable TimeZoneProviderStatus providerStatus)108     public static TimeZoneProviderEvent createSuggestionEvent(
109             @ElapsedRealtimeLong long creationElapsedMillis,
110             @NonNull TimeZoneProviderSuggestion suggestion,
111             @Nullable TimeZoneProviderStatus providerStatus) {
112         return new TimeZoneProviderEvent(EVENT_TYPE_SUGGESTION, creationElapsedMillis,
113                 Objects.requireNonNull(suggestion), null, providerStatus);
114     }
115 
116     /** Returns an event of type {@link #EVENT_TYPE_UNCERTAIN}. */
createUncertainEvent( @lapsedRealtimeLong long creationElapsedMillis, @Nullable TimeZoneProviderStatus timeZoneProviderStatus)117     public static TimeZoneProviderEvent createUncertainEvent(
118             @ElapsedRealtimeLong long creationElapsedMillis,
119             @Nullable TimeZoneProviderStatus timeZoneProviderStatus) {
120 
121         return new TimeZoneProviderEvent(
122                 EVENT_TYPE_UNCERTAIN, creationElapsedMillis, null, null,
123                 timeZoneProviderStatus);
124     }
125 
126     /** Returns an event of type {@link #EVENT_TYPE_PERMANENT_FAILURE}. */
createPermanentFailureEvent( @lapsedRealtimeLong long creationElapsedMillis, @NonNull String cause)127     public static TimeZoneProviderEvent createPermanentFailureEvent(
128             @ElapsedRealtimeLong long creationElapsedMillis,
129             @NonNull String cause) {
130         return new TimeZoneProviderEvent(EVENT_TYPE_PERMANENT_FAILURE, creationElapsedMillis, null,
131                 Objects.requireNonNull(cause), null);
132     }
133 
134     /**
135      * Returns the event type.
136      */
getType()137     public @EventType int getType() {
138         return mType;
139     }
140 
141     /** Returns the time according to the elapsed realtime clock when the event was created. */
142     @ElapsedRealtimeLong
getCreationElapsedMillis()143     public long getCreationElapsedMillis() {
144         return mCreationElapsedMillis;
145     }
146 
147     /**
148      * Returns the suggestion. Populated when {@link #getType()} is {@link #EVENT_TYPE_SUGGESTION}.
149      */
150     @Nullable
getSuggestion()151     public TimeZoneProviderSuggestion getSuggestion() {
152         return mSuggestion;
153     }
154 
155     /**
156      * Returns the failure cause. Populated when {@link #getType()} is {@link
157      * #EVENT_TYPE_PERMANENT_FAILURE}.
158      */
159     @Nullable
getFailureCause()160     public String getFailureCause() {
161         return mFailureCause;
162     }
163 
164     /**
165      * Returns the status of the time zone provider.  May be populated when {@link #getType()} is
166      * {@link #EVENT_TYPE_UNCERTAIN} or {@link #EVENT_TYPE_SUGGESTION}, otherwise {@code null}.
167      */
168     @Nullable
getTimeZoneProviderStatus()169     public TimeZoneProviderStatus getTimeZoneProviderStatus() {
170         return mTimeZoneProviderStatus;
171     }
172 
173     public static final @NonNull Creator<TimeZoneProviderEvent> CREATOR = new Creator<>() {
174         @Override
175         public TimeZoneProviderEvent createFromParcel(Parcel in) {
176             int type = in.readInt();
177             long creationElapsedMillis = in.readLong();
178             TimeZoneProviderSuggestion suggestion = in.readParcelable(
179                     getClass().getClassLoader(), TimeZoneProviderSuggestion.class);
180             String failureCause = in.readString8();
181             TimeZoneProviderStatus status = in.readParcelable(
182                     getClass().getClassLoader(), TimeZoneProviderStatus.class);
183             return new TimeZoneProviderEvent(
184                     type, creationElapsedMillis, suggestion, failureCause, status);
185         }
186 
187         @Override
188         public TimeZoneProviderEvent[] newArray(int size) {
189             return new TimeZoneProviderEvent[size];
190         }
191     };
192 
193     @Override
describeContents()194     public int describeContents() {
195         return 0;
196     }
197 
198     @Override
writeToParcel(@onNull Parcel parcel, int flags)199     public void writeToParcel(@NonNull Parcel parcel, int flags) {
200         parcel.writeInt(mType);
201         parcel.writeLong(mCreationElapsedMillis);
202         parcel.writeParcelable(mSuggestion, 0);
203         parcel.writeString8(mFailureCause);
204         parcel.writeParcelable(mTimeZoneProviderStatus, 0);
205     }
206 
207     @Override
toString()208     public String toString() {
209         return "TimeZoneProviderEvent{"
210                 + "mType=" + mType
211                 + ", mCreationElapsedMillis=" + Duration.ofMillis(mCreationElapsedMillis).toString()
212                 + ", mSuggestion=" + mSuggestion
213                 + ", mFailureCause=" + mFailureCause
214                 + ", mTimeZoneProviderStatus=" + mTimeZoneProviderStatus
215                 + '}';
216     }
217 
218     /**
219      * Similar to {@link #equals} except this methods checks for equivalence, not equality.
220      * i.e. two {@link #EVENT_TYPE_SUGGESTION} events are equivalent if they suggest
221      * the same time zones and have the same provider status, two {@link #EVENT_TYPE_UNCERTAIN}
222      * events are equivalent if they have the same provider status, and {@link
223      * #EVENT_TYPE_PERMANENT_FAILURE} events are always equivalent (the nature of the failure is not
224      * considered).
225      */
226     @SuppressWarnings("ReferenceEquality")
isEquivalentTo(@ullable TimeZoneProviderEvent other)227     public boolean isEquivalentTo(@Nullable TimeZoneProviderEvent other) {
228         if (this == other) {
229             return true;
230         }
231         if (other == null || mType != other.mType) {
232             return false;
233         }
234         if (mType == EVENT_TYPE_SUGGESTION) {
235             return mSuggestion.isEquivalentTo(other.mSuggestion)
236                     && Objects.equals(mTimeZoneProviderStatus, other.mTimeZoneProviderStatus);
237         }
238         return Objects.equals(mTimeZoneProviderStatus, other.mTimeZoneProviderStatus);
239     }
240 
241     @Override
equals(Object o)242     public boolean equals(Object o) {
243         if (this == o) {
244             return true;
245         }
246         if (o == null || getClass() != o.getClass()) {
247             return false;
248         }
249         TimeZoneProviderEvent that = (TimeZoneProviderEvent) o;
250         return mType == that.mType
251                 && mCreationElapsedMillis == that.mCreationElapsedMillis
252                 && Objects.equals(mSuggestion, that.mSuggestion)
253                 && Objects.equals(mFailureCause, that.mFailureCause)
254                 && Objects.equals(mTimeZoneProviderStatus, that.mTimeZoneProviderStatus);
255     }
256 
257     @Override
hashCode()258     public int hashCode() {
259         return Objects.hash(mType, mCreationElapsedMillis, mSuggestion, mFailureCause,
260                 mTimeZoneProviderStatus);
261     }
262 }
263