blob: 37095eb594774360606503c2ca519fd7cf15425c [file] [log] [blame]
/*
* 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 android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static com.android.internal.util.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.os.Parcel;
import android.os.Parcelable;
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.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
@RequiresApi(TIRAMISU)
public final class SafetySourceStatus implements Parcelable {
@NonNull
public static final Creator<SafetySourceStatus> CREATOR =
new Creator<SafetySourceStatus>() {
@Override
public SafetySourceStatus createFromParcel(Parcel in) {
CharSequence title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
CharSequence summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
int severityLevel = in.readInt();
return new Builder(title, summary, severityLevel)
.setPendingIntent(in.readTypedObject(PendingIntent.CREATOR))
.setIconAction(in.readTypedObject(IconAction.CREATOR))
.setEnabled(in.readBoolean())
.build();
}
@Override
public SafetySourceStatus[] newArray(int size) {
return new SafetySourceStatus[size];
}
};
@NonNull private final CharSequence mTitle;
@NonNull private final CharSequence mSummary;
@SafetySourceData.SeverityLevel private final int mSeverityLevel;
@Nullable private final PendingIntent mPendingIntent;
@Nullable private final IconAction mIconAction;
private final boolean mEnabled;
private SafetySourceStatus(
@NonNull CharSequence title,
@NonNull CharSequence summary,
@SafetySourceData.SeverityLevel int severityLevel,
@Nullable PendingIntent pendingIntent,
@Nullable IconAction iconAction,
boolean enabled) {
this.mTitle = title;
this.mSummary = summary;
this.mSeverityLevel = severityLevel;
this.mPendingIntent = pendingIntent;
this.mIconAction = iconAction;
this.mEnabled = enabled;
}
/** 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 SafetySourceData.SeverityLevel} of the status. */
@SafetySourceData.SeverityLevel
public int getSeverityLevel() {
return mSeverityLevel;
}
/**
* Returns an optional {@link PendingIntent} that will start an activity when the safety source
* status UI is clicked on.
*
* <p>The action contained in the {@link PendingIntent} must start an activity.
*
* <p>If {@code null} the intent action defined in the Safety Center configuration will be
* invoked when the safety source status UI is clicked on. If the intent action is undefined or
* disabled the source is considered as disabled.
*/
@Nullable
public PendingIntent getPendingIntent() {
return mPendingIntent;
}
/**
* 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.
*/
@Nullable
public IconAction getIconAction() {
return mIconAction;
}
/**
* Returns whether the safety source status is enabled.
*
* <p>A safety source status should be disabled if it is currently unavailable on the device
*
* <p>If disabled, the status will show as grayed out in the UI, and interactions with it may be
* limited.
*/
public boolean isEnabled() {
return mEnabled;
}
@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.writeTypedObject(mPendingIntent, flags);
dest.writeTypedObject(mIconAction, flags);
dest.writeBoolean(mEnabled);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SafetySourceStatus)) return false;
SafetySourceStatus that = (SafetySourceStatus) o;
return mSeverityLevel == that.mSeverityLevel
&& mEnabled == that.mEnabled
&& TextUtils.equals(mTitle, that.mTitle)
&& TextUtils.equals(mSummary, that.mSummary)
&& Objects.equals(mPendingIntent, that.mPendingIntent)
&& Objects.equals(mIconAction, that.mIconAction);
}
@Override
public int hashCode() {
return Objects.hash(
mTitle, mSummary, mSeverityLevel, mPendingIntent, mIconAction, mEnabled);
}
@Override
public String toString() {
return "SafetySourceStatus{"
+ "mTitle="
+ mTitle
+ ", mSummary="
+ mSummary
+ ", mSeverityLevel="
+ mSeverityLevel
+ ", mPendingIntent="
+ mPendingIntent
+ ", mIconAction="
+ mIconAction
+ ", mEnabled="
+ mEnabled
+ '}';
}
/**
* Data for an action supported from a safety source status {@link SafetySourceStatus} in the
* Safety Center page.
*
* <p>The purpose of the action is to add a surface to allow the user to perform an action
* relating to the safety source status.
*
* <p>The action will be shown as a clickable icon chosen from a predefined set of icons (see
* {@link IconType}). The icon should indicate to the user what action will be performed on
* clicking on it.
*/
public static final class IconAction implements Parcelable {
@NonNull
public static final Creator<IconAction> CREATOR =
new Creator<IconAction>() {
@Override
public IconAction createFromParcel(Parcel in) {
int iconType = in.readInt();
PendingIntent pendingIntent = in.readTypedObject(PendingIntent.CREATOR);
return new IconAction(iconType, pendingIntent);
}
@Override
public IconAction[] newArray(int size) {
return new IconAction[size];
}
};
/** Indicates a gear (cog) icon. */
public static final int ICON_TYPE_GEAR = 100;
/** Indicates an information icon. */
public static final int ICON_TYPE_INFO = 200;
/**
* All possible icons which can be displayed in an {@link IconAction}.
*
* @hide
*/
@IntDef(
prefix = {"ICON_TYPE_"},
value = {
ICON_TYPE_GEAR,
ICON_TYPE_INFO,
})
@Retention(RetentionPolicy.SOURCE)
public @interface IconType {}
@IconType private final int mIconType;
@NonNull private final PendingIntent mPendingIntent;
public IconAction(@IconType int iconType, @NonNull PendingIntent pendingIntent) {
this.mIconType = validateIconType(iconType);
this.mPendingIntent = requireNonNull(pendingIntent);
}
/**
* Returns the type of icon to be displayed in the UI.
*
* <p>The icon type should indicate what action will be performed if when invoked.
*/
@IconType
public int getIconType() {
return mIconType;
}
/**
* Returns a {@link PendingIntent} that will start an activity when the icon action is
* clicked on.
*/
@NonNull
public PendingIntent getPendingIntent() {
return mPendingIntent;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mIconType);
dest.writeTypedObject(mPendingIntent, flags);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof IconAction)) return false;
IconAction that = (IconAction) o;
return mIconType == that.mIconType && mPendingIntent.equals(that.mPendingIntent);
}
@Override
public int hashCode() {
return Objects.hash(mIconType, mPendingIntent);
}
@Override
public String toString() {
return "IconAction{"
+ "mIconType="
+ mIconType
+ ", mPendingIntent="
+ mPendingIntent
+ '}';
}
@IconType
private static int validateIconType(int value) {
switch (value) {
case ICON_TYPE_GEAR:
case ICON_TYPE_INFO:
return value;
default:
}
throw new IllegalArgumentException("Unexpected IconType for IconAction: " + value);
}
}
/** Builder class for {@link SafetySourceStatus}. */
public static final class Builder {
@NonNull private final CharSequence mTitle;
@NonNull private final CharSequence mSummary;
@SafetySourceData.SeverityLevel private final int mSeverityLevel;
@Nullable private PendingIntent mPendingIntent;
@Nullable private IconAction mIconAction;
private boolean mEnabled = true;
/** Creates a {@link Builder} for a {@link SafetySourceStatus}. */
public Builder(
@NonNull CharSequence title,
@NonNull CharSequence summary,
@SafetySourceData.SeverityLevel int severityLevel) {
this.mTitle = requireNonNull(title);
this.mSummary = requireNonNull(summary);
this.mSeverityLevel = validateSeverityLevel(severityLevel);
}
/** Creates a {@link Builder} with the values of the given {@link SafetySourceStatus}. */
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder(@NonNull SafetySourceStatus safetySourceStatus) {
if (!SdkLevel.isAtLeastU()) {
throw new UnsupportedOperationException();
}
requireNonNull(safetySourceStatus);
mTitle = safetySourceStatus.mTitle;
mSummary = safetySourceStatus.mSummary;
mSeverityLevel = safetySourceStatus.mSeverityLevel;
mPendingIntent = safetySourceStatus.mPendingIntent;
mIconAction = safetySourceStatus.mIconAction;
mEnabled = safetySourceStatus.mEnabled;
}
/**
* Sets an optional {@link PendingIntent} for the safety source status.
*
* <p>The action contained in the {@link PendingIntent} must start an activity.
*
* @see #getPendingIntent()
*/
@NonNull
public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
checkArgument(
pendingIntent == null || pendingIntent.isActivity(),
"Safety source status pending intent must start an activity");
this.mPendingIntent = pendingIntent;
return this;
}
/**
* Sets an optional {@link IconAction} for the safety source status.
*
* @see #getIconAction()
*/
@NonNull
public Builder setIconAction(@Nullable IconAction iconAction) {
this.mIconAction = iconAction;
return this;
}
/**
* Sets whether the safety source status is enabled.
*
* <p>By default, the safety source status will be enabled. If disabled, the status severity
* level must be set to {@link SafetySourceData#SEVERITY_LEVEL_UNSPECIFIED}.
*
* @see #isEnabled()
*/
@NonNull
public Builder setEnabled(boolean enabled) {
checkArgument(
enabled || mSeverityLevel == SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED,
"Safety source status must have a severity level of "
+ "SEVERITY_LEVEL_UNSPECIFIED when disabled");
this.mEnabled = enabled;
return this;
}
/** Creates the {@link SafetySourceStatus} defined by this {@link Builder}. */
@NonNull
public SafetySourceStatus build() {
return new SafetySourceStatus(
mTitle, mSummary, mSeverityLevel, mPendingIntent, mIconAction, mEnabled);
}
}
@SafetySourceData.SeverityLevel
private static int validateSeverityLevel(int value) {
switch (value) {
case SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED:
case SafetySourceData.SEVERITY_LEVEL_INFORMATION:
case SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION:
case SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING:
return value;
default:
}
throw new IllegalArgumentException(
"Unexpected SeverityLevel for SafetySourceStatus: " + value);
}
}