Prepare to validate incoming parcels using builders

* Change source and config parcels to be parsed using builders
* Change config parcels to use typed lists

Test: atest CtsSafetyCenterTestCases
Bug: 207329841
Bug: 222683944
Change-Id: I51e712141b07698bbbf85cf71e21f9d01c7db944
diff --git a/framework-s/java/android/safetycenter/SafetySourceData.java b/framework-s/java/android/safetycenter/SafetySourceData.java
index 954b279..70c0a81 100644
--- a/framework-s/java/android/safetycenter/SafetySourceData.java
+++ b/framework-s/java/android/safetycenter/SafetySourceData.java
@@ -29,6 +29,7 @@
 import androidx.annotation.RequiresApi;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -50,7 +51,12 @@
                     SafetySourceStatus status = in.readTypedObject(SafetySourceStatus.CREATOR);
                     List<SafetySourceIssue> issues =
                             in.createTypedArrayList(SafetySourceIssue.CREATOR);
-                    return new SafetySourceData(status, issues);
+                    Builder builder = new Builder().setStatus(status);
+                    // TODO(b/224513050): Consider simplifying by adding a new API to the builder.
+                    for (int i = 0; i < issues.size(); i++) {
+                        builder.addIssue(issues.get(i));
+                    }
+                    return builder.build();
                 }
 
                 @Override
@@ -70,13 +76,13 @@
         this.mIssues = new ArrayList<>(issues);
     }
 
-    /** Returns the data for the safety source status to be shown in UI. */
+    /** Returns the data for the {@link SafetySourceStatus} to be shown in UI. */
     @Nullable
     public SafetySourceStatus getStatus() {
         return mStatus;
     }
 
-    /** Returns the data for the list of safety source issues to be shown in UI. */
+    /** Returns the data for the list of {@link SafetySourceIssue}s to be shown in UI. */
     @NonNull
     public List<SafetySourceIssue> getIssues() {
         return new ArrayList<>(mIssues);
@@ -124,14 +130,14 @@
         @Nullable
         private SafetySourceStatus mStatus;
 
-        /** Sets data for the safety source status to be shown in UI. */
+        /** Sets data for the {@link SafetySourceStatus} to be shown in UI. */
         @NonNull
         public Builder setStatus(@Nullable SafetySourceStatus status) {
             mStatus = status;
             return this;
         }
 
-        /** Adds data for a safety source issue to be shown in UI. */
+        /** Adds data for a {@link SafetySourceIssue} to be shown in UI. */
         @NonNull
         public Builder addIssue(@NonNull SafetySourceIssue safetySourceIssue) {
             mIssues.add(requireNonNull(safetySourceIssue));
@@ -139,7 +145,8 @@
         }
 
         /**
-         * Clears data for all the safety source issues that were added to this {@link Builder}.
+         * Clears data for all the {@link SafetySourceIssue}s that were added to this
+         * {@link Builder}.
          */
         @NonNull
         public Builder clearIssues() {
@@ -152,7 +159,7 @@
         public SafetySourceData build() {
             // TODO(b/207329841): Validate data matches validation in S, for eg that the status
             //  and severity levels of the settings and issues are compatible.
-            return new SafetySourceData(mStatus, mIssues);
+            return new SafetySourceData(mStatus, Collections.unmodifiableList(mIssues));
         }
     }
 }
diff --git a/framework-s/java/android/safetycenter/SafetySourceIssue.java b/framework-s/java/android/safetycenter/SafetySourceIssue.java
index 950fc35..dc7d912 100644
--- a/framework-s/java/android/safetycenter/SafetySourceIssue.java
+++ b/framework-s/java/android/safetycenter/SafetySourceIssue.java
@@ -37,6 +37,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -112,8 +113,15 @@
                     PendingIntent onDismissPendingIntent =
                             PendingIntent.readPendingIntentOrNullFromParcel(in);
                     String issueTypeId = requireNonNull(in.readString());
-                    return new SafetySourceIssue(id, title, subtitle, summary, severityLevel,
-                            issueCategory, actions, onDismissPendingIntent, issueTypeId);
+                    Builder builder = new Builder(id, title, summary, severityLevel, issueTypeId)
+                            .setSubtitle(subtitle)
+                            .setIssueCategory(issueCategory)
+                            .setOnDismissPendingIntent(onDismissPendingIntent);
+                    // TODO(b/224513050): Consider simplifying by adding a new API to the builder.
+                    for (int i = 0; i < actions.size(); i++) {
+                        builder.addAction(actions.get(i));
+                    }
+                    return builder.build();
                 }
 
                 @Override
@@ -209,8 +217,7 @@
     }
 
     /**
-     * Returns a list of {@link Action} instances representing actions supported in the UI for this
-     * issue.
+     * Returns a list of {@link Action}s representing actions supported in the UI for this issue.
      *
      * <p>Each issue must contain at least one action, in order to help the user resolve the issue.
      *
@@ -383,16 +390,14 @@
                 new Parcelable.Creator<Action>() {
                     @Override
                     public Action createFromParcel(Parcel in) {
-                        String id = requireNonNull(in.readString());
-                        CharSequence label =
-                                requireNonNull(
-                                        TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
-                        PendingIntent pendingIntent =
-                                requireNonNull(PendingIntent.readPendingIntentOrNullFromParcel(in));
-                        boolean willResolve = in.readBoolean();
-                        CharSequence successMessage =
-                                TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-                        return new Action(id, label, pendingIntent, willResolve, successMessage);
+                        return new Builder(
+                                in.readString(),
+                                TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in),
+                                PendingIntent.readPendingIntentOrNullFromParcel(in))
+                                .setWillResolve(in.readBoolean())
+                                .setSuccessMessage(
+                                        TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in))
+                                .build();
                     }
 
                     @Override
@@ -618,14 +623,14 @@
             return this;
         }
 
-        /** Adds data for an action to be shown in UI. */
+        /** Adds data for an {@link Action} to be shown in UI. */
         @NonNull
         public Builder addAction(@NonNull Action actionData) {
             mActions.add(requireNonNull(actionData));
             return this;
         }
 
-        /** Clears data for all the actions that were added to this {@link Builder}. */
+        /** Clears data for all the {@link Action}s that were added to this {@link Builder}. */
         @NonNull
         public Builder clearActions() {
             mActions.clear();
@@ -656,7 +661,8 @@
             checkArgument(mActions.size() <= 2,
                     "Safety source issue must not contain more than 2 actions");
             return new SafetySourceIssue(mId, mTitle, mSubtitle, mSummary, mSeverityLevel,
-                    mIssueCategory, mActions, mOnDismissPendingIntent, mIssueTypeId);
+                    mIssueCategory, Collections.unmodifiableList(mActions), mOnDismissPendingIntent,
+                    mIssueTypeId);
         }
     }
 }
diff --git a/framework-s/java/android/safetycenter/SafetySourceStatus.java b/framework-s/java/android/safetycenter/SafetySourceStatus.java
index 83a5410..9868064 100644
--- a/framework-s/java/android/safetycenter/SafetySourceStatus.java
+++ b/framework-s/java/android/safetycenter/SafetySourceStatus.java
@@ -80,17 +80,14 @@
             new Parcelable.Creator<SafetySourceStatus>() {
                 @Override
                 public SafetySourceStatus createFromParcel(Parcel in) {
-                    CharSequence title =
-                            requireNonNull(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
-                    CharSequence summary =
-                            requireNonNull(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
-                    int statusLevel = in.readInt();
-                    PendingIntent pendingIntent =
-                            requireNonNull(PendingIntent.readPendingIntentOrNullFromParcel(in));
-                    IconAction iconAction = in.readTypedObject(IconAction.CREATOR);
-                    boolean enabled = in.readBoolean();
-                    return new SafetySourceStatus(title, summary, statusLevel, pendingIntent,
-                            iconAction, enabled);
+                    return new Builder(
+                            TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in),
+                            TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in),
+                            in.readInt(),
+                            PendingIntent.readPendingIntentOrNullFromParcel(in))
+                            .setIconAction(in.readTypedObject(IconAction.CREATOR))
+                            .setEnabled(in.readBoolean())
+                            .build();
                 }
 
                 @Override
@@ -150,7 +147,7 @@
     }
 
     /**
-     * Returns an optional icon action to be displayed in the safety source status UI.
+     * Returns an optional {@link IconAction} to be displayed in the safety source status UI.
      *
      * <p>The icon action will be a clickable icon which performs an action as indicated by the
      * icon.
@@ -387,7 +384,7 @@
         }
 
         /**
-         * Sets an optional icon action for the safety source status.
+         * Sets an optional {@link IconAction} for the safety source status.
          *
          * @see #getIconAction()
          */
diff --git a/framework-s/java/android/safetycenter/config/BuilderUtils.java b/framework-s/java/android/safetycenter/config/BuilderUtils.java
index b94f9fe..a01eb65 100644
--- a/framework-s/java/android/safetycenter/config/BuilderUtils.java
+++ b/framework-s/java/android/safetycenter/config/BuilderUtils.java
@@ -16,38 +16,50 @@
 
 package android.safetycenter.config;
 
-import android.annotation.IdRes;
+import android.annotation.AnyRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.Resources;
 
+import java.util.Objects;
+
 final class BuilderUtils {
     private BuilderUtils() {
     }
 
-    static void validateAttribute(@Nullable Object attribute, @NonNull String name,
-            boolean required, boolean prohibited) {
+    private static void validateAttribute(@Nullable Object attribute, @NonNull String name,
+            boolean required, boolean prohibited, @Nullable Object defaultValue) {
         if (attribute == null && required) {
             throw new IllegalStateException(String.format("Required attribute %s missing", name));
         }
-        if (attribute != null && prohibited) {
+        boolean nonDefaultValueProvided = !Objects.equals(attribute, defaultValue);
+        boolean checkProhibited = prohibited && nonDefaultValueProvided;
+        if (attribute != null && checkProhibited) {
             throw new IllegalStateException(String.format("Prohibited attribute %s present", name));
         }
     }
 
-    @IdRes
-    static int validateResId(@Nullable @IdRes Integer value, @NonNull String name, boolean required,
-            boolean prohibited) {
-        validateAttribute(value, name, required, prohibited);
+    static void validateAttribute(@Nullable Object attribute, @NonNull String name,
+            boolean required, boolean prohibited) {
+        validateAttribute(attribute, name, required, prohibited, null);
+    }
+
+    @AnyRes
+    static int validateResId(@Nullable @AnyRes Integer value, @NonNull String name,
+            boolean required, boolean prohibited) {
+        validateAttribute(value, name, required, prohibited, Integer.valueOf(Resources.ID_NULL));
         if (value == null) {
             return Resources.ID_NULL;
         }
+        if (required && value == Resources.ID_NULL) {
+            throw new IllegalStateException(String.format("Required attribute %s invalid", name));
+        }
         return value;
     }
 
     static int validateIntDef(@Nullable Integer value, @NonNull String name,
             boolean required, boolean prohibited, int defaultValue, int... validValues) {
-        validateAttribute(value, name, required, prohibited);
+        validateAttribute(value, name, required, prohibited, Integer.valueOf(defaultValue));
         if (value == null) {
             return defaultValue;
         }
@@ -64,7 +76,7 @@
 
     static int validateInteger(@Nullable Integer value, @NonNull String name,
             boolean required, boolean prohibited, int defaultValue) {
-        validateAttribute(value, name, required, prohibited);
+        validateAttribute(value, name, required, prohibited, Integer.valueOf(defaultValue));
         if (value == null) {
             return defaultValue;
         }
@@ -73,7 +85,7 @@
 
     static boolean validateBoolean(@Nullable Boolean value, @NonNull String name,
             boolean required, boolean prohibited, boolean defaultValue) {
-        validateAttribute(value, name, required, prohibited);
+        validateAttribute(value, name, required, prohibited, Boolean.valueOf(defaultValue));
         if (value == null) {
             return defaultValue;
         }
diff --git a/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java b/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java
index 9f74be0..6f2bb8d 100644
--- a/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java
+++ b/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java
@@ -50,7 +50,7 @@
         mSafetySourcesGroups = safetySourcesGroups;
     }
 
-    /** Returns the list of safety sources groups in the configuration. */
+    /** Returns the list of {@link SafetySourcesGroup}s in the configuration. */
     @NonNull
     public List<SafetySourcesGroup> getSafetySourcesGroups() {
         return mSafetySourcesGroups;
@@ -83,7 +83,7 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeParcelableList(mSafetySourcesGroups, flags);
+        dest.writeTypedList(mSafetySourcesGroups);
     }
 
     @NonNull
@@ -91,11 +91,14 @@
             new Parcelable.Creator<SafetyCenterConfig>() {
                 @Override
                 public SafetyCenterConfig createFromParcel(Parcel in) {
-                    List<SafetySourcesGroup> safetySourcesGroups = new ArrayList<>();
-                    in.readParcelableList(safetySourcesGroups,
-                            SafetySourcesGroup.class.getClassLoader());
-                    return new SafetyCenterConfig(
-                            Collections.unmodifiableList(safetySourcesGroups));
+                    List<SafetySourcesGroup> safetySourcesGroups =
+                            in.createTypedArrayList(SafetySourcesGroup.CREATOR);
+                    Builder builder = new Builder();
+                    // TODO(b/224513050): Consider simplifying by adding a new API to the builder.
+                    for (int i = 0; i < safetySourcesGroups.size(); i++) {
+                        builder.addSafetySourcesGroup(safetySourcesGroups.get(i));
+                    }
+                    return builder.build();
                 }
 
                 @Override
@@ -113,7 +116,7 @@
         public Builder() {
         }
 
-        /** Adds a safety source group to the configuration. */
+        /** Adds a {@link SafetySourcesGroup} to the configuration. */
         @NonNull
         public Builder addSafetySourcesGroup(@NonNull SafetySourcesGroup safetySourcesGroup) {
             mSafetySourcesGroups.add(requireNonNull(safetySourcesGroup));
diff --git a/framework-s/java/android/safetycenter/config/SafetySource.java b/framework-s/java/android/safetycenter/config/SafetySource.java
index 2ec6cfd..2ed7463 100644
--- a/framework-s/java/android/safetycenter/config/SafetySource.java
+++ b/framework-s/java/android/safetycenter/config/SafetySource.java
@@ -387,24 +387,21 @@
             new Parcelable.Creator<SafetySource>() {
                 @Override
                 public SafetySource createFromParcel(Parcel in) {
-                    int type = in.readInt();
-                    String id = in.readString();
-                    String packageName = in.readString();
-                    int titleResId = in.readInt();
-                    int titleForWorkResId = in.readInt();
-                    int summaryResId = in.readInt();
-                    String intentAction = in.readString();
-                    int profile = in.readInt();
-                    int initialDisplayState = in.readInt();
-                    int maxSeverityLevel = in.readInt();
-                    int searchTermsResId = in.readInt();
-                    String broadcastReceiverClassName = in.readString();
-                    boolean loggingAllowed = in.readBoolean();
-                    boolean refreshOnPageOpenAllowed = in.readBoolean();
-                    return new SafetySource(type, id, packageName, titleResId, titleForWorkResId,
-                            summaryResId, intentAction, profile, initialDisplayState,
-                            maxSeverityLevel, searchTermsResId, broadcastReceiverClassName,
-                            loggingAllowed, refreshOnPageOpenAllowed);
+                    return new Builder(in.readInt())
+                            .setId(in.readString())
+                            .setPackageName(in.readString())
+                            .setTitleResId(in.readInt())
+                            .setTitleForWorkResId(in.readInt())
+                            .setSummaryResId(in.readInt())
+                            .setIntentAction(in.readString())
+                            .setProfile(in.readInt())
+                            .setInitialDisplayState(in.readInt())
+                            .setMaxSeverityLevel(in.readInt())
+                            .setSearchTermsResId(in.readInt())
+                            .setBroadcastReceiverClassName(in.readString())
+                            .setLoggingAllowed(in.readBoolean())
+                            .setRefreshOnPageOpenAllowed(in.readBoolean())
+                            .build();
                 }
 
                 @Override
diff --git a/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java b/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java
index 84ce677..e76d9a6 100644
--- a/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java
+++ b/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java
@@ -163,7 +163,7 @@
         return mStatelessIconType;
     }
 
-    /** Returns the list of safety sources in this safety sources group. */
+    /** Returns the list of {@link SafetySource}s in this safety sources group. */
     @NonNull
     public List<SafetySource> getSafetySources() {
         return mSafetySources;
@@ -208,7 +208,7 @@
         dest.writeInt(mTitleResId);
         dest.writeInt(mSummaryResId);
         dest.writeInt(mStatelessIconType);
-        dest.writeParcelableList(mSafetySources, flags);
+        dest.writeTypedList(mSafetySources);
     }
 
     @NonNull
@@ -216,14 +216,18 @@
             new Parcelable.Creator<SafetySourcesGroup>() {
                 @Override
                 public SafetySourcesGroup createFromParcel(Parcel in) {
-                    String id = in.readString();
-                    int titleResId = in.readInt();
-                    int summaryResId = in.readInt();
-                    int statelessIconType = in.readInt();
-                    List<SafetySource> safetySources = new ArrayList<>();
-                    in.readParcelableList(safetySources, SafetySource.class.getClassLoader());
-                    return new SafetySourcesGroup(id, titleResId, summaryResId, statelessIconType,
-                            Collections.unmodifiableList(safetySources));
+                    Builder builder = new Builder()
+                            .setId(in.readString())
+                            .setTitleResId(in.readInt())
+                            .setSummaryResId(in.readInt())
+                            .setStatelessIconType(in.readInt());
+                    List<SafetySource> safetySources =
+                            in.createTypedArrayList(SafetySource.CREATOR);
+                    // TODO(b/224513050): Consider simplifying by adding a new API to the builder.
+                    for (int i = 0; i < safetySources.size(); i++) {
+                        builder.addSafetySource(safetySources.get(i));
+                    }
+                    return builder.build();
                 }
 
                 @Override
@@ -280,7 +284,7 @@
             return this;
         }
 
-        /** Adds a safety source to this safety sources group. */
+        /** Adds a {@link SafetySource} to this safety sources group. */
         @NonNull
         public Builder addSafetySource(@NonNull SafetySource safetySource) {
             mSafetySources.add(requireNonNull(safetySource));