[Angel] Add support for PendingIntents to SafetySourceData and address comments from ag/16182563
* Adds SafetySourceIssue.Action representing button actions to safety issue, and add PendingIntents for safety preferences.
* Rename
SafetyPreferenceData -> SafetySourceStatus, SafetyIssueData ->
SafetySourceIssue, SafetyPreferenceData.SeverityLevel -> SafetySourceStatus.StatusLevel.I
chose to keep the levels separate as their javadocs/guidelines for
choosing a level will be different.
* I haven't folded SafetyPreferenceData into SafetySourceData as the
SafetyPreferenceData will be @Nullable. UX has confirmed we want this
behaviour for sources - sources can provide issues without the
preference, but in this case the warnings will not be allowed to affect
the overall status.
* Refine javadocs for API
* Delete aidl files other than SafetySourceData as that's the only class
to be used as arguments in IPC
Test: Will add CTS tests when API is no longer hidden.
Bug: 206752239
Change-Id: I868bb324f14e2f4262b8796bccdde34f6aa9ba68
diff --git a/framework-s/java/android/safetycenter/SafetyIssueData.aidl b/framework-s/java/android/safetycenter/SafetyIssueData.aidl
deleted file mode 100644
index 9a33ded..0000000
--- a/framework-s/java/android/safetycenter/SafetyIssueData.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.safetycenter;
-
-/**
- * Parcelable AIDL SafetyIssueData.
- *
- * @hide
- */
-parcelable SafetyIssueData;
diff --git a/framework-s/java/android/safetycenter/SafetyIssueData.java b/framework-s/java/android/safetycenter/SafetyIssueData.java
deleted file mode 100644
index c115231..0000000
--- a/framework-s/java/android/safetycenter/SafetyIssueData.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.safetycenter;
-
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import androidx.annotation.RequiresApi;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-/**
- * Data for a safety issue in the Safety Center page. A safety issue represents an actionable issue
- * relating to a particular safety source.
- *
- * @hide
- */
-// @SystemApi -- Add this line back when ready for API council review.
-// TODO(b/205551986): Move this class into `framework-s`, add NonNull annotations, replace usages of
-// `androidx.annotation.IntDef` with `android.annotation.IntDef` and add prefixes to IntDefs.
-@RequiresApi(TIRAMISU)
-public final class SafetyIssueData implements Parcelable {
-
- /**
- * All possible severity levels for the safety issue.
- *
- * @hide
- */
- @IntDef(prefix = { "SEVERITY_LEVEL_" }, value = {
- SEVERITY_LEVEL_INFORMATION,
- SEVERITY_LEVEL_RECOMMENDATION,
- SEVERITY_LEVEL_CRITICAL_WARNING
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface SeverityLevel {}
-
- /**
- * Indicates an informational message. This severity will be reflected in the UI through a
- * green icon.
- */
- public static final int SEVERITY_LEVEL_INFORMATION = 200;
-
- /**
- * Indicates a medium-severity issue which the user is encouraged to act on. This severity will
- * be reflected in the UI through a yellow icon.
- */
- public static final int SEVERITY_LEVEL_RECOMMENDATION = 300;
-
- /**
- * Indicates a critical or urgent security issue that should be addressed by the user. This
- * severity will be reflected in the UI through a red icon.
- */
- public static final int SEVERITY_LEVEL_CRITICAL_WARNING = 400;
-
- @NonNull
- public static final Parcelable.Creator<SafetyIssueData> CREATOR =
- new Parcelable.Creator<SafetyIssueData>() {
- @Override
- public SafetyIssueData createFromParcel(Parcel in) {
- CharSequence title =
- requireNonNull(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
- CharSequence summary =
- requireNonNull(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
- int severityLevel = in.readInt();
- return new SafetyIssueData(title, summary, severityLevel);
- }
-
- @Override
- public SafetyIssueData[] newArray(int size) {
- return new SafetyIssueData[size];
- }
- };
-
- @NonNull
- private final CharSequence mTitle;
-
- @NonNull
- private final CharSequence mSummary;
-
- private final @SeverityLevel int mSeverityLevel;
-
- private SafetyIssueData(@NonNull CharSequence title, @NonNull CharSequence summary,
- @SeverityLevel int severityLevel) {
- this.mTitle = title;
- this.mSummary = summary;
- this.mSeverityLevel = severityLevel;
- }
-
- /** Returns the localized title of the safety issue to be displayed in the UI. */
- @NonNull
- public CharSequence getTitle() {
- return mTitle;
- }
-
- /** Returns the localized summary of the safety issue to be displayed in the UI. */
- @NonNull
- public CharSequence getSummary() {
- return mSummary;
- }
-
- /** Returns the {@link SeverityLevel} of the safety issue. */
- @SeverityLevel
- public int getSeverityLevel() {
- return mSeverityLevel;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- TextUtils.writeToParcel(mTitle, dest, flags);
- TextUtils.writeToParcel(mSummary, dest, flags);
- dest.writeInt(mSeverityLevel);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof SafetyIssueData)) return false;
- SafetyIssueData that = (SafetyIssueData) o;
- return mSeverityLevel == that.mSeverityLevel && mTitle.equals(that.mTitle)
- && mSummary.equals(
- that.mSummary);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mTitle, mSummary, mSeverityLevel);
- }
-
- @Override
- public String toString() {
- return "SafetyIssueData{"
- + "mTitle='"
- + mTitle
- + '\''
- + ", mSummary='"
- + mSummary
- + '\''
- + ", mSeverityLevel="
- + mSeverityLevel
- + '}';
- }
-
- /** Builder class for {@link SafetyIssueData}. */
- public static final class Builder {
- @NonNull
- private final CharSequence mTitle;
-
- @NonNull
- private final CharSequence mSummary;
-
- private @SeverityLevel final int mSeverityLevel;
-
- /** Creates a {@link Builder} for a {@link SafetyIssueData}. */
- public Builder(@NonNull CharSequence title, @NonNull CharSequence summary,
- @SeverityLevel int severityLevel) {
- this.mTitle = requireNonNull(title);
- this.mSummary = requireNonNull(summary);
- this.mSeverityLevel = severityLevel;
- }
-
- /** Creates the {@link SafetyIssueData} defined by this {@link Builder}. */
- @NonNull
- public SafetyIssueData build() {
- return new SafetyIssueData(mTitle, mSummary, mSeverityLevel);
- }
- }
-}
diff --git a/framework-s/java/android/safetycenter/SafetyPreferenceData.aidl b/framework-s/java/android/safetycenter/SafetyPreferenceData.aidl
deleted file mode 100644
index 36bcc19..0000000
--- a/framework-s/java/android/safetycenter/SafetyPreferenceData.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.safetycenter;
-
-/**
- * Parcelable AIDL SafetyPreferenceData.
- *
- * @hide
- */
-parcelable SafetyPreferenceData;
diff --git a/framework-s/java/android/safetycenter/SafetyPreferenceData.java b/framework-s/java/android/safetycenter/SafetyPreferenceData.java
deleted file mode 100644
index c97d3ca..0000000
--- a/framework-s/java/android/safetycenter/SafetyPreferenceData.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.safetycenter;
-
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import androidx.annotation.RequiresApi;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-/**
- * Data for a safety preference in the Safety Center page. A safety preference represents the
- * overall safety state of a safety source.
- *
- * @hide
- */
-// @SystemApi -- Add this line back when ready for API council review.
-// TODO(b/205551986): Move this class into `framework-s`, add NonNull annotations, replace usages of
-// `androidx.annotation.IntDef` with `android.annotation.IntDef` and add prefixes to IntDefs.
-@RequiresApi(TIRAMISU)
-public final class SafetyPreferenceData implements Parcelable {
-
- /**
- * All possible severity levels for the safety source's safety state.
- *
- * @hide
- */
- // TODO(b/205806500): Determine full list of severity levels. We may add a new one to signify
- // that there was an error retrieving data.
- @IntDef(prefix = { "SEVERITY_LEVEL_" }, value = {
- SEVERITY_LEVEL_NONE,
- SEVERITY_LEVEL_NO_ISSUES,
- SEVERITY_LEVEL_RECOMMENDATION,
- SEVERITY_LEVEL_CRITICAL_WARNING
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface SeverityLevel {}
-
- /**
- * Indicates that no severity is associated with the message. This severity will be reflected in
- * the UI through the absence of an icon.
- */
- public static final int SEVERITY_LEVEL_NONE = 100;
-
- /**
- * Indicates that no issues were detected. This severity will be reflected in the UI through a
- * green icon.
- */
- public static final int SEVERITY_LEVEL_NO_ISSUES = 200;
-
- /**
- * Indicates the presence of a medium-severity issue which the user is encouraged to act on.
- * This severity will be reflected in the UI through a yellow icon.
- */
- public static final int SEVERITY_LEVEL_RECOMMENDATION = 300;
-
- /**
- * Indicates the presence of a critical or urgent security issue that should be addressed by the
- * user. This severity will be reflected in the UI through a red icon.
- */
- public static final int SEVERITY_LEVEL_CRITICAL_WARNING = 400;
-
- @NonNull
- public static final Parcelable.Creator<SafetyPreferenceData> CREATOR =
- new Parcelable.Creator<SafetyPreferenceData>() {
- @Override
- public SafetyPreferenceData createFromParcel(Parcel in) {
- CharSequence title =
- requireNonNull(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
- CharSequence summary =
- requireNonNull(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
- @SeverityLevel int severityLevel = in.readInt();
- return new SafetyPreferenceData(title, summary, severityLevel);
- }
-
- @Override
- public SafetyPreferenceData[] newArray(int size) {
- return new SafetyPreferenceData[size];
- }
- };
-
- @NonNull
- private final CharSequence mTitle;
-
- @NonNull
- private final CharSequence mSummary;
-
- private final @SeverityLevel int mSeverityLevel;
-
- private SafetyPreferenceData(@NonNull CharSequence title, @NonNull CharSequence summary,
- @SeverityLevel int severityLevel) {
- this.mTitle = title;
- this.mSummary = summary;
- this.mSeverityLevel = severityLevel;
- }
-
- /** Returns the localized title of the safety preference to be displayed in the UI. */
- @NonNull
- public CharSequence getTitle() {
- return mTitle;
- }
-
- /** Returns the localized summary of the safety preference to be displayed in the UI. */
- @NonNull
- public CharSequence getSummary() {
- return mSummary;
- }
-
- /** Returns the {@link SeverityLevel} of the safety preference. */
- @SeverityLevel
- public int getSeverityLevel() {
- return mSeverityLevel;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- TextUtils.writeToParcel(mTitle, dest, flags);
- TextUtils.writeToParcel(mSummary, dest, flags);
- dest.writeInt(mSeverityLevel);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof SafetyPreferenceData)) return false;
- SafetyPreferenceData that = (SafetyPreferenceData) o;
- return mSeverityLevel == that.mSeverityLevel && mTitle.equals(that.mTitle)
- && mSummary.equals(
- that.mSummary);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mTitle, mSummary, mSeverityLevel);
- }
-
- @Override
- public String toString() {
- return "SafetyPreferenceData{"
- + "mTitle='"
- + mTitle
- + '\''
- + ", mSummary='"
- + mSummary
- + '\''
- + ", mSeverityLevel="
- + mSeverityLevel
- + '}';
- }
-
- /** Builder class for {@link SafetyPreferenceData}. */
- public static final class Builder {
- @NonNull
- private final CharSequence mTitle;
-
- @NonNull
- private final CharSequence mSummary;
-
- private @SeverityLevel final int mSeverityLevel;
-
- /** Creates a {@link Builder} for a {@link SafetyPreferenceData}. */
- public Builder(@NonNull CharSequence title, @NonNull CharSequence summary,
- @SeverityLevel int severityLevel) {
- this.mTitle = requireNonNull(title);
- this.mSummary = requireNonNull(summary);
- this.mSeverityLevel = severityLevel;
- }
-
- /** Creates the {@link SafetyPreferenceData} defined by this {@link Builder}. */
- @NonNull
- public SafetyPreferenceData build() {
- return new SafetyPreferenceData(mTitle, mSummary, mSeverityLevel);
- }
- }
-}
diff --git a/framework-s/java/android/safetycenter/SafetySourceData.java b/framework-s/java/android/safetycenter/SafetySourceData.java
index 7e1984a..b237330 100644
--- a/framework-s/java/android/safetycenter/SafetySourceData.java
+++ b/framework-s/java/android/safetycenter/SafetySourceData.java
@@ -32,16 +32,14 @@
import java.util.Objects;
/**
- * Data class used by safety sources to propagate safety information such as a generic status,
- * potential warnings and other related metadata.
+ * Data class used by safety sources to propagate safety information such as their safety status and
+ * safety issues.
*
* @hide
*/
// @SystemApi -- Add this line back when ready for API council review.
+// TODO(b/207399899): Add timestamp field(s) to data model classes.
@RequiresApi(TIRAMISU)
-// TODO(b/205551986): Move this to the right place and add back the NonNull annotations once
-// b/205289292 is fixed.
-// TODO(b/206089303): Add Builders as more fields are added to this class.
public final class SafetySourceData implements Parcelable {
@NonNull
@@ -49,14 +47,13 @@
new Parcelable.Creator<SafetySourceData>() {
@Override
public SafetySourceData createFromParcel(Parcel in) {
- String safetySourceId = requireNonNull(in.readString());
- SafetyPreferenceData safetyPreferenceData =
- requireNonNull(
- in.readParcelable(SafetyPreferenceData.class.getClassLoader()));
- List<SafetyIssueData> safetyIssuesData = new ArrayList<>();
- in.readParcelableList(safetyIssuesData, SafetyIssueData.class.getClassLoader());
- return new SafetySourceData(safetySourceId,
- safetyPreferenceData, safetyIssuesData);
+ String id = requireNonNull(in.readString());
+ SafetySourceStatus status =
+ in.readParcelable(SafetySourceStatus.class.getClassLoader(),
+ SafetySourceStatus.class);
+ List<SafetySourceIssue> issues = new ArrayList<>();
+ in.readParcelableList(issues, SafetySourceIssue.class.getClassLoader());
+ return new SafetySourceData(id, status, issues);
}
@Override
public SafetySourceData[] newArray(int size) {
@@ -65,38 +62,42 @@
};
@NonNull
- private final String mSafetySourceId;
+ private final String mId;
@Nullable
- private final SafetyPreferenceData mSafetyPreferenceData;
+ private final SafetySourceStatus mStatus;
@NonNull
- private final List<SafetyIssueData> mSafetyIssuesData;
+ private final List<SafetySourceIssue> mIssues;
- private SafetySourceData(@NonNull String safetySourceId,
- @Nullable SafetyPreferenceData safetyPreferenceData,
- @NonNull List<SafetyIssueData> safetyIssuesData) {
- this.mSafetySourceId = safetySourceId;
- this.mSafetyPreferenceData = safetyPreferenceData;
- this.mSafetyIssuesData = new ArrayList<>(safetyIssuesData);
+ private SafetySourceData(@NonNull String id, @Nullable SafetySourceStatus status,
+ @NonNull List<SafetySourceIssue> issues) {
+ this.mId = id;
+ this.mStatus = status;
+ this.mIssues = new ArrayList<>(issues);
}
- /** Returns the id of the associated safety source. */
+ /**
+ * Returns the id of the associated safety source.
+ *
+ * <p>The id uniquely identifies a safety source within the scope of the application that is
+ * creating the source.
+ */
@NonNull
- public String getSafetySourceId() {
- return mSafetySourceId;
+ public String getId() {
+ return mId;
}
- /** Returns the data for the safety preference to be shown in UI. */
+ /** Returns the data for the safety source status to be shown in UI. */
@Nullable
- public SafetyPreferenceData getSafetyPreferenceData() {
- return mSafetyPreferenceData;
+ public SafetySourceStatus getStatus() {
+ return mStatus;
}
- /** Returns the data for the list of safety issues to be shown in UI. */
+ /** Returns the data for the list of safety source issues to be shown in UI. */
@NonNull
- public List<SafetyIssueData> getSafetyIssuesData() {
- return new ArrayList<>(mSafetyIssuesData);
+ public List<SafetySourceIssue> getIssues() {
+ return new ArrayList<>(mIssues);
}
@Override
@@ -106,9 +107,9 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(mSafetySourceId);
- dest.writeParcelable(mSafetyPreferenceData, flags);
- dest.writeParcelableList(mSafetyIssuesData, flags);
+ dest.writeString(mId);
+ dest.writeParcelable(mStatus, flags);
+ dest.writeParcelableList(mIssues, flags);
}
@Override
@@ -116,26 +117,25 @@
if (this == o) return true;
if (!(o instanceof SafetySourceData)) return false;
SafetySourceData that = (SafetySourceData) o;
- return mSafetySourceId.equals(that.mSafetySourceId)
- && mSafetyPreferenceData.equals(that.mSafetyPreferenceData)
- && mSafetyIssuesData.equals(that.mSafetyIssuesData);
+ return mId.equals(that.mId) && Objects.equals(mStatus, that.mStatus)
+ && mIssues.equals(that.mIssues);
}
@Override
public int hashCode() {
- return Objects.hash(mSafetySourceId, mSafetyPreferenceData, mSafetyIssuesData);
+ return Objects.hash(mId, mStatus, mIssues);
}
@Override
public String toString() {
return "SafetySourceData{"
- + "mSafetySourceId='"
- + mSafetySourceId
+ + "mId='"
+ + mId
+ '\''
- + ", mSafetyPreferenceData="
- + mSafetyPreferenceData
- + ", mSafetyIssuesData="
- + mSafetyIssuesData
+ + ", mStatus="
+ + mStatus
+ + ", mIssues="
+ + mIssues
+ '}';
}
@@ -145,49 +145,49 @@
private final String mId;
@Nullable
- private SafetyPreferenceData mSafetyPreferenceData;
+ private SafetySourceStatus mStatus;
@NonNull
- private List<SafetyIssueData> mSafetyIssuesData = new ArrayList<>();
+ private final List<SafetySourceIssue> mIssues = new ArrayList<>();
- /**
- * Creates a {@link Builder} for a {@link SafetySourceData} using the id of the associated
- * safety source.
+ /** Creates a {@link Builder} for a {@link SafetySourceData}.
+ *
+ * @param id uniquely identifies this safety source, scoped within the
+ * application that is creating the safety source.
*/
public Builder(@NonNull String id) {
this.mId = requireNonNull(id);
}
- /** Sets data for the safety preference to be shown in UI. */
+ /** Sets data for the safety source status to be shown in UI. */
@NonNull
- public Builder setSafetyPreferenceData(
- @Nullable SafetyPreferenceData safetyPreferenceData) {
- mSafetyPreferenceData = safetyPreferenceData;
+ public Builder setStatus(@Nullable SafetySourceStatus status) {
+ mStatus = status;
return this;
}
- /** Adds data for a safety issue to be shown in UI. */
+ /** Adds data for a safety source issue to be shown in UI. */
@NonNull
- // @SuppressWarnings("MissingGetterMatchingBuilder")
- // The MissingGetterMatchingBuilder warning has been suppressed as it expects the
- // corresponding getter to be named `getSafetyIssueDatas()` which would be grammatically
- // incorrect.
- public Builder addSafetyIssueData(@NonNull SafetyIssueData safetyIssueData) {
- mSafetyIssuesData.add(requireNonNull(safetyIssueData));
+ public Builder addIssue(@NonNull SafetySourceIssue safetySourceIssue) {
+ mIssues.add(requireNonNull(safetySourceIssue));
return this;
}
- /** Clears data for all the safety issues that were added to this {@link Builder}. */
+ /**
+ * Clears data for all the safety source issues that were added to this {@link Builder}.
+ */
@NonNull
- public Builder clearSafetyIssuesData() {
- mSafetyIssuesData.clear();
+ public Builder clearIssues() {
+ mIssues.clear();
return this;
}
/** Creates the {@link SafetySourceData} defined by this {@link Builder}. */
@NonNull
public SafetySourceData build() {
- return new SafetySourceData(mId, mSafetyPreferenceData, mSafetyIssuesData);
+ // 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(mId, mStatus, mIssues);
}
}
}
diff --git a/framework-s/java/android/safetycenter/SafetySourceIssue.java b/framework-s/java/android/safetycenter/SafetySourceIssue.java
new file mode 100644
index 0000000..1800efc
--- /dev/null
+++ b/framework-s/java/android/safetycenter/SafetySourceIssue.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter;
+
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.PendingIntent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.RequiresApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Data for a safety source issue in the Safety Center page.
+ *
+ * <p>An issue represents an actionable matter relating to a particular safety source.
+ *
+ * <p>The safety issue will contain localized messages to be shown in UI explaining the potential
+ * threat or warning and suggested fixes, as well as actions a user is allowed to take from the UI
+ * to resolve the issue.
+ *
+ * @hide
+ */
+// @SystemApi -- Add this line back when ready for API council review.
+@RequiresApi(TIRAMISU)
+public final class SafetySourceIssue implements Parcelable {
+
+ /**
+ * All possible severity levels for the safety source issue.
+ *
+ * <p>The severity level is meant to convey the severity of the individual issue.
+ *
+ * <p>The higher the severity level, the worse the safety level of the source and the higher
+ * the threat to the user.
+ *
+ * <p>The numerical values of the levels are not used directly, rather they are used to build
+ * a continuum of levels which support relative comparison.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "SEVERITY_LEVEL_" }, value = {
+ SEVERITY_LEVEL_INFORMATION,
+ SEVERITY_LEVEL_RECOMMENDATION,
+ SEVERITY_LEVEL_CRITICAL_WARNING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SeverityLevel {}
+
+ /**
+ * Indicates an informational message. This severity will be reflected in the UI through a
+ * green icon.
+ */
+ public static final int SEVERITY_LEVEL_INFORMATION = 200;
+
+ /**
+ * Indicates a medium-severity issue which the user is encouraged to act on. This severity will
+ * be reflected in the UI through a yellow icon.
+ */
+ public static final int SEVERITY_LEVEL_RECOMMENDATION = 300;
+
+ /**
+ * Indicates a critical or urgent safety issue that should be addressed by the user. This
+ * severity will be reflected in the UI through a red icon.
+ */
+ public static final int SEVERITY_LEVEL_CRITICAL_WARNING = 400;
+
+ /**
+ * Data for an action supported from a safety issue {@link SafetySourceIssue} in the Safety
+ * Center page.
+ *
+ * <p>The purpose of the action is to allow the user to address the safety issue, either by
+ * performing a fix suggested in the issue, or by navigating the user to the source of the issue
+ * where they can be exposed to details about the issue and further suggestions to resolve it.
+ *
+ * <p>The user will be allowed to invoke the action from the UI by clicking on a UI element and
+ * consequently resolve the issue.
+ *
+ * @hide
+ */
+ // @SystemApi -- Add this line back when ready for API council review.
+ public static final class Action implements Parcelable {
+
+ @NonNull
+ public static final Parcelable.Creator<Action> CREATOR =
+ new Parcelable.Creator<Action>() {
+ @Override
+ public Action createFromParcel(Parcel in) {
+ CharSequence label =
+ requireNonNull(
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
+ PendingIntent clickPendingIntent =
+ requireNonNull(PendingIntent.readPendingIntentOrNullFromParcel(in));
+ boolean resolving = in.readBoolean();
+ return new Action(label, clickPendingIntent, resolving);
+ }
+
+ @Override
+ public Action[] newArray(int size) {
+ return new Action[size];
+ }
+ };
+
+ @NonNull
+ private final CharSequence mLabel;
+
+ @NonNull
+ private final PendingIntent mClickPendingIntent;
+
+ private final boolean mResolving;
+
+ private Action(@NonNull CharSequence label, @NonNull PendingIntent clickPendingIntent,
+ boolean resolving) {
+ this.mLabel = label;
+ this.mClickPendingIntent = clickPendingIntent;
+ this.mResolving = resolving;
+ }
+
+ /**
+ * Returns the localized label of the action to be displayed in the UI.
+ *
+ * <p>The label should indicate what action will be performed if when invoked.
+ */
+ @NonNull
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Returns a {@link PendingIntent} to be fired when the action is clicked on.
+ *
+ * <p>The {@link PendingIntent} should perform the action referred to by
+ * {@link #getLabel()}.
+ */
+ @NonNull
+ public PendingIntent getClickPendingIntent() {
+ return mClickPendingIntent;
+ }
+
+ /**
+ * Returns whether invoking this action will fix or address the issue sufficiently for it
+ * to be considered resolved i.e. the issue will no longer need to be conveyed to the user
+ * in the UI.
+ */
+ public boolean isResolving() {
+ return mResolving;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ TextUtils.writeToParcel(mLabel, dest, flags);
+ mClickPendingIntent.writeToParcel(dest, flags);
+ dest.writeBoolean(mResolving);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Action)) return false;
+ Action that = (Action) o;
+ return mResolving == that.mResolving
+ && TextUtils.equals(mLabel, that.mLabel)
+ && mClickPendingIntent.equals(that.mClickPendingIntent);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mLabel, mClickPendingIntent, mResolving);
+ }
+
+ @Override
+ public String toString() {
+ return "Action{"
+ + "mLabel="
+ + mLabel
+ + ", mClickPendingIntent="
+ + mClickPendingIntent
+ + ", mResolving="
+ + mResolving
+ + '}';
+ }
+
+ /** Builder class for {@link Action}. */
+ public static final class Builder {
+ @NonNull
+ private final CharSequence mLabel;
+
+ @NonNull
+ private final PendingIntent mClickPendingIntent;
+
+ private boolean mResolving = false;
+
+ /** Creates a {@link Builder} for an {@link Action}. */
+ public Builder(@NonNull CharSequence label, @NonNull PendingIntent clickPendingIntent) {
+ this.mLabel = requireNonNull(label);
+ this.mClickPendingIntent = requireNonNull(clickPendingIntent);
+ }
+
+ /**
+ * Sets whether the action will resolve the safety issue.
+ *
+ * @see #isResolving()
+ */
+ @NonNull
+ public Builder setResolving(boolean resolving) {
+ this.mResolving = resolving;
+ return this;
+ }
+
+ /** Creates the {@link Action} defined by this {@link Builder}. */
+ @NonNull
+ public Action build() {
+ return new Action(mLabel, mClickPendingIntent, mResolving);
+ }
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<SafetySourceIssue> CREATOR =
+ new Parcelable.Creator<SafetySourceIssue>() {
+ @Override
+ public SafetySourceIssue createFromParcel(Parcel in) {
+ CharSequence title =
+ requireNonNull(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
+ CharSequence summary =
+ requireNonNull(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
+ int severityLevel = in.readInt();
+ List<Action> actions = new ArrayList<>();
+ in.readParcelableList(actions, Action.class.getClassLoader());
+ return new SafetySourceIssue(title, summary, severityLevel, actions);
+ }
+
+ @Override
+ public SafetySourceIssue[] newArray(int size) {
+ return new SafetySourceIssue[size];
+ }
+ };
+
+ @NonNull
+ private final CharSequence mTitle;
+
+ @NonNull
+ private final CharSequence mSummary;
+
+ private final @SeverityLevel int mSeverityLevel;
+
+ @NonNull
+ private final List<Action> mActions;
+
+ private SafetySourceIssue(@NonNull CharSequence title, @NonNull CharSequence summary,
+ @SeverityLevel int severityLevel, @NonNull List<Action> actions) {
+ this.mTitle = title;
+ this.mSummary = summary;
+ this.mSeverityLevel = severityLevel;
+ this.mActions = actions;
+ }
+
+ /** Returns the localized title of the issue to be displayed in the UI. */
+ @NonNull
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /** Returns the localized summary of the issue to be displayed in the UI. */
+ @NonNull
+ public CharSequence getSummary() {
+ return mSummary;
+ }
+
+ /** Returns the {@link SeverityLevel} of the issue. */
+ @SeverityLevel
+ public int getSeverityLevel() {
+ return mSeverityLevel;
+ }
+
+ /**
+ * Returns a list of {@link Action} instances 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.
+ *
+ * <p>In Android {@link android.os.Build.VERSION_CODES#TIRAMISU}, each issue can contain at most
+ * two actions supported from the UI.
+ */
+ @NonNull
+ public List<Action> getActions() {
+ return new ArrayList<>(mActions);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ TextUtils.writeToParcel(mTitle, dest, flags);
+ TextUtils.writeToParcel(mSummary, dest, flags);
+ dest.writeInt(mSeverityLevel);
+ dest.writeParcelableList(mActions, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SafetySourceIssue)) return false;
+ SafetySourceIssue that = (SafetySourceIssue) o;
+ return mSeverityLevel == that.mSeverityLevel
+ && TextUtils.equals(mTitle, that.mTitle)
+ && TextUtils.equals(mSummary, that.mSummary)
+ && mActions.equals(that.mActions);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTitle, mSummary, mSeverityLevel, mActions);
+ }
+
+ @Override
+ public String toString() {
+ return "SafetySourceIssue{"
+ + "mTitle="
+ + mTitle
+ + ", mSummary="
+ + mSummary
+ + ", mSeverityLevel="
+ + mSeverityLevel
+ + ", mActions="
+ + mActions
+ + '}';
+ }
+
+ /** Builder class for {@link SafetySourceIssue}. */
+ public static final class Builder {
+ @NonNull
+ private final CharSequence mTitle;
+
+ @NonNull
+ private final CharSequence mSummary;
+
+ private @SeverityLevel final int mSeverityLevel;
+
+ @NonNull
+ private final List<Action> mActions = new ArrayList<>();
+
+ /** Creates a {@link Builder} for a {@link SafetySourceIssue}. */
+ public Builder(@NonNull CharSequence title, @NonNull CharSequence summary,
+ @SeverityLevel int severityLevel) {
+ this.mTitle = requireNonNull(title);
+ this.mSummary = requireNonNull(summary);
+ this.mSeverityLevel = severityLevel;
+ }
+
+ /** Adds data for an 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}. */
+ @NonNull
+ public Builder clearActions() {
+ mActions.clear();
+ return this;
+ }
+
+ /** Creates the {@link SafetySourceIssue} defined by this {@link Builder}. */
+ @NonNull
+ public SafetySourceIssue build() {
+ // TODO(b/207402324): Check with UX whether issues without any actions are permitted.
+ checkArgument(!mActions.isEmpty(),
+ "Safety source issue must contain at least 1 action");
+ checkArgument(mActions.size() <= 2,
+ "Safety source issue must not contain more than 2 actions");
+ return new SafetySourceIssue(mTitle, mSummary, mSeverityLevel, mActions);
+ }
+ }
+}
diff --git a/framework-s/java/android/safetycenter/SafetySourceStatus.java b/framework-s/java/android/safetycenter/SafetySourceStatus.java
new file mode 100644
index 0000000..687f518
--- /dev/null
+++ b/framework-s/java/android/safetycenter/SafetySourceStatus.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter;
+
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.PendingIntent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.RequiresApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Data for a safety source status in the Safety Center page, which conveys the overall state of
+ * the safety source and allows a user to navigate to the source.
+ *
+ * @hide
+ */
+// @SystemApi -- Add this line back when ready for API council review.
+@RequiresApi(TIRAMISU)
+public final class SafetySourceStatus implements Parcelable {
+
+ /**
+ * All possible status levels for the safety source status.
+ *
+ * <p>The status level is meant to convey the overall state of the safety source and contributes
+ * to the top-level safety status of the user. Choose the status level to represent the most
+ * severe of all the safety source's issues.
+ *
+ * <p>The numerical values of the levels are not used directly, rather they are used to build
+ * a continuum of levels which support relative comparison.
+ *
+ * <p>The higher the status level, the worse the safety level of the source and the higher
+ * the threat to the user.
+ *
+ * @hide
+ */
+ // TODO(b/205806500): Determine full list of status levels. We may add a new one to signify
+ // that there was an error retrieving data.
+ @IntDef(prefix = { "STATUS_LEVEL_" }, value = {
+ STATUS_LEVEL_NONE,
+ STATUS_LEVEL_NO_ISSUES,
+ STATUS_LEVEL_RECOMMENDATION,
+ STATUS_LEVEL_CRITICAL_WARNING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StatusLevel {}
+
+ /**
+ * Indicates that no status is associated with the information. This status will be reflected in
+ * the UI through the absence of an icon.
+ */
+ public static final int STATUS_LEVEL_NONE = 100;
+
+ /**
+ * Indicates that no issues were detected. This status will be reflected in the UI through a
+ * green icon.
+ */
+ public static final int STATUS_LEVEL_NO_ISSUES = 200;
+
+ /**
+ * Indicates the presence of a medium-status issue which the user is encouraged to act on.
+ * This status will be reflected in the UI through a yellow icon.
+ */
+ public static final int STATUS_LEVEL_RECOMMENDATION = 300;
+
+ /**
+ * Indicates the presence of a critical or urgent safety issue that should be addressed by the
+ * user. This status will be reflected in the UI through a red icon.
+ */
+ public static final int STATUS_LEVEL_CRITICAL_WARNING = 400;
+
+ @NonNull
+ public static final Parcelable.Creator<SafetySourceStatus> CREATOR =
+ 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 clickPendingIntent =
+ requireNonNull(PendingIntent.readPendingIntentOrNullFromParcel(in));
+ return new SafetySourceStatus(title, summary, statusLevel, clickPendingIntent);
+ }
+
+ @Override
+ public SafetySourceStatus[] newArray(int size) {
+ return new SafetySourceStatus[size];
+ }
+ };
+
+ @NonNull
+ private final CharSequence mTitle;
+
+ @NonNull
+ private final CharSequence mSummary;
+
+ private final @StatusLevel int mStatusLevel;
+
+ @NonNull final PendingIntent mClickPendingIntent;
+
+ private SafetySourceStatus(@NonNull CharSequence title, @NonNull CharSequence summary,
+ @StatusLevel int statusLevel, @NonNull PendingIntent clickPendingIntent) {
+ this.mTitle = title;
+ this.mSummary = summary;
+ this.mStatusLevel = statusLevel;
+ this.mClickPendingIntent = clickPendingIntent;
+ }
+
+ /** Returns the localized title of the safety source status to be displayed in the UI. */
+ @NonNull
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /** Returns the localized summary of the safety source status to be displayed in the UI. */
+ @NonNull
+ public CharSequence getSummary() {
+ return mSummary;
+ }
+
+ /** Returns the {@link StatusLevel} of the status. */
+ @StatusLevel
+ public int getStatusLevel() {
+ return mStatusLevel;
+ }
+
+ /**
+ * Returns the {@link PendingIntent} that will be invoked when the safety source status UI is
+ * clicked on.
+ */
+ @NonNull
+ public PendingIntent getClickPendingIntent() {
+ return mClickPendingIntent;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ TextUtils.writeToParcel(mTitle, dest, flags);
+ TextUtils.writeToParcel(mSummary, dest, flags);
+ dest.writeInt(mStatusLevel);
+ mClickPendingIntent.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SafetySourceStatus)) return false;
+ SafetySourceStatus that = (SafetySourceStatus) o;
+ return mStatusLevel == that.mStatusLevel
+ && TextUtils.equals(mTitle, that.mTitle)
+ && TextUtils.equals(mSummary, that.mSummary)
+ && mClickPendingIntent.equals(that.mClickPendingIntent);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTitle, mSummary, mStatusLevel, mClickPendingIntent);
+ }
+
+ @Override
+ public String toString() {
+ return "SafetySourceStatus{"
+ + "mTitle="
+ + mTitle
+ + ", mSummary="
+ + mSummary
+ + ", mStatusLevel="
+ + mStatusLevel
+ + ", mClickPendingIntent="
+ + mClickPendingIntent
+ + '}';
+ }
+
+ /** Builder class for {@link SafetySourceStatus}. */
+ public static final class Builder {
+ @NonNull
+ private final CharSequence mTitle;
+
+ @NonNull
+ private final CharSequence mSummary;
+
+ private @StatusLevel final int mStatusLevel;
+
+ @NonNull
+ private final PendingIntent mClickPendingIntent;
+
+ /** Creates a {@link Builder} for a {@link SafetySourceStatus}. */
+ public Builder(@NonNull CharSequence title, @NonNull CharSequence summary,
+ @StatusLevel int statusLevel, @NonNull PendingIntent clickPendingIntent) {
+ this.mTitle = requireNonNull(title);
+ this.mSummary = requireNonNull(summary);
+ this.mStatusLevel = statusLevel;
+ this.mClickPendingIntent = requireNonNull(clickPendingIntent);
+ }
+
+ /** Creates the {@link SafetySourceStatus} defined by this {@link Builder}. */
+ @NonNull
+ public SafetySourceStatus build() {
+ return new SafetySourceStatus(mTitle, mSummary, mStatusLevel, mClickPendingIntent);
+ }
+ }
+}