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.config; 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.Collections.unmodifiableList; 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.StringRes; 29 import android.annotation.SuppressLint; 30 import android.annotation.SystemApi; 31 import android.content.res.Resources; 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.List; 43 import java.util.Objects; 44 45 /** 46 * Data class used to represent the initial configuration of a group of safety sources. 47 * 48 * @hide 49 */ 50 @SystemApi 51 @RequiresApi(TIRAMISU) 52 public final class SafetySourcesGroup implements Parcelable { 53 54 /** 55 * Indicates that the safety sources group should be displayed as a collapsible group with an 56 * icon (stateless or stateful) and an optional default summary. 57 * 58 * @deprecated use {@link #SAFETY_SOURCES_GROUP_TYPE_STATEFUL} instead. 59 */ 60 public static final int SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE = 0; 61 62 /** 63 * Indicates that the safety sources group should be displayed as a group that may contribute to 64 * the overall Safety Center status. This is indicated by a group stateful icon. If all sources 65 * in the group have an unspecified status then a stateless group icon might be applied. 66 */ 67 public static final int SAFETY_SOURCES_GROUP_TYPE_STATEFUL = 68 SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE; 69 70 /** 71 * Indicates that the safety sources group should be displayed as a rigid group with no icon and 72 * no summary. 73 * 74 * @deprecated use {@link #SAFETY_SOURCES_GROUP_TYPE_STATELESS} instead. 75 */ 76 public static final int SAFETY_SOURCES_GROUP_TYPE_RIGID = 1; 77 78 /** 79 * Indicates that the safety sources group should be displayed as a group that does not 80 * contribute to the overall Safety Center status. All sources of type dynamic in the group can 81 * only report an unspecified status. The stateless icon and summary may be ignored and not be 82 * displayed. 83 */ 84 public static final int SAFETY_SOURCES_GROUP_TYPE_STATELESS = SAFETY_SOURCES_GROUP_TYPE_RIGID; 85 86 /** 87 * Indicates that the safety sources group should not be displayed. All sources in the group 88 * must be of type issue-only. 89 */ 90 public static final int SAFETY_SOURCES_GROUP_TYPE_HIDDEN = 2; 91 92 /** 93 * All possible types for a safety sources group. 94 * 95 * @hide 96 */ 97 @SuppressLint("UniqueConstants") // Intentionally renaming the COLLAPSIBLE and RIGID constants. 98 @Retention(RetentionPolicy.SOURCE) 99 @IntDef( 100 prefix = "SAFETY_SOURCES_GROUP_TYPE_", 101 value = { 102 SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE, 103 SAFETY_SOURCES_GROUP_TYPE_STATEFUL, 104 SAFETY_SOURCES_GROUP_TYPE_RIGID, 105 SAFETY_SOURCES_GROUP_TYPE_STATELESS, 106 SAFETY_SOURCES_GROUP_TYPE_HIDDEN 107 }) 108 public @interface SafetySourceGroupType {} 109 110 /** 111 * Indicates that no special icon will be displayed by a safety sources group when all the 112 * sources contained in it are stateless. 113 */ 114 public static final int STATELESS_ICON_TYPE_NONE = 0; 115 116 /** 117 * Indicates that the privacy icon will be displayed by a safety sources group when all the 118 * sources contained in it are stateless. 119 */ 120 public static final int STATELESS_ICON_TYPE_PRIVACY = 1; 121 122 /** 123 * All possible stateless icon types for a safety sources group. 124 * 125 * @hide 126 */ 127 @Retention(RetentionPolicy.SOURCE) 128 @IntDef( 129 prefix = "STATELESS_ICON_TYPE_", 130 value = {STATELESS_ICON_TYPE_NONE, STATELESS_ICON_TYPE_PRIVACY}) 131 public @interface StatelessIconType {} 132 133 @NonNull 134 public static final Creator<SafetySourcesGroup> CREATOR = 135 new Creator<SafetySourcesGroup>() { 136 @Override 137 public SafetySourcesGroup createFromParcel(Parcel in) { 138 Builder builder = 139 new Builder() 140 .setId(in.readString()) 141 .setTitleResId(in.readInt()) 142 .setSummaryResId(in.readInt()) 143 .setStatelessIconType(in.readInt()); 144 List<SafetySource> safetySources = 145 requireNonNull(in.createTypedArrayList(SafetySource.CREATOR)); 146 for (int i = 0; i < safetySources.size(); i++) { 147 builder.addSafetySource(safetySources.get(i)); 148 } 149 if (SdkLevel.isAtLeastU()) { 150 builder.setType(in.readInt()); 151 } 152 return builder.build(); 153 } 154 155 @Override 156 public SafetySourcesGroup[] newArray(int size) { 157 return new SafetySourcesGroup[size]; 158 } 159 }; 160 161 @SafetySourceGroupType private final int mType; 162 @NonNull private final String mId; 163 @StringRes private final int mTitleResId; 164 @StringRes private final int mSummaryResId; 165 @StatelessIconType private final int mStatelessIconType; 166 @NonNull private final List<SafetySource> mSafetySources; 167 SafetySourcesGroup( @afetySourceGroupType int type, @NonNull String id, @StringRes int titleResId, @StringRes int summaryResId, @StatelessIconType int statelessIconType, @NonNull List<SafetySource> safetySources)168 private SafetySourcesGroup( 169 @SafetySourceGroupType int type, 170 @NonNull String id, 171 @StringRes int titleResId, 172 @StringRes int summaryResId, 173 @StatelessIconType int statelessIconType, 174 @NonNull List<SafetySource> safetySources) { 175 mType = type; 176 mId = id; 177 mTitleResId = titleResId; 178 mSummaryResId = summaryResId; 179 mStatelessIconType = statelessIconType; 180 mSafetySources = safetySources; 181 } 182 183 /** Returns the type of this safety sources group. */ 184 @SafetySourceGroupType getType()185 public int getType() { 186 return mType; 187 } 188 189 /** 190 * Returns the id of this safety sources group. 191 * 192 * <p>The id is unique among safety sources groups in a Safety Center configuration. 193 */ 194 @NonNull getId()195 public String getId() { 196 return mId; 197 } 198 199 /** 200 * Returns the resource id of the title of this safety sources group. 201 * 202 * <p>The id refers to a string resource that is either accessible from any resource context or 203 * that is accessible from the same resource context that was used to load the Safety Center 204 * configuration. The id is {@link Resources#ID_NULL} when a title is not provided. 205 */ 206 @StringRes getTitleResId()207 public int getTitleResId() { 208 return mTitleResId; 209 } 210 211 /** 212 * Returns the resource id of the summary of this safety sources group. 213 * 214 * <p>The id refers to a string resource that is either accessible from any resource context or 215 * that is accessible from the same resource context that was used to load the Safety Center 216 * configuration. The id is {@link Resources#ID_NULL} when a summary is not provided. 217 */ 218 @StringRes getSummaryResId()219 public int getSummaryResId() { 220 return mSummaryResId; 221 } 222 223 /** 224 * Returns the stateless icon type of this safety sources group. 225 * 226 * <p>If set to a value other than {@link SafetySourcesGroup#STATELESS_ICON_TYPE_NONE}, the icon 227 * specified will be displayed for collapsible groups when all the sources contained in the 228 * group are stateless. 229 */ 230 @StatelessIconType getStatelessIconType()231 public int getStatelessIconType() { 232 return mStatelessIconType; 233 } 234 235 /** 236 * Returns the list of {@link SafetySource}s in this safety sources group. 237 * 238 * <p>A safety sources group contains at least one {@link SafetySource}. 239 */ 240 @NonNull getSafetySources()241 public List<SafetySource> getSafetySources() { 242 return mSafetySources; 243 } 244 245 @Override equals(Object o)246 public boolean equals(Object o) { 247 if (this == o) return true; 248 if (!(o instanceof SafetySourcesGroup)) return false; 249 SafetySourcesGroup that = (SafetySourcesGroup) o; 250 return mType == that.mType 251 && Objects.equals(mId, that.mId) 252 && mTitleResId == that.mTitleResId 253 && mSummaryResId == that.mSummaryResId 254 && mStatelessIconType == that.mStatelessIconType 255 && Objects.equals(mSafetySources, that.mSafetySources); 256 } 257 258 @Override hashCode()259 public int hashCode() { 260 return Objects.hash( 261 mType, mId, mTitleResId, mSummaryResId, mStatelessIconType, mSafetySources); 262 } 263 264 @Override toString()265 public String toString() { 266 return "SafetySourcesGroup{" 267 + "mType=" 268 + mType 269 + ", mId=" 270 + mId 271 + ", mTitleResId=" 272 + mTitleResId 273 + ", mSummaryResId=" 274 + mSummaryResId 275 + ", mStatelessIconType=" 276 + mStatelessIconType 277 + ", mSafetySources=" 278 + mSafetySources 279 + '}'; 280 } 281 282 @Override describeContents()283 public int describeContents() { 284 return 0; 285 } 286 287 @Override writeToParcel(@onNull Parcel dest, int flags)288 public void writeToParcel(@NonNull Parcel dest, int flags) { 289 dest.writeString(mId); 290 dest.writeInt(mTitleResId); 291 dest.writeInt(mSummaryResId); 292 dest.writeInt(mStatelessIconType); 293 dest.writeTypedList(mSafetySources); 294 if (SdkLevel.isAtLeastU()) { 295 dest.writeInt(mType); 296 } 297 } 298 299 /** Builder class for {@link SafetySourcesGroup}. */ 300 public static final class Builder { 301 302 private final List<SafetySource> mSafetySources = new ArrayList<>(); 303 304 @Nullable @SafetySourceGroupType private Integer mType; 305 @Nullable private String mId; 306 @Nullable @StringRes private Integer mTitleResId; 307 @Nullable @StringRes private Integer mSummaryResId; 308 @Nullable @StatelessIconType private Integer mStatelessIconType; 309 310 /** Creates a {@link Builder} for a {@link SafetySourcesGroup}. */ Builder()311 public Builder() {} 312 313 /** Creates a {@link Builder} with the values from the given {@link SafetySourcesGroup}. */ 314 @RequiresApi(UPSIDE_DOWN_CAKE) Builder(@onNull SafetySourcesGroup original)315 public Builder(@NonNull SafetySourcesGroup original) { 316 if (!SdkLevel.isAtLeastU()) { 317 throw new UnsupportedOperationException(); 318 } 319 requireNonNull(original); 320 mSafetySources.addAll(original.mSafetySources); 321 mType = original.mType; 322 mId = original.mId; 323 mTitleResId = original.mTitleResId; 324 mSummaryResId = original.mSummaryResId; 325 mStatelessIconType = original.mStatelessIconType; 326 } 327 328 /** 329 * Sets the type of this safety sources group. 330 * 331 * <p>If the type is not explicitly set, the type is inferred according to the state of 332 * certain fields. If no title is provided when building the group, the group is of type 333 * hidden. If a title is provided but no summary or stateless icon are provided when 334 * building the group, the group is of type stateless. Otherwise, the group is of type 335 * stateful. 336 */ 337 @NonNull 338 @RequiresApi(UPSIDE_DOWN_CAKE) setType(@afetySourceGroupType int type)339 public Builder setType(@SafetySourceGroupType int type) { 340 mType = type; 341 return this; 342 } 343 344 /** 345 * Sets the id of this safety sources group. 346 * 347 * <p>The id must be unique among safety sources groups in a Safety Center configuration. 348 */ 349 @NonNull setId(@ullable String id)350 public Builder setId(@Nullable String id) { 351 mId = id; 352 return this; 353 } 354 355 /** 356 * Sets the resource id of the title of this safety sources group. 357 * 358 * <p>The id must refer to a string resource that is either accessible from any resource 359 * context or that is accessible from the same resource context that was used to load the 360 * Safety Center configuration. The id defaults to {@link Resources#ID_NULL} when a title is 361 * not provided. A title is required unless the group only contains safety sources of type 362 * issue only. 363 */ 364 @NonNull setTitleResId(@tringRes int titleResId)365 public Builder setTitleResId(@StringRes int titleResId) { 366 mTitleResId = titleResId; 367 return this; 368 } 369 370 /** 371 * Sets the resource id of the summary of this safety sources group. 372 * 373 * <p>The id must refer to a string resource that is either accessible from any resource 374 * context or that is accessible from the same resource context that was used to load the 375 * Safety Center configuration. The id defaults to {@link Resources#ID_NULL} when a summary 376 * is not provided. 377 */ 378 @NonNull setSummaryResId(@tringRes int summaryResId)379 public Builder setSummaryResId(@StringRes int summaryResId) { 380 mSummaryResId = summaryResId; 381 return this; 382 } 383 384 /** 385 * Sets the stateless icon type of this safety sources group. 386 * 387 * <p>If set to a value other than {@link SafetySourcesGroup#STATELESS_ICON_TYPE_NONE}, the 388 * icon specified will be displayed for collapsible groups when all the sources contained in 389 * the group are stateless. 390 */ 391 @NonNull setStatelessIconType(@tatelessIconType int statelessIconType)392 public Builder setStatelessIconType(@StatelessIconType int statelessIconType) { 393 mStatelessIconType = statelessIconType; 394 return this; 395 } 396 397 /** 398 * Adds a {@link SafetySource} to this safety sources group. 399 * 400 * <p>A safety sources group must contain at least one {@link SafetySource}. 401 */ 402 @NonNull addSafetySource(@onNull SafetySource safetySource)403 public Builder addSafetySource(@NonNull SafetySource safetySource) { 404 mSafetySources.add(requireNonNull(safetySource)); 405 return this; 406 } 407 408 /** 409 * Creates the {@link SafetySourcesGroup} defined by this {@link Builder}. 410 * 411 * @throws IllegalStateException if any constraint on the safety sources group is violated 412 */ 413 @NonNull build()414 public SafetySourcesGroup build() { 415 String id = mId; 416 BuilderUtils.validateId(id, "id", true, false); 417 418 List<SafetySource> safetySources = unmodifiableList(new ArrayList<>(mSafetySources)); 419 if (safetySources.isEmpty()) { 420 throw new IllegalStateException("Safety sources group empty"); 421 } 422 423 int summaryResId = BuilderUtils.validateResId(mSummaryResId, "summary", false, false); 424 425 int statelessIconType = 426 BuilderUtils.validateIntDef( 427 mStatelessIconType, 428 "statelessIconType", 429 false, 430 false, 431 STATELESS_ICON_TYPE_NONE, 432 STATELESS_ICON_TYPE_NONE, 433 STATELESS_ICON_TYPE_PRIVACY); 434 435 boolean hasOnlyIssueOnlySources = true; 436 int safetySourcesSize = safetySources.size(); 437 for (int i = 0; i < safetySourcesSize; i++) { 438 int type = safetySources.get(i).getType(); 439 if (type != SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY) { 440 hasOnlyIssueOnlySources = false; 441 break; 442 } 443 } 444 445 int inferredGroupType = SAFETY_SOURCES_GROUP_TYPE_STATELESS; 446 if (hasOnlyIssueOnlySources) { 447 inferredGroupType = SAFETY_SOURCES_GROUP_TYPE_HIDDEN; 448 } else if (summaryResId != Resources.ID_NULL 449 || statelessIconType != STATELESS_ICON_TYPE_NONE) { 450 inferredGroupType = SAFETY_SOURCES_GROUP_TYPE_STATEFUL; 451 } 452 int type = 453 BuilderUtils.validateIntDef( 454 mType, 455 "type", 456 false, 457 false, 458 inferredGroupType, 459 SAFETY_SOURCES_GROUP_TYPE_STATEFUL, 460 SAFETY_SOURCES_GROUP_TYPE_STATELESS, 461 SAFETY_SOURCES_GROUP_TYPE_HIDDEN); 462 if (type == SAFETY_SOURCES_GROUP_TYPE_HIDDEN && !hasOnlyIssueOnlySources) { 463 throw new IllegalStateException( 464 "Safety sources groups of type hidden can only contain sources of type " 465 + "issue-only"); 466 } 467 if (type != SAFETY_SOURCES_GROUP_TYPE_HIDDEN && hasOnlyIssueOnlySources) { 468 throw new IllegalStateException( 469 "Safety sources groups containing only sources of type issue-only must be " 470 + "of type hidden"); 471 } 472 473 boolean isStateful = type == SAFETY_SOURCES_GROUP_TYPE_STATEFUL; 474 boolean isStateless = type == SAFETY_SOURCES_GROUP_TYPE_STATELESS; 475 int titleResId = 476 BuilderUtils.validateResId( 477 mTitleResId, "title", isStateful || isStateless, false); 478 479 return new SafetySourcesGroup( 480 type, id, titleResId, summaryResId, statelessIconType, safetySources); 481 } 482 } 483 } 484