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