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