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                         "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
245             }
246             requireNonNull(safetyEvent);
247             mType = safetyEvent.mType;
248             mRefreshBroadcastId = safetyEvent.mRefreshBroadcastId;
249             mSafetySourceIssueId = safetyEvent.mSafetySourceIssueId;
250             mSafetySourceIssueActionId = safetyEvent.mSafetySourceIssueActionId;
251         }
252 
253         /**
254          * Sets an optional broadcast id provided by Safety Center when requesting a refresh,
255          * through {@link SafetyCenterManager#EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID}.
256          *
257          * <p>This will only be relevant for events of type {@link
258          * #SAFETY_EVENT_TYPE_REFRESH_REQUESTED}.
259          *
260          * @see #getType()
261          */
262         @NonNull
setRefreshBroadcastId(@ullable String refreshBroadcastId)263         public Builder setRefreshBroadcastId(@Nullable String refreshBroadcastId) {
264             mRefreshBroadcastId = refreshBroadcastId;
265             return this;
266         }
267 
268         /**
269          * Sets the id of the {@link SafetySourceIssue} this event is associated with (if any).
270          *
271          * <p>This will only be relevant for events of type {@link
272          * #SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED} or {@link
273          * #SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED}.
274          *
275          * @see #getType()
276          * @see SafetySourceIssue#getId()
277          */
278         @NonNull
setSafetySourceIssueId(@ullable String safetySourceIssueId)279         public Builder setSafetySourceIssueId(@Nullable String safetySourceIssueId) {
280             mSafetySourceIssueId = safetySourceIssueId;
281             return this;
282         }
283 
284         /**
285          * Sets the id of the {@link SafetySourceIssue.Action} this event is associated with (if
286          * any).
287          *
288          * <p>This will only be relevant for events of type {@link
289          * #SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED} or {@link
290          * #SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED}.
291          *
292          * @see #getType()
293          * @see SafetySourceIssue.Action#getId()
294          */
295         @NonNull
setSafetySourceIssueActionId(@ullable String safetySourceIssueActionId)296         public Builder setSafetySourceIssueActionId(@Nullable String safetySourceIssueActionId) {
297             mSafetySourceIssueActionId = safetySourceIssueActionId;
298             return this;
299         }
300 
301         /** Creates the {@link SafetyEvent} represented by this {@link Builder}. */
302         @NonNull
build()303         public SafetyEvent build() {
304             switch (mType) {
305                 case SAFETY_EVENT_TYPE_REFRESH_REQUESTED:
306                     if (mRefreshBroadcastId == null) {
307                         throw new IllegalArgumentException(
308                                 "Missing refresh broadcast id for refresh requested safety event");
309                     }
310                     break;
311                 case SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED:
312                 case SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED:
313                     if (mSafetySourceIssueId == null) {
314                         throw new IllegalArgumentException(
315                                 "Missing issue id for resolving action safety event: " + mType);
316                     }
317                     if (mSafetySourceIssueActionId == null) {
318                         throw new IllegalArgumentException(
319                                 "Missing issue action id for resolving action safety event: "
320                                         + mType);
321                     }
322                     break;
323                 case SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED:
324                 case SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED:
325                 case SAFETY_EVENT_TYPE_DEVICE_REBOOTED:
326                 default:
327             }
328             return new SafetyEvent(
329                     mType, mRefreshBroadcastId, mSafetySourceIssueId, mSafetySourceIssueActionId);
330         }
331     }
332 
333     @Type
validateType(int value)334     private static int validateType(int value) {
335         switch (value) {
336             case SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED:
337             case SAFETY_EVENT_TYPE_REFRESH_REQUESTED:
338             case SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED:
339             case SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED:
340             case SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED:
341             case SAFETY_EVENT_TYPE_DEVICE_REBOOTED:
342                 return value;
343             default:
344         }
345         throw new IllegalArgumentException("Unexpected Type for SafetyEvent: " + value);
346     }
347 }
348