1 /* 2 * Copyright (C) 2021 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 com.android.internal.util.Preconditions.checkArgument; 22 23 import static java.util.Objects.requireNonNull; 24 25 import android.annotation.IntDef; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.annotation.SystemApi; 29 import android.app.PendingIntent; 30 import android.os.Parcel; 31 import android.os.Parcelable; 32 import android.text.TextUtils; 33 34 import androidx.annotation.RequiresApi; 35 36 import java.lang.annotation.Retention; 37 import java.lang.annotation.RetentionPolicy; 38 import java.util.Objects; 39 40 /** 41 * Data for a safety source status in the Safety Center page, which conveys the overall state of the 42 * safety source and allows a user to navigate to the source. 43 * 44 * @hide 45 */ 46 @SystemApi 47 @RequiresApi(TIRAMISU) 48 public final class SafetySourceStatus implements Parcelable { 49 50 @NonNull 51 public static final Creator<SafetySourceStatus> CREATOR = 52 new Creator<SafetySourceStatus>() { 53 @Override 54 public SafetySourceStatus createFromParcel(Parcel in) { 55 CharSequence title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 56 CharSequence summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 57 int severityLevel = in.readInt(); 58 return new Builder(title, summary, severityLevel) 59 .setPendingIntent(in.readTypedObject(PendingIntent.CREATOR)) 60 .setIconAction(in.readTypedObject(IconAction.CREATOR)) 61 .setEnabled(in.readBoolean()) 62 .build(); 63 } 64 65 @Override 66 public SafetySourceStatus[] newArray(int size) { 67 return new SafetySourceStatus[size]; 68 } 69 }; 70 71 @NonNull private final CharSequence mTitle; 72 @NonNull private final CharSequence mSummary; 73 @SafetySourceData.SeverityLevel private final int mSeverityLevel; 74 @Nullable private final PendingIntent mPendingIntent; 75 @Nullable private final IconAction mIconAction; 76 private final boolean mEnabled; 77 SafetySourceStatus( @onNull CharSequence title, @NonNull CharSequence summary, @SafetySourceData.SeverityLevel int severityLevel, @Nullable PendingIntent pendingIntent, @Nullable IconAction iconAction, boolean enabled)78 private SafetySourceStatus( 79 @NonNull CharSequence title, 80 @NonNull CharSequence summary, 81 @SafetySourceData.SeverityLevel int severityLevel, 82 @Nullable PendingIntent pendingIntent, 83 @Nullable IconAction iconAction, 84 boolean enabled) { 85 this.mTitle = title; 86 this.mSummary = summary; 87 this.mSeverityLevel = severityLevel; 88 this.mPendingIntent = pendingIntent; 89 this.mIconAction = iconAction; 90 this.mEnabled = enabled; 91 } 92 93 /** Returns the localized title of the safety source status to be displayed in the UI. */ 94 @NonNull getTitle()95 public CharSequence getTitle() { 96 return mTitle; 97 } 98 99 /** Returns the localized summary of the safety source status to be displayed in the UI. */ 100 @NonNull getSummary()101 public CharSequence getSummary() { 102 return mSummary; 103 } 104 105 /** Returns the {@link SafetySourceData.SeverityLevel} of the status. */ 106 @SafetySourceData.SeverityLevel getSeverityLevel()107 public int getSeverityLevel() { 108 return mSeverityLevel; 109 } 110 111 /** 112 * Returns an optional {@link PendingIntent} that will start an activity when the safety source 113 * status UI is clicked on. 114 * 115 * <p>The action contained in the {@link PendingIntent} must start an activity. 116 * 117 * <p>If {@code null} the intent action defined in the Safety Center configuration will be 118 * invoked when the safety source status UI is clicked on. If the intent action is undefined or 119 * disabled the source is considered as disabled. 120 */ 121 @Nullable getPendingIntent()122 public PendingIntent getPendingIntent() { 123 return mPendingIntent; 124 } 125 126 /** 127 * Returns an optional {@link IconAction} to be displayed in the safety source status UI. 128 * 129 * <p>The icon action will be a clickable icon which performs an action as indicated by the 130 * icon. 131 */ 132 @Nullable getIconAction()133 public IconAction getIconAction() { 134 return mIconAction; 135 } 136 137 /** 138 * Returns whether the safety source status is enabled. 139 * 140 * <p>A safety source status should be disabled if it is currently unavailable on the device 141 * 142 * <p>If disabled, the status will show as grayed out in the UI, and interactions with it may be 143 * limited. 144 */ isEnabled()145 public boolean isEnabled() { 146 return mEnabled; 147 } 148 149 @Override describeContents()150 public int describeContents() { 151 return 0; 152 } 153 154 @Override writeToParcel(@onNull Parcel dest, int flags)155 public void writeToParcel(@NonNull Parcel dest, int flags) { 156 TextUtils.writeToParcel(mTitle, dest, flags); 157 TextUtils.writeToParcel(mSummary, dest, flags); 158 dest.writeInt(mSeverityLevel); 159 dest.writeTypedObject(mPendingIntent, flags); 160 dest.writeTypedObject(mIconAction, flags); 161 dest.writeBoolean(mEnabled); 162 } 163 164 @Override equals(Object o)165 public boolean equals(Object o) { 166 if (this == o) return true; 167 if (!(o instanceof SafetySourceStatus)) return false; 168 SafetySourceStatus that = (SafetySourceStatus) o; 169 return mSeverityLevel == that.mSeverityLevel 170 && mEnabled == that.mEnabled 171 && TextUtils.equals(mTitle, that.mTitle) 172 && TextUtils.equals(mSummary, that.mSummary) 173 && Objects.equals(mPendingIntent, that.mPendingIntent) 174 && Objects.equals(mIconAction, that.mIconAction); 175 } 176 177 @Override hashCode()178 public int hashCode() { 179 return Objects.hash( 180 mTitle, mSummary, mSeverityLevel, mPendingIntent, mIconAction, mEnabled); 181 } 182 183 @Override toString()184 public String toString() { 185 return "SafetySourceStatus{" 186 + "mTitle=" 187 + mTitle 188 + ", mSummary=" 189 + mSummary 190 + ", mSeverityLevel=" 191 + mSeverityLevel 192 + ", mPendingIntent=" 193 + mPendingIntent 194 + ", mIconAction=" 195 + mIconAction 196 + ", mEnabled=" 197 + mEnabled 198 + '}'; 199 } 200 201 /** 202 * Data for an action supported from a safety source status {@link SafetySourceStatus} in the 203 * Safety Center page. 204 * 205 * <p>The purpose of the action is to add a surface to allow the user to perform an action 206 * relating to the safety source status. 207 * 208 * <p>The action will be shown as a clickable icon chosen from a predefined set of icons (see 209 * {@link IconType}). The icon should indicate to the user what action will be performed on 210 * clicking on it. 211 * 212 * @hide 213 */ 214 @SystemApi 215 public static final class IconAction implements Parcelable { 216 217 @NonNull 218 public static final Creator<IconAction> CREATOR = 219 new Creator<IconAction>() { 220 @Override 221 public IconAction createFromParcel(Parcel in) { 222 int iconType = in.readInt(); 223 PendingIntent pendingIntent = in.readTypedObject(PendingIntent.CREATOR); 224 return new IconAction(iconType, pendingIntent); 225 } 226 227 @Override 228 public IconAction[] newArray(int size) { 229 return new IconAction[size]; 230 } 231 }; 232 233 /** Indicates a gear (cog) icon. */ 234 public static final int ICON_TYPE_GEAR = 100; 235 236 /** Indicates an information icon. */ 237 public static final int ICON_TYPE_INFO = 200; 238 239 /** 240 * All possible icons which can be displayed in an {@link IconAction}. 241 * 242 * @hide 243 */ 244 @IntDef( 245 prefix = {"ICON_TYPE_"}, 246 value = { 247 ICON_TYPE_GEAR, 248 ICON_TYPE_INFO, 249 }) 250 @Retention(RetentionPolicy.SOURCE) 251 public @interface IconType {} 252 253 @IconType private final int mIconType; 254 @NonNull private final PendingIntent mPendingIntent; 255 IconAction(@conType int iconType, @NonNull PendingIntent pendingIntent)256 public IconAction(@IconType int iconType, @NonNull PendingIntent pendingIntent) { 257 this.mIconType = validateIconType(iconType); 258 this.mPendingIntent = requireNonNull(pendingIntent); 259 } 260 261 /** 262 * Returns the type of icon to be displayed in the UI. 263 * 264 * <p>The icon type should indicate what action will be performed if when invoked. 265 */ 266 @IconType getIconType()267 public int getIconType() { 268 return mIconType; 269 } 270 271 /** 272 * Returns a {@link PendingIntent} that will start an activity when the icon action is 273 * clicked on. 274 */ 275 @NonNull getPendingIntent()276 public PendingIntent getPendingIntent() { 277 return mPendingIntent; 278 } 279 280 @Override describeContents()281 public int describeContents() { 282 return 0; 283 } 284 285 @Override writeToParcel(@onNull Parcel dest, int flags)286 public void writeToParcel(@NonNull Parcel dest, int flags) { 287 dest.writeInt(mIconType); 288 dest.writeTypedObject(mPendingIntent, flags); 289 } 290 291 @Override equals(Object o)292 public boolean equals(Object o) { 293 if (this == o) return true; 294 if (!(o instanceof IconAction)) return false; 295 IconAction that = (IconAction) o; 296 return mIconType == that.mIconType && mPendingIntent.equals(that.mPendingIntent); 297 } 298 299 @Override hashCode()300 public int hashCode() { 301 return Objects.hash(mIconType, mPendingIntent); 302 } 303 304 @Override toString()305 public String toString() { 306 return "IconAction{" 307 + "mIconType=" 308 + mIconType 309 + ", mPendingIntent=" 310 + mPendingIntent 311 + '}'; 312 } 313 314 @IconType validateIconType(int value)315 private static int validateIconType(int value) { 316 switch (value) { 317 case ICON_TYPE_GEAR: 318 case ICON_TYPE_INFO: 319 return value; 320 default: 321 } 322 throw new IllegalArgumentException( 323 String.format("Unexpected IconType for IconAction: %s", value)); 324 } 325 } 326 327 /** Builder class for {@link SafetySourceStatus}. */ 328 public static final class Builder { 329 330 @NonNull private final CharSequence mTitle; 331 @NonNull private final CharSequence mSummary; 332 @SafetySourceData.SeverityLevel private final int mSeverityLevel; 333 334 @Nullable private PendingIntent mPendingIntent; 335 @Nullable private IconAction mIconAction; 336 private boolean mEnabled = true; 337 338 /** Creates a {@link Builder} for a {@link SafetySourceStatus}. */ Builder( @onNull CharSequence title, @NonNull CharSequence summary, @SafetySourceData.SeverityLevel int severityLevel)339 public Builder( 340 @NonNull CharSequence title, 341 @NonNull CharSequence summary, 342 @SafetySourceData.SeverityLevel int severityLevel) { 343 this.mTitle = requireNonNull(title); 344 this.mSummary = requireNonNull(summary); 345 this.mSeverityLevel = validateSeverityLevel(severityLevel); 346 } 347 348 /** 349 * Sets an optional {@link PendingIntent} for the safety source status. 350 * 351 * <p>The action contained in the {@link PendingIntent} must start an activity. 352 * 353 * @see #getPendingIntent() 354 */ 355 @NonNull setPendingIntent(@ullable PendingIntent pendingIntent)356 public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) { 357 checkArgument( 358 pendingIntent == null || pendingIntent.isActivity(), 359 "Safety source status pending intent must start an activity"); 360 this.mPendingIntent = pendingIntent; 361 return this; 362 } 363 364 /** 365 * Sets an optional {@link IconAction} for the safety source status. 366 * 367 * @see #getIconAction() 368 */ 369 @NonNull setIconAction(@ullable IconAction iconAction)370 public Builder setIconAction(@Nullable IconAction iconAction) { 371 this.mIconAction = iconAction; 372 return this; 373 } 374 375 /** 376 * Sets whether the safety source status is enabled. 377 * 378 * <p>By default, the safety source status will be enabled. If disabled, the status severity 379 * level must be set to {@link SafetySourceData#SEVERITY_LEVEL_UNSPECIFIED}. 380 * 381 * @see #isEnabled() 382 */ 383 @NonNull setEnabled(boolean enabled)384 public Builder setEnabled(boolean enabled) { 385 checkArgument( 386 enabled || mSeverityLevel == SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED, 387 "Safety source status must have a severity level of " 388 + "SEVERITY_LEVEL_UNSPECIFIED when disabled"); 389 this.mEnabled = enabled; 390 return this; 391 } 392 393 /** Creates the {@link SafetySourceStatus} defined by this {@link Builder}. */ 394 @NonNull build()395 public SafetySourceStatus build() { 396 return new SafetySourceStatus( 397 mTitle, mSummary, mSeverityLevel, mPendingIntent, mIconAction, mEnabled); 398 } 399 } 400 401 @SafetySourceData.SeverityLevel validateSeverityLevel(int value)402 private static int validateSeverityLevel(int value) { 403 switch (value) { 404 case SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED: 405 case SafetySourceData.SEVERITY_LEVEL_INFORMATION: 406 case SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION: 407 case SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING: 408 return value; 409 default: 410 } 411 throw new IllegalArgumentException( 412 String.format("Unexpected SeverityLevel for SafetySourceStatus: %s", value)); 413 } 414 } 415