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 
17 package android.ext.services.notification;
18 
19 import android.os.Parcel;
20 import android.os.Parcelable;
21 import android.text.TextUtils;
22 import android.util.Log;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 
26 import org.xmlpull.v1.XmlPullParser;
27 import org.xmlpull.v1.XmlSerializer;
28 
29 import java.io.IOException;
30 
31 public final class ChannelImpressions implements Parcelable {
32     private static final String TAG = "ExtAssistant.CI";
33     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
34 
35     static final float DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT = .8f;
36     static final int DEFAULT_STREAK_LIMIT = 2;
37     static final String ATT_DISMISSALS = "dismisses";
38     static final String ATT_VIEWS = "views";
39     static final String ATT_STREAK = "streak";
40     static final String ATT_SENT = "sent";
41     static final String ATT_INTERRUPTIVE = "interruptive";
42 
43     private int mDismissals = 0;
44     private int mViews = 0;
45     private int mStreak = 0;
46 
47     private float mDismissToViewRatioLimit;
48     private int mStreakLimit;
49 
ChannelImpressions()50     public ChannelImpressions() {
51         mDismissToViewRatioLimit = DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT;
52         mStreakLimit = DEFAULT_STREAK_LIMIT;
53     }
54 
ChannelImpressions(Parcel in)55     protected ChannelImpressions(Parcel in) {
56         mDismissals = in.readInt();
57         mViews = in.readInt();
58         mStreak = in.readInt();
59         mDismissToViewRatioLimit = in.readFloat();
60         mStreakLimit = in.readInt();
61     }
62 
getStreak()63     public int getStreak() {
64         return mStreak;
65     }
66 
getDismissals()67     public int getDismissals() {
68         return mDismissals;
69     }
70 
getViews()71     public int getViews() {
72         return mViews;
73     }
74 
incrementDismissals()75     public void incrementDismissals() {
76         mDismissals++;
77         mStreak++;
78     }
79 
updateThresholds(float dismissToViewRatioLimit, int streakLimit)80     void updateThresholds(float dismissToViewRatioLimit, int streakLimit) {
81         mDismissToViewRatioLimit = dismissToViewRatioLimit;
82         mStreakLimit = streakLimit;
83     }
84 
85     @VisibleForTesting
getDismissToViewRatioLimit()86     float getDismissToViewRatioLimit() {
87         return mDismissToViewRatioLimit;
88     }
89 
90     @VisibleForTesting
getStreakLimit()91     int getStreakLimit() {
92         return mStreakLimit;
93     }
94 
append(ChannelImpressions additionalImpressions)95     public void append(ChannelImpressions additionalImpressions) {
96         if (additionalImpressions != null) {
97             mViews += additionalImpressions.getViews();
98             mStreak += additionalImpressions.getStreak();
99             mDismissals += additionalImpressions.getDismissals();
100         }
101     }
102 
incrementViews()103     public void incrementViews() {
104         mViews++;
105     }
106 
resetStreak()107     public void resetStreak() {
108         mStreak = 0;
109     }
110 
shouldTriggerBlock()111     public boolean shouldTriggerBlock() {
112         if (getViews() == 0) {
113             return false;
114         }
115         if (DEBUG) {
116             Log.d(TAG, "should trigger? " + getDismissals() + " " + getViews() + " " + getStreak());
117         }
118         return ((float) getDismissals() / getViews()) > mDismissToViewRatioLimit
119                 && getStreak() > mStreakLimit;
120     }
121 
122     @Override
writeToParcel(Parcel dest, int flags)123     public void writeToParcel(Parcel dest, int flags) {
124         dest.writeInt(mDismissals);
125         dest.writeInt(mViews);
126         dest.writeInt(mStreak);
127         dest.writeFloat(mDismissToViewRatioLimit);
128         dest.writeInt(mStreakLimit);
129     }
130 
131     @Override
describeContents()132     public int describeContents() {
133         return 0;
134     }
135 
136     public static final Creator<ChannelImpressions> CREATOR = new Creator<ChannelImpressions>() {
137         @Override
138         public ChannelImpressions createFromParcel(Parcel in) {
139             return new ChannelImpressions(in);
140         }
141 
142         @Override
143         public ChannelImpressions[] newArray(int size) {
144             return new ChannelImpressions[size];
145         }
146     };
147 
148     @Override
equals(Object o)149     public boolean equals(Object o) {
150         if (this == o) return true;
151         if (o == null || getClass() != o.getClass()) return false;
152 
153         ChannelImpressions that = (ChannelImpressions) o;
154 
155         if (mDismissals != that.mDismissals) return false;
156         if (mViews != that.mViews) return false;
157         return mStreak == that.mStreak;
158     }
159 
160     @Override
hashCode()161     public int hashCode() {
162         int result = mDismissals;
163         result = 31 * result + mViews;
164         result = 31 * result + mStreak;
165         return result;
166     }
167 
168     @Override
toString()169     public String toString() {
170         final StringBuilder sb = new StringBuilder("ChannelImpressions{");
171         sb.append("mDismissals=").append(mDismissals);
172         sb.append(", mViews=").append(mViews);
173         sb.append(", mStreak=").append(mStreak);
174         sb.append(", thresholds=(").append(mDismissToViewRatioLimit);
175         sb.append(",").append(mStreakLimit);
176         sb.append(")}");
177         return sb.toString();
178     }
179 
populateFromXml(XmlPullParser parser)180     protected void populateFromXml(XmlPullParser parser) {
181         mDismissals = safeInt(parser, ATT_DISMISSALS, 0);
182         mStreak = safeInt(parser, ATT_STREAK, 0);
183         mViews = safeInt(parser, ATT_VIEWS, 0);
184     }
185 
writeXml(XmlSerializer out)186     protected void writeXml(XmlSerializer out) throws IOException {
187         if (mDismissals != 0) {
188             out.attribute(null, ATT_DISMISSALS, String.valueOf(mDismissals));
189         }
190         if (mStreak != 0) {
191             out.attribute(null, ATT_STREAK, String.valueOf(mStreak));
192         }
193         if (mViews != 0) {
194             out.attribute(null, ATT_VIEWS, String.valueOf(mViews));
195         }
196     }
197 
safeInt(XmlPullParser parser, String att, int defValue)198     private static int safeInt(XmlPullParser parser, String att, int defValue) {
199         final String val = parser.getAttributeValue(null, att);
200         return tryParseInt(val, defValue);
201     }
202 
tryParseInt(String value, int defValue)203     private static int tryParseInt(String value, int defValue) {
204         if (TextUtils.isEmpty(value)) return defValue;
205         try {
206             return Integer.parseInt(value);
207         } catch (NumberFormatException e) {
208             return defValue;
209         }
210     }
211 }
212