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