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