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