content: /*
   * 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.telephony;
  
  import android.annotation.BytesLong;
  import android.annotation.CurrentTimeMillisLong;
  import android.annotation.IntDef;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.SystemApi;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.telephony.TelephonyManager.NetworkType;
  import android.util.Range;
  import android.util.RecurrenceRule;
  
  import com.android.internal.util.Preconditions;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.time.Period;
  import java.time.ZonedDateTime;
  import java.util.Arrays;
  import java.util.Iterator;
  import java.util.Objects;
  
  /**
   * 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.
   *
   * If specifying network types, the developer must supply at least one plan
   * that applies to all network types (default), and all additional plans
   * may not include a particular network type more than once.
   * This is enforced by {@link SubscriptionManager} when setting the plans.
   *
   * Plan selection will prefer plans that have specific network types defined
   * over plans that apply to all network types.
   *
   * @see SubscriptionManager#setSubscriptionPlans(int, java.util.List)
   * @see SubscriptionManager#getSubscriptionPlans(int)
   */
  public final class SubscriptionPlan implements Parcelable {
      /** {@hide} */
      @IntDef(prefix = "LIMIT_BEHAVIOR_", value = {
              LIMIT_BEHAVIOR_UNKNOWN,
              LIMIT_BEHAVIOR_DISABLED,
              LIMIT_BEHAVIOR_BILLED,
              LIMIT_BEHAVIOR_THROTTLED,
      })
      @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 RecurrenceRule cycleRule;
      private CharSequence title;
      private CharSequence summary;
      private long dataLimitBytes = BYTES_UNKNOWN;
      private int dataLimitBehavior = LIMIT_BEHAVIOR_UNKNOWN;
      private long dataUsageBytes = BYTES_UNKNOWN;
      private long dataUsageTime = TIME_UNKNOWN;
      private @NetworkType int[] networkTypes;
      private long networkTypesBitMask;
  
      private SubscriptionPlan(RecurrenceRule cycleRule) {
          this.cycleRule = Preconditions.checkNotNull(cycleRule);
      }
  
      private SubscriptionPlan(Parcel source) {
          cycleRule = source.readParcelable(null);
          title = source.readCharSequence();
          summary = source.readCharSequence();
          dataLimitBytes = source.readLong();
          dataLimitBehavior = source.readInt();
          dataUsageBytes = source.readLong();
          dataUsageTime = source.readLong();
          networkTypes = source.createIntArray();
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public void writeToParcel(Parcel dest, int flags) {
          dest.writeParcelable(cycleRule, flags);
          dest.writeCharSequence(title);
          dest.writeCharSequence(summary);
          dest.writeLong(dataLimitBytes);
          dest.writeInt(dataLimitBehavior);
          dest.writeLong(dataUsageBytes);
          dest.writeLong(dataUsageTime);
          dest.writeIntArray(networkTypes);
      }
  
      @Override
      public String toString() {
          return new StringBuilder("SubscriptionPlan{")
                  .append("cycleRule=").append(cycleRule)
                  .append(" title=").append(title)
                  .append(" summary=").append(summary)
                  .append(" dataLimitBytes=").append(dataLimitBytes)
                  .append(" dataLimitBehavior=").append(dataLimitBehavior)
                  .append(" dataUsageBytes=").append(dataUsageBytes)
                  .append(" dataUsageTime=").append(dataUsageTime)
                  .append(" networkTypes=").append(Arrays.toString(networkTypes))
                  .append("}").toString();
      }
  
      @Override
      public int hashCode() {
          return Objects.hash(cycleRule, title, summary, dataLimitBytes, dataLimitBehavior,
                  dataUsageBytes, dataUsageTime, Arrays.hashCode(networkTypes));
      }
  
      @Override
      public boolean equals(Object obj) {
          if (obj instanceof SubscriptionPlan) {
              final SubscriptionPlan other = (SubscriptionPlan) obj;
              return Objects.equals(cycleRule, other.cycleRule)
                      && Objects.equals(title, other.title)
                      && Objects.equals(summary, other.summary)
                      && dataLimitBytes == other.dataLimitBytes
                      && dataLimitBehavior == other.dataLimitBehavior
                      && dataUsageBytes == other.dataUsageBytes
                      && dataUsageTime == other.dataUsageTime
                      && Arrays.equals(networkTypes, other.networkTypes);
          }
          return false;
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<SubscriptionPlan> CREATOR = new Parcelable.Creator<SubscriptionPlan>() {
          @Override
          public SubscriptionPlan createFromParcel(Parcel source) {
              return new SubscriptionPlan(source);
          }
  
          @Override
          public SubscriptionPlan[] newArray(int size) {
              return new SubscriptionPlan[size];
          }
      };
  
      /** {@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;
      }
  
      /**
       * 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;
      }
  
      /**
       * Return an array containing all {@link NetworkType}s this SubscriptionPlan applies to.
       * A null value means this SubscriptionPlan applies to all network types.
       * @hide
       */
      public @Nullable @NetworkType int[] getNetworkTypes() {
          return networkTypes;
      }
  
      /**
       * Return the networkTypes array converted to a {@link TelephonyManager.NetworkTypeBitMask}
       * @hide
       */
      public long getNetworkTypesBitMask() {
          // calculate bitmask the first time and save for future calls
          if (networkTypesBitMask == 0) {
              if (networkTypes == null) {
                  networkTypesBitMask = ~0;
              } else {
                  for (int networkType : networkTypes) {
                      networkTypesBitMask |= ServiceState.getBitmaskForTech(networkType);
                  }
              }
          }
          return networkTypesBitMask;
      }
  
      /**
       * Temporary solution to allow 5G unmetered for March QPR
       * TODO: remove once {@link Builder#setNetworkTypes(int[])} is public in R.
       * @hide
       */
      public void setNetworkTypes(int[] networkTypes) {
          if (networkTypes == null || networkTypes.length == 0) {
              this.networkTypes = null;
              this.networkTypesBitMask = ~0;
          } else {
              this.networkTypes = networkTypes;
              for (int networkType : networkTypes) {
                  this.networkTypesBitMask |= ServiceState.getBitmaskForTech(networkType);
              }
          }
      }
  
      /**
       * 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<Range<ZonedDateTime>> cycleIterator() {
          return cycleRule.cycleIterator();
      }
  
      /**
       * Builder for a {@link SubscriptionPlan}.
       */
      public static class Builder {
          private final SubscriptionPlan plan;
  
          /** {@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.
           *
           * @param start The exact time at which the plan starts.
           * @param end The exact time at which the plan ends.
           */
          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(start, end, null);
          }
  
          /**
           * Start defining a {@link SubscriptionPlan} that starts at a specific
           * time, and automatically recurs after each specific period of time,
           * repeating indefinitely.
           * <p>
           * When the given period is set to exactly one month, the plan will
           * always recur on the day of the month defined by
           * {@link ZonedDateTime#getDayOfMonth()}. When a particular month ends
           * before this day, the plan will recur on the last possible instant of
           * that month.
           *
           * @param start The exact time at which the plan starts.
           * @param period The period after which the plan automatically recurs.
           */
          public static Builder createRecurring(ZonedDateTime start, Period period) {
              if (period.isZero() || period.isNegative()) {
                  throw new IllegalArgumentException("Period " + period + " must be positive");
              }
              return new Builder(start, null, period);
          }
  
          /** {@hide} */
          @SystemApi
          @Deprecated
          public static Builder createRecurringMonthly(ZonedDateTime start) {
              return new Builder(start, null, Period.ofMonths(1));
          }
  
          /** {@hide} */
          @SystemApi
          @Deprecated
          public static Builder createRecurringWeekly(ZonedDateTime start) {
              return new Builder(start, null, Period.ofDays(7));
          }
  
          /** {@hide} */
          @SystemApi
          @Deprecated
          public static Builder createRecurringDaily(ZonedDateTime start) {
              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;
          }
  
          /**
           * 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 < 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;
          }
  
          /**
           * 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 < 0) {
                  throw new IllegalArgumentException("Usage bytes must be positive");
              }
              if (dataUsageTime < 0) {
                  throw new IllegalArgumentException("Usage time must be positive");
              }
              plan.dataUsageBytes = dataUsageBytes;
              plan.dataUsageTime = dataUsageTime;
              return this;
          }
  
          /**
           * Set the network types this SubscriptionPlan applies to.
           *
           * @param networkTypes a set of all {@link NetworkType}s that apply to this plan.
           *            A null value means the plan applies to all network types,
           *            and an empty array means the plan applies to no network types.
           * @hide
           */
          public @NonNull Builder setNetworkTypes(@Nullable @NetworkType int[] networkTypes) {
              plan.networkTypes = networkTypes;
              return this;
          }
      }
  }
  