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 
21 import static java.util.Objects.requireNonNull;
22 
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.SystemApi;
27 import android.app.PendingIntent;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.text.TextUtils;
31 
32 import androidx.annotation.RequiresApi;
33 
34 import java.lang.annotation.Retention;
35 import java.lang.annotation.RetentionPolicy;
36 import java.util.Objects;
37 
38 /**
39  * An individual entry in the Safety Center.
40  *
41  * <p>A {@link SafetyCenterEntry} conveys the current status of an individual safety feature on the
42  * device. Entries are present even if they have no associated active issues. In contrast, a {@link
43  * SafetyCenterIssue} is ephemeral and disappears when the issue is resolved.
44  *
45  * <p>Entries link to their corresponding component or an action on it via {@link
46  * #getPendingIntent()}.
47  *
48  * @hide
49  */
50 @SystemApi
51 @RequiresApi(TIRAMISU)
52 public final class SafetyCenterEntry implements Parcelable {
53 
54     /**
55      * Indicates the severity level of this entry is not currently known. This may be because of an
56      * error or because some information is missing.
57      */
58     public static final int ENTRY_SEVERITY_LEVEL_UNKNOWN = 3000;
59 
60     /**
61      * Indicates this entry does not have a severity level.
62      *
63      * <p>This is used when the Safety Center has no opinion on the severity of this entry (e.g. a
64      * security setting isn't configured, but it's not considered a risk, or for privacy-related
65      * entries).
66      */
67     public static final int ENTRY_SEVERITY_LEVEL_UNSPECIFIED = 3100;
68 
69     /** Indicates that there are no problems present with this entry. */
70     public static final int ENTRY_SEVERITY_LEVEL_OK = 3200;
71 
72     /** Indicates there are safety recommendations for this entry. */
73     public static final int ENTRY_SEVERITY_LEVEL_RECOMMENDATION = 3300;
74 
75     /** Indicates there are critical safety warnings for this entry. */
76     public static final int ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING = 3400;
77 
78     /**
79      * All possible severity levels for a {@link SafetyCenterEntry}.
80      *
81      * @hide
82      * @see SafetyCenterEntry#getSeverityLevel()
83      * @see Builder#setSeverityLevel(int)
84      */
85     @Retention(RetentionPolicy.SOURCE)
86     @IntDef(
87             prefix = "ENTRY_SEVERITY_LEVEL_",
88             value = {
89                 ENTRY_SEVERITY_LEVEL_UNKNOWN,
90                 ENTRY_SEVERITY_LEVEL_UNSPECIFIED,
91                 ENTRY_SEVERITY_LEVEL_OK,
92                 ENTRY_SEVERITY_LEVEL_RECOMMENDATION,
93                 ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING,
94             })
95     public @interface EntrySeverityLevel {}
96 
97     /** Indicates an entry with {@link #ENTRY_SEVERITY_LEVEL_UNSPECIFIED} should not use an icon. */
98     public static final int SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON = 0;
99 
100     /**
101      * Indicates an entry with {@link #ENTRY_SEVERITY_LEVEL_UNSPECIFIED} should use the privacy
102      * icon, for privacy features.
103      */
104     public static final int SEVERITY_UNSPECIFIED_ICON_TYPE_PRIVACY = 1;
105 
106     /**
107      * Indicates an entry with {@link #ENTRY_SEVERITY_LEVEL_UNSPECIFIED} should use an icon
108      * indicating it has no current recommendation.
109      */
110     public static final int SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION = 2;
111 
112     /**
113      * All possible icon types for a {@link SafetyCenterEntry} to use when its severity level is
114      * {@link #ENTRY_SEVERITY_LEVEL_UNSPECIFIED}.
115      *
116      * <p>It is only relevant when the entry's severity level is {@link
117      * #ENTRY_SEVERITY_LEVEL_UNSPECIFIED}.
118      *
119      * @hide
120      */
121     @Retention(RetentionPolicy.SOURCE)
122     @IntDef(
123             prefix = "SEVERITY_UNSPECIFIED_ICON_TYPE_",
124             value = {
125                 SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON,
126                 SEVERITY_UNSPECIFIED_ICON_TYPE_PRIVACY,
127                 SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION,
128             })
129     public @interface SeverityUnspecifiedIconType {}
130 
131     @NonNull
132     public static final Creator<SafetyCenterEntry> CREATOR =
133             new Creator<SafetyCenterEntry>() {
134                 @Override
135                 public SafetyCenterEntry createFromParcel(Parcel in) {
136                     String id = in.readString();
137                     CharSequence title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
138                     return new Builder(id, title)
139                             .setSummary(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in))
140                             .setSeverityLevel(in.readInt())
141                             .setSeverityUnspecifiedIconType(in.readInt())
142                             .setEnabled(in.readBoolean())
143                             .setPendingIntent(in.readTypedObject(PendingIntent.CREATOR))
144                             .setIconAction(in.readTypedObject(IconAction.CREATOR))
145                             .build();
146                 }
147 
148                 @Override
149                 public SafetyCenterEntry[] newArray(int size) {
150                     return new SafetyCenterEntry[size];
151                 }
152             };
153 
154     @NonNull private final String mId;
155     @NonNull private final CharSequence mTitle;
156     @Nullable private final CharSequence mSummary;
157     @EntrySeverityLevel private final int mSeverityLevel;
158     @SeverityUnspecifiedIconType private final int mSeverityUnspecifiedIconType;
159     private final boolean mEnabled;
160     @Nullable private final PendingIntent mPendingIntent;
161     @Nullable private final IconAction mIconAction;
162 
SafetyCenterEntry( @onNull String id, @NonNull CharSequence title, @Nullable CharSequence summary, @EntrySeverityLevel int severityLevel, @SeverityUnspecifiedIconType int severityUnspecifiedIconType, boolean enabled, @Nullable PendingIntent pendingIntent, @Nullable IconAction iconAction)163     private SafetyCenterEntry(
164             @NonNull String id,
165             @NonNull CharSequence title,
166             @Nullable CharSequence summary,
167             @EntrySeverityLevel int severityLevel,
168             @SeverityUnspecifiedIconType int severityUnspecifiedIconType,
169             boolean enabled,
170             @Nullable PendingIntent pendingIntent,
171             @Nullable IconAction iconAction) {
172         mId = id;
173         mTitle = title;
174         mSummary = summary;
175         mSeverityLevel = severityLevel;
176         mSeverityUnspecifiedIconType = severityUnspecifiedIconType;
177         mEnabled = enabled;
178         mPendingIntent = pendingIntent;
179         mIconAction = iconAction;
180     }
181 
182     /**
183      * Returns the encoded string ID which uniquely identifies this entry within the Safety Center
184      * on the device for the current user across all profiles and accounts.
185      */
186     @NonNull
getId()187     public String getId() {
188         return mId;
189     }
190 
191     /** Returns the title that describes this entry. */
192     @NonNull
getTitle()193     public CharSequence getTitle() {
194         return mTitle;
195     }
196 
197     /** Returns the summary text that describes this entry if present, or {@code null} otherwise. */
198     @Nullable
getSummary()199     public CharSequence getSummary() {
200         return mSummary;
201     }
202 
203     /** Returns the {@link EntrySeverityLevel} of this entry. */
204     @EntrySeverityLevel
getSeverityLevel()205     public int getSeverityLevel() {
206         return mSeverityLevel;
207     }
208 
209     /** Returns the {@link SeverityUnspecifiedIconType} of this entry. */
210     @SeverityUnspecifiedIconType
getSeverityUnspecifiedIconType()211     public int getSeverityUnspecifiedIconType() {
212         return mSeverityUnspecifiedIconType;
213     }
214 
215     /** Returns whether this entry is enabled. */
isEnabled()216     public boolean isEnabled() {
217         return mEnabled;
218     }
219 
220     /**
221      * Returns the optional {@link PendingIntent} to execute when this entry is selected if present,
222      * or {@code null} otherwise.
223      */
224     @Nullable
getPendingIntent()225     public PendingIntent getPendingIntent() {
226         return mPendingIntent;
227     }
228 
229     /**
230      * Returns the optional {@link IconAction} for this entry if present, or {@code null} otherwise.
231      */
232     @Nullable
getIconAction()233     public IconAction getIconAction() {
234         return mIconAction;
235     }
236 
237     @Override
equals(Object o)238     public boolean equals(Object o) {
239         if (this == o) return true;
240         if (!(o instanceof SafetyCenterEntry)) return false;
241         SafetyCenterEntry that = (SafetyCenterEntry) o;
242         return mSeverityLevel == that.mSeverityLevel
243                 && mSeverityUnspecifiedIconType == that.mSeverityUnspecifiedIconType
244                 && mEnabled == that.mEnabled
245                 && Objects.equals(mId, that.mId)
246                 && TextUtils.equals(mTitle, that.mTitle)
247                 && TextUtils.equals(mSummary, that.mSummary)
248                 && Objects.equals(mPendingIntent, that.mPendingIntent)
249                 && Objects.equals(mIconAction, that.mIconAction);
250     }
251 
252     @Override
hashCode()253     public int hashCode() {
254         return Objects.hash(
255                 mId,
256                 mTitle,
257                 mSummary,
258                 mSeverityLevel,
259                 mSeverityUnspecifiedIconType,
260                 mEnabled,
261                 mPendingIntent,
262                 mIconAction);
263     }
264 
265     @Override
toString()266     public String toString() {
267         return "SafetyCenterEntry{"
268                 + "mId="
269                 + mId
270                 + ", mTitle="
271                 + mTitle
272                 + ", mSummary="
273                 + mSummary
274                 + ", mSeverityLevel="
275                 + mSeverityLevel
276                 + ", mSeverityUnspecifiedIconType="
277                 + mSeverityUnspecifiedIconType
278                 + ", mEnabled="
279                 + mEnabled
280                 + ", mPendingIntent="
281                 + mPendingIntent
282                 + ", mIconAction="
283                 + mIconAction
284                 + '}';
285     }
286 
287     @Override
describeContents()288     public int describeContents() {
289         return 0;
290     }
291 
292     @Override
writeToParcel(@onNull Parcel dest, int flags)293     public void writeToParcel(@NonNull Parcel dest, int flags) {
294         dest.writeString(mId);
295         TextUtils.writeToParcel(mTitle, dest, flags);
296         TextUtils.writeToParcel(mSummary, dest, flags);
297         dest.writeInt(mSeverityLevel);
298         dest.writeInt(mSeverityUnspecifiedIconType);
299         dest.writeBoolean(mEnabled);
300         dest.writeTypedObject(mPendingIntent, flags);
301         dest.writeTypedObject(mIconAction, flags);
302     }
303 
304     /** Builder class for {@link SafetyCenterEntry}. */
305     public static final class Builder {
306 
307         @NonNull private String mId;
308         @NonNull private CharSequence mTitle;
309         @Nullable private CharSequence mSummary;
310         @EntrySeverityLevel private int mSeverityLevel = ENTRY_SEVERITY_LEVEL_UNKNOWN;
311 
312         @SeverityUnspecifiedIconType
313         private int mSeverityUnspecifiedIconType = SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON;
314 
315         private boolean mEnabled = true;
316         @Nullable private PendingIntent mPendingIntent;
317         @Nullable private IconAction mIconAction;
318 
319         /**
320          * Creates a {@link Builder} for a {@link SafetyCenterEntry}.
321          *
322          * @param id a unique encoded string ID, see {@link #getId()} for details
323          * @param title a title that describes this entry
324          */
Builder(@onNull String id, @NonNull CharSequence title)325         public Builder(@NonNull String id, @NonNull CharSequence title) {
326             mId = requireNonNull(id);
327             mTitle = requireNonNull(title);
328         }
329 
330         /** Creates a {@link Builder} with the values from the given {@link SafetyCenterEntry}. */
Builder(@onNull SafetyCenterEntry safetyCenterEntry)331         public Builder(@NonNull SafetyCenterEntry safetyCenterEntry) {
332             mId = safetyCenterEntry.mId;
333             mTitle = safetyCenterEntry.mTitle;
334             mSummary = safetyCenterEntry.mSummary;
335             mSeverityLevel = safetyCenterEntry.mSeverityLevel;
336             mSeverityUnspecifiedIconType = safetyCenterEntry.mSeverityUnspecifiedIconType;
337             mEnabled = safetyCenterEntry.mEnabled;
338             mPendingIntent = safetyCenterEntry.mPendingIntent;
339             mIconAction = safetyCenterEntry.mIconAction;
340         }
341 
342         /** Sets the ID for this entry. */
343         @NonNull
setId(@onNull String id)344         public Builder setId(@NonNull String id) {
345             mId = requireNonNull(id);
346             return this;
347         }
348 
349         /** Sets the title for this entry. */
350         @NonNull
setTitle(@onNull CharSequence title)351         public Builder setTitle(@NonNull CharSequence title) {
352             mTitle = requireNonNull(title);
353             return this;
354         }
355 
356         /** Sets the optional summary text for this entry. */
357         @NonNull
setSummary(@ullable CharSequence summary)358         public Builder setSummary(@Nullable CharSequence summary) {
359             mSummary = summary;
360             return this;
361         }
362 
363         /**
364          * Sets the {@link EntrySeverityLevel} for this entry. Defaults to {@link
365          * #ENTRY_SEVERITY_LEVEL_UNKNOWN}.
366          */
367         @NonNull
setSeverityLevel(@ntrySeverityLevel int severityLevel)368         public Builder setSeverityLevel(@EntrySeverityLevel int severityLevel) {
369             mSeverityLevel = validateEntrySeverityLevel(severityLevel);
370             return this;
371         }
372 
373         /**
374          * Sets the {@link SeverityUnspecifiedIconType} for this entry. Defaults to {@link
375          * #SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON}.
376          */
377         @NonNull
setSeverityUnspecifiedIconType( @everityUnspecifiedIconType int severityUnspecifiedIconType)378         public Builder setSeverityUnspecifiedIconType(
379                 @SeverityUnspecifiedIconType int severityUnspecifiedIconType) {
380             mSeverityUnspecifiedIconType =
381                     validateSeverityUnspecifiedIconType(severityUnspecifiedIconType);
382             return this;
383         }
384 
385         /** Sets whether this entry is enabled. Defaults to {@code true}. */
386         @NonNull
setEnabled(boolean enabled)387         public Builder setEnabled(boolean enabled) {
388             mEnabled = enabled;
389             return this;
390         }
391 
392         /** Sets the optional {@link PendingIntent} to execute when this entry is selected. */
393         @NonNull
setPendingIntent(@ullable PendingIntent pendingIntent)394         public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
395             mPendingIntent = pendingIntent;
396             return this;
397         }
398 
399         /** Sets the optional {@link IconAction} for this entry. */
400         @NonNull
setIconAction(@ullable IconAction iconAction)401         public Builder setIconAction(@Nullable IconAction iconAction) {
402             mIconAction = iconAction;
403             return this;
404         }
405 
406         /** Sets the optional {@link IconAction} for this entry. */
407         @NonNull
setIconAction( @conAction.IconActionType int type, @NonNull PendingIntent pendingIntent)408         public Builder setIconAction(
409                 @IconAction.IconActionType int type, @NonNull PendingIntent pendingIntent) {
410             mIconAction = new IconAction(type, pendingIntent);
411             return this;
412         }
413 
414         /** Creates the {@link SafetyCenterEntry} defined by this {@link Builder}. */
415         @NonNull
build()416         public SafetyCenterEntry build() {
417             return new SafetyCenterEntry(
418                     mId,
419                     mTitle,
420                     mSummary,
421                     mSeverityLevel,
422                     mSeverityUnspecifiedIconType,
423                     mEnabled,
424                     mPendingIntent,
425                     mIconAction);
426         }
427     }
428 
429     /** An optional additional action with an icon for a {@link SafetyCenterEntry}. */
430     public static final class IconAction implements Parcelable {
431 
432         /** A gear-type icon action, e.g. that links to a settings page for a specific entry. */
433         public static final int ICON_ACTION_TYPE_GEAR = 30100;
434 
435         /**
436          * An info-type icon action, e.g. that displays some additional detailed info about a
437          * specific entry.
438          */
439         public static final int ICON_ACTION_TYPE_INFO = 30200;
440 
441         /**
442          * All possible icon action types.
443          *
444          * @hide
445          */
446         @Retention(RetentionPolicy.SOURCE)
447         @IntDef(
448                 prefix = "ICON_ACTION_TYPE_",
449                 value = {
450                     ICON_ACTION_TYPE_GEAR,
451                     ICON_ACTION_TYPE_INFO,
452                 })
453         public @interface IconActionType {}
454 
455         @NonNull
456         public static final Creator<IconAction> CREATOR =
457                 new Creator<IconAction>() {
458                     @Override
459                     public IconAction createFromParcel(Parcel in) {
460                         int type = in.readInt();
461                         PendingIntent pendingIntent = in.readTypedObject(PendingIntent.CREATOR);
462                         return new IconAction(type, pendingIntent);
463                     }
464 
465                     @Override
466                     public IconAction[] newArray(int size) {
467                         return new IconAction[size];
468                     }
469                 };
470 
471         @IconActionType private final int mType;
472         @NonNull private final PendingIntent mPendingIntent;
473 
474         /** Creates an icon action for a {@link SafetyCenterEntry}. */
IconAction(@conActionType int type, @NonNull PendingIntent pendingIntent)475         public IconAction(@IconActionType int type, @NonNull PendingIntent pendingIntent) {
476             mType = validateIconActionType(type);
477             mPendingIntent = requireNonNull(pendingIntent);
478         }
479 
480         /** Returns the {@link IconActionType} of this icon action. */
481         @IconActionType
getType()482         public int getType() {
483             return mType;
484         }
485 
486         /** Returns the {@link PendingIntent} to execute when this icon action is selected. */
487         @NonNull
getPendingIntent()488         public PendingIntent getPendingIntent() {
489             return mPendingIntent;
490         }
491 
492         @Override
equals(Object o)493         public boolean equals(Object o) {
494             if (this == o) return true;
495             if (!(o instanceof IconAction)) return false;
496             IconAction that = (IconAction) o;
497             return mType == that.mType && Objects.equals(mPendingIntent, that.mPendingIntent);
498         }
499 
500         @Override
hashCode()501         public int hashCode() {
502             return Objects.hash(mType, mPendingIntent);
503         }
504 
505         @Override
toString()506         public String toString() {
507             return "IconAction{" + "mType=" + mType + ", mPendingIntent=" + mPendingIntent + '}';
508         }
509 
510         @Override
describeContents()511         public int describeContents() {
512             return 0;
513         }
514 
515         @Override
writeToParcel(@onNull Parcel dest, int flags)516         public void writeToParcel(@NonNull Parcel dest, int flags) {
517             dest.writeInt(mType);
518             dest.writeTypedObject(mPendingIntent, flags);
519         }
520 
521         @IconActionType
validateIconActionType(int value)522         private static int validateIconActionType(int value) {
523             switch (value) {
524                 case ICON_ACTION_TYPE_GEAR:
525                 case ICON_ACTION_TYPE_INFO:
526                     return value;
527                 default:
528             }
529             throw new IllegalArgumentException(
530                     "Unexpected IconActionType for IconAction: " + value);
531         }
532     }
533 
534     @EntrySeverityLevel
validateEntrySeverityLevel(int value)535     private static int validateEntrySeverityLevel(int value) {
536         switch (value) {
537             case ENTRY_SEVERITY_LEVEL_UNKNOWN:
538             case ENTRY_SEVERITY_LEVEL_UNSPECIFIED:
539             case ENTRY_SEVERITY_LEVEL_OK:
540             case ENTRY_SEVERITY_LEVEL_RECOMMENDATION:
541             case ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING:
542                 return value;
543             default:
544         }
545         throw new IllegalArgumentException(
546                 "Unexpected EntrySeverityLevel for SafetyCenterEntry: " + value);
547     }
548 
549     @SeverityUnspecifiedIconType
validateSeverityUnspecifiedIconType(int value)550     private static int validateSeverityUnspecifiedIconType(int value) {
551         switch (value) {
552             case SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON:
553             case SEVERITY_UNSPECIFIED_ICON_TYPE_PRIVACY:
554             case SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION:
555                 return value;
556             default:
557         }
558         throw new IllegalArgumentException(
559                 "Unexpected SeverityUnspecifiedIconType for SafetyCenterEntry: " + value);
560     }
561 }
562