| /* |
| * 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( |
| "Method not supported on versions lower than UPSIDE_DOWN_CAKE"); |
| } |
| 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); |
| } |
| } |