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 "Method not supported on versions lower than UPSIDE_DOWN_CAKE"); 319 } 320 requireNonNull(original); 321 mSafetySources.addAll(original.mSafetySources); 322 mType = original.mType; 323 mId = original.mId; 324 mTitleResId = original.mTitleResId; 325 mSummaryResId = original.mSummaryResId; 326 mStatelessIconType = original.mStatelessIconType; 327 } 328 329 /** 330 * Sets the type of this safety sources group. 331 * 332 * <p>If the type is not explicitly set, the type is inferred according to the state of 333 * certain fields. If no title is provided when building the group, the group is of type 334 * hidden. If a title is provided but no summary or stateless icon are provided when 335 * building the group, the group is of type stateless. Otherwise, the group is of type 336 * stateful. 337 */ 338 @NonNull 339 @RequiresApi(UPSIDE_DOWN_CAKE) setType(@afetySourceGroupType int type)340 public Builder setType(@SafetySourceGroupType int type) { 341 mType = type; 342 return this; 343 } 344 345 /** 346 * Sets the id of this safety sources group. 347 * 348 * <p>The id must be unique among safety sources groups in a Safety Center configuration. 349 */ 350 @NonNull setId(@ullable String id)351 public Builder setId(@Nullable String id) { 352 mId = id; 353 return this; 354 } 355 356 /** 357 * Sets the resource id of the title of this safety sources group. 358 * 359 * <p>The id must refer to a string resource that is either accessible from any resource 360 * context or that is accessible from the same resource context that was used to load the 361 * Safety Center configuration. The id defaults to {@link Resources#ID_NULL} when a title is 362 * not provided. A title is required unless the group only contains safety sources of type 363 * issue only. 364 */ 365 @NonNull setTitleResId(@tringRes int titleResId)366 public Builder setTitleResId(@StringRes int titleResId) { 367 mTitleResId = titleResId; 368 return this; 369 } 370 371 /** 372 * Sets the resource id of the summary of this safety sources group. 373 * 374 * <p>The id must refer to a string resource that is either accessible from any resource 375 * context or that is accessible from the same resource context that was used to load the 376 * Safety Center configuration. The id defaults to {@link Resources#ID_NULL} when a summary 377 * is not provided. 378 */ 379 @NonNull setSummaryResId(@tringRes int summaryResId)380 public Builder setSummaryResId(@StringRes int summaryResId) { 381 mSummaryResId = summaryResId; 382 return this; 383 } 384 385 /** 386 * Sets the stateless icon type of this safety sources group. 387 * 388 * <p>If set to a value other than {@link SafetySourcesGroup#STATELESS_ICON_TYPE_NONE}, the 389 * icon specified will be displayed for collapsible groups when all the sources contained in 390 * the group are stateless. 391 */ 392 @NonNull setStatelessIconType(@tatelessIconType int statelessIconType)393 public Builder setStatelessIconType(@StatelessIconType int statelessIconType) { 394 mStatelessIconType = statelessIconType; 395 return this; 396 } 397 398 /** 399 * Adds a {@link SafetySource} to this safety sources group. 400 * 401 * <p>A safety sources group must contain at least one {@link SafetySource}. 402 */ 403 @NonNull addSafetySource(@onNull SafetySource safetySource)404 public Builder addSafetySource(@NonNull SafetySource safetySource) { 405 mSafetySources.add(requireNonNull(safetySource)); 406 return this; 407 } 408 409 /** 410 * Creates the {@link SafetySourcesGroup} defined by this {@link Builder}. 411 * 412 * @throws IllegalStateException if any constraint on the safety sources group is violated 413 */ 414 @NonNull build()415 public SafetySourcesGroup build() { 416 String id = mId; 417 BuilderUtils.validateId(id, "id", true, false); 418 419 List<SafetySource> safetySources = unmodifiableList(new ArrayList<>(mSafetySources)); 420 if (safetySources.isEmpty()) { 421 throw new IllegalStateException("Safety sources group empty"); 422 } 423 424 int summaryResId = BuilderUtils.validateResId(mSummaryResId, "summary", false, false); 425 426 int statelessIconType = 427 BuilderUtils.validateIntDef( 428 mStatelessIconType, 429 "statelessIconType", 430 false, 431 false, 432 STATELESS_ICON_TYPE_NONE, 433 STATELESS_ICON_TYPE_NONE, 434 STATELESS_ICON_TYPE_PRIVACY); 435 436 boolean hasOnlyIssueOnlySources = true; 437 int safetySourcesSize = safetySources.size(); 438 for (int i = 0; i < safetySourcesSize; i++) { 439 int type = safetySources.get(i).getType(); 440 if (type != SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY) { 441 hasOnlyIssueOnlySources = false; 442 break; 443 } 444 } 445 446 int inferredGroupType = SAFETY_SOURCES_GROUP_TYPE_STATELESS; 447 if (hasOnlyIssueOnlySources) { 448 inferredGroupType = SAFETY_SOURCES_GROUP_TYPE_HIDDEN; 449 } else if (summaryResId != Resources.ID_NULL 450 || statelessIconType != STATELESS_ICON_TYPE_NONE) { 451 inferredGroupType = SAFETY_SOURCES_GROUP_TYPE_STATEFUL; 452 } 453 int type = 454 BuilderUtils.validateIntDef( 455 mType, 456 "type", 457 false, 458 false, 459 inferredGroupType, 460 SAFETY_SOURCES_GROUP_TYPE_STATEFUL, 461 SAFETY_SOURCES_GROUP_TYPE_STATELESS, 462 SAFETY_SOURCES_GROUP_TYPE_HIDDEN); 463 if (type == SAFETY_SOURCES_GROUP_TYPE_HIDDEN && !hasOnlyIssueOnlySources) { 464 throw new IllegalStateException( 465 "Safety sources groups of type hidden can only contain sources of type " 466 + "issue-only"); 467 } 468 if (type != SAFETY_SOURCES_GROUP_TYPE_HIDDEN && hasOnlyIssueOnlySources) { 469 throw new IllegalStateException( 470 "Safety sources groups containing only sources of type issue-only must be " 471 + "of type hidden"); 472 } 473 474 boolean isStateful = type == SAFETY_SOURCES_GROUP_TYPE_STATEFUL; 475 boolean isStateless = type == SAFETY_SOURCES_GROUP_TYPE_STATELESS; 476 int titleResId = 477 BuilderUtils.validateResId( 478 mTitleResId, "title", isStateful || isStateless, false); 479 480 return new SafetySourcesGroup( 481 type, id, titleResId, summaryResId, statelessIconType, safetySources); 482 } 483 } 484 } 485