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