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.telephony;
18 
19 import android.annotation.BytesLong;
20 import android.annotation.CurrentTimeMillisLong;
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.SystemApi;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.util.Range;
28 import android.util.RecurrenceRule;
29 
30 import com.android.internal.util.Preconditions;
31 
32 import java.lang.annotation.Retention;
33 import java.lang.annotation.RetentionPolicy;
34 import java.time.Period;
35 import java.time.ZonedDateTime;
36 import java.util.Iterator;
37 import java.util.Objects;
38 
39 /**
40  * Description of a billing relationship plan between a carrier and a specific
41  * subscriber. This information is used to present more useful UI to users, such
42  * as explaining how much mobile data they have remaining, and what will happen
43  * when they run out.
44  *
45  * @see SubscriptionManager#setSubscriptionPlans(int, java.util.List)
46  * @see SubscriptionManager#getSubscriptionPlans(int)
47  */
48 @SystemApi
49 public final class SubscriptionPlan implements Parcelable {
50     /** {@hide} */
51     @IntDef(prefix = "LIMIT_BEHAVIOR_", value = {
52             LIMIT_BEHAVIOR_UNKNOWN,
53             LIMIT_BEHAVIOR_DISABLED,
54             LIMIT_BEHAVIOR_BILLED,
55             LIMIT_BEHAVIOR_THROTTLED,
56     })
57     @Retention(RetentionPolicy.SOURCE)
58     public @interface LimitBehavior {}
59 
60     /** When a resource limit is hit, the behavior is unknown. */
61     public static final int LIMIT_BEHAVIOR_UNKNOWN = -1;
62     /** When a resource limit is hit, access is disabled. */
63     public static final int LIMIT_BEHAVIOR_DISABLED = 0;
64     /** When a resource limit is hit, the user is billed automatically. */
65     public static final int LIMIT_BEHAVIOR_BILLED = 1;
66     /** When a resource limit is hit, access is throttled to a slower rate. */
67     public static final int LIMIT_BEHAVIOR_THROTTLED = 2;
68 
69     /** Value indicating a number of bytes is unknown. */
70     public static final long BYTES_UNKNOWN = -1;
71     /** Value indicating a number of bytes is unlimited. */
72     public static final long BYTES_UNLIMITED = Long.MAX_VALUE;
73 
74     /** Value indicating a timestamp is unknown. */
75     public static final long TIME_UNKNOWN = -1;
76 
77     private final RecurrenceRule cycleRule;
78     private CharSequence title;
79     private CharSequence summary;
80     private long dataLimitBytes = BYTES_UNKNOWN;
81     private int dataLimitBehavior = LIMIT_BEHAVIOR_UNKNOWN;
82     private long dataUsageBytes = BYTES_UNKNOWN;
83     private long dataUsageTime = TIME_UNKNOWN;
84 
SubscriptionPlan(RecurrenceRule cycleRule)85     private SubscriptionPlan(RecurrenceRule cycleRule) {
86         this.cycleRule = Preconditions.checkNotNull(cycleRule);
87     }
88 
SubscriptionPlan(Parcel source)89     private SubscriptionPlan(Parcel source) {
90         cycleRule = source.readParcelable(null);
91         title = source.readCharSequence();
92         summary = source.readCharSequence();
93         dataLimitBytes = source.readLong();
94         dataLimitBehavior = source.readInt();
95         dataUsageBytes = source.readLong();
96         dataUsageTime = source.readLong();
97     }
98 
99     @Override
describeContents()100     public int describeContents() {
101         return 0;
102     }
103 
104     @Override
writeToParcel(Parcel dest, int flags)105     public void writeToParcel(Parcel dest, int flags) {
106         dest.writeParcelable(cycleRule, flags);
107         dest.writeCharSequence(title);
108         dest.writeCharSequence(summary);
109         dest.writeLong(dataLimitBytes);
110         dest.writeInt(dataLimitBehavior);
111         dest.writeLong(dataUsageBytes);
112         dest.writeLong(dataUsageTime);
113     }
114 
115     @Override
toString()116     public String toString() {
117         return new StringBuilder("SubscriptionPlan{")
118                 .append("cycleRule=").append(cycleRule)
119                 .append(" title=").append(title)
120                 .append(" summary=").append(summary)
121                 .append(" dataLimitBytes=").append(dataLimitBytes)
122                 .append(" dataLimitBehavior=").append(dataLimitBehavior)
123                 .append(" dataUsageBytes=").append(dataUsageBytes)
124                 .append(" dataUsageTime=").append(dataUsageTime)
125                 .append("}").toString();
126     }
127 
128     @Override
hashCode()129     public int hashCode() {
130         return Objects.hash(cycleRule, title, summary, dataLimitBytes, dataLimitBehavior,
131                 dataUsageBytes, dataUsageTime);
132     }
133 
134     @Override
equals(Object obj)135     public boolean equals(Object obj) {
136         if (obj instanceof SubscriptionPlan) {
137             final SubscriptionPlan other = (SubscriptionPlan) obj;
138             return Objects.equals(cycleRule, other.cycleRule)
139                     && Objects.equals(title, other.title)
140                     && Objects.equals(summary, other.summary)
141                     && dataLimitBytes == other.dataLimitBytes
142                     && dataLimitBehavior == other.dataLimitBehavior
143                     && dataUsageBytes == other.dataUsageBytes
144                     && dataUsageTime == other.dataUsageTime;
145         }
146         return false;
147     }
148 
149     public static final Parcelable.Creator<SubscriptionPlan> CREATOR = new Parcelable.Creator<SubscriptionPlan>() {
150         @Override
151         public SubscriptionPlan createFromParcel(Parcel source) {
152             return new SubscriptionPlan(source);
153         }
154 
155         @Override
156         public SubscriptionPlan[] newArray(int size) {
157             return new SubscriptionPlan[size];
158         }
159     };
160 
161     /** {@hide} */
getCycleRule()162     public @NonNull RecurrenceRule getCycleRule() {
163         return cycleRule;
164     }
165 
166     /** Return the short title of this plan. */
getTitle()167     public @Nullable CharSequence getTitle() {
168         return title;
169     }
170 
171     /** Return the short summary of this plan. */
getSummary()172     public @Nullable CharSequence getSummary() {
173         return summary;
174     }
175 
176     /**
177      * Return the usage threshold at which data access changes according to
178      * {@link #getDataLimitBehavior()}.
179      */
getDataLimitBytes()180     public @BytesLong long getDataLimitBytes() {
181         return dataLimitBytes;
182     }
183 
184     /**
185      * Return the behavior of data access when usage reaches
186      * {@link #getDataLimitBytes()}.
187      */
getDataLimitBehavior()188     public @LimitBehavior int getDataLimitBehavior() {
189         return dataLimitBehavior;
190     }
191 
192     /**
193      * Return a snapshot of currently known mobile data usage at
194      * {@link #getDataUsageTime()}.
195      */
getDataUsageBytes()196     public @BytesLong long getDataUsageBytes() {
197         return dataUsageBytes;
198     }
199 
200     /**
201      * Return the time at which {@link #getDataUsageBytes()} was valid.
202      */
getDataUsageTime()203     public @CurrentTimeMillisLong long getDataUsageTime() {
204         return dataUsageTime;
205     }
206 
207     /**
208      * Return an iterator that will return all valid data usage cycles based on
209      * any recurrence rules. The iterator starts from the currently active cycle
210      * and walks backwards through time.
211      */
cycleIterator()212     public Iterator<Range<ZonedDateTime>> cycleIterator() {
213         return cycleRule.cycleIterator();
214     }
215 
216     /**
217      * Builder for a {@link SubscriptionPlan}.
218      */
219     public static class Builder {
220         private final SubscriptionPlan plan;
221 
222         /** {@hide} */
Builder(ZonedDateTime start, ZonedDateTime end, Period period)223         public Builder(ZonedDateTime start, ZonedDateTime end, Period period) {
224             plan = new SubscriptionPlan(new RecurrenceRule(start, end, period));
225         }
226 
227         /**
228          * Start defining a {@link SubscriptionPlan} that covers a very specific
229          * window of time, and never automatically recurs.
230          *
231          * @param start The exact time at which the plan starts.
232          * @param end The exact time at which the plan ends.
233          */
createNonrecurring(ZonedDateTime start, ZonedDateTime end)234         public static Builder createNonrecurring(ZonedDateTime start, ZonedDateTime end) {
235             if (!end.isAfter(start)) {
236                 throw new IllegalArgumentException(
237                         "End " + end + " isn't after start " + start);
238             }
239             return new Builder(start, end, null);
240         }
241 
242         /**
243          * Start defining a {@link SubscriptionPlan} that starts at a specific
244          * time, and automatically recurs after each specific period of time,
245          * repeating indefinitely.
246          * <p>
247          * When the given period is set to exactly one month, the plan will
248          * always recur on the day of the month defined by
249          * {@link ZonedDateTime#getDayOfMonth()}. When a particular month ends
250          * before this day, the plan will recur on the last possible instant of
251          * that month.
252          *
253          * @param start The exact time at which the plan starts.
254          * @param period The period after which the plan automatically recurs.
255          */
createRecurring(ZonedDateTime start, Period period)256         public static Builder createRecurring(ZonedDateTime start, Period period) {
257             if (period.isZero() || period.isNegative()) {
258                 throw new IllegalArgumentException("Period " + period + " must be positive");
259             }
260             return new Builder(start, null, period);
261         }
262 
263         /** {@hide} */
264         @SystemApi
265         @Deprecated
createRecurringMonthly(ZonedDateTime start)266         public static Builder createRecurringMonthly(ZonedDateTime start) {
267             return new Builder(start, null, Period.ofMonths(1));
268         }
269 
270         /** {@hide} */
271         @SystemApi
272         @Deprecated
createRecurringWeekly(ZonedDateTime start)273         public static Builder createRecurringWeekly(ZonedDateTime start) {
274             return new Builder(start, null, Period.ofDays(7));
275         }
276 
277         /** {@hide} */
278         @SystemApi
279         @Deprecated
createRecurringDaily(ZonedDateTime start)280         public static Builder createRecurringDaily(ZonedDateTime start) {
281             return new Builder(start, null, Period.ofDays(1));
282         }
283 
build()284         public SubscriptionPlan build() {
285             return plan;
286         }
287 
288         /** Set the short title of this plan. */
setTitle(@ullable CharSequence title)289         public Builder setTitle(@Nullable CharSequence title) {
290             plan.title = title;
291             return this;
292         }
293 
294         /** Set the short summary of this plan. */
setSummary(@ullable CharSequence summary)295         public Builder setSummary(@Nullable CharSequence summary) {
296             plan.summary = summary;
297             return this;
298         }
299 
300         /**
301          * Set the usage threshold at which data access changes.
302          *
303          * @param dataLimitBytes the usage threshold at which data access
304          *            changes
305          * @param dataLimitBehavior the behavior of data access when usage
306          *            reaches the threshold
307          */
setDataLimit(@ytesLong long dataLimitBytes, @LimitBehavior int dataLimitBehavior)308         public Builder setDataLimit(@BytesLong long dataLimitBytes,
309                 @LimitBehavior int dataLimitBehavior) {
310             if (dataLimitBytes < 0) {
311                 throw new IllegalArgumentException("Limit bytes must be positive");
312             }
313             if (dataLimitBehavior < 0) {
314                 throw new IllegalArgumentException("Limit behavior must be defined");
315             }
316             plan.dataLimitBytes = dataLimitBytes;
317             plan.dataLimitBehavior = dataLimitBehavior;
318             return this;
319         }
320 
321         /**
322          * Set a snapshot of currently known mobile data usage.
323          *
324          * @param dataUsageBytes the currently known mobile data usage
325          * @param dataUsageTime the time at which this snapshot was valid
326          */
setDataUsage(@ytesLong long dataUsageBytes, @CurrentTimeMillisLong long dataUsageTime)327         public Builder setDataUsage(@BytesLong long dataUsageBytes,
328                 @CurrentTimeMillisLong long dataUsageTime) {
329             if (dataUsageBytes < 0) {
330                 throw new IllegalArgumentException("Usage bytes must be positive");
331             }
332             if (dataUsageTime < 0) {
333                 throw new IllegalArgumentException("Usage time must be positive");
334             }
335             plan.dataUsageBytes = dataUsageBytes;
336             plan.dataUsageTime = dataUsageTime;
337             return this;
338         }
339     }
340 }
341