930 lines
34 KiB
Java
930 lines
34 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2022 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 android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
|
||
|
|
||
|
import static java.util.Collections.unmodifiableList;
|
||
|
import static java.util.Objects.requireNonNull;
|
||
|
|
||
|
import android.annotation.IntDef;
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.annotation.SuppressLint;
|
||
|
import android.annotation.SystemApi;
|
||
|
import android.app.PendingIntent;
|
||
|
import android.os.Parcel;
|
||
|
import android.os.Parcelable;
|
||
|
import android.safetycenter.config.SafetySourcesGroup;
|
||
|
import android.text.TextUtils;
|
||
|
|
||
|
import androidx.annotation.RequiresApi;
|
||
|
|
||
|
import com.android.modules.utils.build.SdkLevel;
|
||
|
|
||
|
import java.lang.annotation.Retention;
|
||
|
import java.lang.annotation.RetentionPolicy;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.List;
|
||
|
import java.util.Objects;
|
||
|
|
||
|
/**
|
||
|
* An issue in the Safety Center.
|
||
|
*
|
||
|
* <p>An issue represents an actionable matter on the device of elevated importance.
|
||
|
*
|
||
|
* <p>It contains localized messages to display to the user, explaining the underlying threat or
|
||
|
* warning and suggested fixes, and contains actions that a user may take from the UI to resolve the
|
||
|
* issue.
|
||
|
*
|
||
|
* <p>Issues are ephemeral and disappear when resolved by user action or dismissal.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
@SystemApi
|
||
|
@RequiresApi(TIRAMISU)
|
||
|
public final class SafetyCenterIssue implements Parcelable {
|
||
|
|
||
|
/** Indicates that this is low-severity, and informational. */
|
||
|
public static final int ISSUE_SEVERITY_LEVEL_OK = 2100;
|
||
|
|
||
|
/** Indicates that this issue describes a safety recommendation. */
|
||
|
public static final int ISSUE_SEVERITY_LEVEL_RECOMMENDATION = 2200;
|
||
|
|
||
|
/** Indicates that this issue describes a critical safety warning. */
|
||
|
public static final int ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING = 2300;
|
||
|
|
||
|
/**
|
||
|
* All possible severity levels for a {@link SafetyCenterIssue}.
|
||
|
*
|
||
|
* @hide
|
||
|
* @see SafetyCenterIssue#getSeverityLevel()
|
||
|
* @see Builder#setSeverityLevel(int)
|
||
|
*/
|
||
|
@Retention(RetentionPolicy.SOURCE)
|
||
|
@IntDef(
|
||
|
prefix = "ISSUE_SEVERITY_LEVEL_",
|
||
|
value = {
|
||
|
ISSUE_SEVERITY_LEVEL_OK,
|
||
|
ISSUE_SEVERITY_LEVEL_RECOMMENDATION,
|
||
|
ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING,
|
||
|
})
|
||
|
public @interface IssueSeverityLevel {}
|
||
|
|
||
|
@NonNull
|
||
|
public static final Creator<SafetyCenterIssue> CREATOR =
|
||
|
new Creator<SafetyCenterIssue>() {
|
||
|
@Override
|
||
|
public SafetyCenterIssue createFromParcel(Parcel in) {
|
||
|
String id = in.readString();
|
||
|
CharSequence title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||
|
CharSequence subtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||
|
CharSequence summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||
|
SafetyCenterIssue.Builder builder =
|
||
|
new Builder(id, title, summary)
|
||
|
.setSubtitle(subtitle)
|
||
|
.setSeverityLevel(in.readInt())
|
||
|
.setDismissible(in.readBoolean())
|
||
|
.setShouldConfirmDismissal(in.readBoolean())
|
||
|
.setActions(in.createTypedArrayList(Action.CREATOR));
|
||
|
if (SdkLevel.isAtLeastU()) {
|
||
|
builder.setAttributionTitle(
|
||
|
TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
|
||
|
builder.setGroupId(in.readString());
|
||
|
}
|
||
|
return builder.build();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public SafetyCenterIssue[] newArray(int size) {
|
||
|
return new SafetyCenterIssue[size];
|
||
|
}
|
||
|
};
|
||
|
|
||
|
@NonNull private final String mId;
|
||
|
@NonNull private final CharSequence mTitle;
|
||
|
@Nullable private final CharSequence mSubtitle;
|
||
|
@NonNull private final CharSequence mSummary;
|
||
|
@IssueSeverityLevel private final int mSeverityLevel;
|
||
|
private final boolean mDismissible;
|
||
|
private final boolean mShouldConfirmDismissal;
|
||
|
@NonNull private final List<Action> mActions;
|
||
|
@Nullable private final CharSequence mAttributionTitle;
|
||
|
@Nullable private final String mGroupId;
|
||
|
|
||
|
private SafetyCenterIssue(
|
||
|
@NonNull String id,
|
||
|
@NonNull CharSequence title,
|
||
|
@Nullable CharSequence subtitle,
|
||
|
@NonNull CharSequence summary,
|
||
|
@IssueSeverityLevel int severityLevel,
|
||
|
boolean isDismissible,
|
||
|
boolean shouldConfirmDismissal,
|
||
|
@NonNull List<Action> actions,
|
||
|
@Nullable CharSequence attributionTitle,
|
||
|
@Nullable String groupId) {
|
||
|
mId = id;
|
||
|
mTitle = title;
|
||
|
mSubtitle = subtitle;
|
||
|
mSummary = summary;
|
||
|
mSeverityLevel = severityLevel;
|
||
|
mDismissible = isDismissible;
|
||
|
mShouldConfirmDismissal = shouldConfirmDismissal;
|
||
|
mActions = actions;
|
||
|
mAttributionTitle = attributionTitle;
|
||
|
mGroupId = groupId;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the encoded string ID which uniquely identifies this issue within the Safety Center
|
||
|
* on the device for the current user across all profiles and accounts.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public String getId() {
|
||
|
return mId;
|
||
|
}
|
||
|
|
||
|
/** Returns the title that describes this issue. */
|
||
|
@NonNull
|
||
|
public CharSequence getTitle() {
|
||
|
return mTitle;
|
||
|
}
|
||
|
|
||
|
/** Returns the subtitle of this issue, or {@code null} if it has none. */
|
||
|
@Nullable
|
||
|
public CharSequence getSubtitle() {
|
||
|
return mSubtitle;
|
||
|
}
|
||
|
|
||
|
/** Returns the summary text that describes this issue. */
|
||
|
@NonNull
|
||
|
public CharSequence getSummary() {
|
||
|
return mSummary;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the attribution title of this issue, or {@code null} if it has none.
|
||
|
*
|
||
|
* <p>This is displayed in the UI and helps to attribute issue cards to a particular source.
|
||
|
*
|
||
|
* @throws UnsupportedOperationException if accessed from a version lower than {@link
|
||
|
* UPSIDE_DOWN_CAKE}
|
||
|
*/
|
||
|
@Nullable
|
||
|
@RequiresApi(UPSIDE_DOWN_CAKE)
|
||
|
public CharSequence getAttributionTitle() {
|
||
|
if (!SdkLevel.isAtLeastU()) {
|
||
|
throw new UnsupportedOperationException(
|
||
|
"Method not supported on versions lower than UPSIDE_DOWN_CAKE");
|
||
|
}
|
||
|
return mAttributionTitle;
|
||
|
}
|
||
|
|
||
|
/** Returns the {@link IssueSeverityLevel} of this issue. */
|
||
|
@IssueSeverityLevel
|
||
|
public int getSeverityLevel() {
|
||
|
return mSeverityLevel;
|
||
|
}
|
||
|
|
||
|
/** Returns {@code true} if this issue can be dismissed. */
|
||
|
public boolean isDismissible() {
|
||
|
return mDismissible;
|
||
|
}
|
||
|
|
||
|
/** Returns {@code true} if this issue should have its dismissal confirmed. */
|
||
|
public boolean shouldConfirmDismissal() {
|
||
|
return mShouldConfirmDismissal;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the ordered list of {@link Action} objects that may be taken to resolve this issue.
|
||
|
*
|
||
|
* <p>An issue may have 0-2 actions. The first action will be considered the "Primary" action of
|
||
|
* the issue.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public List<Action> getActions() {
|
||
|
return mActions;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the ID of the {@link SafetySourcesGroup} that this issue belongs to, or {@code null}
|
||
|
* if it has none.
|
||
|
*
|
||
|
* <p>This ID is used for displaying the issue on its corresponding subpage in the Safety Center
|
||
|
* UI.
|
||
|
*
|
||
|
* @throws UnsupportedOperationException if accessed from a version lower than {@link
|
||
|
* UPSIDE_DOWN_CAKE}
|
||
|
*/
|
||
|
@Nullable
|
||
|
@RequiresApi(UPSIDE_DOWN_CAKE)
|
||
|
public String getGroupId() {
|
||
|
if (!SdkLevel.isAtLeastU()) {
|
||
|
throw new UnsupportedOperationException(
|
||
|
"Method not supported on versions lower than UPSIDE_DOWN_CAKE");
|
||
|
}
|
||
|
return mGroupId;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean equals(Object o) {
|
||
|
if (this == o) return true;
|
||
|
if (!(o instanceof SafetyCenterIssue)) return false;
|
||
|
SafetyCenterIssue that = (SafetyCenterIssue) o;
|
||
|
return mSeverityLevel == that.mSeverityLevel
|
||
|
&& mDismissible == that.mDismissible
|
||
|
&& mShouldConfirmDismissal == that.mShouldConfirmDismissal
|
||
|
&& Objects.equals(mId, that.mId)
|
||
|
&& TextUtils.equals(mTitle, that.mTitle)
|
||
|
&& TextUtils.equals(mSubtitle, that.mSubtitle)
|
||
|
&& TextUtils.equals(mSummary, that.mSummary)
|
||
|
&& Objects.equals(mActions, that.mActions)
|
||
|
&& TextUtils.equals(mAttributionTitle, that.mAttributionTitle)
|
||
|
&& Objects.equals(mGroupId, that.mGroupId);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int hashCode() {
|
||
|
return Objects.hash(
|
||
|
mId,
|
||
|
mTitle,
|
||
|
mSubtitle,
|
||
|
mSummary,
|
||
|
mSeverityLevel,
|
||
|
mDismissible,
|
||
|
mShouldConfirmDismissal,
|
||
|
mActions,
|
||
|
mAttributionTitle,
|
||
|
mGroupId);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String toString() {
|
||
|
return "SafetyCenterIssue{"
|
||
|
+ "mId="
|
||
|
+ mId
|
||
|
+ ", mTitle="
|
||
|
+ mTitle
|
||
|
+ ", mSubtitle="
|
||
|
+ mSubtitle
|
||
|
+ ", mSummary="
|
||
|
+ mSummary
|
||
|
+ ", mSeverityLevel="
|
||
|
+ mSeverityLevel
|
||
|
+ ", mDismissible="
|
||
|
+ mDismissible
|
||
|
+ ", mConfirmDismissal="
|
||
|
+ mShouldConfirmDismissal
|
||
|
+ ", mActions="
|
||
|
+ mActions
|
||
|
+ ", mAttributionTitle="
|
||
|
+ mAttributionTitle
|
||
|
+ ", mGroupId="
|
||
|
+ mGroupId
|
||
|
+ '}';
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int describeContents() {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||
|
dest.writeString(mId);
|
||
|
TextUtils.writeToParcel(mTitle, dest, flags);
|
||
|
TextUtils.writeToParcel(mSubtitle, dest, flags);
|
||
|
TextUtils.writeToParcel(mSummary, dest, flags);
|
||
|
dest.writeInt(mSeverityLevel);
|
||
|
dest.writeBoolean(mDismissible);
|
||
|
dest.writeBoolean(mShouldConfirmDismissal);
|
||
|
dest.writeTypedList(mActions);
|
||
|
if (SdkLevel.isAtLeastU()) {
|
||
|
TextUtils.writeToParcel(mAttributionTitle, dest, flags);
|
||
|
dest.writeString(mGroupId);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Builder class for {@link SafetyCenterIssue}. */
|
||
|
public static final class Builder {
|
||
|
|
||
|
@NonNull private String mId;
|
||
|
@NonNull private CharSequence mTitle;
|
||
|
@NonNull private CharSequence mSummary;
|
||
|
@Nullable private CharSequence mSubtitle;
|
||
|
@IssueSeverityLevel private int mSeverityLevel = ISSUE_SEVERITY_LEVEL_OK;
|
||
|
private boolean mDismissible = true;
|
||
|
private boolean mShouldConfirmDismissal = true;
|
||
|
private List<Action> mActions = new ArrayList<>();
|
||
|
@Nullable private CharSequence mAttributionTitle;
|
||
|
@Nullable private String mGroupId;
|
||
|
|
||
|
/**
|
||
|
* Creates a {@link Builder} for a {@link SafetyCenterIssue}.
|
||
|
*
|
||
|
* @param id a unique encoded string ID, see {@link #getId()} for details
|
||
|
* @param title a title that describes this issue
|
||
|
* @param summary a summary of this issue
|
||
|
*/
|
||
|
public Builder(
|
||
|
@NonNull String id, @NonNull CharSequence title, @NonNull CharSequence summary) {
|
||
|
mId = requireNonNull(id);
|
||
|
mTitle = requireNonNull(title);
|
||
|
mSummary = requireNonNull(summary);
|
||
|
}
|
||
|
|
||
|
/** Creates a {@link Builder} with the values from the given {@link SafetyCenterIssue}. */
|
||
|
public Builder(@NonNull SafetyCenterIssue issue) {
|
||
|
mId = issue.mId;
|
||
|
mTitle = issue.mTitle;
|
||
|
mSubtitle = issue.mSubtitle;
|
||
|
mSummary = issue.mSummary;
|
||
|
mSeverityLevel = issue.mSeverityLevel;
|
||
|
mDismissible = issue.mDismissible;
|
||
|
mShouldConfirmDismissal = issue.mShouldConfirmDismissal;
|
||
|
mActions = new ArrayList<>(issue.mActions);
|
||
|
mAttributionTitle = issue.mAttributionTitle;
|
||
|
mGroupId = issue.mGroupId;
|
||
|
}
|
||
|
|
||
|
/** Sets the ID for this issue. */
|
||
|
@NonNull
|
||
|
public Builder setId(@NonNull String id) {
|
||
|
mId = requireNonNull(id);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Sets the title for this issue. */
|
||
|
@NonNull
|
||
|
public Builder setTitle(@NonNull CharSequence title) {
|
||
|
mTitle = requireNonNull(title);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Sets or clears the optional subtitle for this issue. */
|
||
|
@NonNull
|
||
|
public Builder setSubtitle(@Nullable CharSequence subtitle) {
|
||
|
mSubtitle = subtitle;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Sets the summary for this issue. */
|
||
|
@NonNull
|
||
|
public Builder setSummary(@NonNull CharSequence summary) {
|
||
|
mSummary = requireNonNull(summary);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets or clears the optional attribution title for this issue.
|
||
|
*
|
||
|
* <p>This is displayed in the UI and helps to attribute issue cards to a particular source.
|
||
|
*
|
||
|
* @throws UnsupportedOperationException if accessed from a version lower than {@link
|
||
|
* UPSIDE_DOWN_CAKE}
|
||
|
*/
|
||
|
@NonNull
|
||
|
@RequiresApi(UPSIDE_DOWN_CAKE)
|
||
|
public Builder setAttributionTitle(@Nullable CharSequence attributionTitle) {
|
||
|
if (!SdkLevel.isAtLeastU()) {
|
||
|
throw new UnsupportedOperationException(
|
||
|
"Method not supported on versions lower than UPSIDE_DOWN_CAKE");
|
||
|
}
|
||
|
mAttributionTitle = attributionTitle;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets {@link IssueSeverityLevel} for this issue. Defaults to {@link
|
||
|
* #ISSUE_SEVERITY_LEVEL_OK}.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public Builder setSeverityLevel(@IssueSeverityLevel int severityLevel) {
|
||
|
mSeverityLevel = validateIssueSeverityLevel(severityLevel);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Sets whether this issue can be dismissed. Defaults to {@code true}. */
|
||
|
@NonNull
|
||
|
public Builder setDismissible(boolean dismissible) {
|
||
|
mDismissible = dismissible;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets whether this issue should have its dismissal confirmed. Defaults to {@code true}.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public Builder setShouldConfirmDismissal(boolean confirmDismissal) {
|
||
|
mShouldConfirmDismissal = confirmDismissal;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the list of potential actions to be taken to resolve this issue. Defaults to an
|
||
|
* empty list.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public Builder setActions(@NonNull List<Action> actions) {
|
||
|
mActions = requireNonNull(actions);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the ID of {@link SafetySourcesGroup} that this issue belongs to. Defaults to a
|
||
|
* {@code null} value.
|
||
|
*
|
||
|
* <p>This ID is used for displaying the issue on its corresponding subpage in the Safety
|
||
|
* Center UI.
|
||
|
*
|
||
|
* @throws UnsupportedOperationException if accessed from a version lower than {@link
|
||
|
* UPSIDE_DOWN_CAKE}
|
||
|
*/
|
||
|
@NonNull
|
||
|
@RequiresApi(UPSIDE_DOWN_CAKE)
|
||
|
public Builder setGroupId(@Nullable String groupId) {
|
||
|
if (!SdkLevel.isAtLeastU()) {
|
||
|
throw new UnsupportedOperationException(
|
||
|
"Method not supported on versions lower than UPSIDE_DOWN_CAKE");
|
||
|
}
|
||
|
mGroupId = groupId;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Creates the {@link SafetyCenterIssue} defined by this {@link Builder}. */
|
||
|
@NonNull
|
||
|
public SafetyCenterIssue build() {
|
||
|
return new SafetyCenterIssue(
|
||
|
mId,
|
||
|
mTitle,
|
||
|
mSubtitle,
|
||
|
mSummary,
|
||
|
mSeverityLevel,
|
||
|
mDismissible,
|
||
|
mShouldConfirmDismissal,
|
||
|
unmodifiableList(new ArrayList<>(mActions)),
|
||
|
mAttributionTitle,
|
||
|
mGroupId);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* An action that can be taken to resolve a given issue.
|
||
|
*
|
||
|
* <p>When a user initiates an {@link Action}, that action's associated {@link PendingIntent}
|
||
|
* will be executed, and the {@code successMessage} will be displayed if present.
|
||
|
*/
|
||
|
public static final class Action implements Parcelable {
|
||
|
|
||
|
@NonNull
|
||
|
public static final Creator<Action> CREATOR =
|
||
|
new Creator<Action>() {
|
||
|
@Override
|
||
|
public Action createFromParcel(Parcel in) {
|
||
|
String id = in.readString();
|
||
|
CharSequence label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||
|
PendingIntent pendingIntent = in.readTypedObject(PendingIntent.CREATOR);
|
||
|
Builder builder =
|
||
|
new Builder(id, label, pendingIntent)
|
||
|
.setWillResolve(in.readBoolean())
|
||
|
.setIsInFlight(in.readBoolean())
|
||
|
.setSuccessMessage(
|
||
|
TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(
|
||
|
in));
|
||
|
if (SdkLevel.isAtLeastU()) {
|
||
|
ConfirmationDialogDetails confirmationDialogDetails =
|
||
|
in.readTypedObject(ConfirmationDialogDetails.CREATOR);
|
||
|
builder.setConfirmationDialogDetails(confirmationDialogDetails);
|
||
|
}
|
||
|
return builder.build();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Action[] newArray(int size) {
|
||
|
return new Action[size];
|
||
|
}
|
||
|
};
|
||
|
|
||
|
@NonNull private final String mId;
|
||
|
@NonNull private final CharSequence mLabel;
|
||
|
@NonNull private final PendingIntent mPendingIntent;
|
||
|
private final boolean mWillResolve;
|
||
|
private final boolean mInFlight;
|
||
|
@Nullable private final CharSequence mSuccessMessage;
|
||
|
@Nullable private final ConfirmationDialogDetails mConfirmationDialogDetails;
|
||
|
|
||
|
private Action(
|
||
|
@NonNull String id,
|
||
|
@NonNull CharSequence label,
|
||
|
@NonNull PendingIntent pendingIntent,
|
||
|
boolean willResolve,
|
||
|
boolean inFlight,
|
||
|
@Nullable CharSequence successMessage,
|
||
|
@Nullable ConfirmationDialogDetails confirmationDialogDetails) {
|
||
|
mId = id;
|
||
|
mLabel = label;
|
||
|
mPendingIntent = pendingIntent;
|
||
|
mWillResolve = willResolve;
|
||
|
mInFlight = inFlight;
|
||
|
mSuccessMessage = successMessage;
|
||
|
mConfirmationDialogDetails = confirmationDialogDetails;
|
||
|
}
|
||
|
|
||
|
/** Returns the ID of this action. */
|
||
|
@NonNull
|
||
|
public String getId() {
|
||
|
return mId;
|
||
|
}
|
||
|
|
||
|
/** Returns a label describing this {@link Action}. */
|
||
|
@NonNull
|
||
|
public CharSequence getLabel() {
|
||
|
return mLabel;
|
||
|
}
|
||
|
|
||
|
/** Returns the {@link PendingIntent} to execute when this {@link Action} is taken. */
|
||
|
@NonNull
|
||
|
public PendingIntent getPendingIntent() {
|
||
|
return mPendingIntent;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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 willResolve() {
|
||
|
return mWillResolve;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns whether this action is currently being executed (i.e. the user clicked on a
|
||
|
* button that triggered this action, and now the Safety Center is waiting for the action's
|
||
|
* result).
|
||
|
*/
|
||
|
public boolean isInFlight() {
|
||
|
return mInFlight;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the success message to display after successfully completing this {@link Action}
|
||
|
* or {@code null} if none should be displayed.
|
||
|
*/
|
||
|
@Nullable
|
||
|
public CharSequence getSuccessMessage() {
|
||
|
return mSuccessMessage;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the optional data to be displayed in the confirmation dialog prior to launching
|
||
|
* the {@link PendingIntent} when the action is clicked on.
|
||
|
*/
|
||
|
@Nullable
|
||
|
@RequiresApi(UPSIDE_DOWN_CAKE)
|
||
|
public ConfirmationDialogDetails getConfirmationDialogDetails() {
|
||
|
if (!SdkLevel.isAtLeastU()) {
|
||
|
throw new UnsupportedOperationException(
|
||
|
"Method not supported on versions lower than UPSIDE_DOWN_CAKE");
|
||
|
}
|
||
|
return mConfirmationDialogDetails;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean equals(Object o) {
|
||
|
if (this == o) return true;
|
||
|
if (!(o instanceof Action)) return false;
|
||
|
Action action = (Action) o;
|
||
|
return Objects.equals(mId, action.mId)
|
||
|
&& TextUtils.equals(mLabel, action.mLabel)
|
||
|
&& Objects.equals(mPendingIntent, action.mPendingIntent)
|
||
|
&& mWillResolve == action.mWillResolve
|
||
|
&& mInFlight == action.mInFlight
|
||
|
&& TextUtils.equals(mSuccessMessage, action.mSuccessMessage)
|
||
|
&& Objects.equals(
|
||
|
mConfirmationDialogDetails, action.mConfirmationDialogDetails);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int hashCode() {
|
||
|
return Objects.hash(
|
||
|
mId,
|
||
|
mLabel,
|
||
|
mSuccessMessage,
|
||
|
mWillResolve,
|
||
|
mInFlight,
|
||
|
mPendingIntent,
|
||
|
mConfirmationDialogDetails);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String toString() {
|
||
|
return "Action{"
|
||
|
+ "mId="
|
||
|
+ mId
|
||
|
+ ", mLabel="
|
||
|
+ mLabel
|
||
|
+ ", mPendingIntent="
|
||
|
+ mPendingIntent
|
||
|
+ ", mWillResolve="
|
||
|
+ mWillResolve
|
||
|
+ ", mInFlight="
|
||
|
+ mInFlight
|
||
|
+ ", mSuccessMessage="
|
||
|
+ mSuccessMessage
|
||
|
+ ", mConfirmationDialogDetails="
|
||
|
+ mConfirmationDialogDetails
|
||
|
+ '}';
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int describeContents() {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||
|
dest.writeString(mId);
|
||
|
TextUtils.writeToParcel(mLabel, dest, flags);
|
||
|
dest.writeTypedObject(mPendingIntent, flags);
|
||
|
dest.writeBoolean(mWillResolve);
|
||
|
dest.writeBoolean(mInFlight);
|
||
|
TextUtils.writeToParcel(mSuccessMessage, dest, flags);
|
||
|
if (SdkLevel.isAtLeastU()) {
|
||
|
dest.writeTypedObject(mConfirmationDialogDetails, flags);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Data for an action confirmation dialog to be shown before action is executed. */
|
||
|
@RequiresApi(UPSIDE_DOWN_CAKE)
|
||
|
public static final class ConfirmationDialogDetails implements Parcelable {
|
||
|
|
||
|
@NonNull
|
||
|
public static final Creator<ConfirmationDialogDetails> CREATOR =
|
||
|
new Creator<ConfirmationDialogDetails>() {
|
||
|
@Override
|
||
|
public ConfirmationDialogDetails createFromParcel(Parcel in) {
|
||
|
CharSequence title =
|
||
|
TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||
|
CharSequence text =
|
||
|
TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||
|
CharSequence acceptButtonText =
|
||
|
TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||
|
CharSequence denyButtonText =
|
||
|
TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||
|
return new ConfirmationDialogDetails(
|
||
|
title, text, acceptButtonText, denyButtonText);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ConfirmationDialogDetails[] newArray(int size) {
|
||
|
return new ConfirmationDialogDetails[size];
|
||
|
}
|
||
|
};
|
||
|
|
||
|
@NonNull private final CharSequence mTitle;
|
||
|
@NonNull private final CharSequence mText;
|
||
|
@NonNull private final CharSequence mAcceptButtonText;
|
||
|
@NonNull private final CharSequence mDenyButtonText;
|
||
|
|
||
|
public ConfirmationDialogDetails(
|
||
|
@NonNull CharSequence title,
|
||
|
@NonNull CharSequence text,
|
||
|
@NonNull CharSequence acceptButtonText,
|
||
|
@NonNull CharSequence denyButtonText) {
|
||
|
mTitle = requireNonNull(title);
|
||
|
mText = requireNonNull(text);
|
||
|
mAcceptButtonText = requireNonNull(acceptButtonText);
|
||
|
mDenyButtonText = requireNonNull(denyButtonText);
|
||
|
}
|
||
|
|
||
|
/** Returns the title of action confirmation dialog. */
|
||
|
@NonNull
|
||
|
public CharSequence getTitle() {
|
||
|
return mTitle;
|
||
|
}
|
||
|
|
||
|
/** Returns the text of action confirmation dialog. */
|
||
|
@NonNull
|
||
|
public CharSequence getText() {
|
||
|
return mText;
|
||
|
}
|
||
|
|
||
|
/** Returns the text of the button to accept action execution. */
|
||
|
@NonNull
|
||
|
public CharSequence getAcceptButtonText() {
|
||
|
return mAcceptButtonText;
|
||
|
}
|
||
|
|
||
|
/** Returns the text of the button to deny action execution. */
|
||
|
@NonNull
|
||
|
public CharSequence getDenyButtonText() {
|
||
|
return mDenyButtonText;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int describeContents() {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||
|
TextUtils.writeToParcel(mTitle, dest, flags);
|
||
|
TextUtils.writeToParcel(mText, dest, flags);
|
||
|
TextUtils.writeToParcel(mAcceptButtonText, dest, flags);
|
||
|
TextUtils.writeToParcel(mDenyButtonText, dest, flags);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean equals(Object o) {
|
||
|
if (this == o) return true;
|
||
|
if (!(o instanceof ConfirmationDialogDetails)) return false;
|
||
|
ConfirmationDialogDetails that = (ConfirmationDialogDetails) o;
|
||
|
return TextUtils.equals(mTitle, that.mTitle)
|
||
|
&& TextUtils.equals(mText, that.mText)
|
||
|
&& TextUtils.equals(mAcceptButtonText, that.mAcceptButtonText)
|
||
|
&& TextUtils.equals(mDenyButtonText, that.mDenyButtonText);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int hashCode() {
|
||
|
return Objects.hash(mTitle, mText, mAcceptButtonText, mDenyButtonText);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String toString() {
|
||
|
return "ConfirmationDialogDetails{"
|
||
|
+ "mTitle="
|
||
|
+ mTitle
|
||
|
+ ", mText="
|
||
|
+ mText
|
||
|
+ ", mAcceptButtonText="
|
||
|
+ mAcceptButtonText
|
||
|
+ ", mDenyButtonText="
|
||
|
+ mDenyButtonText
|
||
|
+ '}';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Builder class for {@link Action}. */
|
||
|
public static final class Builder {
|
||
|
|
||
|
@NonNull private String mId;
|
||
|
@NonNull private CharSequence mLabel;
|
||
|
@NonNull private PendingIntent mPendingIntent;
|
||
|
private boolean mWillResolve;
|
||
|
private boolean mInFlight;
|
||
|
@Nullable private CharSequence mSuccessMessage;
|
||
|
@Nullable private ConfirmationDialogDetails mConfirmationDialogDetails;
|
||
|
|
||
|
/**
|
||
|
* Creates a new {@link Builder} for an {@link Action}.
|
||
|
*
|
||
|
* @param id a unique ID for this action
|
||
|
* @param label a label describing this action
|
||
|
* @param pendingIntent a {@link PendingIntent} to be sent when this action is taken
|
||
|
*/
|
||
|
public Builder(
|
||
|
@NonNull String id,
|
||
|
@NonNull CharSequence label,
|
||
|
@NonNull PendingIntent pendingIntent) {
|
||
|
mId = requireNonNull(id);
|
||
|
mLabel = requireNonNull(label);
|
||
|
mPendingIntent = requireNonNull(pendingIntent);
|
||
|
}
|
||
|
|
||
|
/** Creates a {@link Builder} with the values from the given {@link Action}. */
|
||
|
@RequiresApi(UPSIDE_DOWN_CAKE)
|
||
|
public Builder(@NonNull Action action) {
|
||
|
if (!SdkLevel.isAtLeastU()) {
|
||
|
throw new UnsupportedOperationException(
|
||
|
"Method not supported on versions lower than UPSIDE_DOWN_CAKE");
|
||
|
}
|
||
|
requireNonNull(action);
|
||
|
mId = action.mId;
|
||
|
mLabel = action.mLabel;
|
||
|
mPendingIntent = action.mPendingIntent;
|
||
|
mWillResolve = action.mWillResolve;
|
||
|
mInFlight = action.mInFlight;
|
||
|
mSuccessMessage = action.mSuccessMessage;
|
||
|
mConfirmationDialogDetails = action.mConfirmationDialogDetails;
|
||
|
}
|
||
|
|
||
|
/** Sets the ID of this {@link Action} */
|
||
|
@NonNull
|
||
|
public Builder setId(@NonNull String id) {
|
||
|
mId = requireNonNull(id);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Sets the label of this {@link Action}. */
|
||
|
@NonNull
|
||
|
public Builder setLabel(@NonNull CharSequence label) {
|
||
|
mLabel = requireNonNull(label);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Sets the {@link PendingIntent} to be sent when this {@link Action} is taken. */
|
||
|
@NonNull
|
||
|
public Builder setPendingIntent(@NonNull PendingIntent pendingIntent) {
|
||
|
mPendingIntent = requireNonNull(pendingIntent);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets whether this action will resolve the issue when executed. Defaults to {@code
|
||
|
* false}.
|
||
|
*
|
||
|
* @see #willResolve()
|
||
|
*/
|
||
|
@SuppressLint("MissingGetterMatchingBuilder")
|
||
|
@NonNull
|
||
|
public Builder setWillResolve(boolean willResolve) {
|
||
|
mWillResolve = willResolve;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets a boolean that indicates whether this action is currently being executed (i.e.
|
||
|
* the user clicked on a button that triggered this action, and now the Safety Center is
|
||
|
* waiting for the action's result). Defaults to {@code false}.
|
||
|
*
|
||
|
* @see #isInFlight()
|
||
|
*/
|
||
|
@SuppressLint("MissingGetterMatchingBuilder")
|
||
|
@NonNull
|
||
|
public Builder setIsInFlight(boolean inFlight) {
|
||
|
mInFlight = inFlight;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets or clears the optional success message to be displayed when this {@link Action}
|
||
|
* completes.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public Builder setSuccessMessage(@Nullable CharSequence successMessage) {
|
||
|
mSuccessMessage = successMessage;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the optional data to be displayed in the confirmation dialog prior to launching
|
||
|
* the {@link PendingIntent} when the action is clicked on.
|
||
|
*/
|
||
|
@NonNull
|
||
|
@RequiresApi(UPSIDE_DOWN_CAKE)
|
||
|
public Builder setConfirmationDialogDetails(
|
||
|
@Nullable ConfirmationDialogDetails confirmationDialogDetails) {
|
||
|
if (!SdkLevel.isAtLeastU()) {
|
||
|
throw new UnsupportedOperationException(
|
||
|
"Method not supported on versions lower than UPSIDE_DOWN_CAKE");
|
||
|
}
|
||
|
mConfirmationDialogDetails = confirmationDialogDetails;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Creates the {@link Action} defined by this {@link Builder}. */
|
||
|
@NonNull
|
||
|
public Action build() {
|
||
|
return new Action(
|
||
|
mId,
|
||
|
mLabel,
|
||
|
mPendingIntent,
|
||
|
mWillResolve,
|
||
|
mInFlight,
|
||
|
mSuccessMessage,
|
||
|
mConfirmationDialogDetails);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@IssueSeverityLevel
|
||
|
private static int validateIssueSeverityLevel(int value) {
|
||
|
switch (value) {
|
||
|
case ISSUE_SEVERITY_LEVEL_OK:
|
||
|
case ISSUE_SEVERITY_LEVEL_RECOMMENDATION:
|
||
|
case ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING:
|
||
|
return value;
|
||
|
default:
|
||
|
}
|
||
|
throw new IllegalArgumentException(
|
||
|
"Unexpected IssueSeverityLevel for SafetyCenterIssue: " + value);
|
||
|
}
|
||
|
}
|