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.app.timezone;
  
  import static android.app.timezone.Utils.validateConditionalNull;
  import static android.app.timezone.Utils.validateNotNull;
  import static android.app.timezone.Utils.validateRulesVersion;
  
  import android.annotation.IntDef;
  import android.annotation.Nullable;
  import android.os.Parcel;
  import android.os.Parcelable;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  
  /**
   * Description of the state of time zone rules on a device.
   *
   * <p>The following properties are included:
   * <dl>
   *     <dt>baseRulesVersion</dt>
   *     <dd>the IANA rules version that shipped with the OS. Always present. e.g. "2017a".</dd>
   *     <dt>distroFormatVersionSupported</dt>
   *     <dd>the distro format version supported by this device. Always present.</dd>
   *     <dt>operationInProgress</dt>
   *     <dd>{@code true} if there is an install / uninstall operation currently happening.</dd>
   *     <dt>stagedOperationType</dt>
   *     <dd>one of {@link #STAGED_OPERATION_UNKNOWN}, {@link #STAGED_OPERATION_NONE},
   *     {@link #STAGED_OPERATION_UNINSTALL} and {@link #STAGED_OPERATION_INSTALL} indicating whether
   *     there is a currently staged time zone distro operation. {@link #STAGED_OPERATION_UNKNOWN} is
   *     used when {@link #isOperationInProgress()} is {@code true}. Staged operations currently
   *     require a reboot to become active.</dd>
   *     <dt>stagedDistroRulesVersion</dt>
   *     <dd>[present if distroStagedState == STAGED_STATE_INSTALL], the rules version of the distro
   *     currently staged for installation.</dd>
   *     <dt>distroStatus</dt>
   *     <dd>{@link #DISTRO_STATUS_INSTALLED} if there is a time zone distro installed and active,
   *     {@link #DISTRO_STATUS_NONE} if there is no active installed distro.
   *     {@link #DISTRO_STATUS_UNKNOWN} is used when {@link #isOperationInProgress()} is {@code true}.
   *     </dd>
   *     <dt>installedDistroRulesVersion</dt>
   *     <dd>[present if distroStatus == {@link #DISTRO_STATUS_INSTALLED}], the rules version of the
   *     installed and active distro.</dd>
   * </dl>
   *
   * @hide
   */
  public final class RulesState implements Parcelable {
  
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(prefix = { "STAGED_OPERATION_" }, value = {
              STAGED_OPERATION_UNKNOWN,
              STAGED_OPERATION_NONE,
              STAGED_OPERATION_UNINSTALL,
              STAGED_OPERATION_INSTALL
      })
      private @interface StagedOperationType {}
  
      /** Staged state could not be determined. */
      public static final int STAGED_OPERATION_UNKNOWN = 0;
      /** Nothing is staged. */
      public static final int STAGED_OPERATION_NONE = 1;
      /** An uninstall is staged. */
      public static final int STAGED_OPERATION_UNINSTALL = 2;
      /** An install is staged. */
      public static final int STAGED_OPERATION_INSTALL = 3;
  
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(prefix = { "DISTRO_STATUS_" }, value = {
              DISTRO_STATUS_UNKNOWN,
              DISTRO_STATUS_NONE,
              DISTRO_STATUS_INSTALLED
      })
      private @interface DistroStatus {}
  
      /** The current distro status could not be determined. */
      public static final int DISTRO_STATUS_UNKNOWN = 0;
      /** There is no active installed time zone distro. */
      public static final int DISTRO_STATUS_NONE = 1;
      /** The is an active, installed time zone distro. */
      public static final int DISTRO_STATUS_INSTALLED = 2;
  
      private static final byte BYTE_FALSE = 0;
      private static final byte BYTE_TRUE = 1;
  
      private final String mBaseRulesVersion;
      private final DistroFormatVersion mDistroFormatVersionSupported;
      private final boolean mOperationInProgress;
      @StagedOperationType private final int mStagedOperationType;
      @Nullable private final DistroRulesVersion mStagedDistroRulesVersion;
      @DistroStatus private final int mDistroStatus;
      @Nullable private final DistroRulesVersion mInstalledDistroRulesVersion;
  
      public RulesState(String baseRulesVersion, DistroFormatVersion distroFormatVersionSupported,
              boolean operationInProgress,
              @StagedOperationType int stagedOperationType,
              @Nullable DistroRulesVersion stagedDistroRulesVersion,
              @DistroStatus int distroStatus,
              @Nullable DistroRulesVersion installedDistroRulesVersion) {
          this.mBaseRulesVersion = validateRulesVersion("baseRulesVersion", baseRulesVersion);
          this.mDistroFormatVersionSupported =
                  validateNotNull("distroFormatVersionSupported", distroFormatVersionSupported);
          this.mOperationInProgress = operationInProgress;
  
          if (operationInProgress && stagedOperationType != STAGED_OPERATION_UNKNOWN) {
              throw new IllegalArgumentException(
                      "stagedOperationType != STAGED_OPERATION_UNKNOWN");
          }
          this.mStagedOperationType = validateStagedOperation(stagedOperationType);
          this.mStagedDistroRulesVersion = validateConditionalNull(
                  mStagedOperationType == STAGED_OPERATION_INSTALL /* requireNotNull */,
                  "stagedDistroRulesVersion", stagedDistroRulesVersion);
  
          this.mDistroStatus = validateDistroStatus(distroStatus);
          this.mInstalledDistroRulesVersion = validateConditionalNull(
                  mDistroStatus == DISTRO_STATUS_INSTALLED/* requireNotNull */,
                  "installedDistroRulesVersion", installedDistroRulesVersion);
      }
  
      public String getBaseRulesVersion() {
          return mBaseRulesVersion;
      }
  
      public boolean isOperationInProgress() {
          return mOperationInProgress;
      }
  
      public @StagedOperationType int getStagedOperationType() {
          return mStagedOperationType;
      }
  
      /**
       * Returns the staged rules version when {@link #getStagedOperationType()} is
       * {@link #STAGED_OPERATION_INSTALL}.
       */
      public @Nullable DistroRulesVersion getStagedDistroRulesVersion() {
          return mStagedDistroRulesVersion;
      }
  
      public @DistroStatus int getDistroStatus() {
          return mDistroStatus;
      }
  
      /**
       * Returns the installed rules version when {@link #getDistroStatus()} is
       * {@link #DISTRO_STATUS_INSTALLED}.
       */
      public @Nullable DistroRulesVersion getInstalledDistroRulesVersion() {
          return mInstalledDistroRulesVersion;
      }
  
      /**
       * Returns true if a distro in the specified format is supported on this device.
       */
      public boolean isDistroFormatVersionSupported(DistroFormatVersion distroFormatVersion) {
          return mDistroFormatVersionSupported.supports(distroFormatVersion);
      }
  
      /**
       * Returns true if the base data files contain IANA rules data that are newer than the
       * distro IANA rules version supplied, i.e. true when the version specified would be "worse"
       * than the one that is in the base data. Returns false if the base version is the
       * same or older, i.e. false when the version specified would be "better" than the one that is
       * in the base set.
       */
      public boolean isBaseVersionNewerThan(DistroRulesVersion distroRulesVersion) {
          return mBaseRulesVersion.compareTo(distroRulesVersion.getRulesVersion()) > 0;
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<RulesState> CREATOR =
              new Parcelable.Creator<RulesState>() {
          public RulesState createFromParcel(Parcel in) {
              return RulesState.createFromParcel(in);
          }
  
          public RulesState[] newArray(int size) {
              return new RulesState[size];
          }
      };
  
      private static RulesState createFromParcel(Parcel in) {
          String baseRulesVersion = in.readString();
          DistroFormatVersion distroFormatVersionSupported = in.readParcelable(null);
          boolean operationInProgress = in.readByte() == BYTE_TRUE;
          int distroStagedState = in.readByte();
          DistroRulesVersion stagedDistroRulesVersion = in.readParcelable(null);
          int installedDistroStatus = in.readByte();
          DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null);
          return new RulesState(baseRulesVersion, distroFormatVersionSupported, operationInProgress,
                  distroStagedState, stagedDistroRulesVersion,
                  installedDistroStatus, installedDistroRulesVersion);
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public void writeToParcel(Parcel out, int flags) {
          out.writeString(mBaseRulesVersion);
          out.writeParcelable(mDistroFormatVersionSupported, 0);
          out.writeByte(mOperationInProgress ? BYTE_TRUE : BYTE_FALSE);
          out.writeByte((byte) mStagedOperationType);
          out.writeParcelable(mStagedDistroRulesVersion, 0);
          out.writeByte((byte) mDistroStatus);
          out.writeParcelable(mInstalledDistroRulesVersion, 0);
      }
  
      @Override
      public boolean equals(Object o) {
          if (this == o) {
              return true;
          }
          if (o == null || getClass() != o.getClass()) {
              return false;
          }
  
          RulesState that = (RulesState) o;
  
          if (mOperationInProgress != that.mOperationInProgress) {
              return false;
          }
          if (mStagedOperationType != that.mStagedOperationType) {
              return false;
          }
          if (mDistroStatus != that.mDistroStatus) {
              return false;
          }
          if (!mBaseRulesVersion.equals(that.mBaseRulesVersion)) {
              return false;
          }
          if (!mDistroFormatVersionSupported.equals(that.mDistroFormatVersionSupported)) {
              return false;
          }
          if (mStagedDistroRulesVersion != null ? !mStagedDistroRulesVersion
                  .equals(that.mStagedDistroRulesVersion) : that.mStagedDistroRulesVersion != null) {
              return false;
          }
          return mInstalledDistroRulesVersion != null ? mInstalledDistroRulesVersion
                  .equals(that.mInstalledDistroRulesVersion)
                  : that.mInstalledDistroRulesVersion == null;
      }
  
      @Override
      public int hashCode() {
          int result = mBaseRulesVersion.hashCode();
          result = 31 * result + mDistroFormatVersionSupported.hashCode();
          result = 31 * result + (mOperationInProgress ? 1 : 0);
          result = 31 * result + mStagedOperationType;
          result = 31 * result + (mStagedDistroRulesVersion != null ? mStagedDistroRulesVersion
                  .hashCode()
                  : 0);
          result = 31 * result + mDistroStatus;
          result = 31 * result + (mInstalledDistroRulesVersion != null ? mInstalledDistroRulesVersion
                  .hashCode() : 0);
          return result;
      }
  
      @Override
      public String toString() {
          return "RulesState{"
                  + "mBaseRulesVersion='" + mBaseRulesVersion + '\''
                  + ", mDistroFormatVersionSupported=" + mDistroFormatVersionSupported
                  + ", mOperationInProgress=" + mOperationInProgress
                  + ", mStagedOperationType=" + mStagedOperationType
                  + ", mStagedDistroRulesVersion=" + mStagedDistroRulesVersion
                  + ", mDistroStatus=" + mDistroStatus
                  + ", mInstalledDistroRulesVersion=" + mInstalledDistroRulesVersion
                  + '}';
      }
  
      private static int validateStagedOperation(int stagedOperationType) {
          if (stagedOperationType < STAGED_OPERATION_UNKNOWN
                  || stagedOperationType > STAGED_OPERATION_INSTALL) {
              throw new IllegalArgumentException("Unknown operation type=" + stagedOperationType);
          }
          return stagedOperationType;
      }
  
      private static int validateDistroStatus(int distroStatus) {
          if (distroStatus < DISTRO_STATUS_UNKNOWN || distroStatus > DISTRO_STATUS_INSTALLED) {
              throw new IllegalArgumentException("Unknown distro status=" + distroStatus);
          }
          return distroStatus;
      }
  }
  