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