1 /** 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.service.notification; 17 18 import android.annotation.IntDef; 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.annotation.TestApi; 23 import android.app.RemoteInput; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 27 import java.lang.annotation.Retention; 28 import java.lang.annotation.RetentionPolicy; 29 30 /** 31 * Information about how the user has interacted with a given notification. 32 * @hide 33 */ 34 @TestApi 35 @SystemApi 36 public final class NotificationStats implements Parcelable { 37 38 private boolean mSeen; 39 private boolean mExpanded; 40 private boolean mDirectReplied; 41 private boolean mSnoozed; 42 private boolean mViewedSettings; 43 private boolean mInteracted; 44 45 /** @hide */ 46 @IntDef(prefix = { "DISMISSAL_SURFACE_" }, value = { 47 DISMISSAL_NOT_DISMISSED, DISMISSAL_OTHER, DISMISSAL_PEEK, DISMISSAL_AOD, DISMISSAL_SHADE 48 }) 49 @Retention(RetentionPolicy.SOURCE) 50 public @interface DismissalSurface {} 51 52 53 private @DismissalSurface int mDismissalSurface = DISMISSAL_NOT_DISMISSED; 54 55 /** 56 * Notification has not been dismissed yet. 57 */ 58 public static final int DISMISSAL_NOT_DISMISSED = -1; 59 /** 60 * Notification has been dismissed from a {@link NotificationListenerService} or the app 61 * itself. 62 */ 63 public static final int DISMISSAL_OTHER = 0; 64 /** 65 * Notification has been dismissed while peeking. 66 */ 67 public static final int DISMISSAL_PEEK = 1; 68 /** 69 * Notification has been dismissed from always on display. 70 */ 71 public static final int DISMISSAL_AOD = 2; 72 /** 73 * Notification has been dismissed from the notification shade. 74 */ 75 public static final int DISMISSAL_SHADE = 3; 76 77 /** @hide */ 78 @IntDef(prefix = { "DISMISS_SENTIMENT_" }, value = { 79 DISMISS_SENTIMENT_UNKNOWN, DISMISS_SENTIMENT_NEGATIVE, DISMISS_SENTIMENT_NEUTRAL, 80 DISMISS_SENTIMENT_POSITIVE 81 }) 82 @Retention(RetentionPolicy.SOURCE) 83 public @interface DismissalSentiment {} 84 85 /** 86 * No information is available about why this notification was dismissed, or the notification 87 * isn't dismissed yet. 88 */ 89 public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; 90 /** 91 * The user indicated while dismissing that they did not like the notification. 92 */ 93 public static final int DISMISS_SENTIMENT_NEGATIVE = 0; 94 /** 95 * The user didn't indicate one way or another how they felt about the notification while 96 * dismissing it. 97 */ 98 public static final int DISMISS_SENTIMENT_NEUTRAL = 1; 99 /** 100 * The user indicated while dismissing that they did like the notification. 101 */ 102 public static final int DISMISS_SENTIMENT_POSITIVE = 2; 103 104 105 private @DismissalSentiment 106 int mDismissalSentiment = DISMISS_SENTIMENT_UNKNOWN; 107 NotificationStats()108 public NotificationStats() { 109 } 110 111 /** 112 * @hide 113 */ 114 @SystemApi NotificationStats(Parcel in)115 protected NotificationStats(Parcel in) { 116 mSeen = in.readByte() != 0; 117 mExpanded = in.readByte() != 0; 118 mDirectReplied = in.readByte() != 0; 119 mSnoozed = in.readByte() != 0; 120 mViewedSettings = in.readByte() != 0; 121 mInteracted = in.readByte() != 0; 122 mDismissalSurface = in.readInt(); 123 mDismissalSentiment = in.readInt(); 124 } 125 126 @Override writeToParcel(Parcel dest, int flags)127 public void writeToParcel(Parcel dest, int flags) { 128 dest.writeByte((byte) (mSeen ? 1 : 0)); 129 dest.writeByte((byte) (mExpanded ? 1 : 0)); 130 dest.writeByte((byte) (mDirectReplied ? 1 : 0)); 131 dest.writeByte((byte) (mSnoozed ? 1 : 0)); 132 dest.writeByte((byte) (mViewedSettings ? 1 : 0)); 133 dest.writeByte((byte) (mInteracted ? 1 : 0)); 134 dest.writeInt(mDismissalSurface); 135 dest.writeInt(mDismissalSentiment); 136 } 137 138 @Override describeContents()139 public int describeContents() { 140 return 0; 141 } 142 143 public static final @android.annotation.NonNull Creator<NotificationStats> CREATOR = new Creator<NotificationStats>() { 144 @Override 145 public NotificationStats createFromParcel(Parcel in) { 146 return new NotificationStats(in); 147 } 148 149 @Override 150 public NotificationStats[] newArray(int size) { 151 return new NotificationStats[size]; 152 } 153 }; 154 155 /** 156 * Returns whether the user has seen this notification at least once. 157 */ hasSeen()158 public boolean hasSeen() { 159 return mSeen; 160 } 161 162 /** 163 * Records that the user as seen this notification at least once. 164 */ setSeen()165 public void setSeen() { 166 mSeen = true; 167 } 168 169 /** 170 * Returns whether the user has expanded this notification at least once. 171 */ hasExpanded()172 public boolean hasExpanded() { 173 return mExpanded; 174 } 175 176 /** 177 * Records that the user has expanded this notification at least once. 178 */ setExpanded()179 public void setExpanded() { 180 mExpanded = true; 181 mInteracted = true; 182 } 183 184 /** 185 * Returns whether the user has replied to a notification that has a 186 * {@link android.app.Notification.Action.Builder#addRemoteInput(RemoteInput) direct reply} at 187 * least once. 188 */ hasDirectReplied()189 public boolean hasDirectReplied() { 190 return mDirectReplied; 191 } 192 193 /** 194 * Records that the user has replied to a notification that has a 195 * {@link android.app.Notification.Action.Builder#addRemoteInput(RemoteInput) direct reply} 196 * at least once. 197 */ setDirectReplied()198 public void setDirectReplied() { 199 mDirectReplied = true; 200 mInteracted = true; 201 } 202 203 /** 204 * Returns whether the user has snoozed this notification at least once. 205 */ hasSnoozed()206 public boolean hasSnoozed() { 207 return mSnoozed; 208 } 209 210 /** 211 * Records that the user has snoozed this notification at least once. 212 */ setSnoozed()213 public void setSnoozed() { 214 mSnoozed = true; 215 mInteracted = true; 216 } 217 218 /** 219 * Returns whether the user has viewed the in-shade settings for this notification at least 220 * once. 221 */ hasViewedSettings()222 public boolean hasViewedSettings() { 223 return mViewedSettings; 224 } 225 226 /** 227 * Records that the user has viewed the in-shade settings for this notification at least once. 228 */ setViewedSettings()229 public void setViewedSettings() { 230 mViewedSettings = true; 231 mInteracted = true; 232 } 233 234 /** 235 * Returns whether the user has interacted with this notification beyond having viewed it. 236 */ hasInteracted()237 public boolean hasInteracted() { 238 return mInteracted; 239 } 240 241 /** 242 * Returns from which surface the notification was dismissed. 243 */ getDismissalSurface()244 public @DismissalSurface int getDismissalSurface() { 245 return mDismissalSurface; 246 } 247 248 /** 249 * Returns from which surface the notification was dismissed. 250 */ setDismissalSurface(@ismissalSurface int dismissalSurface)251 public void setDismissalSurface(@DismissalSurface int dismissalSurface) { 252 mDismissalSurface = dismissalSurface; 253 } 254 255 /** 256 * Records whether the user indicated how they felt about a notification before or 257 * during dismissal. 258 */ setDismissalSentiment(@ismissalSentiment int dismissalSentiment)259 public void setDismissalSentiment(@DismissalSentiment int dismissalSentiment) { 260 mDismissalSentiment = dismissalSentiment; 261 } 262 263 /** 264 * Returns how the user indicated they felt about a notification before or during dismissal. 265 */ getDismissalSentiment()266 public @DismissalSentiment int getDismissalSentiment() { 267 return mDismissalSentiment; 268 } 269 270 @Override equals(@ullable Object o)271 public boolean equals(@Nullable Object o) { 272 if (this == o) return true; 273 if (o == null || getClass() != o.getClass()) return false; 274 275 NotificationStats that = (NotificationStats) o; 276 277 if (mSeen != that.mSeen) return false; 278 if (mExpanded != that.mExpanded) return false; 279 if (mDirectReplied != that.mDirectReplied) return false; 280 if (mSnoozed != that.mSnoozed) return false; 281 if (mViewedSettings != that.mViewedSettings) return false; 282 if (mInteracted != that.mInteracted) return false; 283 return mDismissalSurface == that.mDismissalSurface; 284 } 285 286 @Override hashCode()287 public int hashCode() { 288 int result = (mSeen ? 1 : 0); 289 result = 31 * result + (mExpanded ? 1 : 0); 290 result = 31 * result + (mDirectReplied ? 1 : 0); 291 result = 31 * result + (mSnoozed ? 1 : 0); 292 result = 31 * result + (mViewedSettings ? 1 : 0); 293 result = 31 * result + (mInteracted ? 1 : 0); 294 result = 31 * result + mDismissalSurface; 295 return result; 296 } 297 298 @NonNull 299 @Override toString()300 public String toString() { 301 final StringBuilder sb = new StringBuilder("NotificationStats{"); 302 sb.append("mSeen=").append(mSeen); 303 sb.append(", mExpanded=").append(mExpanded); 304 sb.append(", mDirectReplied=").append(mDirectReplied); 305 sb.append(", mSnoozed=").append(mSnoozed); 306 sb.append(", mViewedSettings=").append(mViewedSettings); 307 sb.append(", mInteracted=").append(mInteracted); 308 sb.append(", mDismissalSurface=").append(mDismissalSurface); 309 sb.append('}'); 310 return sb.toString(); 311 } 312 } 313