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