method public android.os.PersistableBundle getConfigForSubId(int);
method public void notifyConfigChangedForSubId(int);
field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
- field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
+ field public static final deprecated int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool";
field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+ field public static final java.lang.String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
- field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
- field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
+ field public static final deprecated java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
+ field public static final deprecated java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName";
field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent";
- field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
+ field public static final deprecated java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
method public static int getDefaultSmsSubscriptionId();
method public static int getDefaultSubscriptionId();
method public static int getDefaultVoiceSubscriptionId();
+ method public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
method public boolean isNetworkRoaming(int);
method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
+ method public void setSubscriptionPlans(int, java.util.List<android.telephony.SubscriptionPlan>);
field public static final java.lang.String ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED";
field public static final java.lang.String ACTION_DEFAULT_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED";
field public static final int DATA_ROAMING_DISABLE = 0; // 0x0
method public void onSubscriptionsChanged();
}
+ public final class SubscriptionPlan implements android.os.Parcelable {
+ method public java.util.Iterator<android.util.Pair<java.time.ZonedDateTime, java.time.ZonedDateTime>> cycleIterator();
+ method public int describeContents();
+ method public int getDataLimitBehavior();
+ method public long getDataLimitBytes();
+ method public long getDataUsageBytes();
+ method public long getDataUsageTime();
+ method public java.lang.CharSequence getSummary();
+ method public java.lang.CharSequence getTitle();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final long BYTES_UNKNOWN = -1L; // 0xffffffffffffffffL
+ field public static final long BYTES_UNLIMITED = 9223372036854775807L; // 0x7fffffffffffffffL
+ field public static final android.os.Parcelable.Creator<android.telephony.SubscriptionPlan> CREATOR;
+ field public static final int LIMIT_BEHAVIOR_BILLED = 1; // 0x1
+ field public static final int LIMIT_BEHAVIOR_DISABLED = 0; // 0x0
+ field public static final int LIMIT_BEHAVIOR_THROTTLED = 2; // 0x2
+ field public static final int LIMIT_BEHAVIOR_UNKNOWN = -1; // 0xffffffff
+ field public static final long TIME_UNKNOWN = -1L; // 0xffffffffffffffffL
+ }
+
+ public static class SubscriptionPlan.Builder {
+ method public android.telephony.SubscriptionPlan build();
+ method public static android.telephony.SubscriptionPlan.Builder createNonrecurring(java.time.ZonedDateTime, java.time.ZonedDateTime);
+ method public static android.telephony.SubscriptionPlan.Builder createRecurringDaily(java.time.ZonedDateTime);
+ method public static android.telephony.SubscriptionPlan.Builder createRecurringMonthly(java.time.ZonedDateTime);
+ method public static android.telephony.SubscriptionPlan.Builder createRecurringWeekly(java.time.ZonedDateTime);
+ method public android.telephony.SubscriptionPlan.Builder setDataLimit(long, int);
+ method public android.telephony.SubscriptionPlan.Builder setDataUsage(long, long);
+ method public android.telephony.SubscriptionPlan.Builder setSummary(java.lang.CharSequence);
+ method public android.telephony.SubscriptionPlan.Builder setTitle(java.lang.CharSequence);
+ }
+
public class TelephonyManager {
method public boolean canChangeDtmfToneLength();
method public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle);
field public static final java.lang.String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES";
field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final java.lang.String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
- field public static final java.lang.String MANAGE_FALLBACK_SUBSCRIPTION_PLANS = "android.permission.MANAGE_FALLBACK_SUBSCRIPTION_PLANS";
field public static final java.lang.String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
field public static final java.lang.String MANAGE_USB = "android.permission.MANAGE_USB";
field public static final java.lang.String MANAGE_USERS = "android.permission.MANAGE_USERS";
method public void notifyConfigChangedForSubId(int);
method public void updateConfigForPhoneId(int, java.lang.String);
field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
- field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
+ field public static final deprecated int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool";
field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+ field public static final java.lang.String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
- field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
- field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
+ field public static final deprecated java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
+ field public static final deprecated java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName";
field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent";
- field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
+ field public static final deprecated java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
method public static int getDefaultSmsSubscriptionId();
method public static int getDefaultSubscriptionId();
method public static int getDefaultVoiceSubscriptionId();
+ method public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
method public boolean isNetworkRoaming(int);
method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
+ method public void setSubscriptionPlans(int, java.util.List<android.telephony.SubscriptionPlan>);
field public static final java.lang.String ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED";
field public static final java.lang.String ACTION_DEFAULT_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED";
field public static final int DATA_ROAMING_DISABLE = 0; // 0x0
method public void onSubscriptionsChanged();
}
+ public final class SubscriptionPlan implements android.os.Parcelable {
+ method public java.util.Iterator<android.util.Pair<java.time.ZonedDateTime, java.time.ZonedDateTime>> cycleIterator();
+ method public int describeContents();
+ method public int getDataLimitBehavior();
+ method public long getDataLimitBytes();
+ method public long getDataUsageBytes();
+ method public long getDataUsageTime();
+ method public java.lang.CharSequence getSummary();
+ method public java.lang.CharSequence getTitle();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final long BYTES_UNKNOWN = -1L; // 0xffffffffffffffffL
+ field public static final long BYTES_UNLIMITED = 9223372036854775807L; // 0x7fffffffffffffffL
+ field public static final android.os.Parcelable.Creator<android.telephony.SubscriptionPlan> CREATOR;
+ field public static final int LIMIT_BEHAVIOR_BILLED = 1; // 0x1
+ field public static final int LIMIT_BEHAVIOR_DISABLED = 0; // 0x0
+ field public static final int LIMIT_BEHAVIOR_THROTTLED = 2; // 0x2
+ field public static final int LIMIT_BEHAVIOR_UNKNOWN = -1; // 0xffffffff
+ field public static final long TIME_UNKNOWN = -1L; // 0xffffffffffffffffL
+ }
+
+ public static class SubscriptionPlan.Builder {
+ method public android.telephony.SubscriptionPlan build();
+ method public static android.telephony.SubscriptionPlan.Builder createNonrecurring(java.time.ZonedDateTime, java.time.ZonedDateTime);
+ method public static android.telephony.SubscriptionPlan.Builder createRecurringDaily(java.time.ZonedDateTime);
+ method public static android.telephony.SubscriptionPlan.Builder createRecurringMonthly(java.time.ZonedDateTime);
+ method public static android.telephony.SubscriptionPlan.Builder createRecurringWeekly(java.time.ZonedDateTime);
+ method public android.telephony.SubscriptionPlan.Builder setDataLimit(long, int);
+ method public android.telephony.SubscriptionPlan.Builder setDataUsage(long, long);
+ method public android.telephony.SubscriptionPlan.Builder setSummary(java.lang.CharSequence);
+ method public android.telephony.SubscriptionPlan.Builder setTitle(java.lang.CharSequence);
+ }
+
public final class TelephonyHistogram implements android.os.Parcelable {
ctor public TelephonyHistogram(int, int, int);
ctor public TelephonyHistogram(android.telephony.TelephonyHistogram);
method public android.os.PersistableBundle getConfigForSubId(int);
method public void notifyConfigChangedForSubId(int);
field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
- field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
+ field public static final deprecated int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool";
field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+ field public static final java.lang.String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
- field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
- field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
+ field public static final deprecated java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
+ field public static final deprecated java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName";
field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent";
- field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
+ field public static final deprecated java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
method public static int getDefaultSmsSubscriptionId();
method public static int getDefaultSubscriptionId();
method public static int getDefaultVoiceSubscriptionId();
+ method public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
method public boolean isNetworkRoaming(int);
method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
+ method public void setSubscriptionPlans(int, java.util.List<android.telephony.SubscriptionPlan>);
field public static final java.lang.String ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED";
field public static final java.lang.String ACTION_DEFAULT_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED";
field public static final int DATA_ROAMING_DISABLE = 0; // 0x0
method public void onSubscriptionsChanged();
}
+ public final class SubscriptionPlan implements android.os.Parcelable {
+ method public java.util.Iterator<android.util.Pair<java.time.ZonedDateTime, java.time.ZonedDateTime>> cycleIterator();
+ method public int describeContents();
+ method public int getDataLimitBehavior();
+ method public long getDataLimitBytes();
+ method public long getDataUsageBytes();
+ method public long getDataUsageTime();
+ method public java.lang.CharSequence getSummary();
+ method public java.lang.CharSequence getTitle();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final long BYTES_UNKNOWN = -1L; // 0xffffffffffffffffL
+ field public static final long BYTES_UNLIMITED = 9223372036854775807L; // 0x7fffffffffffffffL
+ field public static final android.os.Parcelable.Creator<android.telephony.SubscriptionPlan> CREATOR;
+ field public static final int LIMIT_BEHAVIOR_BILLED = 1; // 0x1
+ field public static final int LIMIT_BEHAVIOR_DISABLED = 0; // 0x0
+ field public static final int LIMIT_BEHAVIOR_THROTTLED = 2; // 0x2
+ field public static final int LIMIT_BEHAVIOR_UNKNOWN = -1; // 0xffffffff
+ field public static final long TIME_UNKNOWN = -1L; // 0xffffffffffffffffL
+ }
+
+ public static class SubscriptionPlan.Builder {
+ method public android.telephony.SubscriptionPlan build();
+ method public static android.telephony.SubscriptionPlan.Builder createNonrecurring(java.time.ZonedDateTime, java.time.ZonedDateTime);
+ method public static android.telephony.SubscriptionPlan.Builder createRecurringDaily(java.time.ZonedDateTime);
+ method public static android.telephony.SubscriptionPlan.Builder createRecurringMonthly(java.time.ZonedDateTime);
+ method public static android.telephony.SubscriptionPlan.Builder createRecurringWeekly(java.time.ZonedDateTime);
+ method public android.telephony.SubscriptionPlan.Builder setDataLimit(long, int);
+ method public android.telephony.SubscriptionPlan.Builder setDataUsage(long, long);
+ method public android.telephony.SubscriptionPlan.Builder setSummary(java.lang.CharSequence);
+ method public android.telephony.SubscriptionPlan.Builder setTitle(java.lang.CharSequence);
+ }
+
public class TelephonyManager {
method public boolean canChangeDtmfToneLength();
method public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle);
SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage);
void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage);
- String getSubscriptionPlanOwner(int subId);
-
void factoryReset(String subscriber);
}
package android.net;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
import android.os.Parcel;
import android.os.Parcelable;
import android.util.BackupUtils;
+import android.util.Pair;
+import android.util.RecurrenceRule;
+
+import com.android.internal.util.Preconditions;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Iterator;
import java.util.Objects;
/**
* @hide
*/
public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
- /**
- * Current Version of the Backup Serializer.
- */
- private static final int BACKUP_VERSION = 1;
+ private static final int VERSION_INIT = 1;
+ private static final int VERSION_RULE = 2;
public static final int CYCLE_NONE = -1;
public static final long WARNING_DISABLED = -1;
public static final long SNOOZE_NEVER = -1;
public NetworkTemplate template;
- @Deprecated public int cycleDay = CYCLE_NONE;
- @Deprecated public String cycleTimezone = "UTC";
+ public RecurrenceRule cycleRule;
public long warningBytes = WARNING_DISABLED;
public long limitBytes = LIMIT_DISABLED;
public long lastWarningSnooze = SNOOZE_NEVER;
private static final long DEFAULT_MTU = 1500;
- public NetworkPolicy() {
+ public static RecurrenceRule buildRule(int cycleDay, ZoneId cycleTimezone) {
+ if (cycleDay != NetworkPolicy.CYCLE_NONE) {
+ return RecurrenceRule.buildRecurringMonthly(cycleDay, cycleTimezone);
+ } else {
+ return RecurrenceRule.buildNever();
+ }
}
@Deprecated
SNOOZE_NEVER, metered, false);
}
+ @Deprecated
public NetworkPolicy(NetworkTemplate template, int cycleDay, String cycleTimezone,
long warningBytes, long limitBytes, long lastWarningSnooze, long lastLimitSnooze,
boolean metered, boolean inferred) {
- this.template = checkNotNull(template, "missing NetworkTemplate");
- this.cycleDay = cycleDay;
- this.cycleTimezone = checkNotNull(cycleTimezone, "missing cycleTimezone");
+ this(template, buildRule(cycleDay, ZoneId.of(cycleTimezone)), warningBytes,
+ limitBytes, lastWarningSnooze, lastLimitSnooze, metered, inferred);
+ }
+
+ public NetworkPolicy(NetworkTemplate template, RecurrenceRule cycleRule, long warningBytes,
+ long limitBytes, long lastWarningSnooze, long lastLimitSnooze, boolean metered,
+ boolean inferred) {
+ this.template = Preconditions.checkNotNull(template, "missing NetworkTemplate");
+ this.cycleRule = Preconditions.checkNotNull(cycleRule, "missing RecurrenceRule");
this.warningBytes = warningBytes;
this.limitBytes = limitBytes;
this.lastWarningSnooze = lastWarningSnooze;
this.inferred = inferred;
}
- public NetworkPolicy(Parcel in) {
- template = in.readParcelable(null);
- cycleDay = in.readInt();
- cycleTimezone = in.readString();
- warningBytes = in.readLong();
- limitBytes = in.readLong();
- lastWarningSnooze = in.readLong();
- lastLimitSnooze = in.readLong();
- metered = in.readInt() != 0;
- inferred = in.readInt() != 0;
+ private NetworkPolicy(Parcel source) {
+ template = source.readParcelable(null);
+ cycleRule = source.readParcelable(null);
+ warningBytes = source.readLong();
+ limitBytes = source.readLong();
+ lastWarningSnooze = source.readLong();
+ lastLimitSnooze = source.readLong();
+ metered = source.readInt() != 0;
+ inferred = source.readInt() != 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(template, flags);
- dest.writeInt(cycleDay);
- dest.writeString(cycleTimezone);
+ dest.writeParcelable(cycleRule, flags);
dest.writeLong(warningBytes);
dest.writeLong(limitBytes);
dest.writeLong(lastWarningSnooze);
return 0;
}
+ public Iterator<Pair<ZonedDateTime, ZonedDateTime>> cycleIterator() {
+ return cycleRule.cycleIterator();
+ }
+
/**
* Test if given measurement is over {@link #warningBytes}.
*/
* Test if this policy has a cycle defined, after which usage should reset.
*/
public boolean hasCycle() {
- return cycleDay != CYCLE_NONE;
+ return cycleRule.cycleIterator().hasNext();
}
@Override
@Override
public int hashCode() {
- return Objects.hash(template, cycleDay, cycleTimezone, warningBytes, limitBytes,
+ return Objects.hash(template, cycleRule, warningBytes, limitBytes,
lastWarningSnooze, lastLimitSnooze, metered, inferred);
}
public boolean equals(Object obj) {
if (obj instanceof NetworkPolicy) {
final NetworkPolicy other = (NetworkPolicy) obj;
- return cycleDay == other.cycleDay && warningBytes == other.warningBytes
+ return warningBytes == other.warningBytes
&& limitBytes == other.limitBytes
&& lastWarningSnooze == other.lastWarningSnooze
&& lastLimitSnooze == other.lastLimitSnooze && metered == other.metered
&& inferred == other.inferred
- && Objects.equals(cycleTimezone, other.cycleTimezone)
- && Objects.equals(template, other.template);
+ && Objects.equals(template, other.template)
+ && Objects.equals(cycleRule, other.cycleRule);
}
return false;
}
@Override
public String toString() {
- final StringBuilder builder = new StringBuilder("NetworkPolicy");
- builder.append("[").append(template).append("]:");
- builder.append(" cycleDay=").append(cycleDay);
- builder.append(", cycleTimezone=").append(cycleTimezone);
- builder.append(", warningBytes=").append(warningBytes);
- builder.append(", limitBytes=").append(limitBytes);
- builder.append(", lastWarningSnooze=").append(lastWarningSnooze);
- builder.append(", lastLimitSnooze=").append(lastLimitSnooze);
- builder.append(", metered=").append(metered);
- builder.append(", inferred=").append(inferred);
- return builder.toString();
+ return new StringBuilder("NetworkPolicy{")
+ .append("template=").append(template)
+ .append(" cycleRule=").append(cycleRule)
+ .append(" warningBytes=").append(warningBytes)
+ .append(" limitBytes=").append(limitBytes)
+ .append(" lastWarningSnooze=").append(lastWarningSnooze)
+ .append(" lastLimitSnooze=").append(lastLimitSnooze)
+ .append(" metered=").append(metered)
+ .append(" inferred=").append(inferred)
+ .append("}").toString();
}
public static final Creator<NetworkPolicy> CREATOR = new Creator<NetworkPolicy>() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos);
- out.writeInt(BACKUP_VERSION);
+ out.writeInt(VERSION_RULE);
out.write(template.getBytesForBackup());
- out.writeInt(cycleDay);
- BackupUtils.writeString(out, cycleTimezone);
+ cycleRule.writeToStream(out);
out.writeLong(warningBytes);
out.writeLong(limitBytes);
out.writeLong(lastWarningSnooze);
public static NetworkPolicy getNetworkPolicyFromBackup(DataInputStream in) throws IOException,
BackupUtils.BadVersionException {
- int version = in.readInt();
- if (version < 1 || version > BACKUP_VERSION) {
- throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version");
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_INIT: {
+ NetworkTemplate template = NetworkTemplate.getNetworkTemplateFromBackup(in);
+ int cycleDay = in.readInt();
+ String cycleTimeZone = BackupUtils.readString(in);
+ long warningBytes = in.readLong();
+ long limitBytes = in.readLong();
+ long lastWarningSnooze = in.readLong();
+ long lastLimitSnooze = in.readLong();
+ boolean metered = in.readInt() == 1;
+ boolean inferred = in.readInt() == 1;
+ return new NetworkPolicy(template, cycleDay, cycleTimeZone, warningBytes,
+ limitBytes, lastWarningSnooze, lastLimitSnooze, metered, inferred);
+ }
+ case VERSION_RULE: {
+ NetworkTemplate template = NetworkTemplate.getNetworkTemplateFromBackup(in);
+ RecurrenceRule cycleRule = new RecurrenceRule(in);
+ long warningBytes = in.readLong();
+ long limitBytes = in.readLong();
+ long lastWarningSnooze = in.readLong();
+ long lastLimitSnooze = in.readLong();
+ boolean metered = in.readInt() == 1;
+ boolean inferred = in.readInt() == 1;
+ return new NetworkPolicy(template, cycleRule, warningBytes,
+ limitBytes, lastWarningSnooze, lastLimitSnooze, metered, inferred);
+ }
+ default: {
+ throw new BackupUtils.BadVersionException("Unknown backup version: " + version);
+ }
}
-
- NetworkTemplate template = NetworkTemplate.getNetworkTemplateFromBackup(in);
- int cycleDay = in.readInt();
- String cycleTimeZone = BackupUtils.readString(in);
- long warningBytes = in.readLong();
- long limitBytes = in.readLong();
- long lastWarningSnooze = in.readLong();
- long lastLimitSnooze = in.readLong();
- boolean metered = in.readInt() == 1;
- boolean inferred = in.readInt() == 1;
- return new NetworkPolicy(template, cycleDay, cycleTimeZone, warningBytes, limitBytes,
- lastWarningSnooze, lastLimitSnooze, metered, inferred);
}
}
/** {@hide} */
public static Iterator<Pair<ZonedDateTime, ZonedDateTime>> cycleIterator(NetworkPolicy policy) {
- return SubscriptionPlan.convert(policy).cycleIterator();
+ return policy.cycleIterator();
}
/**
--- /dev/null
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.time.Clock;
+import java.time.LocalTime;
+import java.time.Period;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Iterator;
+import java.util.Objects;
+
+/**
+ * Description of an event that should recur over time at a specific interval
+ * between two anchor points in time.
+ *
+ * @hide
+ */
+public class RecurrenceRule implements Parcelable {
+ private static final String TAG = "RecurrenceRule";
+ private static final boolean DEBUG = true;
+
+ private static final int VERSION_INIT = 0;
+
+ /** {@hide} */
+ @VisibleForTesting
+ public static Clock sClock = Clock.systemDefaultZone();
+
+ public final ZonedDateTime start;
+ public final ZonedDateTime end;
+ public final Period period;
+
+ public RecurrenceRule(ZonedDateTime start, ZonedDateTime end, Period period) {
+ this.start = start;
+ this.end = end;
+ this.period = period;
+ }
+
+ @Deprecated
+ public static RecurrenceRule buildNever() {
+ return new RecurrenceRule(null, null, null);
+ }
+
+ @Deprecated
+ public static RecurrenceRule buildRecurringMonthly(int dayOfMonth, ZoneId zone) {
+ // Assume we started last January, since it has all possible days
+ final ZonedDateTime now = ZonedDateTime.now(sClock).withZoneSameInstant(zone);
+ final ZonedDateTime start = ZonedDateTime.of(
+ now.toLocalDate().minusYears(1).withMonth(1).withDayOfMonth(dayOfMonth),
+ LocalTime.MIDNIGHT, zone);
+ return new RecurrenceRule(start, null, Period.ofMonths(1));
+ }
+
+ private RecurrenceRule(Parcel source) {
+ start = convertZonedDateTime(source.readString());
+ end = convertZonedDateTime(source.readString());
+ period = convertPeriod(source.readString());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(convertZonedDateTime(start));
+ dest.writeString(convertZonedDateTime(end));
+ dest.writeString(convertPeriod(period));
+ }
+
+ public RecurrenceRule(DataInputStream in) throws IOException {
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_INIT:
+ start = convertZonedDateTime(BackupUtils.readString(in));
+ end = convertZonedDateTime(BackupUtils.readString(in));
+ period = convertPeriod(BackupUtils.readString(in));
+ default:
+ throw new ProtocolException("Unknown version " + version);
+ }
+ }
+
+ public void writeToStream(DataOutputStream out) throws IOException {
+ out.writeInt(VERSION_INIT);
+ BackupUtils.writeString(out, convertZonedDateTime(start));
+ BackupUtils.writeString(out, convertZonedDateTime(end));
+ BackupUtils.writeString(out, convertPeriod(period));
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("RecurrenceRule{")
+ .append("start=").append(start)
+ .append(" end=").append(end)
+ .append(" period=").append(period)
+ .append("}").toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(start, end, period);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof RecurrenceRule) {
+ final RecurrenceRule other = (RecurrenceRule) obj;
+ return Objects.equals(start, other.start)
+ && Objects.equals(end, other.end)
+ && Objects.equals(period, other.period);
+ }
+ return false;
+ }
+
+ public static final Parcelable.Creator<RecurrenceRule> CREATOR = new Parcelable.Creator<RecurrenceRule>() {
+ @Override
+ public RecurrenceRule createFromParcel(Parcel source) {
+ return new RecurrenceRule(source);
+ }
+
+ @Override
+ public RecurrenceRule[] newArray(int size) {
+ return new RecurrenceRule[size];
+ }
+ };
+
+ @Deprecated
+ public boolean isMonthly() {
+ return start != null
+ && period != null
+ && period.getYears() == 0
+ && period.getMonths() == 1
+ && period.getDays() == 0;
+ }
+
+ public Iterator<Pair<ZonedDateTime, ZonedDateTime>> cycleIterator() {
+ if (period != null) {
+ return new RecurringIterator();
+ } else {
+ return new NonrecurringIterator();
+ }
+ }
+
+ private class NonrecurringIterator implements Iterator<Pair<ZonedDateTime, ZonedDateTime>> {
+ boolean hasNext;
+
+ public NonrecurringIterator() {
+ hasNext = (start != null) && (end != null);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return hasNext;
+ }
+
+ @Override
+ public Pair<ZonedDateTime, ZonedDateTime> next() {
+ hasNext = false;
+ return new Pair<>(start, end);
+ }
+ }
+
+ private class RecurringIterator implements Iterator<Pair<ZonedDateTime, ZonedDateTime>> {
+ int i;
+ ZonedDateTime cycleStart;
+ ZonedDateTime cycleEnd;
+
+ public RecurringIterator() {
+ final ZonedDateTime anchor = (end != null) ? end
+ : ZonedDateTime.now(sClock).withZoneSameInstant(start.getZone());
+ if (DEBUG) Log.d(TAG, "Resolving using anchor " + anchor);
+
+ updateCycle();
+
+ // Walk forwards until we find first cycle after now
+ while (anchor.toEpochSecond() > cycleEnd.toEpochSecond()) {
+ i++;
+ updateCycle();
+ }
+
+ // Walk backwards until we find first cycle before now
+ while (anchor.toEpochSecond() <= cycleStart.toEpochSecond()) {
+ i--;
+ updateCycle();
+ }
+ }
+
+ private void updateCycle() {
+ cycleStart = roundBoundaryTime(start.plus(period.multipliedBy(i)));
+ cycleEnd = roundBoundaryTime(start.plus(period.multipliedBy(i + 1)));
+ }
+
+ private ZonedDateTime roundBoundaryTime(ZonedDateTime boundary) {
+ if (isMonthly() && (boundary.getDayOfMonth() < start.getDayOfMonth())) {
+ // When forced to end a monthly cycle early, we want to count
+ // that entire day against the boundary.
+ return ZonedDateTime.of(boundary.toLocalDate(), LocalTime.MAX, start.getZone());
+ } else {
+ return boundary;
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return cycleStart.toEpochSecond() >= start.toEpochSecond();
+ }
+
+ @Override
+ public Pair<ZonedDateTime, ZonedDateTime> next() {
+ if (DEBUG) Log.d(TAG, "Cycle " + i + " from " + cycleStart + " to " + cycleEnd);
+ Pair<ZonedDateTime, ZonedDateTime> p = new Pair<>(cycleStart, cycleEnd);
+ i--;
+ updateCycle();
+ return p;
+ }
+ }
+
+ public static String convertZonedDateTime(ZonedDateTime time) {
+ return time != null ? time.toString() : null;
+ }
+
+ public static ZonedDateTime convertZonedDateTime(String time) {
+ return time != null ? ZonedDateTime.parse(time) : null;
+ }
+
+ public static String convertPeriod(Period period) {
+ return period != null ? period.toString() : null;
+ }
+
+ public static Period convertPeriod(String period) {
+ return period != null ? Period.parse(period) : null;
+ }
+}
return in.getAttributeValue(null, name);
}
- public static void writeStringAttribute(XmlSerializer out, String name, String value)
+ public static void writeStringAttribute(XmlSerializer out, String name, CharSequence value)
throws IOException {
if (value != null) {
- out.attribute(null, name, value);
+ out.attribute(null, name, value.toString());
}
}
<permission android:name="android.permission.MANAGE_NETWORK_POLICY"
android:protectionLevel="signature" />
- <!-- @SystemApi Allows an application to manage fallback subscription plans.
- Note that another app providing plans for an explicit HNI will always
- take precidence over these fallback plans. @hide -->
- <permission android:name="android.permission.MANAGE_FALLBACK_SUBSCRIPTION_PLANS"
- android:protectionLevel="signature|privileged" />
-
<!-- @SystemApi @hide @deprecated use UPDATE_DEVICE_STATS instead -->
<permission android:name="android.permission.MODIFY_NETWORK_ACCOUNTING"
android:protectionLevel="signature|privileged" />
--- /dev/null
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.support.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.time.Period;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Iterator;
+
+@SmallTest
+public class RecurrenceRuleTest extends TestCase {
+
+ static Clock sOriginalClock;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ sOriginalClock = RecurrenceRule.sClock;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ RecurrenceRule.sClock = sOriginalClock;
+ }
+
+ private void setClock(Instant instant) {
+ RecurrenceRule.sClock = Clock.fixed(instant, ZoneId.systemDefault());
+ }
+
+ public void testSimpleMonth() throws Exception {
+ setClock(Instant.parse("2015-11-20T10:15:30.00Z"));
+ final RecurrenceRule r = new RecurrenceRule(
+ ZonedDateTime.parse("2010-11-14T00:00:00.000Z"),
+ null,
+ Period.ofMonths(1));
+
+ assertTrue(r.isMonthly());
+
+ final Iterator<Pair<ZonedDateTime, ZonedDateTime>> it = r.cycleIterator();
+ assertTrue(it.hasNext());
+ assertEquals(Pair.create(
+ ZonedDateTime.parse("2015-11-14T00:00:00.00Z"),
+ ZonedDateTime.parse("2015-12-14T00:00:00.00Z")), it.next());
+ assertTrue(it.hasNext());
+ assertEquals(Pair.create(
+ ZonedDateTime.parse("2015-10-14T00:00:00.00Z"),
+ ZonedDateTime.parse("2015-11-14T00:00:00.00Z")), it.next());
+ }
+
+ public void testSimpleDays() throws Exception {
+ setClock(Instant.parse("2015-01-01T10:15:30.00Z"));
+ final RecurrenceRule r = new RecurrenceRule(
+ ZonedDateTime.parse("2010-11-14T00:11:00.000Z"),
+ ZonedDateTime.parse("2010-11-20T00:11:00.000Z"),
+ Period.ofDays(3));
+
+ assertFalse(r.isMonthly());
+
+ final Iterator<Pair<ZonedDateTime, ZonedDateTime>> it = r.cycleIterator();
+ assertTrue(it.hasNext());
+ assertEquals(Pair.create(
+ ZonedDateTime.parse("2010-11-17T00:11:00.00Z"),
+ ZonedDateTime.parse("2010-11-20T00:11:00.00Z")), it.next());
+ assertTrue(it.hasNext());
+ assertEquals(Pair.create(
+ ZonedDateTime.parse("2010-11-14T00:11:00.00Z"),
+ ZonedDateTime.parse("2010-11-17T00:11:00.00Z")), it.next());
+ assertFalse(it.hasNext());
+ }
+
+ public void testNotRecurring() throws Exception {
+ setClock(Instant.parse("2015-01-01T10:15:30.00Z"));
+ final RecurrenceRule r = new RecurrenceRule(
+ ZonedDateTime.parse("2010-11-14T00:11:00.000Z"),
+ ZonedDateTime.parse("2010-11-20T00:11:00.000Z"),
+ null);
+
+ assertFalse(r.isMonthly());
+
+ final Iterator<Pair<ZonedDateTime, ZonedDateTime>> it = r.cycleIterator();
+ assertTrue(it.hasNext());
+ assertEquals(Pair.create(
+ ZonedDateTime.parse("2010-11-14T00:11:00.000Z"),
+ ZonedDateTime.parse("2010-11-20T00:11:00.000Z")), it.next());
+ assertFalse(it.hasNext());
+ }
+
+ public void testNever() throws Exception {
+ setClock(Instant.parse("2015-01-01T10:15:30.00Z"));
+ final RecurrenceRule r = RecurrenceRule.buildNever();
+
+ assertFalse(r.isMonthly());
+
+ final Iterator<Pair<ZonedDateTime, ZonedDateTime>> it = r.cycleIterator();
+ assertFalse(it.hasNext());
+ }
+
+ public void testSane() throws Exception {
+ final RecurrenceRule r = new RecurrenceRule(
+ ZonedDateTime.parse("1980-01-31T00:00:00.000Z"),
+ ZonedDateTime.parse("2030-01-31T00:00:00.000Z"),
+ Period.ofMonths(1));
+
+ final Iterator<Pair<ZonedDateTime, ZonedDateTime>> it = r.cycleIterator();
+ ZonedDateTime lastStart = null;
+ int months = 0;
+ while (it.hasNext()) {
+ final Pair<ZonedDateTime, ZonedDateTime> cycle = it.next();
+
+ // Make sure cycle has reasonable length
+ final long length = cycle.second.toEpochSecond() - cycle.first.toEpochSecond();
+ assertTrue(cycle + " must be more than 4 weeks", length >= 2419200);
+ assertTrue(cycle + " must be less than 5 weeks", length <= 3024000);
+
+ // Make sure we have no gaps
+ if (lastStart != null) {
+ assertEquals(lastStart, cycle.second);
+ }
+ lastStart = cycle.first;
+ months++;
+ }
+
+ assertEquals(600, months);
+ }
+}
import android.net.wifi.WifiInfo;
import android.os.AsyncTask;
import android.text.TextUtils;
-import android.text.format.Time;
+import android.util.RecurrenceRule;
import com.google.android.collect.Lists;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
import java.util.ArrayList;
/**
@Deprecated
private static NetworkPolicy buildDefaultPolicy(NetworkTemplate template) {
// TODO: move this into framework to share with NetworkPolicyManagerService
- final int cycleDay;
- final String cycleTimezone;
+ final RecurrenceRule cycleRule;
final boolean metered;
if (template.getMatchRule() == MATCH_WIFI) {
- cycleDay = CYCLE_NONE;
- cycleTimezone = Time.TIMEZONE_UTC;
+ cycleRule = RecurrenceRule.buildNever();
metered = false;
} else {
- final Time time = new Time();
- time.setToNow();
- cycleDay = time.monthDay;
- cycleTimezone = time.timezone;
+ cycleRule = RecurrenceRule.buildRecurringMonthly(ZonedDateTime.now().getDayOfMonth(),
+ ZoneId.systemDefault());
metered = true;
}
- return new NetworkPolicy(template, cycleDay, cycleTimezone, WARNING_DISABLED,
+ return new NetworkPolicy(template, cycleRule, WARNING_DISABLED,
LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, metered, true);
}
+ @Deprecated
public int getPolicyCycleDay(NetworkTemplate template) {
final NetworkPolicy policy = getPolicy(template);
- return (policy != null) ? policy.cycleDay : CYCLE_NONE;
+ if (policy != null && policy.cycleRule.isMonthly()) {
+ return policy.cycleRule.start.getDayOfMonth();
+ } else {
+ return CYCLE_NONE;
+ }
}
+ @Deprecated
public void setPolicyCycleDay(NetworkTemplate template, int cycleDay, String cycleTimezone) {
final NetworkPolicy policy = getOrCreatePolicy(template);
- policy.cycleDay = cycleDay;
- policy.cycleTimezone = cycleTimezone;
+ policy.cycleRule = NetworkPolicy.buildRule(cycleDay, ZoneId.of(cycleTimezone));
policy.inferred = false;
policy.clearSnooze();
writeAsync();
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
-import static android.Manifest.permission.MANAGE_FALLBACK_SUBSCRIPTION_PLANS;
import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.Manifest.permission.READ_PHONE_STATE;
import static android.content.Intent.ACTION_USER_ADDED;
import static android.content.Intent.ACTION_USER_REMOVED;
import static android.content.Intent.EXTRA_UID;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.readStringAttribute;
import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.Formatter;
-import android.text.format.Time;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Log;
import android.util.NtpTrustedTime;
import android.util.Pair;
+import android.util.RecurrenceRule;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TrustedTime;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
import com.android.server.DeviceIdleController;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.google.android.collect.Lists;
import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
+import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
private static final int VERSION_SWITCH_APP_ID = 8;
private static final int VERSION_ADDED_NETWORK_ID = 9;
private static final int VERSION_SWITCH_UID = 10;
- private static final int VERSION_LATEST = VERSION_SWITCH_UID;
+ private static final int VERSION_ADDED_CYCLE = 11;
+ private static final int VERSION_LATEST = VERSION_ADDED_CYCLE;
/**
* Max items written to {@link #ProcStateSeqHistory}.
private static final String TAG_POLICY_LIST = "policy-list";
private static final String TAG_NETWORK_POLICY = "network-policy";
+ private static final String TAG_SUBSCRIPTION_PLAN = "subscription-plan";
private static final String TAG_UID_POLICY = "uid-policy";
private static final String TAG_APP_POLICY = "app-policy";
private static final String TAG_WHITELIST = "whitelist";
private static final String ATTR_NETWORK_TEMPLATE = "networkTemplate";
private static final String ATTR_SUBSCRIBER_ID = "subscriberId";
private static final String ATTR_NETWORK_ID = "networkId";
- private static final String ATTR_CYCLE_DAY = "cycleDay";
- private static final String ATTR_CYCLE_TIMEZONE = "cycleTimezone";
+ @Deprecated private static final String ATTR_CYCLE_DAY = "cycleDay";
+ @Deprecated private static final String ATTR_CYCLE_TIMEZONE = "cycleTimezone";
+ private static final String ATTR_CYCLE_START = "cycleStart";
+ private static final String ATTR_CYCLE_END = "cycleEnd";
+ private static final String ATTR_CYCLE_PERIOD = "cyclePeriod";
private static final String ATTR_WARNING_BYTES = "warningBytes";
private static final String ATTR_LIMIT_BYTES = "limitBytes";
private static final String ATTR_LAST_SNOOZE = "lastSnooze";
private static final String ATTR_UID = "uid";
private static final String ATTR_APP_ID = "appId";
private static final String ATTR_POLICY = "policy";
+ private static final String ATTR_SUB_ID = "subId";
+ private static final String ATTR_TITLE = "title";
+ private static final String ATTR_SUMMARY = "summary";
+ private static final String ATTR_LIMIT_BEHAVIOR = "limitBehavior";
+ private static final String ATTR_USAGE_BYTES = "usageBytes";
+ private static final String ATTR_USAGE_TIME = "usageTime";
private static final String ACTION_ALLOW_BACKGROUND =
"com.android.server.net.action.ALLOW_BACKGROUND";
/** Currently active network rules for ifaces. */
final ArrayMap<NetworkPolicy, String[]> mNetworkRules = new ArrayMap<>();
+ /** Defined subscription plans. */
+ final SparseArray<SubscriptionPlan[]> mSubscriptionPlans = new SparseArray<>();
+
/** Defined UID policies. */
@GuardedBy("mUidRulesFirstLock") final SparseIntArray mUidPolicy = new SparseIntArray();
/** Currently derived rules for each UID. */
// cycle boundary to recompute notifications.
// examine stats for each active policy
- final long currentTime = currentTimeMillis();
for (int i = mNetworkPolicy.size()-1; i >= 0; i--) {
final NetworkPolicy policy = mNetworkPolicy.valueAt(i);
// ignore policies that aren't relevant to user
continue;
}
- final int cycleDay = getCycleDayFromCarrierConfig(config, policy.cycleDay);
+ final int currentCycleDay;
+ if (policy.cycleRule.isMonthly()) {
+ currentCycleDay = policy.cycleRule.start.getDayOfMonth();
+ } else {
+ currentCycleDay = NetworkPolicy.CYCLE_NONE;
+ }
+
+ final int cycleDay = getCycleDayFromCarrierConfig(config, currentCycleDay);
final long warningBytes = getWarningBytesFromCarrierConfig(config,
policy.warningBytes);
final long limitBytes = getLimitBytesFromCarrierConfig(config,
policy.limitBytes);
- if (policy.cycleDay == cycleDay &&
+ if (currentCycleDay == cycleDay &&
policy.warningBytes == warningBytes &&
policy.limitBytes == limitBytes) {
continue;
}
policyUpdated = true;
- policy.cycleDay = cycleDay;
+ policy.cycleRule = NetworkPolicy.buildRule(cycleDay, ZoneId.systemDefault());
policy.warningBytes = warningBytes;
policy.limitBytes = limitBytes;
// TODO: reset any policy-disabled networks when any policy is removed
// completely, which is currently rare case.
- final long currentTime = currentTimeMillis();
for (int i = mNetworkPolicy.size()-1; i >= 0; i--) {
final NetworkPolicy policy = mNetworkPolicy.valueAt(i);
// shortcut when policy has no limit
// apply each policy that we found ifaces for; compute remaining data
// based on current cycle and historical stats, and push to kernel.
- final long currentTime = currentTimeMillis();
for (int i = mNetworkRules.size()-1; i >= 0; i--) {
final NetworkPolicy policy = mNetworkRules.keyAt(i);
final String[] ifaces = mNetworkRules.valueAt(i);
public NetworkPolicy buildDefaultMobilePolicy(int subId, String subscriberId) {
PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
- // assume usage cycle starts today
- final Time time = new Time();
- time.setToNow();
-
- final String cycleTimezone = time.timezone;
-
- final int cycleDay = getCycleDayFromCarrierConfig(config, time.monthDay);
+ final int cycleDay = getCycleDayFromCarrierConfig(config,
+ ZonedDateTime.now().getDayOfMonth());
final long warningBytes = getWarningBytesFromCarrierConfig(config,
getPlatformDefaultWarningBytes());
final long limitBytes = getLimitBytesFromCarrierConfig(config,
getPlatformDefaultLimitBytes());
final NetworkTemplate template = buildTemplateMobileAll(subscriberId);
- final NetworkPolicy policy = new NetworkPolicy(template, cycleDay, cycleTimezone,
+ final RecurrenceRule cycleRule = NetworkPolicy.buildRule(cycleDay, ZoneId.systemDefault());
+ final NetworkPolicy policy = new NetworkPolicy(template, cycleRule,
warningBytes, limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, true);
return policy;
}
// clear any existing policy and read from disk
mNetworkPolicy.clear();
+ mSubscriptionPlans.clear();
mUidPolicy.clear();
FileInputStream fis = null;
} else {
networkId = null;
}
- final int cycleDay = readIntAttribute(in, ATTR_CYCLE_DAY);
- final String cycleTimezone;
- if (version >= VERSION_ADDED_TIMEZONE) {
- cycleTimezone = in.getAttributeValue(null, ATTR_CYCLE_TIMEZONE);
+ final RecurrenceRule cycleRule;
+ if (version >= VERSION_ADDED_CYCLE) {
+ final String start = readStringAttribute(in, ATTR_CYCLE_START);
+ final String end = readStringAttribute(in, ATTR_CYCLE_END);
+ final String period = readStringAttribute(in, ATTR_CYCLE_PERIOD);
+ cycleRule = new RecurrenceRule(
+ RecurrenceRule.convertZonedDateTime(start),
+ RecurrenceRule.convertZonedDateTime(end),
+ RecurrenceRule.convertPeriod(period));
} else {
- cycleTimezone = Time.TIMEZONE_UTC;
+ final int cycleDay = readIntAttribute(in, ATTR_CYCLE_DAY);
+ final String cycleTimezone;
+ if (version >= VERSION_ADDED_TIMEZONE) {
+ cycleTimezone = in.getAttributeValue(null, ATTR_CYCLE_TIMEZONE);
+ } else {
+ cycleTimezone = "UTC";
+ }
+ cycleRule = NetworkPolicy.buildRule(cycleDay, ZoneId.of(cycleTimezone));
}
final long warningBytes = readLongAttribute(in, ATTR_WARNING_BYTES);
final long limitBytes = readLongAttribute(in, ATTR_LIMIT_BYTES);
final NetworkTemplate template = new NetworkTemplate(networkTemplate,
subscriberId, networkId);
if (template.isPersistable()) {
- mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay,
- cycleTimezone, warningBytes, limitBytes, lastWarningSnooze,
+ mNetworkPolicy.put(template, new NetworkPolicy(template, cycleRule,
+ warningBytes, limitBytes, lastWarningSnooze,
lastLimitSnooze, metered, inferred));
}
+
+ } else if (TAG_SUBSCRIPTION_PLAN.equals(tag)) {
+ final String start = readStringAttribute(in, ATTR_CYCLE_START);
+ final String end = readStringAttribute(in, ATTR_CYCLE_END);
+ final String period = readStringAttribute(in, ATTR_CYCLE_PERIOD);
+ final SubscriptionPlan.Builder builder = new SubscriptionPlan.Builder(
+ RecurrenceRule.convertZonedDateTime(start),
+ RecurrenceRule.convertZonedDateTime(end),
+ RecurrenceRule.convertPeriod(period));
+ builder.setTitle(readStringAttribute(in, ATTR_TITLE));
+ builder.setSummary(readStringAttribute(in, ATTR_SUMMARY));
+
+ final long limitBytes = readLongAttribute(in, ATTR_LIMIT_BYTES,
+ SubscriptionPlan.BYTES_UNKNOWN);
+ final int limitBehavior = readIntAttribute(in, ATTR_LIMIT_BEHAVIOR,
+ SubscriptionPlan.LIMIT_BEHAVIOR_UNKNOWN);
+ if (limitBytes != SubscriptionPlan.BYTES_UNKNOWN
+ && limitBehavior != SubscriptionPlan.LIMIT_BEHAVIOR_UNKNOWN) {
+ builder.setDataLimit(limitBytes, limitBehavior);
+ }
+
+ final long usageBytes = readLongAttribute(in, ATTR_USAGE_BYTES,
+ SubscriptionPlan.BYTES_UNKNOWN);
+ final long usageTime = readLongAttribute(in, ATTR_USAGE_TIME,
+ SubscriptionPlan.TIME_UNKNOWN);
+ if (usageBytes != SubscriptionPlan.BYTES_UNKNOWN
+ && usageTime != SubscriptionPlan.TIME_UNKNOWN) {
+ builder.setDataUsage(usageBytes, usageTime);
+ }
+
+ final int subId = readIntAttribute(in, ATTR_SUB_ID);
+ final SubscriptionPlan plan = builder.build();
+ mSubscriptionPlans.put(subId, ArrayUtils.appendElement(
+ SubscriptionPlan.class, mSubscriptionPlans.get(subId), plan));
+
} else if (TAG_UID_POLICY.equals(tag)) {
final int uid = readIntAttribute(in, ATTR_UID);
final int policy = readIntAttribute(in, ATTR_POLICY);
} catch (FileNotFoundException e) {
// missing policy is okay, probably first boot
upgradeDefaultBackgroundDataUL();
- } catch (IOException e) {
- Log.wtf(TAG, "problem reading network policy", e);
- } catch (XmlPullParserException e) {
+ } catch (Exception e) {
Log.wtf(TAG, "problem reading network policy", e);
} finally {
IoUtils.closeQuietly(fis);
if (networkId != null) {
out.attribute(null, ATTR_NETWORK_ID, networkId);
}
- writeIntAttribute(out, ATTR_CYCLE_DAY, policy.cycleDay);
- out.attribute(null, ATTR_CYCLE_TIMEZONE, policy.cycleTimezone);
+ writeStringAttribute(out, ATTR_CYCLE_START,
+ RecurrenceRule.convertZonedDateTime(policy.cycleRule.start));
+ writeStringAttribute(out, ATTR_CYCLE_END,
+ RecurrenceRule.convertZonedDateTime(policy.cycleRule.end));
+ writeStringAttribute(out, ATTR_CYCLE_PERIOD,
+ RecurrenceRule.convertPeriod(policy.cycleRule.period));
writeLongAttribute(out, ATTR_WARNING_BYTES, policy.warningBytes);
writeLongAttribute(out, ATTR_LIMIT_BYTES, policy.limitBytes);
writeLongAttribute(out, ATTR_LAST_WARNING_SNOOZE, policy.lastWarningSnooze);
out.endTag(null, TAG_NETWORK_POLICY);
}
+ // write all known subscription plans
+ for (int i = 0; i < mSubscriptionPlans.size(); i++) {
+ final int subId = mSubscriptionPlans.keyAt(i);
+ final SubscriptionPlan[] plans = mSubscriptionPlans.valueAt(i);
+ if (ArrayUtils.isEmpty(plans)) continue;
+
+ for (SubscriptionPlan plan : plans) {
+ out.startTag(null, TAG_SUBSCRIPTION_PLAN);
+ writeIntAttribute(out, ATTR_SUB_ID, subId);
+ final RecurrenceRule cycleRule = plan.getCycleRule();
+ writeStringAttribute(out, ATTR_CYCLE_START,
+ RecurrenceRule.convertZonedDateTime(cycleRule.start));
+ writeStringAttribute(out, ATTR_CYCLE_END,
+ RecurrenceRule.convertZonedDateTime(cycleRule.end));
+ writeStringAttribute(out, ATTR_CYCLE_PERIOD,
+ RecurrenceRule.convertPeriod(cycleRule.period));
+ writeStringAttribute(out, ATTR_TITLE, plan.getTitle());
+ writeStringAttribute(out, ATTR_SUMMARY, plan.getSummary());
+ writeLongAttribute(out, ATTR_LIMIT_BYTES, plan.getDataLimitBytes());
+ writeIntAttribute(out, ATTR_LIMIT_BEHAVIOR, plan.getDataLimitBehavior());
+ writeLongAttribute(out, ATTR_USAGE_BYTES, plan.getDataUsageBytes());
+ writeLongAttribute(out, ATTR_USAGE_TIME, plan.getDataUsageTime());
+ out.endTag(null, TAG_SUBSCRIPTION_PLAN);
+ }
+ }
+
// write all known uid policies
for (int i = 0; i < mUidPolicy.size(); i++) {
final int uid = mUidPolicy.keyAt(i);
}
final SubscriptionInfo si;
+ final PersistableBundle config;
final long token = Binder.clearCallingIdentity();
try {
si = mContext.getSystemService(SubscriptionManager.class)
.getActiveSubscriptionInfo(subId);
+ config = mCarrierConfigManager.getConfigForSubId(subId);
} finally {
Binder.restoreCallingIdentity(token);
}
- // First check: does caller have carrier access?
+ // First check: is caller the CarrierService?
if (si.isEmbedded() && si.canManageSubscription(mContext, callingPackage)) {
- Slog.v(TAG, "Granting access because " + callingPackage + " is carrier");
return;
}
- // Second check: was caller first to claim this HNI?
- // TODO: extend to support external data sources
+ // Second check: has the CarrierService delegated access?
+ if (config != null) {
+ final String overridePackage = config
+ .getString(CarrierConfigManager.KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING, null);
+ if (!TextUtils.isEmpty(overridePackage)
+ && Objects.equals(overridePackage, callingPackage)) {
+ return;
+ }
+ }
- // Final check: does caller have fallback permission?
- if (mContext.checkCallingOrSelfPermission(
- MANAGE_FALLBACK_SUBSCRIPTION_PLANS) == PERMISSION_GRANTED) {
- Slog.v(TAG, "Granting access because " + callingPackage + " is fallback");
+ // Third check: is caller the fallback/default CarrierService?
+ final String defaultPackage = mCarrierConfigManager.getDefaultCarrierServicePackageName();
+ if (!TextUtils.isEmpty(defaultPackage)
+ && Objects.equals(defaultPackage, callingPackage)) {
return;
}
public SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage) {
enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
- // TODO: extend to support external data sources
- if (!"com.android.settings".equals(callingPackage)) {
- throw new UnsupportedOperationException();
- }
-
final String fake = SystemProperties.get("fw.fake_plan");
if (!TextUtils.isEmpty(fake)) {
final List<SubscriptionPlan> plans = new ArrayList<>();
plans.add(SubscriptionPlan.Builder
.createRecurringMonthly(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"))
.setTitle("G-Mobile")
- .setDataWarning(2 * TrafficStats.GB_IN_BYTES)
.setDataLimit(5 * TrafficStats.GB_IN_BYTES,
SubscriptionPlan.LIMIT_BEHAVIOR_BILLED)
.setDataUsage(1 * TrafficStats.GB_IN_BYTES,
.setTitle("G-Mobile is the carriers name who this plan belongs to")
.setSummary("Crazy unlimited bandwidth plan with incredibly long title "
+ "that should be cut off to prevent UI from looking terrible")
- .setDataWarning(2 * TrafficStats.GB_IN_BYTES)
.setDataLimit(5 * TrafficStats.GB_IN_BYTES,
SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
.setDataUsage(1 * TrafficStats.GB_IN_BYTES,
return plans.toArray(new SubscriptionPlan[plans.size()]);
}
- final long token = Binder.clearCallingIdentity();
- try {
- final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
- final NetworkTemplate template = NetworkTemplate
- .buildTemplateMobileAll(tm.getSubscriberId(subId));
- final NetworkPolicy policy = mNetworkPolicy.get(template);
- if (policy != null) {
- return new SubscriptionPlan[] { SubscriptionPlan.convert(policy) };
- } else {
- return new SubscriptionPlan[0];
+ synchronized (mUidRulesFirstLock) {
+ synchronized (mNetworkPoliciesSecondLock) {
+ return mSubscriptionPlans.get(subId);
}
- } finally {
- Binder.restoreCallingIdentity(token);
}
}
public void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage) {
enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
- // TODO: extend to support external data sources
- if (!"com.android.settings".equals(callingPackage)) {
- throw new UnsupportedOperationException();
+ for (SubscriptionPlan plan : plans) {
+ Preconditions.checkNotNull(plan);
}
final long token = Binder.clearCallingIdentity();
try {
- final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
- final NetworkTemplate template = NetworkTemplate
- .buildTemplateMobileAll(tm.getSubscriberId(subId));
- if (ArrayUtils.isEmpty(plans)) {
- mNetworkPolicy.remove(template);
- } else {
- final NetworkPolicy policy = SubscriptionPlan.convert(plans[0]);
- policy.template = template;
- mNetworkPolicy.put(template, policy);
+ maybeRefreshTrustedTime();
+ synchronized (mUidRulesFirstLock) {
+ synchronized (mNetworkPoliciesSecondLock) {
+ mSubscriptionPlans.put(subId, plans);
+ // TODO: update any implicit details from newly defined plans
+ handleNetworkPoliciesUpdateAL(false);
+ }
}
} finally {
Binder.restoreCallingIdentity(token);
}
@Override
- public String getSubscriptionPlanOwner(int subId) {
- mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
-
- // TODO: extend to support external data sources
- return "com.android.settings";
- }
-
- @Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
import static android.telephony.CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG;
import static android.telephony.CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG;
import static android.telephony.CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT;
-import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.Time.TIMEZONE_UTC;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkPolicy;
-import android.net.NetworkPolicyManager;
import android.net.NetworkState;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
-import android.telephony.SubscriptionPlan;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.Time;
import android.util.Log;
import android.util.Pair;
+import android.util.RecurrenceRule;
import android.util.TrustedTime;
import com.android.internal.telephony.PhoneConstants;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.time.Clock;
import java.time.Instant;
+import java.time.Period;
+import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Calendar;
@After
public void resetClock() throws Exception {
- SubscriptionPlan.sNowOverride = -1;
+ RecurrenceRule.sClock = Clock.systemDefaultZone();
}
@Test
}
private static long computeLastCycleBoundary(long currentTime, NetworkPolicy policy) {
- SubscriptionPlan.sNowOverride = currentTime;
- final Iterator<Pair<ZonedDateTime, ZonedDateTime>> it = NetworkPolicyManager
- .cycleIterator(policy);
+ RecurrenceRule.sClock = Clock.fixed(Instant.ofEpochMilli(currentTime),
+ ZoneId.systemDefault());
+ final Iterator<Pair<ZonedDateTime, ZonedDateTime>> it = policy.cycleIterator();
while (it.hasNext()) {
final Pair<ZonedDateTime, ZonedDateTime> cycle = it.next();
if (cycle.first.toInstant().toEpochMilli() < currentTime) {
}
private static long computeNextCycleBoundary(long currentTime, NetworkPolicy policy) {
- SubscriptionPlan.sNowOverride = currentTime;
- return NetworkPolicyManager.cycleIterator(policy).next().second.toInstant().toEpochMilli();
+ RecurrenceRule.sClock = Clock.fixed(Instant.ofEpochMilli(currentTime),
+ ZoneId.systemDefault());
+ return policy.cycleIterator().next().second.toInstant().toEpochMilli();
}
@Test
}
@Test
- public void testNextCycleSane() throws Exception {
- final NetworkPolicy policy = new NetworkPolicy(
- sTemplateWifi, 31, TIMEZONE_UTC, WARNING_DISABLED, LIMIT_DISABLED, false);
- final LinkedHashSet<Long> seen = new LinkedHashSet<Long>();
-
- // walk forwards, ensuring that cycle boundaries don't get stuck
- long currentCycle = computeNextCycleBoundary(parseTime("2011-08-01T00:00:00.000Z"), policy);
- for (int i = 0; i < 128; i++) {
- long nextCycle = computeNextCycleBoundary(currentCycle, policy);
- assertEqualsFuzzy(DAY_IN_MILLIS * 30, nextCycle - currentCycle, DAY_IN_MILLIS * 3);
- assertUnique(seen, nextCycle);
- currentCycle = nextCycle;
- }
- }
-
- @Test
- public void testLastCycleSane() throws Exception {
- final NetworkPolicy policy = new NetworkPolicy(
- sTemplateWifi, 31, TIMEZONE_UTC, WARNING_DISABLED, LIMIT_DISABLED, false);
- final LinkedHashSet<Long> seen = new LinkedHashSet<Long>();
-
- // walk backwards, ensuring that cycle boundaries look sane
- long currentCycle = computeLastCycleBoundary(parseTime("2011-08-04T00:00:00.000Z"), policy);
- for (int i = 0; i < 128; i++) {
- long lastCycle = computeLastCycleBoundary(currentCycle, policy);
- assertEqualsFuzzy(DAY_IN_MILLIS * 30, currentCycle - lastCycle, DAY_IN_MILLIS * 3);
- assertUnique(seen, lastCycle);
- currentCycle = lastCycle;
- }
- }
-
- @Test
public void testCycleTodayJanuary() throws Exception {
final NetworkPolicy policy = new NetworkPolicy(
sTemplateWifi, 14, "US/Pacific", 1024L, 1024L, false);
}
@Test
- public void testLastCycleBoundaryDST() throws Exception {
- final long currentTime = parseTime("1989-01-02T07:30:00.000Z");
- final long expectedCycle = parseTime("1988-12-03T02:00:00.000Z");
-
- final NetworkPolicy policy = new NetworkPolicy(
- sTemplateWifi, 3, "America/Argentina/Buenos_Aires", 1024L, 1024L, false);
- final long actualCycle = computeLastCycleBoundary(currentTime, policy);
- assertTimeEquals(expectedCycle, actualCycle);
- }
-
- @Test
public void testNetworkPolicyAppliedCycleLastMonth() throws Exception {
NetworkState[] state = null;
NetworkStats stats = null;
}
@Test
- public void testConversion() throws Exception {
- NetworkTemplate template = NetworkTemplate.buildTemplateMobileWildcard();
- NetworkPolicy before = new NetworkPolicy(template, 12, "Israel", 123, 456, true);
- NetworkPolicy after = SubscriptionPlan.convert(SubscriptionPlan.convert(before));
- after.template = before.template;
- assertEquals(before, after);
- }
-
- @Test
public void testOnUidStateChanged_notifyAMS() throws Exception {
final long procStateSeq = 222;
callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE, procStateSeq);
private NetworkPolicy buildDefaultFakeMobilePolicy() {
NetworkPolicy p = mService.buildDefaultMobilePolicy(FAKE_SUB_ID, FAKE_SUBSCRIBER_ID);
// set a deterministic cycle date
- p.cycleDay = DEFAULT_CYCLE_DAY;
+ p.cycleRule = new RecurrenceRule(
+ p.cycleRule.start.withDayOfMonth(DEFAULT_CYCLE_DAY),
+ p.cycleRule.end, Period.ofMonths(1));
return p;
}
}
private void setCurrentTimeMillis(long currentTimeMillis) {
- SubscriptionPlan.sNowOverride = currentTimeMillis;
+ RecurrenceRule.sClock = Clock.fixed(Instant.ofEpochMilli(currentTimeMillis),
+ ZoneId.systemDefault());
mStartTime = currentTimeMillis;
mElapsedRealtime = 0L;
}
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.service.carrier.CarrierService;
import com.android.ims.ImsReasonInfo;
import com.android.internal.telephony.ICarrierConfigLoader;
"config_ims_package_override_string";
/**
+ * Override the package that will manage {@link SubscriptionPlan}
+ * information instead of the {@link CarrierService} that defines this
+ * value.
+ *
+ * @see SubscriptionManager#getSubscriptionPlans(int)
+ * @see SubscriptionManager#setSubscriptionPlans(int, java.util.List)
+ */
+ public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING =
+ "config_plans_package_override_string";
+
+ /**
* Override the platform's notion of a network operator being considered roaming.
* Value is string array of SIDs to be considered roaming for 3GPP2 RATs.
*/
/**
* The day of the month (1-31) on which the data cycle rolls over.
* <p>
- * If the current month does not have this day, the cycle will roll over at the start of the
- * next month.
+ * If the current month does not have this day, the cycle will roll over at
+ * the start of the next month.
* <p>
- * This setting may be still overridden by explicit user choice. By default, the platform value
- * will be used.
+ * This setting may be still overridden by explicit user choice. By default,
+ * the platform value will be used.
+ *
+ * @deprecated replaced by
+ * {@link SubscriptionManager#setSubscriptionPlans(int, java.util.List)}
*/
+ @Deprecated
public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT =
"monthly_data_cycle_day_int";
*
* @hide
*/
+ @Deprecated
public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1;
/**
* default data limit, if one exists, will be disabled. A user selected data limit will not be
* overridden.
*/
+ @Deprecated
public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2;
/**
* <p>
* This setting may be overridden by explicit user choice. By default, the platform value
* will be used.
+ *
+ * @deprecated replaced by
+ * {@link SubscriptionManager#setSubscriptionPlans(int, java.util.List)}
*/
+ @Deprecated
public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG =
"data_warning_threshold_bytes_long";
* <p>
* This setting may be overridden by explicit user choice. By default, the platform value
* will be used.
+ *
+ * @deprecated replaced by
+ * {@link SubscriptionManager#setSubscriptionPlans(int, java.util.List)}
*/
+ @Deprecated
public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG =
"data_limit_threshold_bytes_long";
}
}
+ /** {@hide} */
+ public String getDefaultCarrierServicePackageName() {
+ try {
+ return getICarrierConfigLoader().getDefaultCarrierServicePackageName();
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
/**
* Returns a new bundle with the default value for every supported configuration variable.
*
import android.annotation.NonNull;
import android.annotation.SdkConstant;
-import android.annotation.SystemService;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
return false;
}
- /** {@pending} */
+ /**
+ * Get the description of the billing relationship plan between a carrier
+ * and a specific subscriber.
+ * <p>
+ * This method is only accessible to the following narrow set of apps:
+ * <ul>
+ * <li>The carrier app for this subscriberId, as determined by
+ * {@link TelephonyManager#hasCarrierPrivileges(int)}.
+ * <li>The carrier app explicitly delegated access through
+ * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
+ * </ul>
+ *
+ * @param subId the subscriber this relationship applies to
+ */
public @NonNull List<SubscriptionPlan> getSubscriptionPlans(int subId) {
final INetworkPolicyManager npm = INetworkPolicyManager.Stub
.asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
}
}
- /** {@pending} */
+ /**
+ * Set the description of the billing relationship plan between a carrier
+ * and a specific subscriber.
+ * <p>
+ * This method is only accessible to the following narrow set of apps:
+ * <ul>
+ * <li>The carrier app for this subscriberId, as determined by
+ * {@link TelephonyManager#hasCarrierPrivileges(int)}.
+ * <li>The carrier app explicitly delegated access through
+ * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
+ * </ul>
+ *
+ * @param subId the subscriber this relationship applies to
+ * @param plans the list of plans. The first plan is always the primary and
+ * most important plan. Any additional plans are secondary and
+ * may not be displayed or used by decision making logic.
+ */
public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans) {
final INetworkPolicyManager npm = INetworkPolicyManager.Stub
.asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
throw e.rethrowFromSystemServer();
}
}
-
- /** {@hide} */
- public String getSubscriptionPlanOwner(int subId) {
- final INetworkPolicyManager npm = INetworkPolicyManager.Stub
- .asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
- try {
- return npm.getSubscriptionPlanOwner(subId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
}
import android.annotation.BytesLong;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.net.NetworkPolicy;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
import android.util.Pair;
+import android.util.RecurrenceRule;
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.time.Instant;
-import java.time.LocalTime;
-import java.time.ZoneId;
+import java.time.Period;
import java.time.ZonedDateTime;
-import java.time.temporal.ChronoUnit;
-import java.time.temporal.TemporalUnit;
import java.util.Iterator;
-/** {@pending} */
+/**
+ * Description of a billing relationship plan between a carrier and a specific
+ * subscriber. This information is used to present more useful UI to users, such
+ * as explaining how much mobile data they have remaining, and what will happen
+ * when they run out.
+ *
+ * @see SubscriptionManager#setSubscriptionPlans(int, java.util.List)
+ * @see SubscriptionManager#getSubscriptionPlans(int)
+ */
public final class SubscriptionPlan implements Parcelable {
- private static final String TAG = "SubscriptionPlan";
- private static final boolean DEBUG = false;
-
- /** {@hide} */
- @IntDef(prefix = "TYPE_", value = {
- TYPE_NONRECURRING,
- TYPE_RECURRING_WEEKLY,
- TYPE_RECURRING_MONTHLY,
- TYPE_RECURRING_DAILY,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface Type {}
-
- public static final int TYPE_NONRECURRING = 0;
- public static final int TYPE_RECURRING_MONTHLY = 1;
- public static final int TYPE_RECURRING_WEEKLY = 2;
- public static final int TYPE_RECURRING_DAILY = 3;
-
/** {@hide} */
@IntDef(prefix = "LIMIT_BEHAVIOR_", value = {
LIMIT_BEHAVIOR_UNKNOWN,
@Retention(RetentionPolicy.SOURCE)
public @interface LimitBehavior {}
+ /** When a resource limit is hit, the behavior is unknown. */
public static final int LIMIT_BEHAVIOR_UNKNOWN = -1;
+ /** When a resource limit is hit, access is disabled. */
public static final int LIMIT_BEHAVIOR_DISABLED = 0;
+ /** When a resource limit is hit, the user is billed automatically. */
public static final int LIMIT_BEHAVIOR_BILLED = 1;
+ /** When a resource limit is hit, access is throttled to a slower rate. */
public static final int LIMIT_BEHAVIOR_THROTTLED = 2;
+ /** Value indicating a number of bytes is unknown. */
public static final long BYTES_UNKNOWN = -1;
+ /** Value indicating a number of bytes is unlimited. */
+ public static final long BYTES_UNLIMITED = Long.MAX_VALUE;
+
+ /** Value indicating a timestamp is unknown. */
public static final long TIME_UNKNOWN = -1;
- private final int type;
- private final ZonedDateTime start;
- private final ZonedDateTime end;
+ private final RecurrenceRule cycleRule;
private CharSequence title;
private CharSequence summary;
- private long dataWarningBytes = BYTES_UNKNOWN;
- private long dataWarningSnoozeTime = TIME_UNKNOWN;
private long dataLimitBytes = BYTES_UNKNOWN;
- private long dataLimitSnoozeTime = TIME_UNKNOWN;
private int dataLimitBehavior = LIMIT_BEHAVIOR_UNKNOWN;
private long dataUsageBytes = BYTES_UNKNOWN;
private long dataUsageTime = TIME_UNKNOWN;
- private SubscriptionPlan(@Type int type, ZonedDateTime start, ZonedDateTime end) {
- this.type = type;
- this.start = start;
- this.end = end;
+ private SubscriptionPlan(RecurrenceRule cycleRule) {
+ this.cycleRule = Preconditions.checkNotNull(cycleRule);
}
private SubscriptionPlan(Parcel source) {
- type = source.readInt();
- if (source.readInt() != 0) {
- start = ZonedDateTime.parse(source.readString());
- } else {
- start = null;
- }
- if (source.readInt() != 0) {
- end = ZonedDateTime.parse(source.readString());
- } else {
- end = null;
- }
+ cycleRule = source.readParcelable(null);
title = source.readCharSequence();
summary = source.readCharSequence();
- dataWarningBytes = source.readLong();
- dataWarningSnoozeTime = source.readLong();
dataLimitBytes = source.readLong();
- dataLimitSnoozeTime = source.readLong();
dataLimitBehavior = source.readInt();
dataUsageBytes = source.readLong();
dataUsageTime = source.readLong();
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(type);
- if (start != null) {
- dest.writeInt(1);
- dest.writeString(start.toString());
- } else {
- dest.writeInt(0);
- }
- if (end != null) {
- dest.writeInt(1);
- dest.writeString(end.toString());
- } else {
- dest.writeInt(0);
- }
+ dest.writeParcelable(cycleRule, flags);
dest.writeCharSequence(title);
dest.writeCharSequence(summary);
- dest.writeLong(dataWarningBytes);
- dest.writeLong(dataWarningSnoozeTime);
dest.writeLong(dataLimitBytes);
- dest.writeLong(dataLimitSnoozeTime);
dest.writeInt(dataLimitBehavior);
dest.writeLong(dataUsageBytes);
dest.writeLong(dataUsageTime);
@Override
public String toString() {
- return new StringBuilder("SubscriptionPlan:")
- .append(" type=").append(type)
- .append(" start=").append(start)
- .append(" end=").append(end)
+ return new StringBuilder("SubscriptionPlan{")
+ .append("cycleRule=").append(cycleRule)
.append(" title=").append(title)
.append(" summary=").append(summary)
- .append(" dataWarningBytes=").append(dataWarningBytes)
- .append(" dataWarningSnoozeTime=").append(dataWarningSnoozeTime)
.append(" dataLimitBytes=").append(dataLimitBytes)
- .append(" dataLimitSnoozeTime=").append(dataLimitSnoozeTime)
.append(" dataLimitBehavior=").append(dataLimitBehavior)
.append(" dataUsageBytes=").append(dataUsageBytes)
.append(" dataUsageTime=").append(dataUsageTime)
- .toString();
+ .append("}").toString();
}
public static final Parcelable.Creator<SubscriptionPlan> CREATOR = new Parcelable.Creator<SubscriptionPlan>() {
}
};
- public @Type int getType() {
- return type;
- }
-
- public ZonedDateTime getStart() {
- return start;
- }
-
- public ZonedDateTime getEnd() {
- return end;
+ /** {@hide} */
+ public @NonNull RecurrenceRule getCycleRule() {
+ return cycleRule;
}
+ /** Return the short title of this plan. */
public @Nullable CharSequence getTitle() {
return title;
}
+ /** Return the short summary of this plan. */
public @Nullable CharSequence getSummary() {
return summary;
}
- public @BytesLong long getDataWarningBytes() {
- return dataWarningBytes;
- }
-
+ /**
+ * Return the usage threshold at which data access changes according to
+ * {@link #getDataLimitBehavior()}.
+ */
public @BytesLong long getDataLimitBytes() {
return dataLimitBytes;
}
+ /**
+ * Return the behavior of data access when usage reaches
+ * {@link #getDataLimitBytes()}.
+ */
public @LimitBehavior int getDataLimitBehavior() {
return dataLimitBehavior;
}
+ /**
+ * Return a snapshot of currently known mobile data usage at
+ * {@link #getDataUsageTime()}.
+ */
public @BytesLong long getDataUsageBytes() {
return dataUsageBytes;
}
+ /**
+ * Return the time at which {@link #getDataUsageBytes()} was valid.
+ */
public @CurrentTimeMillisLong long getDataUsageTime() {
return dataUsageTime;
}
- /** {@hide} */
- @VisibleForTesting
- public static long sNowOverride = -1;
-
- private static ZonedDateTime now(ZoneId zone) {
- return (sNowOverride != -1)
- ? ZonedDateTime.ofInstant(Instant.ofEpochMilli(sNowOverride), zone)
- : ZonedDateTime.now(zone);
- }
-
- /** {@hide} */
- public static SubscriptionPlan convert(NetworkPolicy policy) {
- final ZoneId zone = ZoneId.of(policy.cycleTimezone);
- final ZonedDateTime now = now(zone);
- final Builder builder;
- if (policy.cycleDay != NetworkPolicy.CYCLE_NONE) {
- // Assume we started last January, since it has all possible days
- ZonedDateTime start = ZonedDateTime.of(
- now.toLocalDate().minusYears(1).withMonth(1).withDayOfMonth(policy.cycleDay),
- LocalTime.MIDNIGHT, zone);
- builder = Builder.createRecurringMonthly(start);
- } else {
- Log.w(TAG, "Cycle not defined; assuming last 4 weeks non-recurring");
- ZonedDateTime end = now;
- ZonedDateTime start = end.minusWeeks(4);
- builder = Builder.createNonrecurring(start, end);
- }
- if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
- builder.setDataWarning(policy.warningBytes);
- }
- if (policy.lastWarningSnooze != NetworkPolicy.SNOOZE_NEVER) {
- builder.setDataWarningSnooze(policy.lastWarningSnooze);
- }
- if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
- builder.setDataLimit(policy.limitBytes, LIMIT_BEHAVIOR_DISABLED);
- }
- if (policy.lastLimitSnooze != NetworkPolicy.SNOOZE_NEVER) {
- builder.setDataLimitSnooze(policy.lastLimitSnooze);
- }
- return builder.build();
- }
-
- /** {@hide} */
- public static NetworkPolicy convert(SubscriptionPlan plan) {
- final NetworkPolicy policy = new NetworkPolicy();
- switch (plan.type) {
- case TYPE_RECURRING_MONTHLY:
- policy.cycleDay = plan.start.getDayOfMonth();
- policy.cycleTimezone = plan.start.getZone().getId();
- break;
- default:
- policy.cycleDay = NetworkPolicy.CYCLE_NONE;
- policy.cycleTimezone = "UTC";
- break;
- }
- policy.warningBytes = plan.dataWarningBytes;
- policy.limitBytes = plan.dataLimitBytes;
- policy.lastWarningSnooze = plan.dataWarningSnoozeTime;
- policy.lastLimitSnooze = plan.dataLimitSnoozeTime;
- policy.metered = true;
- policy.inferred = false;
- return policy;
- }
-
- /** {@hide} */
- public TemporalUnit getTemporalUnit() {
- switch (type) {
- case TYPE_RECURRING_DAILY: return ChronoUnit.DAYS;
- case TYPE_RECURRING_WEEKLY: return ChronoUnit.WEEKS;
- case TYPE_RECURRING_MONTHLY: return ChronoUnit.MONTHS;
- default: throw new IllegalArgumentException();
- }
- }
-
/**
- * Return an iterator that returns data usage cycles.
- * <p>
- * For recurring plans, it starts at the currently active cycle, and then
- * walks backwards in time through each previous cycle, back to the defined
- * starting point and no further.
- * <p>
- * For non-recurring plans, it returns one single cycle.
+ * Return an iterator that will return all valid data usage cycles based on
+ * any recurrence rules. The iterator starts from the currently active cycle
+ * and walks backwards through time.
*/
public Iterator<Pair<ZonedDateTime, ZonedDateTime>> cycleIterator() {
- switch (type) {
- case TYPE_NONRECURRING:
- return new NonrecurringIterator();
- case TYPE_RECURRING_WEEKLY:
- case TYPE_RECURRING_MONTHLY:
- case TYPE_RECURRING_DAILY:
- return new RecurringIterator();
- default:
- throw new IllegalStateException("Unknown type: " + type);
- }
- }
-
- private class NonrecurringIterator implements Iterator<Pair<ZonedDateTime, ZonedDateTime>> {
- boolean hasNext = true;
-
- @Override
- public boolean hasNext() {
- return hasNext;
- }
-
- @Override
- public Pair<ZonedDateTime, ZonedDateTime> next() {
- hasNext = false;
- return new Pair<>(start, end);
- }
- }
-
- private class RecurringIterator implements Iterator<Pair<ZonedDateTime, ZonedDateTime>> {
- TemporalUnit unit;
- long i;
- ZonedDateTime cycleStart;
- ZonedDateTime cycleEnd;
-
- public RecurringIterator() {
- final ZonedDateTime now = now(start.getZone());
- if (DEBUG) Log.d(TAG, "Resolving using now " + now);
-
- unit = getTemporalUnit();
- i = unit.between(start, now);
- updateCycle();
-
- // Walk forwards until we find first cycle after now
- while (cycleEnd.toEpochSecond() <= now.toEpochSecond()) {
- i++;
- updateCycle();
- }
-
- // Walk backwards until we find first cycle before now
- while (cycleStart.toEpochSecond() > now.toEpochSecond()) {
- i--;
- updateCycle();
- }
- }
-
- private void updateCycle() {
- cycleStart = roundBoundaryTime(start.plus(i, unit));
- cycleEnd = roundBoundaryTime(start.plus(i + 1, unit));
- }
-
- private ZonedDateTime roundBoundaryTime(ZonedDateTime boundary) {
- if ((type == TYPE_RECURRING_MONTHLY)
- && (boundary.getDayOfMonth() < start.getDayOfMonth())) {
- // When forced to end a monthly cycle early, we want to count
- // that entire day against the boundary.
- return ZonedDateTime.of(boundary.toLocalDate(), LocalTime.MAX, start.getZone());
- } else {
- return boundary;
- }
- }
-
- @Override
- public boolean hasNext() {
- return cycleStart.toEpochSecond() >= start.toEpochSecond();
- }
-
- @Override
- public Pair<ZonedDateTime, ZonedDateTime> next() {
- if (DEBUG) Log.d(TAG, "Cycle " + i + " from " + cycleStart + " to " + cycleEnd);
- Pair<ZonedDateTime, ZonedDateTime> p = new Pair<>(cycleStart, cycleEnd);
- i--;
- updateCycle();
- return p;
- }
+ return cycleRule.cycleIterator();
}
+ /**
+ * Builder for a {@link SubscriptionPlan}.
+ */
public static class Builder {
private final SubscriptionPlan plan;
- private Builder(@Type int type, ZonedDateTime start, ZonedDateTime end) {
- plan = new SubscriptionPlan(type, start, end);
+ /** {@hide} */
+ public Builder(ZonedDateTime start, ZonedDateTime end, Period period) {
+ plan = new SubscriptionPlan(new RecurrenceRule(start, end, period));
}
+ /**
+ * Start defining a {@link SubscriptionPlan} that covers a very specific
+ * window of time, and never automatically recurs.
+ */
public static Builder createNonrecurring(ZonedDateTime start, ZonedDateTime end) {
if (!end.isAfter(start)) {
throw new IllegalArgumentException(
"End " + end + " isn't after start " + start);
}
- return new Builder(TYPE_NONRECURRING, start, end);
+ return new Builder(start, end, null);
}
+ /**
+ * Start defining a {@link SubscriptionPlan} that will recur
+ * automatically every month. It will always recur on the same day of a
+ * particular month. When a particular month ends before the defined
+ * recurrence day, the plan will recur on the last instant of that
+ * month.
+ */
public static Builder createRecurringMonthly(ZonedDateTime start) {
- return new Builder(TYPE_RECURRING_MONTHLY, start, null);
+ return new Builder(start, null, Period.ofMonths(1));
}
+ /**
+ * Start defining a {@link SubscriptionPlan} that will recur
+ * automatically every week.
+ */
public static Builder createRecurringWeekly(ZonedDateTime start) {
- return new Builder(TYPE_RECURRING_WEEKLY, start, null);
+ return new Builder(start, null, Period.ofDays(7));
}
+ /**
+ * Start defining a {@link SubscriptionPlan} that will recur
+ * automatically every day.
+ */
public static Builder createRecurringDaily(ZonedDateTime start) {
- return new Builder(TYPE_RECURRING_DAILY, start, null);
+ return new Builder(start, null, Period.ofDays(1));
}
public SubscriptionPlan build() {
return plan;
}
+ /** Set the short title of this plan. */
public Builder setTitle(@Nullable CharSequence title) {
plan.title = title;
return this;
}
+ /** Set the short summary of this plan. */
public Builder setSummary(@Nullable CharSequence summary) {
plan.summary = summary;
return this;
}
- public Builder setDataWarning(@BytesLong long dataWarningBytes) {
- if (dataWarningBytes < BYTES_UNKNOWN) {
- throw new IllegalArgumentException("Warning must be positive or BYTES_UNKNOWN");
- }
- plan.dataWarningBytes = dataWarningBytes;
- return this;
- }
-
- /** {@hide} */
- public Builder setDataWarningSnooze(@CurrentTimeMillisLong long dataWarningSnoozeTime) {
- plan.dataWarningSnoozeTime = dataWarningSnoozeTime;
- return this;
- }
-
+ /**
+ * Set the usage threshold at which data access changes.
+ *
+ * @param dataLimitBytes the usage threshold at which data access
+ * changes
+ * @param dataLimitBehavior the behavior of data access when usage
+ * reaches the threshold
+ */
public Builder setDataLimit(@BytesLong long dataLimitBytes,
@LimitBehavior int dataLimitBehavior) {
- if (dataLimitBytes < BYTES_UNKNOWN) {
- throw new IllegalArgumentException("Limit must be positive or BYTES_UNKNOWN");
+ if (dataLimitBytes < 0) {
+ throw new IllegalArgumentException("Limit bytes must be positive");
+ }
+ if (dataLimitBehavior < 0) {
+ throw new IllegalArgumentException("Limit behavior must be defined");
}
plan.dataLimitBytes = dataLimitBytes;
plan.dataLimitBehavior = dataLimitBehavior;
return this;
}
- /** {@hide} */
- public Builder setDataLimitSnooze(@CurrentTimeMillisLong long dataLimitSnoozeTime) {
- plan.dataLimitSnoozeTime = dataLimitSnoozeTime;
- return this;
- }
-
+ /**
+ * Set a snapshot of currently known mobile data usage.
+ *
+ * @param dataUsageBytes the currently known mobile data usage
+ * @param dataUsageTime the time at which this snapshot was valid
+ */
public Builder setDataUsage(@BytesLong long dataUsageBytes,
@CurrentTimeMillisLong long dataUsageTime) {
- if (dataUsageBytes < BYTES_UNKNOWN) {
- throw new IllegalArgumentException("Usage must be positive or BYTES_UNKNOWN");
- }
- if (dataUsageTime < TIME_UNKNOWN) {
- throw new IllegalArgumentException("Time must be positive or TIME_UNKNOWN");
+ if (dataUsageBytes < 0) {
+ throw new IllegalArgumentException("Usage bytes must be positive");
}
- if ((dataUsageBytes == BYTES_UNKNOWN) != (dataUsageTime == TIME_UNKNOWN)) {
- throw new IllegalArgumentException("Must provide both usage and time or neither");
+ if (dataUsageTime < 0) {
+ throw new IllegalArgumentException("Usage time must be positive");
}
plan.dataUsageBytes = dataUsageBytes;
plan.dataUsageTime = dataUsageTime;
void notifyConfigChangedForSubId(int subId);
void updateConfigForPhoneId(int phoneId, String simState);
+
+ String getDefaultCarrierServicePackageName();
+
}