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 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 21 22 import static com.android.internal.util.Preconditions.checkArgument; 23 24 import static java.util.Collections.unmodifiableList; 25 import static java.util.Objects.requireNonNull; 26 27 import android.annotation.IntDef; 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.annotation.SystemApi; 31 import android.os.Bundle; 32 import android.os.Parcel; 33 import android.os.Parcelable; 34 35 import androidx.annotation.RequiresApi; 36 37 import com.android.modules.utils.build.SdkLevel; 38 39 import java.lang.annotation.Retention; 40 import java.lang.annotation.RetentionPolicy; 41 import java.util.ArrayList; 42 import java.util.HashSet; 43 import java.util.List; 44 import java.util.Objects; 45 import java.util.Set; 46 47 /** 48 * Data class used by safety sources to propagate safety information such as their safety status and 49 * safety issues. 50 * 51 * @hide 52 */ 53 @SystemApi 54 @RequiresApi(TIRAMISU) 55 public final class SafetySourceData implements Parcelable { 56 57 /** 58 * Indicates that no opinion is currently associated with the information provided. 59 * 60 * <p>This severity level will be reflected in the UI of a {@link SafetySourceStatus} through a 61 * grey icon. 62 * 63 * <p>For a {@link SafetySourceStatus}, this severity level indicates that the safety source 64 * currently does not have sufficient information on the severity level of the {@link 65 * SafetySourceStatus}. 66 * 67 * <p>This severity level cannot be used to indicate the severity level of a {@link 68 * SafetySourceIssue}. 69 */ 70 public static final int SEVERITY_LEVEL_UNSPECIFIED = 100; 71 72 /** 73 * Indicates the presence of an informational message or the absence of any safety issues. 74 * 75 * <p>This severity level will be reflected in the UI of either a {@link SafetySourceStatus} or 76 * a {@link SafetySourceIssue} through a green icon. 77 * 78 * <p>For a {@link SafetySourceStatus}, this severity level indicates either the absence of any 79 * {@link SafetySourceIssue}s or the presence of only {@link SafetySourceIssue}s with the same 80 * severity level. 81 * 82 * <p>For a {@link SafetySourceIssue}, this severity level indicates that the {@link 83 * SafetySourceIssue} represents an informational message relating to the safety source. {@link 84 * SafetySourceIssue}s of this severity level will be dismissible by the user from the UI, and 85 * will not trigger a confirmation dialog upon a user attempting to dismiss the warning. 86 */ 87 public static final int SEVERITY_LEVEL_INFORMATION = 200; 88 89 /** 90 * Indicates the presence of a medium-severity safety issue which the user is encouraged to act 91 * on. 92 * 93 * <p>This severity level will be reflected in the UI of either a {@link SafetySourceStatus} or 94 * a {@link SafetySourceIssue} through a yellow icon. 95 * 96 * <p>For a {@link SafetySourceStatus}, this severity level indicates the presence of at least 97 * one medium-severity {@link SafetySourceIssue} relating to the safety source which the user is 98 * encouraged to act on, and no {@link SafetySourceIssue}s with higher severity level. 99 * 100 * <p>For a {@link SafetySourceIssue}, this severity level indicates that the {@link 101 * SafetySourceIssue} represents a medium-severity safety issue relating to the safety source 102 * which the user is encouraged to act on. {@link SafetySourceIssue}s of this severity level 103 * will be dismissible by the user from the UI, and will trigger a confirmation dialog upon a 104 * user attempting to dismiss the warning. 105 */ 106 public static final int SEVERITY_LEVEL_RECOMMENDATION = 300; 107 108 /** 109 * Indicates the presence of a critical or urgent safety issue that should be addressed by the 110 * user. 111 * 112 * <p>This severity level will be reflected in the UI of either a {@link SafetySourceStatus} or 113 * a {@link SafetySourceIssue} through a red icon. 114 * 115 * <p>For a {@link SafetySourceStatus}, this severity level indicates the presence of at least 116 * one critical or urgent {@link SafetySourceIssue} relating to the safety source that should be 117 * addressed by the user. 118 * 119 * <p>For a {@link SafetySourceIssue}, this severity level indicates that the {@link 120 * SafetySourceIssue} represents a critical or urgent safety issue relating to the safety source 121 * that should be addressed by the user. {@link SafetySourceIssue}s of this severity level will 122 * be dismissible by the user from the UI, and will trigger a confirmation dialog upon a user 123 * attempting to dismiss the warning. 124 */ 125 public static final int SEVERITY_LEVEL_CRITICAL_WARNING = 400; 126 127 /** 128 * All possible severity levels for a {@link SafetySourceStatus} or a {@link SafetySourceIssue}. 129 * 130 * <p>The numerical values of the levels are not used directly, rather they are used to build a 131 * continuum of levels which support relative comparison. The higher the severity level the 132 * higher the threat to the user. 133 * 134 * <p>For a {@link SafetySourceStatus}, the severity level is meant to convey the aggregated 135 * severity of the safety source, and it contributes to the overall severity level in the Safety 136 * Center. If the {@link SafetySourceData} contains {@link SafetySourceIssue}s, the severity 137 * level of the s{@link SafetySourceStatus} must match the highest severity level among the 138 * {@link SafetySourceIssue}s. 139 * 140 * <p>For a {@link SafetySourceIssue}, not all severity levels can be used. The severity level 141 * also determines how a {@link SafetySourceIssue}s is "dismissible" by the user, i.e. how the 142 * user can choose to ignore the issue and remove it from view in the Safety Center. 143 * 144 * @hide 145 */ 146 @IntDef( 147 prefix = {"SEVERITY_LEVEL_"}, 148 value = { 149 SEVERITY_LEVEL_UNSPECIFIED, 150 SEVERITY_LEVEL_INFORMATION, 151 SEVERITY_LEVEL_RECOMMENDATION, 152 SEVERITY_LEVEL_CRITICAL_WARNING 153 }) 154 @Retention(RetentionPolicy.SOURCE) 155 public @interface SeverityLevel {} 156 157 @NonNull 158 public static final Creator<SafetySourceData> CREATOR = 159 new Creator<SafetySourceData>() { 160 @Override 161 public SafetySourceData createFromParcel(Parcel in) { 162 SafetySourceStatus status = in.readTypedObject(SafetySourceStatus.CREATOR); 163 List<SafetySourceIssue> issues = 164 requireNonNull(in.createTypedArrayList(SafetySourceIssue.CREATOR)); 165 Builder builder = new Builder().setStatus(status); 166 for (int i = 0; i < issues.size(); i++) { 167 builder.addIssue(issues.get(i)); 168 } 169 if (SdkLevel.isAtLeastU()) { 170 Bundle extras = in.readBundle(getClass().getClassLoader()); 171 if (extras != null) { 172 builder.setExtras(extras); 173 } 174 } 175 return builder.build(); 176 } 177 178 @Override 179 public SafetySourceData[] newArray(int size) { 180 return new SafetySourceData[size]; 181 } 182 }; 183 184 @Nullable private final SafetySourceStatus mStatus; 185 @NonNull private final List<SafetySourceIssue> mIssues; 186 @NonNull private final Bundle mExtras; 187 SafetySourceData( @ullable SafetySourceStatus status, @NonNull List<SafetySourceIssue> issues, @NonNull Bundle extras)188 private SafetySourceData( 189 @Nullable SafetySourceStatus status, 190 @NonNull List<SafetySourceIssue> issues, 191 @NonNull Bundle extras) { 192 this.mStatus = status; 193 this.mIssues = issues; 194 this.mExtras = extras; 195 } 196 197 /** Returns the data for the {@link SafetySourceStatus} to be shown in UI. */ 198 @Nullable getStatus()199 public SafetySourceStatus getStatus() { 200 return mStatus; 201 } 202 203 /** Returns the data for the list of {@link SafetySourceIssue}s to be shown in UI. */ 204 @NonNull getIssues()205 public List<SafetySourceIssue> getIssues() { 206 return mIssues; 207 } 208 209 /** 210 * Returns a {@link Bundle} containing additional information, {@link Bundle#EMPTY} by default. 211 * 212 * <p>Note: internal state of this {@link Bundle} is not used for {@link Object#equals} and 213 * {@link Object#hashCode} implementation of {@link SafetySourceData}. 214 */ 215 @NonNull 216 @RequiresApi(UPSIDE_DOWN_CAKE) getExtras()217 public Bundle getExtras() { 218 if (!SdkLevel.isAtLeastU()) { 219 throw new UnsupportedOperationException( 220 "Method not supported on versions lower than UPSIDE_DOWN_CAKE"); 221 } 222 return mExtras; 223 } 224 225 @Override describeContents()226 public int describeContents() { 227 return 0; 228 } 229 230 @Override writeToParcel(@onNull Parcel dest, int flags)231 public void writeToParcel(@NonNull Parcel dest, int flags) { 232 dest.writeTypedObject(mStatus, flags); 233 dest.writeTypedList(mIssues); 234 if (SdkLevel.isAtLeastU()) { 235 dest.writeBundle(mExtras); 236 } 237 } 238 239 @Override equals(Object o)240 public boolean equals(Object o) { 241 if (this == o) return true; 242 if (!(o instanceof SafetySourceData)) return false; 243 SafetySourceData that = (SafetySourceData) o; 244 return Objects.equals(mStatus, that.mStatus) && mIssues.equals(that.mIssues); 245 } 246 247 @Override hashCode()248 public int hashCode() { 249 return Objects.hash(mStatus, mIssues); 250 } 251 252 @Override toString()253 public String toString() { 254 return "SafetySourceData{" 255 + "mStatus=" 256 + mStatus 257 + ", mIssues=" 258 + mIssues 259 + (!mExtras.isEmpty() ? ", (has extras)" : "") 260 + '}'; 261 } 262 263 /** Builder class for {@link SafetySourceData}. */ 264 public static final class Builder { 265 266 @NonNull private final List<SafetySourceIssue> mIssues = new ArrayList<>(); 267 268 @Nullable private SafetySourceStatus mStatus; 269 @NonNull private Bundle mExtras = Bundle.EMPTY; 270 271 /** Creates a {@link Builder} for a {@link SafetySourceData}. */ Builder()272 public Builder() {} 273 274 /** Creates a {@link Builder} with the values from the given {@link SafetySourceData}. */ 275 @RequiresApi(UPSIDE_DOWN_CAKE) Builder(@onNull SafetySourceData safetySourceData)276 public Builder(@NonNull SafetySourceData safetySourceData) { 277 if (!SdkLevel.isAtLeastU()) { 278 throw new UnsupportedOperationException( 279 "Method not supported on versions lower than UPSIDE_DOWN_CAKE"); 280 } 281 requireNonNull(safetySourceData); 282 mIssues.addAll(safetySourceData.mIssues); 283 mStatus = safetySourceData.mStatus; 284 mExtras = safetySourceData.mExtras.deepCopy(); 285 } 286 287 /** Sets data for the {@link SafetySourceStatus} to be shown in UI. */ 288 @NonNull setStatus(@ullable SafetySourceStatus status)289 public Builder setStatus(@Nullable SafetySourceStatus status) { 290 mStatus = status; 291 return this; 292 } 293 294 /** Adds data for a {@link SafetySourceIssue} to be shown in UI. */ 295 @NonNull addIssue(@onNull SafetySourceIssue safetySourceIssue)296 public Builder addIssue(@NonNull SafetySourceIssue safetySourceIssue) { 297 mIssues.add(requireNonNull(safetySourceIssue)); 298 return this; 299 } 300 301 /** 302 * Sets additional information for the {@link SafetySourceData}. 303 * 304 * <p>If not set, the default value is {@link Bundle#EMPTY}. 305 */ 306 @NonNull 307 @RequiresApi(UPSIDE_DOWN_CAKE) setExtras(@onNull Bundle extras)308 public Builder setExtras(@NonNull Bundle extras) { 309 if (!SdkLevel.isAtLeastU()) { 310 throw new UnsupportedOperationException( 311 "Method not supported on versions lower than UPSIDE_DOWN_CAKE"); 312 } 313 mExtras = requireNonNull(extras); 314 return this; 315 } 316 317 /** 318 * Resets additional information for the {@link SafetySourceData} to the default value of 319 * {@link Bundle#EMPTY}. 320 */ 321 @NonNull 322 @RequiresApi(UPSIDE_DOWN_CAKE) clearExtras()323 public Builder clearExtras() { 324 if (!SdkLevel.isAtLeastU()) { 325 throw new UnsupportedOperationException( 326 "Method not supported on versions lower than UPSIDE_DOWN_CAKE"); 327 } 328 mExtras = Bundle.EMPTY; 329 return this; 330 } 331 332 /** 333 * Clears data for all the {@link SafetySourceIssue}s that were added to this {@link 334 * Builder}. 335 */ 336 @NonNull clearIssues()337 public Builder clearIssues() { 338 mIssues.clear(); 339 return this; 340 } 341 342 /** Creates the {@link SafetySourceData} defined by this {@link Builder}. */ 343 @NonNull build()344 public SafetySourceData build() { 345 List<SafetySourceIssue> issues = unmodifiableList(new ArrayList<>(mIssues)); 346 int issuesMaxSeverityLevel = getIssuesMaxSeverityLevelEnforcingUniqueIds(issues); 347 if (mStatus == null) { 348 return new SafetySourceData(null, issues, mExtras); 349 } 350 int statusSeverityLevel = mStatus.getSeverityLevel(); 351 boolean requiresAttention = issuesMaxSeverityLevel > SEVERITY_LEVEL_INFORMATION; 352 if (requiresAttention) { 353 checkArgument( 354 statusSeverityLevel >= issuesMaxSeverityLevel, 355 "Safety source data cannot have issues that are more severe than its" 356 + " status"); 357 } 358 359 return new SafetySourceData(mStatus, issues, mExtras); 360 } 361 getIssuesMaxSeverityLevelEnforcingUniqueIds( @onNull List<SafetySourceIssue> issues)362 private static int getIssuesMaxSeverityLevelEnforcingUniqueIds( 363 @NonNull List<SafetySourceIssue> issues) { 364 int max = Integer.MIN_VALUE; 365 Set<String> issueIds = new HashSet<>(); 366 for (int i = 0; i < issues.size(); i++) { 367 SafetySourceIssue safetySourceIssue = issues.get(i); 368 369 String issueId = safetySourceIssue.getId(); 370 checkArgument( 371 !issueIds.contains(issueId), 372 "Safety source data cannot have duplicate issue ids"); 373 max = Math.max(max, safetySourceIssue.getSeverityLevel()); 374 issueIds.add(issueId); 375 } 376 return max; 377 } 378 } 379 } 380