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
17package android.telephony;
18
19import android.annotation.BytesLong;
20import android.annotation.CurrentTimeMillisLong;
21import android.annotation.IntDef;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.annotation.SystemApi;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.util.Pair;
28import android.util.RecurrenceRule;
29
30import com.android.internal.util.Preconditions;
31
32import java.lang.annotation.Retention;
33import java.lang.annotation.RetentionPolicy;
34import java.time.Period;
35import java.time.ZonedDateTime;
36import java.util.Iterator;
37
38/**
39 * Description of a billing relationship plan between a carrier and a specific
40 * subscriber. This information is used to present more useful UI to users, such
41 * as explaining how much mobile data they have remaining, and what will happen
42 * when they run out.
43 *
44 * @see SubscriptionManager#setSubscriptionPlans(int, java.util.List)
45 * @see SubscriptionManager#getSubscriptionPlans(int)
46 * @hide
47 */
48@SystemApi
49public 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
85    private SubscriptionPlan(RecurrenceRule cycleRule) {
86        this.cycleRule = Preconditions.checkNotNull(cycleRule);
87    }
88
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
100    public int describeContents() {
101        return 0;
102    }
103
104    @Override
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
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    public static final Parcelable.Creator<SubscriptionPlan> CREATOR = new Parcelable.Creator<SubscriptionPlan>() {
129        @Override
130        public SubscriptionPlan createFromParcel(Parcel source) {
131            return new SubscriptionPlan(source);
132        }
133
134        @Override
135        public SubscriptionPlan[] newArray(int size) {
136            return new SubscriptionPlan[size];
137        }
138    };
139
140    /** {@hide} */
141    public @NonNull RecurrenceRule getCycleRule() {
142        return cycleRule;
143    }
144
145    /** Return the short title of this plan. */
146    public @Nullable CharSequence getTitle() {
147        return title;
148    }
149
150    /** Return the short summary of this plan. */
151    public @Nullable CharSequence getSummary() {
152        return summary;
153    }
154
155    /**
156     * Return the usage threshold at which data access changes according to
157     * {@link #getDataLimitBehavior()}.
158     */
159    public @BytesLong long getDataLimitBytes() {
160        return dataLimitBytes;
161    }
162
163    /**
164     * Return the behavior of data access when usage reaches
165     * {@link #getDataLimitBytes()}.
166     */
167    public @LimitBehavior int getDataLimitBehavior() {
168        return dataLimitBehavior;
169    }
170
171    /**
172     * Return a snapshot of currently known mobile data usage at
173     * {@link #getDataUsageTime()}.
174     */
175    public @BytesLong long getDataUsageBytes() {
176        return dataUsageBytes;
177    }
178
179    /**
180     * Return the time at which {@link #getDataUsageBytes()} was valid.
181     */
182    public @CurrentTimeMillisLong long getDataUsageTime() {
183        return dataUsageTime;
184    }
185
186    /**
187     * Return an iterator that will return all valid data usage cycles based on
188     * any recurrence rules. The iterator starts from the currently active cycle
189     * and walks backwards through time.
190     */
191    public Iterator<Pair<ZonedDateTime, ZonedDateTime>> cycleIterator() {
192        return cycleRule.cycleIterator();
193    }
194
195    /**
196     * Builder for a {@link SubscriptionPlan}.
197     */
198    public static class Builder {
199        private final SubscriptionPlan plan;
200
201        /** {@hide} */
202        public Builder(ZonedDateTime start, ZonedDateTime end, Period period) {
203            plan = new SubscriptionPlan(new RecurrenceRule(start, end, period));
204        }
205
206        /**
207         * Start defining a {@link SubscriptionPlan} that covers a very specific
208         * window of time, and never automatically recurs.
209         */
210        public static Builder createNonrecurring(ZonedDateTime start, ZonedDateTime end) {
211            if (!end.isAfter(start)) {
212                throw new IllegalArgumentException(
213                        "End " + end + " isn't after start " + start);
214            }
215            return new Builder(start, end, null);
216        }
217
218        /**
219         * Start defining a {@link SubscriptionPlan} that will recur
220         * automatically every month. It will always recur on the same day of a
221         * particular month. When a particular month ends before the defined
222         * recurrence day, the plan will recur on the last instant of that
223         * month.
224         */
225        public static Builder createRecurringMonthly(ZonedDateTime start) {
226            return new Builder(start, null, Period.ofMonths(1));
227        }
228
229        /**
230         * Start defining a {@link SubscriptionPlan} that will recur
231         * automatically every week.
232         */
233        public static Builder createRecurringWeekly(ZonedDateTime start) {
234            return new Builder(start, null, Period.ofDays(7));
235        }
236
237        /**
238         * Start defining a {@link SubscriptionPlan} that will recur
239         * automatically every day.
240         */
241        public static Builder createRecurringDaily(ZonedDateTime start) {
242            return new Builder(start, null, Period.ofDays(1));
243        }
244
245        public SubscriptionPlan build() {
246            return plan;
247        }
248
249        /** Set the short title of this plan. */
250        public Builder setTitle(@Nullable CharSequence title) {
251            plan.title = title;
252            return this;
253        }
254
255        /** Set the short summary of this plan. */
256        public Builder setSummary(@Nullable CharSequence summary) {
257            plan.summary = summary;
258            return this;
259        }
260
261        /**
262         * Set the usage threshold at which data access changes.
263         *
264         * @param dataLimitBytes the usage threshold at which data access
265         *            changes
266         * @param dataLimitBehavior the behavior of data access when usage
267         *            reaches the threshold
268         */
269        public Builder setDataLimit(@BytesLong long dataLimitBytes,
270                @LimitBehavior int dataLimitBehavior) {
271            if (dataLimitBytes < 0) {
272                throw new IllegalArgumentException("Limit bytes must be positive");
273            }
274            if (dataLimitBehavior < 0) {
275                throw new IllegalArgumentException("Limit behavior must be defined");
276            }
277            plan.dataLimitBytes = dataLimitBytes;
278            plan.dataLimitBehavior = dataLimitBehavior;
279            return this;
280        }
281
282        /**
283         * Set a snapshot of currently known mobile data usage.
284         *
285         * @param dataUsageBytes the currently known mobile data usage
286         * @param dataUsageTime the time at which this snapshot was valid
287         */
288        public Builder setDataUsage(@BytesLong long dataUsageBytes,
289                @CurrentTimeMillisLong long dataUsageTime) {
290            if (dataUsageBytes < 0) {
291                throw new IllegalArgumentException("Usage bytes must be positive");
292            }
293            if (dataUsageTime < 0) {
294                throw new IllegalArgumentException("Usage time must be positive");
295            }
296            plan.dataUsageBytes = dataUsageBytes;
297            plan.dataUsageTime = dataUsageTime;
298            return this;
299        }
300    }
301}
302