/** * Copyright (C) 2017 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.service.notification; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Flags; import android.app.RemoteInput; import android.os.Parcel; import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Information about how the user has interacted with a given notification. * @hide */ @SystemApi public final class NotificationStats implements Parcelable { private boolean mSeen; private boolean mExpanded; private boolean mDirectReplied; private boolean mSmartReplied; private boolean mSnoozed; private boolean mViewedSettings; private boolean mInteracted; /** @hide */ @IntDef(prefix = { "DISMISSAL_SURFACE_" }, value = { DISMISSAL_NOT_DISMISSED, DISMISSAL_OTHER, DISMISSAL_PEEK, DISMISSAL_AOD, DISMISSAL_SHADE, DISMISSAL_BUBBLE, DISMISSAL_LOCKSCREEN }) @Retention(RetentionPolicy.SOURCE) public @interface DismissalSurface {} private @DismissalSurface int mDismissalSurface = DISMISSAL_NOT_DISMISSED; /** * Notification has not been dismissed yet. */ public static final int DISMISSAL_NOT_DISMISSED = -1; /** * Notification has been dismissed from a {@link NotificationListenerService} or the app * itself. */ public static final int DISMISSAL_OTHER = 0; /** * Notification has been dismissed while peeking. */ public static final int DISMISSAL_PEEK = 1; /** * Notification has been dismissed from always on display. */ public static final int DISMISSAL_AOD = 2; /** * Notification has been dismissed from the notification shade. */ public static final int DISMISSAL_SHADE = 3; /** * Notification has been dismissed as a bubble. * @hide */ public static final int DISMISSAL_BUBBLE = 4; /** * Notification has been dismissed from the lock screen. * @hide */ public static final int DISMISSAL_LOCKSCREEN = 5; /** @hide */ @IntDef(prefix = { "DISMISS_SENTIMENT_" }, value = { DISMISS_SENTIMENT_UNKNOWN, DISMISS_SENTIMENT_NEGATIVE, DISMISS_SENTIMENT_NEUTRAL, DISMISS_SENTIMENT_POSITIVE }) @Retention(RetentionPolicy.SOURCE) public @interface DismissalSentiment {} /** * No information is available about why this notification was dismissed, or the notification * isn't dismissed yet. */ public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; /** * The user indicated while dismissing that they did not like the notification. */ public static final int DISMISS_SENTIMENT_NEGATIVE = 0; /** * The user didn't indicate one way or another how they felt about the notification while * dismissing it. */ public static final int DISMISS_SENTIMENT_NEUTRAL = 1; /** * The user indicated while dismissing that they did like the notification. */ public static final int DISMISS_SENTIMENT_POSITIVE = 2; private @DismissalSentiment int mDismissalSentiment = DISMISS_SENTIMENT_UNKNOWN; public NotificationStats() { } /** * @hide */ @SystemApi protected NotificationStats(Parcel in) { mSeen = in.readByte() != 0; mExpanded = in.readByte() != 0; mDirectReplied = in.readByte() != 0; if (Flags.lifetimeExtensionRefactor()) { mSmartReplied = in.readByte() != 0; } mSnoozed = in.readByte() != 0; mViewedSettings = in.readByte() != 0; mInteracted = in.readByte() != 0; mDismissalSurface = in.readInt(); mDismissalSentiment = in.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeByte((byte) (mSeen ? 1 : 0)); dest.writeByte((byte) (mExpanded ? 1 : 0)); dest.writeByte((byte) (mDirectReplied ? 1 : 0)); if (Flags.lifetimeExtensionRefactor()) { dest.writeByte((byte) (mSmartReplied ? 1 : 0)); } dest.writeByte((byte) (mSnoozed ? 1 : 0)); dest.writeByte((byte) (mViewedSettings ? 1 : 0)); dest.writeByte((byte) (mInteracted ? 1 : 0)); dest.writeInt(mDismissalSurface); dest.writeInt(mDismissalSentiment); } @Override public int describeContents() { return 0; } public static final @android.annotation.NonNull Creator CREATOR = new Creator() { @Override public NotificationStats createFromParcel(Parcel in) { return new NotificationStats(in); } @Override public NotificationStats[] newArray(int size) { return new NotificationStats[size]; } }; /** * Returns whether the user has seen this notification at least once. */ public boolean hasSeen() { return mSeen; } /** * Records that the user as seen this notification at least once. */ public void setSeen() { mSeen = true; } /** * Returns whether the user has expanded this notification at least once. */ public boolean hasExpanded() { return mExpanded; } /** * Records that the user has expanded this notification at least once. */ public void setExpanded() { mExpanded = true; mInteracted = true; } /** * Returns whether the user has replied to a notification that has a * {@link android.app.Notification.Action.Builder#addRemoteInput(RemoteInput) direct reply} at * least once. */ public boolean hasDirectReplied() { return mDirectReplied; } /** * Records that the user has replied to a notification that has a * {@link android.app.Notification.Action.Builder#addRemoteInput(RemoteInput) direct reply} * at least once. */ public void setDirectReplied() { mDirectReplied = true; mInteracted = true; } /** * Returns whether the user has replied to a notification that has a smart reply at least once. */ @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public boolean hasSmartReplied() { return mSmartReplied; } /** * Records that the user has replied to a notification that has a smart reply at least once. */ @SuppressLint("GetterSetterNames") @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public void setSmartReplied() { mSmartReplied = true; mInteracted = true; } /** * Returns whether the user has snoozed this notification at least once. */ public boolean hasSnoozed() { return mSnoozed; } /** * Records that the user has snoozed this notification at least once. */ public void setSnoozed() { mSnoozed = true; mInteracted = true; } /** * Returns whether the user has viewed the in-shade settings for this notification at least * once. */ public boolean hasViewedSettings() { return mViewedSettings; } /** * Records that the user has viewed the in-shade settings for this notification at least once. */ public void setViewedSettings() { mViewedSettings = true; mInteracted = true; } /** * Returns whether the user has interacted with this notification beyond having viewed it. */ public boolean hasInteracted() { return mInteracted; } /** * Returns from which surface the notification was dismissed. */ public @DismissalSurface int getDismissalSurface() { return mDismissalSurface; } /** * Returns from which surface the notification was dismissed. */ public void setDismissalSurface(@DismissalSurface int dismissalSurface) { mDismissalSurface = dismissalSurface; } /** * Records whether the user indicated how they felt about a notification before or * during dismissal. */ public void setDismissalSentiment(@DismissalSentiment int dismissalSentiment) { mDismissalSentiment = dismissalSentiment; } /** * Returns how the user indicated they felt about a notification before or during dismissal. */ public @DismissalSentiment int getDismissalSentiment() { return mDismissalSentiment; } @Override public boolean equals(@Nullable Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; NotificationStats that = (NotificationStats) o; if (mSeen != that.mSeen) return false; if (mExpanded != that.mExpanded) return false; if (mDirectReplied != that.mDirectReplied) return false; if (Flags.lifetimeExtensionRefactor()) { if (mSmartReplied != that.mSmartReplied) return false; } if (mSnoozed != that.mSnoozed) return false; if (mViewedSettings != that.mViewedSettings) return false; if (mInteracted != that.mInteracted) return false; return mDismissalSurface == that.mDismissalSurface; } @Override public int hashCode() { int result = (mSeen ? 1 : 0); result = 31 * result + (mExpanded ? 1 : 0); result = 31 * result + (mDirectReplied ? 1 : 0); if (Flags.lifetimeExtensionRefactor()) { result = 31 * result + (mSmartReplied ? 1 : 0); } result = 31 * result + (mSnoozed ? 1 : 0); result = 31 * result + (mViewedSettings ? 1 : 0); result = 31 * result + (mInteracted ? 1 : 0); result = 31 * result + mDismissalSurface; return result; } @NonNull @Override public String toString() { final StringBuilder sb = new StringBuilder("NotificationStats{"); sb.append("mSeen=").append(mSeen); sb.append(", mExpanded=").append(mExpanded); sb.append(", mDirectReplied=").append(mDirectReplied); if (Flags.lifetimeExtensionRefactor()) { sb.append(", mSmartReplied=").append(mSmartReplied); } sb.append(", mSnoozed=").append(mSnoozed); sb.append(", mViewedSettings=").append(mViewedSettings); sb.append(", mInteracted=").append(mInteracted); sb.append(", mDismissalSurface=").append(mDismissalSurface); sb.append('}'); return sb.toString(); } }