1 /* 2 * Copyright (C) 2022 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.safetycenter; 18 19 import static android.os.Build.VERSION_CODES.TIRAMISU; 20 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 21 22 import static java.util.Objects.requireNonNull; 23 24 import android.annotation.IntDef; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.SystemApi; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 31 import androidx.annotation.RequiresApi; 32 33 import com.android.modules.utils.build.SdkLevel; 34 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 import java.util.Objects; 38 39 /** 40 * A safety event that may trigger a safety source to set its {@link SafetySourceData}. 41 * 42 * @hide 43 */ 44 @SystemApi 45 @RequiresApi(TIRAMISU) 46 public final class SafetyEvent implements Parcelable { 47 48 /** 49 * Indicates that there has been a change of state for safety source, which may be independent 50 * of Safety Center interactions. 51 */ 52 public static final int SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED = 100; 53 54 /** 55 * Indicates that the safety source performed a data refresh in response to a request from 56 * Safety Center. 57 */ 58 public static final int SAFETY_EVENT_TYPE_REFRESH_REQUESTED = 200; 59 60 /** 61 * Indicates that the safety source successfully completed a resolving {@link 62 * SafetySourceIssue.Action}. 63 */ 64 public static final int SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED = 300; 65 66 /** 67 * Indicates that the safety source failed to complete a resolving {@link 68 * SafetySourceIssue.Action}. 69 */ 70 public static final int SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED = 400; 71 72 /** Indicates that the device's locale changed. */ 73 public static final int SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED = 500; 74 75 /** Indicates that the device was rebooted. */ 76 public static final int SAFETY_EVENT_TYPE_DEVICE_REBOOTED = 600; 77 78 /** 79 * Types of safety events that may trigger a set of a safety source's {@link SafetySourceData}. 80 * 81 * @hide 82 */ 83 @IntDef( 84 prefix = {"SAFETY_EVENT_TYPE_"}, 85 value = { 86 SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED, 87 SAFETY_EVENT_TYPE_REFRESH_REQUESTED, 88 SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED, 89 SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED, 90 SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED, 91 SAFETY_EVENT_TYPE_DEVICE_REBOOTED 92 }) 93 @Retention(RetentionPolicy.SOURCE) 94 public @interface Type {} 95 96 @NonNull 97 public static final Creator<SafetyEvent> CREATOR = 98 new Creator<SafetyEvent>() { 99 @Override 100 public SafetyEvent createFromParcel(Parcel in) { 101 int type = in.readInt(); 102 return new SafetyEvent.Builder(type) 103 .setRefreshBroadcastId(in.readString()) 104 .setSafetySourceIssueId(in.readString()) 105 .setSafetySourceIssueActionId(in.readString()) 106 .build(); 107 } 108 109 @Override 110 public SafetyEvent[] newArray(int size) { 111 return new SafetyEvent[size]; 112 } 113 }; 114 115 @Type private final int mType; 116 @Nullable private final String mRefreshBroadcastId; 117 @Nullable private final String mSafetySourceIssueId; 118 @Nullable private final String mSafetySourceIssueActionId; 119 SafetyEvent( @ype int type, @Nullable String refreshBroadcastId, @Nullable String safetySourceIssueId, @Nullable String safetySourceIssueActionId)120 private SafetyEvent( 121 @Type int type, 122 @Nullable String refreshBroadcastId, 123 @Nullable String safetySourceIssueId, 124 @Nullable String safetySourceIssueActionId) { 125 mType = type; 126 mRefreshBroadcastId = refreshBroadcastId; 127 mSafetySourceIssueId = safetySourceIssueId; 128 mSafetySourceIssueActionId = safetySourceIssueActionId; 129 } 130 131 /** Returns the type of the safety event. */ 132 @Type getType()133 public int getType() { 134 return mType; 135 } 136 137 /** 138 * Returns an optional id provided by Safety Center when requesting a refresh, through {@link 139 * SafetyCenterManager#EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID}. 140 * 141 * <p>This will only be relevant for events of type {@link 142 * #SAFETY_EVENT_TYPE_REFRESH_REQUESTED}. 143 * 144 * @see #getType() 145 */ 146 @Nullable getRefreshBroadcastId()147 public String getRefreshBroadcastId() { 148 return mRefreshBroadcastId; 149 } 150 151 /** 152 * Returns the id of the {@link SafetySourceIssue} this event is associated with (if any). 153 * 154 * <p>This will only be relevant for events of type {@link 155 * #SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED} or {@link 156 * #SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED}. 157 * 158 * @see #getType() 159 * @see SafetySourceIssue#getId() 160 */ 161 @Nullable getSafetySourceIssueId()162 public String getSafetySourceIssueId() { 163 return mSafetySourceIssueId; 164 } 165 166 /** 167 * Returns the id of the {@link SafetySourceIssue.Action} this event is associated with (if 168 * any). 169 * 170 * <p>This will only be relevant for events of type {@link 171 * #SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED} or {@link 172 * #SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED}. 173 * 174 * @see #getType() 175 * @see SafetySourceIssue.Action#getId() 176 */ 177 @Nullable getSafetySourceIssueActionId()178 public String getSafetySourceIssueActionId() { 179 return mSafetySourceIssueActionId; 180 } 181 182 @Override describeContents()183 public int describeContents() { 184 return 0; 185 } 186 187 @Override writeToParcel(@onNull Parcel dest, int flags)188 public void writeToParcel(@NonNull Parcel dest, int flags) { 189 dest.writeInt(mType); 190 dest.writeString(mRefreshBroadcastId); 191 dest.writeString(mSafetySourceIssueId); 192 dest.writeString(mSafetySourceIssueActionId); 193 } 194 195 @Override equals(Object o)196 public boolean equals(Object o) { 197 if (this == o) return true; 198 if (!(o instanceof SafetyEvent)) return false; 199 SafetyEvent that = (SafetyEvent) o; 200 return mType == that.mType 201 && Objects.equals(mRefreshBroadcastId, that.mRefreshBroadcastId) 202 && Objects.equals(mSafetySourceIssueId, that.mSafetySourceIssueId) 203 && Objects.equals(mSafetySourceIssueActionId, that.mSafetySourceIssueActionId); 204 } 205 206 @Override hashCode()207 public int hashCode() { 208 return Objects.hash( 209 mType, mRefreshBroadcastId, mSafetySourceIssueId, mSafetySourceIssueActionId); 210 } 211 212 @Override toString()213 public String toString() { 214 return "SafetyEvent{" 215 + "mType=" 216 + mType 217 + ", mRefreshBroadcastId=" 218 + mRefreshBroadcastId 219 + ", mSafetySourceIssueId=" 220 + mSafetySourceIssueId 221 + ", mSafetySourceIssueActionId=" 222 + mSafetySourceIssueActionId 223 + '}'; 224 } 225 226 /** Builder class for {@link SafetyEvent}. */ 227 public static final class Builder { 228 229 @Type private final int mType; 230 @Nullable private String mRefreshBroadcastId; 231 @Nullable private String mSafetySourceIssueId; 232 @Nullable private String mSafetySourceIssueActionId; 233 234 /** Creates a {@link Builder} for {@link SafetyEvent}. */ Builder(@ype int type)235 public Builder(@Type int type) { 236 mType = validateType(type); 237 } 238 239 /** Creates a {@link Builder} with the values from the given {@link SafetyEvent}. */ 240 @RequiresApi(UPSIDE_DOWN_CAKE) Builder(@onNull SafetyEvent safetyEvent)241 public Builder(@NonNull SafetyEvent safetyEvent) { 242 if (!SdkLevel.isAtLeastU()) { 243 throw new UnsupportedOperationException(); 244 } 245 requireNonNull(safetyEvent); 246 mType = safetyEvent.mType; 247 mRefreshBroadcastId = safetyEvent.mRefreshBroadcastId; 248 mSafetySourceIssueId = safetyEvent.mSafetySourceIssueId; 249 mSafetySourceIssueActionId = safetyEvent.mSafetySourceIssueActionId; 250 } 251 252 /** 253 * Sets an optional broadcast id provided by Safety Center when requesting a refresh, 254 * through {@link SafetyCenterManager#EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID}. 255 * 256 * <p>This will only be relevant for events of type {@link 257 * #SAFETY_EVENT_TYPE_REFRESH_REQUESTED}. 258 * 259 * @see #getType() 260 */ 261 @NonNull setRefreshBroadcastId(@ullable String refreshBroadcastId)262 public Builder setRefreshBroadcastId(@Nullable String refreshBroadcastId) { 263 mRefreshBroadcastId = refreshBroadcastId; 264 return this; 265 } 266 267 /** 268 * Sets the id of the {@link SafetySourceIssue} this event is associated with (if any). 269 * 270 * <p>This will only be relevant for events of type {@link 271 * #SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED} or {@link 272 * #SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED}. 273 * 274 * @see #getType() 275 * @see SafetySourceIssue#getId() 276 */ 277 @NonNull setSafetySourceIssueId(@ullable String safetySourceIssueId)278 public Builder setSafetySourceIssueId(@Nullable String safetySourceIssueId) { 279 mSafetySourceIssueId = safetySourceIssueId; 280 return this; 281 } 282 283 /** 284 * Sets the id of the {@link SafetySourceIssue.Action} this event is associated with (if 285 * any). 286 * 287 * <p>This will only be relevant for events of type {@link 288 * #SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED} or {@link 289 * #SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED}. 290 * 291 * @see #getType() 292 * @see SafetySourceIssue.Action#getId() 293 */ 294 @NonNull setSafetySourceIssueActionId(@ullable String safetySourceIssueActionId)295 public Builder setSafetySourceIssueActionId(@Nullable String safetySourceIssueActionId) { 296 mSafetySourceIssueActionId = safetySourceIssueActionId; 297 return this; 298 } 299 300 /** Creates the {@link SafetyEvent} represented by this {@link Builder}. */ 301 @NonNull build()302 public SafetyEvent build() { 303 switch (mType) { 304 case SAFETY_EVENT_TYPE_REFRESH_REQUESTED: 305 if (mRefreshBroadcastId == null) { 306 throw new IllegalArgumentException( 307 "Missing refresh broadcast id for refresh requested safety event"); 308 } 309 break; 310 case SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED: 311 case SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED: 312 if (mSafetySourceIssueId == null) { 313 throw new IllegalArgumentException( 314 "Missing issue id for resolving action safety event: " + mType); 315 } 316 if (mSafetySourceIssueActionId == null) { 317 throw new IllegalArgumentException( 318 "Missing issue action id for resolving action safety event: " 319 + mType); 320 } 321 break; 322 case SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED: 323 case SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED: 324 case SAFETY_EVENT_TYPE_DEVICE_REBOOTED: 325 default: 326 } 327 return new SafetyEvent( 328 mType, mRefreshBroadcastId, mSafetySourceIssueId, mSafetySourceIssueActionId); 329 } 330 } 331 332 @Type validateType(int value)333 private static int validateType(int value) { 334 switch (value) { 335 case SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED: 336 case SAFETY_EVENT_TYPE_REFRESH_REQUESTED: 337 case SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED: 338 case SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED: 339 case SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED: 340 case SAFETY_EVENT_TYPE_DEVICE_REBOOTED: 341 return value; 342 default: 343 } 344 throw new IllegalArgumentException("Unexpected Type for SafetyEvent: " + value); 345 } 346 } 347