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