/* * 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 CREATOR = new Creator() { @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. * *

The action contained in the {@link PendingIntent} must start an activity. * *

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. * *

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. * *

A safety source status should be disabled if it is currently unavailable on the device * *

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. * *

The purpose of the action is to add a surface to allow the user to perform an action * relating to the safety source status. * *

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 CREATOR = new Creator() { @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. * *

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. * *

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. * *

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); } }