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