content: /*
   * Copyright (C) 2013 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.print;
  
  import android.annotation.IntDef;
  import android.annotation.IntRange;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.StringRes;
  import android.content.pm.PackageManager;
  import android.content.pm.PackageManager.NameNotFoundException;
  import android.content.res.Resources.NotFoundException;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.service.print.PrintAttributesProto;
  import android.text.TextUtils;
  import android.util.ArrayMap;
  import android.util.ArraySet;
  import android.util.Log;
  
  import com.android.internal.R;
  import com.android.internal.util.Preconditions;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.Map;
  
  /**
   * This class represents the attributes of a print job. These attributes
   * describe how the printed content should be laid out. For example, the
   * print attributes may state that the content should be laid out on a
   * letter size with 300 DPI (dots per inch) resolution, have a margin of
   * 10 mills (thousand of an inch) on all sides, and be black and white.
   */
  public final class PrintAttributes implements Parcelable {
      /** @hide */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(flag = true, prefix = { "COLOR_MODE_" }, value = {
              COLOR_MODE_MONOCHROME,
              COLOR_MODE_COLOR
      })
      @interface ColorMode {
      }
      /** Color mode: Monochrome color scheme, for example one color is used. */
      public static final int COLOR_MODE_MONOCHROME = PrintAttributesProto.COLOR_MODE_MONOCHROME;
      /** Color mode: Color color scheme, for example many colors are used. */
      public static final int COLOR_MODE_COLOR = PrintAttributesProto.COLOR_MODE_COLOR;
  
      private static final int VALID_COLOR_MODES =
              COLOR_MODE_MONOCHROME | COLOR_MODE_COLOR;
  
      /** @hide */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(flag = true, prefix = { "DUPLEX_MODE_" }, value = {
              DUPLEX_MODE_NONE,
              DUPLEX_MODE_LONG_EDGE,
              DUPLEX_MODE_SHORT_EDGE
      })
      @interface DuplexMode {
      }
      /** Duplex mode: No duplexing. */
      public static final int DUPLEX_MODE_NONE = PrintAttributesProto.DUPLEX_MODE_NONE;
      /** Duplex mode: Pages are turned sideways along the long edge - like a book. */
      public static final int DUPLEX_MODE_LONG_EDGE = PrintAttributesProto.DUPLEX_MODE_LONG_EDGE;
      /** Duplex mode: Pages are turned upwards along the short edge - like a notpad. */
      public static final int DUPLEX_MODE_SHORT_EDGE = PrintAttributesProto.DUPLEX_MODE_SHORT_EDGE;
  
      private static final int VALID_DUPLEX_MODES =
              DUPLEX_MODE_NONE | DUPLEX_MODE_LONG_EDGE | DUPLEX_MODE_SHORT_EDGE;
  
      private @Nullable MediaSize mMediaSize;
      private @Nullable Resolution mResolution;
      private @Nullable Margins mMinMargins;
  
      private @IntRange(from = 0) int mColorMode;
      private @IntRange(from = 0) int mDuplexMode;
  
      PrintAttributes() {
          /* hide constructor */
      }
  
      private PrintAttributes(@NonNull Parcel parcel) {
          mMediaSize = (parcel.readInt() == 1) ? MediaSize.createFromParcel(parcel) : null;
          mResolution = (parcel.readInt() == 1) ? Resolution.createFromParcel(parcel) : null;
          mMinMargins = (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
          mColorMode = parcel.readInt();
          if (mColorMode != 0) {
              enforceValidColorMode(mColorMode);
          }
          mDuplexMode = parcel.readInt();
          if (mDuplexMode != 0) {
              enforceValidDuplexMode(mDuplexMode);
          }
      }
  
      /**
       * Gets the media size.
       *
       * @return The media size or <code>null</code> if not set.
       */
      public @Nullable MediaSize getMediaSize() {
          return mMediaSize;
      }
  
      /**
       * Sets the media size.
       *
       * @param mediaSize The media size.
       *
       * @hide
       */
      public void setMediaSize(MediaSize mediaSize) {
          mMediaSize = mediaSize;
      }
  
      /**
       * Gets the resolution.
       *
       * @return The resolution or <code>null</code> if not set.
       */
      public @Nullable Resolution getResolution() {
          return mResolution;
      }
  
      /**
       * Sets the resolution.
       *
       * @param resolution The resolution.
       *
       * @hide
       */
      public void setResolution(Resolution resolution) {
          mResolution = resolution;
      }
  
      /**
       * Gets the minimal margins. If the content does not fit
       * these margins it will be clipped.
       * <p>
       * <strong>These margins are physically imposed by the printer and they
       * are <em>not</em> rotated, i.e. they are the same for both portrait and
       * landscape. For example, a printer may not be able to print in a stripe
       * on both left and right sides of the page.
       * </strong>
       * </p>
       *
       * @return The margins or <code>null</code> if not set.
       */
      public @Nullable Margins getMinMargins() {
          return mMinMargins;
      }
  
      /**
       * Sets the minimal margins. If the content does not fit
       * these margins it will be clipped.
       * <p>
       * <strong>These margins are physically imposed by the printer and they
       * are <em>not</em> rotated, i.e. they are the same for both portrait and
       * landscape. For example, a printer may not be able to print in a stripe
       * on both left and right sides of the page.
       * </strong>
       * </p>
       *
       * @param margins The margins.
       *
       * @hide
       */
      public void setMinMargins(Margins margins) {
          mMinMargins = margins;
      }
  
      /**
       * Gets the color mode.
       *
       * @return The color mode or zero if not set.
       *
       * @see #COLOR_MODE_COLOR
       * @see #COLOR_MODE_MONOCHROME
       */
      public @IntRange(from = 0) int getColorMode() {
          return mColorMode;
      }
  
      /**
       * Sets the color mode.
       *
       * @param colorMode The color mode.
       *
       * @see #COLOR_MODE_MONOCHROME
       * @see #COLOR_MODE_COLOR
       *
       * @hide
       */
      public void setColorMode(int colorMode) {
          enforceValidColorMode(colorMode);
          mColorMode = colorMode;
      }
  
      /**
       * Gets whether this print attributes are in portrait orientation,
       * which is the media size is in portrait and all orientation dependent
       * attributes such as resolution and margins are properly adjusted.
       *
       * @return Whether this print attributes are in portrait.
       *
       * @hide
       */
      public boolean isPortrait() {
          return mMediaSize.isPortrait();
      }
  
      /**
       * Gets the duplex mode.
       *
       * @return The duplex mode or zero if not set.
       *
       * @see #DUPLEX_MODE_NONE
       * @see #DUPLEX_MODE_LONG_EDGE
       * @see #DUPLEX_MODE_SHORT_EDGE
       */
      public @IntRange(from = 0) int getDuplexMode() {
          return mDuplexMode;
      }
  
      /**
       * Sets the duplex mode.
       *
       * @param duplexMode The duplex mode.
       *
       * @see #DUPLEX_MODE_NONE
       * @see #DUPLEX_MODE_LONG_EDGE
       * @see #DUPLEX_MODE_SHORT_EDGE
       *
       * @hide
       */
      public void setDuplexMode(int duplexMode) {
          enforceValidDuplexMode(duplexMode);
          mDuplexMode = duplexMode;
      }
  
      /**
       * Gets a new print attributes instance which is in portrait orientation,
       * which is the media size is in portrait and all orientation dependent
       * attributes such as resolution and margins are properly adjusted.
       *
       * @return New instance in portrait orientation if this one is in
       * landscape, otherwise this instance.
       *
       * @hide
       */
      public PrintAttributes asPortrait() {
          if (isPortrait()) {
              return this;
          }
  
          PrintAttributes attributes = new PrintAttributes();
  
          // Rotate the media size.
          attributes.setMediaSize(getMediaSize().asPortrait());
  
          // Rotate the resolution.
          Resolution oldResolution = getResolution();
          Resolution newResolution = new Resolution(
                  oldResolution.getId(),
                  oldResolution.getLabel(),
                  oldResolution.getVerticalDpi(),
                  oldResolution.getHorizontalDpi());
          attributes.setResolution(newResolution);
  
          // Do not rotate the physical margins.
          attributes.setMinMargins(getMinMargins());
  
          attributes.setColorMode(getColorMode());
          attributes.setDuplexMode(getDuplexMode());
  
          return attributes;
      }
  
      /**
       * Gets a new print attributes instance which is in landscape orientation,
       * which is the media size is in landscape and all orientation dependent
       * attributes such as resolution and margins are properly adjusted.
       *
       * @return New instance in landscape orientation if this one is in
       * portrait, otherwise this instance.
       *
       * @hide
       */
      public PrintAttributes asLandscape() {
          if (!isPortrait()) {
              return this;
          }
  
          PrintAttributes attributes = new PrintAttributes();
  
          // Rotate the media size.
          attributes.setMediaSize(getMediaSize().asLandscape());
  
          // Rotate the resolution.
          Resolution oldResolution = getResolution();
          Resolution newResolution = new Resolution(
                  oldResolution.getId(),
                  oldResolution.getLabel(),
                  oldResolution.getVerticalDpi(),
                  oldResolution.getHorizontalDpi());
          attributes.setResolution(newResolution);
  
          // Do not rotate the physical margins.
          attributes.setMinMargins(getMinMargins());
  
          attributes.setColorMode(getColorMode());
          attributes.setDuplexMode(getDuplexMode());
  
          return attributes;
      }
  
      @Override
      public void writeToParcel(Parcel parcel, int flags) {
          if (mMediaSize != null) {
              parcel.writeInt(1);
              mMediaSize.writeToParcel(parcel);
          } else {
              parcel.writeInt(0);
          }
          if (mResolution != null) {
              parcel.writeInt(1);
              mResolution.writeToParcel(parcel);
          } else {
              parcel.writeInt(0);
          }
          if (mMinMargins != null) {
              parcel.writeInt(1);
              mMinMargins.writeToParcel(parcel);
          } else {
              parcel.writeInt(0);
          }
          parcel.writeInt(mColorMode);
          parcel.writeInt(mDuplexMode);
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public int hashCode() {
          final int prime = 31;
          int result = 1;
          result = prime * result + mColorMode;
          result = prime * result + mDuplexMode;
          result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode());
          result = prime * result + ((mMediaSize == null) ? 0 : mMediaSize.hashCode());
          result = prime * result + ((mResolution == null) ? 0 : mResolution.hashCode());
          return result;
      }
  
      @Override
      public boolean equals(Object obj) {
          if (this == obj) {
              return true;
          }
          if (obj == null) {
              return false;
          }
          if (getClass() != obj.getClass()) {
              return false;
          }
          PrintAttributes other = (PrintAttributes) obj;
          if (mColorMode != other.mColorMode) {
              return false;
          }
          if (mDuplexMode != other.mDuplexMode) {
              return false;
          }
          if (mMinMargins == null) {
              if (other.mMinMargins != null) {
                  return false;
              }
          } else if (!mMinMargins.equals(other.mMinMargins)) {
              return false;
          }
          if (mMediaSize == null) {
              if (other.mMediaSize != null) {
                  return false;
              }
          } else if (!mMediaSize.equals(other.mMediaSize)) {
              return false;
          }
          if (mResolution == null) {
              if (other.mResolution != null) {
                  return false;
              }
          } else if (!mResolution.equals(other.mResolution)) {
              return false;
          }
          return true;
      }
  
      @Override
      public String toString() {
          StringBuilder builder = new StringBuilder();
          builder.append("PrintAttributes{");
          builder.append("mediaSize: ").append(mMediaSize);
          if (mMediaSize != null) {
              builder.append(", orientation: ").append(mMediaSize.isPortrait()
                      ? "portrait" : "landscape");
          } else {
              builder.append(", orientation: ").append("null");
          }
          builder.append(", resolution: ").append(mResolution);
          builder.append(", minMargins: ").append(mMinMargins);
          builder.append(", colorMode: ").append(colorModeToString(mColorMode));
          builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode));
          builder.append("}");
          return builder.toString();
      }
  
      /** @hide */
      public void clear() {
          mMediaSize = null;
          mResolution = null;
          mMinMargins = null;
          mColorMode = 0;
          mDuplexMode = 0;
      }
  
      /**
       * @hide
       */
      public void copyFrom(PrintAttributes other) {
          mMediaSize = other.mMediaSize;
          mResolution = other.mResolution;
          mMinMargins = other.mMinMargins;
          mColorMode = other.mColorMode;
          mDuplexMode = other.mDuplexMode;
      }
  
      /**
       * This class specifies a supported media size. Media size is the
       * dimension of the media on which the content is printed. For
       * example, the {@link #NA_LETTER} media size designates a page
       * with size 8.5" x 11".
       */
      public static final class MediaSize {
          private static final String LOG_TAG = "MediaSize";
  
          private static final Map<String, MediaSize> sIdToMediaSizeMap =
                  new ArrayMap<>();
  
          /**
           * Unknown media size in portrait mode.
           * <p>
           * <strong>Note: </strong>This is for specifying orientation without media
           * size. You should not use the dimensions reported by this instance.
           * </p>
           */
          public static final MediaSize UNKNOWN_PORTRAIT =
                  new MediaSize("UNKNOWN_PORTRAIT", "android",
                          R.string.mediasize_unknown_portrait, 1, Integer.MAX_VALUE);
  
          /**
           * Unknown media size in landscape mode.
           * <p>
           * <strong>Note: </strong>This is for specifying orientation without media
           * size. You should not use the dimensions reported by this instance.
           * </p>
           */
          public static final MediaSize UNKNOWN_LANDSCAPE =
                  new MediaSize("UNKNOWN_LANDSCAPE", "android",
                          R.string.mediasize_unknown_landscape, Integer.MAX_VALUE, 1);
  
          // ISO sizes
  
          /** ISO A0 media size: 841mm x 1189mm (33.11" x 46.81") */
          public static final MediaSize ISO_A0 =
                  new MediaSize("ISO_A0", "android", R.string.mediasize_iso_a0, 33110, 46810);
          /** ISO A1 media size: 594mm x 841mm (23.39" x 33.11") */
          public static final MediaSize ISO_A1 =
                  new MediaSize("ISO_A1", "android", R.string.mediasize_iso_a1, 23390, 33110);
          /** ISO A2 media size: 420mm x 594mm (16.54" x 23.39") */
          public static final MediaSize ISO_A2 =
                  new MediaSize("ISO_A2", "android", R.string.mediasize_iso_a2, 16540, 23390);
          /** ISO A3 media size: 297mm x 420mm (11.69" x 16.54") */
          public static final MediaSize ISO_A3 =
                  new MediaSize("ISO_A3", "android", R.string.mediasize_iso_a3, 11690, 16540);
          /** ISO A4 media size: 210mm x 297mm (8.27" x 11.69") */
          public static final MediaSize ISO_A4 =
                  new MediaSize("ISO_A4", "android", R.string.mediasize_iso_a4, 8270, 11690);
          /** ISO A5 media size: 148mm x 210mm (5.83" x 8.27") */
          public static final MediaSize ISO_A5 =
                  new MediaSize("ISO_A5", "android", R.string.mediasize_iso_a5, 5830, 8270);
          /** ISO A6 media size: 105mm x 148mm (4.13" x 5.83") */
          public static final MediaSize ISO_A6 =
                  new MediaSize("ISO_A6", "android", R.string.mediasize_iso_a6, 4130, 5830);
          /** ISO A7 media size: 74mm x 105mm (2.91" x 4.13") */
          public static final MediaSize ISO_A7 =
                  new MediaSize("ISO_A7", "android", R.string.mediasize_iso_a7, 2910, 4130);
          /** ISO A8 media size: 52mm x 74mm (2.05" x 2.91") */
          public static final MediaSize ISO_A8 =
                  new MediaSize("ISO_A8", "android", R.string.mediasize_iso_a8, 2050, 2910);
          /** ISO A9 media size: 37mm x 52mm (1.46" x 2.05") */
          public static final MediaSize ISO_A9 =
                  new MediaSize("ISO_A9", "android", R.string.mediasize_iso_a9, 1460, 2050);
          /** ISO A10 media size: 26mm x 37mm (1.02" x 1.46") */
          public static final MediaSize ISO_A10 =
                  new MediaSize("ISO_A10", "android", R.string.mediasize_iso_a10, 1020, 1460);
  
          /** ISO B0 media size: 1000mm x 1414mm (39.37" x 55.67") */
          public static final MediaSize ISO_B0 =
                  new MediaSize("ISO_B0", "android", R.string.mediasize_iso_b0, 39370, 55670);
          /** ISO B1 media size: 707mm x 1000mm (27.83" x 39.37") */
          public static final MediaSize ISO_B1 =
                  new MediaSize("ISO_B1", "android", R.string.mediasize_iso_b1, 27830, 39370);
          /** ISO B2 media size: 500mm x 707mm (19.69" x 27.83") */
          public static final MediaSize ISO_B2 =
                  new MediaSize("ISO_B2", "android", R.string.mediasize_iso_b2, 19690, 27830);
          /** ISO B3 media size: 353mm x 500mm (13.90" x 19.69") */
          public static final MediaSize ISO_B3 =
                  new MediaSize("ISO_B3", "android", R.string.mediasize_iso_b3, 13900, 19690);
          /** ISO B4 media size: 250mm x 353mm (9.84" x 13.90") */
          public static final MediaSize ISO_B4 =
                  new MediaSize("ISO_B4", "android", R.string.mediasize_iso_b4, 9840, 13900);
          /** ISO B5 media size: 176mm x 250mm (6.93" x 9.84") */
          public static final MediaSize ISO_B5 =
                  new MediaSize("ISO_B5", "android", R.string.mediasize_iso_b5, 6930, 9840);
          /** ISO B6 media size: 125mm x 176mm (4.92" x 6.93") */
          public static final MediaSize ISO_B6 =
                  new MediaSize("ISO_B6", "android", R.string.mediasize_iso_b6, 4920, 6930);
          /** ISO B7 media size: 88mm x 125mm (3.46" x 4.92") */
          public static final MediaSize ISO_B7 =
                  new MediaSize("ISO_B7", "android", R.string.mediasize_iso_b7, 3460, 4920);
          /** ISO B8 media size: 62mm x 88mm (2.44" x 3.46") */
          public static final MediaSize ISO_B8 =
                  new MediaSize("ISO_B8", "android", R.string.mediasize_iso_b8, 2440, 3460);
          /** ISO B9 media size: 44mm x 62mm (1.73" x 2.44") */
          public static final MediaSize ISO_B9 =
                  new MediaSize("ISO_B9", "android", R.string.mediasize_iso_b9, 1730, 2440);
          /** ISO B10 media size: 31mm x 44mm (1.22" x 1.73") */
          public static final MediaSize ISO_B10 =
                  new MediaSize("ISO_B10", "android", R.string.mediasize_iso_b10, 1220, 1730);
  
          /** ISO C0 media size: 917mm x 1297mm (36.10" x 51.06") */
          public static final MediaSize ISO_C0 =
                  new MediaSize("ISO_C0", "android", R.string.mediasize_iso_c0, 36100, 51060);
          /** ISO C1 media size: 648mm x 917mm (25.51" x 36.10") */
          public static final MediaSize ISO_C1 =
                  new MediaSize("ISO_C1", "android", R.string.mediasize_iso_c1, 25510, 36100);
          /** ISO C2 media size: 458mm x 648mm (18.03" x 25.51") */
          public static final MediaSize ISO_C2 =
                  new MediaSize("ISO_C2", "android", R.string.mediasize_iso_c2, 18030, 25510);
          /** ISO C3 media size: 324mm x 458mm (12.76" x 18.03") */
          public static final MediaSize ISO_C3 =
                  new MediaSize("ISO_C3", "android", R.string.mediasize_iso_c3, 12760, 18030);
          /** ISO C4 media size: 229mm x 324mm (9.02" x 12.76") */
          public static final MediaSize ISO_C4 =
                  new MediaSize("ISO_C4", "android", R.string.mediasize_iso_c4, 9020, 12760);
          /** ISO C5 media size: 162mm x 229mm (6.38" x 9.02") */
          public static final MediaSize ISO_C5 =
                  new MediaSize("ISO_C5", "android", R.string.mediasize_iso_c5, 6380, 9020);
          /** ISO C6 media size: 114mm x 162mm (4.49" x 6.38") */
          public static final MediaSize ISO_C6 =
                  new MediaSize("ISO_C6", "android", R.string.mediasize_iso_c6, 4490, 6380);
          /** ISO C7 media size: 81mm x 114mm (3.19" x 4.49") */
          public static final MediaSize ISO_C7 =
                  new MediaSize("ISO_C7", "android", R.string.mediasize_iso_c7, 3190, 4490);
          /** ISO C8 media size: 57mm x 81mm (2.24" x 3.19") */
          public static final MediaSize ISO_C8 =
                  new MediaSize("ISO_C8", "android", R.string.mediasize_iso_c8, 2240, 3190);
          /** ISO C9 media size: 40mm x 57mm (1.57" x 2.24") */
          public static final MediaSize ISO_C9 =
                  new MediaSize("ISO_C9", "android", R.string.mediasize_iso_c9, 1570, 2240);
          /** ISO C10 media size: 28mm x 40mm (1.10" x 1.57") */
          public static final MediaSize ISO_C10 =
                  new MediaSize("ISO_C10", "android", R.string.mediasize_iso_c10, 1100, 1570);
  
          // North America
  
          /** North America Letter media size: 8.5" x 11" (279mm x 216mm) */
          public static final MediaSize NA_LETTER =
                  new MediaSize("NA_LETTER", "android", R.string.mediasize_na_letter, 8500, 11000);
          /** North America Government-Letter media size: 8.0" x 10.5" (203mm x 267mm) */
          public static final MediaSize NA_GOVT_LETTER =
                  new MediaSize("NA_GOVT_LETTER", "android",
                          R.string.mediasize_na_gvrnmt_letter, 8000, 10500);
          /** North America Legal media size: 8.5" x 14" (216mm x 356mm) */
          public static final MediaSize NA_LEGAL =
                  new MediaSize("NA_LEGAL", "android", R.string.mediasize_na_legal, 8500, 14000);
          /** North America Junior Legal media size: 8.0" x 5.0" (203mm × 127mm) */
          public static final MediaSize NA_JUNIOR_LEGAL =
                  new MediaSize("NA_JUNIOR_LEGAL", "android",
                          R.string.mediasize_na_junior_legal, 8000, 5000);
          /** North America Ledger media size: 17" x 11" (432mm × 279mm) */
          public static final MediaSize NA_LEDGER =
                  new MediaSize("NA_LEDGER", "android", R.string.mediasize_na_ledger, 17000, 11000);
          /** North America Tabloid media size: 11" x 17" (279mm × 432mm) */
          public static final MediaSize NA_TABLOID =
                  new MediaSize("NA_TABLOID", "android",
                          R.string.mediasize_na_tabloid, 11000, 17000);
          /** North America Index Card 3x5 media size: 3" x 5" (76mm x 127mm) */
          public static final MediaSize NA_INDEX_3X5 =
                  new MediaSize("NA_INDEX_3X5", "android",
                          R.string.mediasize_na_index_3x5, 3000, 5000);
          /** North America Index Card 4x6 media size: 4" x 6" (102mm x 152mm) */
          public static final MediaSize NA_INDEX_4X6 =
                  new MediaSize("NA_INDEX_4X6", "android",
                          R.string.mediasize_na_index_4x6, 4000, 6000);
          /** North America Index Card 5x8 media size: 5" x 8" (127mm x 203mm) */
          public static final MediaSize NA_INDEX_5X8 =
                  new MediaSize("NA_INDEX_5X8", "android",
                          R.string.mediasize_na_index_5x8, 5000, 8000);
          /** North America Monarch media size: 7.25" x 10.5" (184mm x 267mm) */
          public static final MediaSize NA_MONARCH =
                  new MediaSize("NA_MONARCH", "android",
                          R.string.mediasize_na_monarch, 7250, 10500);
          /** North America Quarto media size: 8" x 10" (203mm x 254mm) */
          public static final MediaSize NA_QUARTO =
                  new MediaSize("NA_QUARTO", "android",
                          R.string.mediasize_na_quarto, 8000, 10000);
          /** North America Foolscap media size: 8" x 13" (203mm x 330mm) */
          public static final MediaSize NA_FOOLSCAP =
                  new MediaSize("NA_FOOLSCAP", "android",
                          R.string.mediasize_na_foolscap, 8000, 13000);
  
          // Chinese
  
          /** Chinese ROC 8K media size: 270mm x 390mm (10.629" x 15.3543") */
          public static final MediaSize ROC_8K =
                  new MediaSize("ROC_8K", "android",
                          R.string.mediasize_chinese_roc_8k, 10629, 15354);
          /** Chinese ROC 16K media size: 195mm x 270mm (7.677" x 10.629") */
          public static final MediaSize ROC_16K =
                  new MediaSize("ROC_16K", "android",
                          R.string.mediasize_chinese_roc_16k, 7677, 10629);
  
          /** Chinese PRC 1 media size: 102mm x 165mm (4.015" x 6.496") */
          public static final MediaSize PRC_1 =
                  new MediaSize("PRC_1", "android",
                          R.string.mediasize_chinese_prc_1, 4015, 6496);
          /** Chinese PRC 2 media size: 102mm x 176mm (4.015" x 6.929") */
          public static final MediaSize PRC_2 =
                  new MediaSize("PRC_2", "android",
                          R.string.mediasize_chinese_prc_2, 4015, 6929);
          /** Chinese PRC 3 media size: 125mm x 176mm (4.921" x 6.929") */
          public static final MediaSize PRC_3 =
                  new MediaSize("PRC_3", "android",
                          R.string.mediasize_chinese_prc_3, 4921, 6929);
          /** Chinese PRC 4 media size: 110mm x 208mm (4.330" x 8.189") */
          public static final MediaSize PRC_4 =
                  new MediaSize("PRC_4", "android",
                          R.string.mediasize_chinese_prc_4, 4330, 8189);
          /** Chinese PRC 5 media size: 110mm x 220mm (4.330" x 8.661") */
          public static final MediaSize PRC_5 =
                  new MediaSize("PRC_5", "android",
                          R.string.mediasize_chinese_prc_5, 4330, 8661);
          /** Chinese PRC 6 media size: 120mm x 320mm (4.724" x 12.599") */
          public static final MediaSize PRC_6 =
                  new MediaSize("PRC_6", "android",
                          R.string.mediasize_chinese_prc_6, 4724, 12599);
          /** Chinese PRC 7 media size: 160mm x 230mm (6.299" x 9.055") */
          public static final MediaSize PRC_7 =
                  new MediaSize("PRC_7", "android",
                          R.string.mediasize_chinese_prc_7, 6299, 9055);
          /** Chinese PRC 8 media size: 120mm x 309mm (4.724" x 12.165") */
          public static final MediaSize PRC_8 =
                  new MediaSize("PRC_8", "android",
                          R.string.mediasize_chinese_prc_8, 4724, 12165);
          /** Chinese PRC 9 media size: 229mm x 324mm (9.016" x 12.756") */
          public static final MediaSize PRC_9 =
                  new MediaSize("PRC_9", "android",
                          R.string.mediasize_chinese_prc_9, 9016, 12756);
          /** Chinese PRC 10 media size: 324mm x 458mm (12.756" x 18.032") */
          public static final MediaSize PRC_10 =
                  new MediaSize("PRC_10", "android",
                          R.string.mediasize_chinese_prc_10, 12756, 18032);
  
          /** Chinese PRC 16k media size: 146mm x 215mm (5.749" x 8.465") */
          public static final MediaSize PRC_16K =
                  new MediaSize("PRC_16K", "android",
                          R.string.mediasize_chinese_prc_16k, 5749, 8465);
          /** Chinese Pa Kai media size: 267mm x 389mm (10.512" x 15.315") */
          public static final MediaSize OM_PA_KAI =
                  new MediaSize("OM_PA_KAI", "android",
                          R.string.mediasize_chinese_om_pa_kai, 10512, 15315);
          /** Chinese Dai Pa Kai media size: 275mm x 395mm (10.827" x 15.551") */
          public static final MediaSize OM_DAI_PA_KAI =
                  new MediaSize("OM_DAI_PA_KAI", "android",
                          R.string.mediasize_chinese_om_dai_pa_kai, 10827, 15551);
          /** Chinese Jurro Ku Kai media size: 198mm x 275mm (7.796" x 10.827") */
          public static final MediaSize OM_JUURO_KU_KAI =
                  new MediaSize("OM_JUURO_KU_KAI", "android",
                          R.string.mediasize_chinese_om_jurro_ku_kai, 7796, 10827);
  
          // Japanese
  
          /** Japanese JIS B10 media size: 32mm x 45mm (1.259" x 1.772") */
          public static final MediaSize JIS_B10 =
                  new MediaSize("JIS_B10", "android",
                          R.string.mediasize_japanese_jis_b10, 1259, 1772);
          /** Japanese JIS B9 media size: 45mm x 64mm (1.772" x 2.52") */
          public static final MediaSize JIS_B9 =
                  new MediaSize("JIS_B9", "android",
                          R.string.mediasize_japanese_jis_b9, 1772, 2520);
          /** Japanese JIS B8 media size: 64mm x 91mm (2.52" x 3.583") */
          public static final MediaSize JIS_B8 =
                  new MediaSize("JIS_B8", "android",
                          R.string.mediasize_japanese_jis_b8, 2520, 3583);
          /** Japanese JIS B7 media size: 91mm x 128mm (3.583" x 5.049") */
          public static final MediaSize JIS_B7 =
                  new MediaSize("JIS_B7", "android",
                          R.string.mediasize_japanese_jis_b7, 3583, 5049);
          /** Japanese JIS B6 media size: 128mm x 182mm (5.049" x 7.165") */
          public static final MediaSize JIS_B6 =
                  new MediaSize("JIS_B6", "android",
                          R.string.mediasize_japanese_jis_b6, 5049, 7165);
          /** Japanese JIS B5 media size: 182mm x 257mm (7.165" x 10.118") */
          public static final MediaSize JIS_B5 =
                  new MediaSize("JIS_B5", "android",
                          R.string.mediasize_japanese_jis_b5, 7165, 10118);
          /** Japanese JIS B4 media size: 257mm x 364mm (10.118" x 14.331") */
          public static final MediaSize JIS_B4 =
                  new MediaSize("JIS_B4", "android",
                          R.string.mediasize_japanese_jis_b4, 10118, 14331);
          /** Japanese JIS B3 media size: 364mm x 515mm (14.331" x 20.276") */
          public static final MediaSize JIS_B3 =
                  new MediaSize("JIS_B3", "android",
                          R.string.mediasize_japanese_jis_b3, 14331, 20276);
          /** Japanese JIS B2 media size: 515mm x 728mm (20.276" x 28.661") */
          public static final MediaSize JIS_B2 =
                  new MediaSize("JIS_B2", "android",
                          R.string.mediasize_japanese_jis_b2, 20276, 28661);
          /** Japanese JIS B1 media size: 728mm x 1030mm (28.661" x 40.551") */
          public static final MediaSize JIS_B1 =
                  new MediaSize("JIS_B1", "android",
                          R.string.mediasize_japanese_jis_b1, 28661, 40551);
          /** Japanese JIS B0 media size: 1030mm x 1456mm (40.551" x 57.323") */
          public static final MediaSize JIS_B0 =
                  new MediaSize("JIS_B0", "android",
                          R.string.mediasize_japanese_jis_b0, 40551, 57323);
  
          /** Japanese JIS Exec media size: 216mm x 330mm (8.504" x 12.992") */
          public static final MediaSize JIS_EXEC =
                  new MediaSize("JIS_EXEC", "android",
                          R.string.mediasize_japanese_jis_exec, 8504, 12992);
  
          /** Japanese Chou4 media size: 90mm x 205mm (3.543" x 8.071") */
          public static final MediaSize JPN_CHOU4 =
                  new MediaSize("JPN_CHOU4", "android",
                          R.string.mediasize_japanese_chou4, 3543, 8071);
          /** Japanese Chou3 media size: 120mm x 235mm (4.724" x 9.252") */
          public static final MediaSize JPN_CHOU3 =
                  new MediaSize("JPN_CHOU3", "android",
                          R.string.mediasize_japanese_chou3, 4724, 9252);
          /** Japanese Chou2 media size: 111.1mm x 146mm (4.374" x 5.748") */
          public static final MediaSize JPN_CHOU2 =
                  new MediaSize("JPN_CHOU2", "android",
                          R.string.mediasize_japanese_chou2, 4374, 5748);
  
          /** Japanese Hagaki media size: 100mm x 148mm (3.937" x 5.827") */
          public static final MediaSize JPN_HAGAKI =
                  new MediaSize("JPN_HAGAKI", "android",
                          R.string.mediasize_japanese_hagaki, 3937, 5827);
          /** Japanese Oufuku media size: 148mm x 200mm (5.827" x 7.874") */
          public static final MediaSize JPN_OUFUKU =
                  new MediaSize("JPN_OUFUKU", "android",
                          R.string.mediasize_japanese_oufuku, 5827, 7874);
  
          /** Japanese Kahu media size: 240mm x 322.1mm (9.449" x 12.681") */
          public static final MediaSize JPN_KAHU =
                  new MediaSize("JPN_KAHU", "android",
                          R.string.mediasize_japanese_kahu, 9449, 12681);
          /** Japanese Kaku2 media size: 240mm x 332mm (9.449" x 13.071") */
          public static final MediaSize JPN_KAKU2 =
                  new MediaSize("JPN_KAKU2", "android",
                          R.string.mediasize_japanese_kaku2, 9449, 13071);
  
          /** Japanese You4 media size: 105mm x 235mm (4.134" x 9.252") */
          public static final MediaSize JPN_YOU4 =
                  new MediaSize("JPN_YOU4", "android",
                          R.string.mediasize_japanese_you4, 4134, 9252);
  
          private final @NonNull String mId;
          /**@hide */
          public final @NonNull String mLabel;
          /**@hide */
          public final @Nullable String mPackageName;
          /**@hide */
          public final @StringRes int mLabelResId;
          private final @IntRange(from = 1) int mWidthMils;
          private final @IntRange(from = 1) int mHeightMils;
  
          /**
           * Creates a new instance.
           *
           * @param id The unique media size id.
           * @param packageName The name of the creating package.
           * @param labelResId The resource if of a human readable label.
           * @param widthMils The width in mils (thousandths of an inch).
           * @param heightMils The height in mils (thousandths of an inch).
           *
           * @throws IllegalArgumentException If the id is empty or the label
           * is empty or the widthMils is less than or equal to zero or the
           * heightMils is less than or equal to zero.
           *
           * @hide
           */
          public MediaSize(String id, String packageName, int labelResId,
                  int widthMils, int heightMils) {
              this(id, null, packageName, widthMils, heightMils, labelResId);
  
              // Build this mapping only for predefined media sizes.
              sIdToMediaSizeMap.put(mId, this);
          }
  
          /**
           * Creates a new instance.
           *
           * @param id The unique media size id. It is unique amongst other media sizes
           *        supported by the printer.
           * @param label The <strong>localized</strong> human readable label.
           * @param widthMils The width in mils (thousandths of an inch).
           * @param heightMils The height in mils (thousandths of an inch).
           *
           * @throws IllegalArgumentException If the id is empty or the label is empty
           * or the widthMils is less than or equal to zero or the heightMils is less
           * than or equal to zero.
           */
          public MediaSize(@NonNull String id, @NonNull String label,
                  @IntRange(from = 1) int widthMils, @IntRange(from = 1) int heightMils) {
              this(id, label, null, widthMils, heightMils, 0);
          }
  
          /**
           * Get the Id of all predefined media sizes beside the {@link #UNKNOWN_PORTRAIT} and
           * {@link #UNKNOWN_LANDSCAPE}.
           *
           * @return List of all predefined media sizes
           *
           * @hide
           */
          public static @NonNull ArraySet<MediaSize> getAllPredefinedSizes() {
              ArraySet<MediaSize> definedMediaSizes = new ArraySet<>(sIdToMediaSizeMap.values());
  
              definedMediaSizes.remove(UNKNOWN_PORTRAIT);
              definedMediaSizes.remove(UNKNOWN_LANDSCAPE);
  
              return definedMediaSizes;
          }
  
          /**
           * Creates a new instance.
           *
           * @param id The unique media size id. It is unique amongst other media sizes
           *        supported by the printer.
           * @param label The <strong>localized</strong> human readable label.
           * @param packageName The name of the creating package.
           * @param widthMils The width in mils (thousandths of an inch).
           * @param heightMils The height in mils (thousandths of an inch).
           * @param labelResId The resource if of a human readable label.
           *
           * @throws IllegalArgumentException If the id is empty or the label is unset
           * or the widthMils is less than or equal to zero or the heightMils is less
           * than or equal to zero.
           *
           * @hide
           */
          public MediaSize(String id, String label, String packageName, int widthMils, int heightMils,
                  int labelResId) {
              mPackageName = packageName;
              mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty.");
              mLabelResId = labelResId;
              mWidthMils = Preconditions.checkArgumentPositive(widthMils, "widthMils cannot be " +
                      "less than or equal to zero.");
              mHeightMils = Preconditions.checkArgumentPositive(heightMils, "heightMils cannot be " +
                      "less than or equal to zero.");
              mLabel = label;
  
              // The label has to be either a string ot a StringRes
              Preconditions.checkArgument(!TextUtils.isEmpty(label) !=
                      (!TextUtils.isEmpty(packageName) && labelResId != 0), "label cannot be empty.");
          }
  
          /**
           * Gets the unique media size id. It is unique amongst other media sizes
           * supported by the printer.
           * <p>
           * This id is defined by the client that generated the media size
           * instance and should not be interpreted by other parties.
           * </p>
           *
           * @return The unique media size id.
           */
          public @NonNull String getId() {
              return mId;
          }
  
          /**
           * Gets the human readable media size label.
           *
           * @param packageManager The package manager for loading the label.
           * @return The human readable label.
           */
          public @NonNull String getLabel(@NonNull PackageManager packageManager) {
              if (!TextUtils.isEmpty(mPackageName) && mLabelResId > 0) {
                  try {
                      return packageManager.getResourcesForApplication(
                              mPackageName).getString(mLabelResId);
                  } catch (NotFoundException | NameNotFoundException e) {
                      Log.w(LOG_TAG, "Could not load resouce" + mLabelResId
                              + " from package " + mPackageName);
                  }
              }
              return mLabel;
          }
  
          /**
           * Gets the media width in mils (thousandths of an inch).
           *
           * @return The media width.
           */
          public @IntRange(from = 1) int getWidthMils() {
              return mWidthMils;
          }
  
          /**
           * Gets the media height in mils (thousandths of an inch).
           *
           * @return The media height.
           */
          public @IntRange(from = 1) int getHeightMils() {
              return mHeightMils;
          }
  
          /**
           * Gets whether this media size is in portrait which is the
           * height is greater or equal to the width.
           *
           * @return True if the media size is in portrait, false if
           * it is in landscape.
           */
          public boolean isPortrait() {
              return mHeightMils >= mWidthMils;
          }
  
          /**
           * Returns a new media size instance in a portrait orientation,
           * which is the height is the greater dimension.
           *
           * @return New instance in landscape orientation if this one
           * is in landscape, otherwise this instance.
           */
          public @NonNull MediaSize asPortrait() {
              if (isPortrait()) {
                  return this;
              }
              return new MediaSize(mId, mLabel, mPackageName,
                      Math.min(mWidthMils, mHeightMils),
                      Math.max(mWidthMils, mHeightMils),
                      mLabelResId);
          }
  
          /**
           * Returns a new media size instance in a landscape orientation,
           * which is the height is the lesser dimension.
           *
           * @return New instance in landscape orientation if this one
           * is in portrait, otherwise this instance.
           */
          public @NonNull MediaSize asLandscape() {
              if (!isPortrait()) {
                  return this;
              }
              return new MediaSize(mId, mLabel, mPackageName,
                      Math.max(mWidthMils, mHeightMils),
                      Math.min(mWidthMils, mHeightMils),
                      mLabelResId);
          }
  
          void writeToParcel(Parcel parcel) {
              parcel.writeString(mId);
              parcel.writeString(mLabel);
              parcel.writeString(mPackageName);
              parcel.writeInt(mWidthMils);
              parcel.writeInt(mHeightMils);
              parcel.writeInt(mLabelResId);
          }
  
          static MediaSize createFromParcel(Parcel parcel) {
              return new MediaSize(
                      parcel.readString(),
                      parcel.readString(),
                      parcel.readString(),
                      parcel.readInt(),
                      parcel.readInt(),
                      parcel.readInt());
          }
  
          @Override
          public int hashCode() {
              final int prime = 31;
              int result = 1;
              result = prime * result + mWidthMils;
              result = prime * result + mHeightMils;
              return result;
          }
  
          @Override
          public boolean equals(Object obj) {
              if (this == obj) {
                  return true;
              }
              if (obj == null) {
                  return false;
              }
              if (getClass() != obj.getClass()) {
                  return false;
              }
              MediaSize other = (MediaSize) obj;
              if (mWidthMils != other.mWidthMils) {
                  return false;
              }
              if (mHeightMils != other.mHeightMils) {
                  return false;
              }
              return true;
          }
  
          @Override
          public String toString() {
              StringBuilder builder = new StringBuilder();
              builder.append("MediaSize{");
              builder.append("id: ").append(mId);
              builder.append(", label: ").append(mLabel);
              builder.append(", packageName: ").append(mPackageName);
              builder.append(", heightMils: ").append(mHeightMils);
              builder.append(", widthMils: ").append(mWidthMils);
              builder.append(", labelResId: ").append(mLabelResId);
              builder.append("}");
              return builder.toString();
          }
  
          /**
           * Gets a standard media size given its id.
           *
           * @param id The media size id.
           * @return The media size for the given id or null.
           *
           * @hide
           */
          public static MediaSize getStandardMediaSizeById(String id) {
              return sIdToMediaSizeMap.get(id);
          }
      }
  
      /**
       * This class specifies a supported resolution in DPI (dots per inch).
       * Resolution defines how many points with different color can be placed
       * on one inch in horizontal or vertical direction of the target media.
       * For example, a printer with 600 DPI can produce higher quality images
       * the one with 300 DPI resolution.
       */
      public static final class Resolution {
          private final @NonNull String mId;
          private final @NonNull String mLabel;
          private final @IntRange(from = 1) int mHorizontalDpi;
          private final @IntRange(from = 1) int mVerticalDpi;
  
          /**
           * Creates a new instance.
           *
           * @param id The unique resolution id. It is unique amongst other resolutions
           *        supported by the printer.
           * @param label The <strong>localized</strong> human readable label.
           * @param horizontalDpi The horizontal resolution in DPI (dots per inch).
           * @param verticalDpi The vertical resolution in DPI (dots per inch).
           *
           * @throws IllegalArgumentException If the id is empty or the label is empty
           * or the horizontalDpi is less than or equal to zero or the verticalDpi is
           * less than or equal to zero.
           */
          public Resolution(@NonNull String id, @NonNull String label,
                  @IntRange(from = 1) int horizontalDpi, @IntRange(from = 1) int verticalDpi) {
              if (TextUtils.isEmpty(id)) {
                  throw new IllegalArgumentException("id cannot be empty.");
              }
              if (TextUtils.isEmpty(label)) {
                  throw new IllegalArgumentException("label cannot be empty.");
              }
              if (horizontalDpi <= 0) {
                  throw new IllegalArgumentException("horizontalDpi "
                          + "cannot be less than or equal to zero.");
              }
              if (verticalDpi <= 0) {
                  throw new IllegalArgumentException("verticalDpi"
                         + " cannot be less than or equal to zero.");
              }
              mId = id;
              mLabel = label;
              mHorizontalDpi = horizontalDpi;
              mVerticalDpi = verticalDpi;
          }
  
          /**
           * Gets the unique resolution id. It is unique amongst other resolutions
           * supported by the printer.
           * <p>
           * This id is defined by the client that generated the resolution
           * instance and should not be interpreted by other parties.
           * </p>
           *
           * @return The unique resolution id.
           */
          public @NonNull String getId() {
              return mId;
          }
  
          /**
           * Gets the resolution human readable label.
           *
           * @return The human readable label.
           */
          public @NonNull String getLabel() {
              return mLabel;
          }
  
          /**
           * Gets the horizontal resolution in DPI (dots per inch).
           *
           * @return The horizontal resolution.
           */
          public @IntRange(from = 1) int getHorizontalDpi() {
              return mHorizontalDpi;
          }
  
          /**
           * Gets the vertical resolution in DPI (dots per inch).
           *
           * @return The vertical resolution.
           */
          public @IntRange(from = 1) int getVerticalDpi() {
              return mVerticalDpi;
          }
  
          void writeToParcel(Parcel parcel) {
              parcel.writeString(mId);
              parcel.writeString(mLabel);
              parcel.writeInt(mHorizontalDpi);
              parcel.writeInt(mVerticalDpi);
          }
  
          static Resolution createFromParcel(Parcel parcel) {
              return new Resolution(
                      parcel.readString(),
                      parcel.readString(),
                      parcel.readInt(),
                      parcel.readInt());
          }
  
          @Override
          public int hashCode() {
              final int prime = 31;
              int result = 1;
              result = prime * result + mHorizontalDpi;
              result = prime * result + mVerticalDpi;
              return result;
          }
  
          @Override
          public boolean equals(Object obj) {
              if (this == obj) {
                  return true;
              }
              if (obj == null) {
                  return false;
              }
              if (getClass() != obj.getClass()) {
                  return false;
              }
              Resolution other = (Resolution) obj;
              if (mHorizontalDpi != other.mHorizontalDpi) {
                  return false;
              }
              if (mVerticalDpi != other.mVerticalDpi) {
                  return false;
              }
              return true;
          }
  
          @Override
          public String toString() {
              StringBuilder builder = new StringBuilder();
              builder.append("Resolution{");
              builder.append("id: ").append(mId);
              builder.append(", label: ").append(mLabel);
              builder.append(", horizontalDpi: ").append(mHorizontalDpi);
              builder.append(", verticalDpi: ").append(mVerticalDpi);
              builder.append("}");
              return builder.toString();
          }
      }
  
      /**
       * This class specifies content margins. Margins define the white space
       * around the content where the left margin defines the amount of white
       * space on the left of the content and so on.
       */
      public static final class Margins {
          public static final Margins NO_MARGINS = new Margins(0,  0,  0,  0);
  
          private final int mLeftMils;
          private final int mTopMils;
          private final int mRightMils;
          private final int mBottomMils;
  
          /**
           * Creates a new instance.
           *
           * @param leftMils The left margin in mils (thousandths of an inch).
           * @param topMils The top margin in mils (thousandths of an inch).
           * @param rightMils The right margin in mils (thousandths of an inch).
           * @param bottomMils The bottom margin in mils (thousandths of an inch).
           */
          public Margins(int leftMils, int topMils, int rightMils, int bottomMils) {
              mTopMils = topMils;
              mLeftMils = leftMils;
              mRightMils = rightMils;
              mBottomMils = bottomMils;
          }
  
          /**
           * Gets the left margin in mils (thousandths of an inch).
           *
           * @return The left margin.
           */
          public int getLeftMils() {
              return mLeftMils;
          }
  
          /**
           * Gets the top margin in mils (thousandths of an inch).
           *
           * @return The top margin.
           */
          public int getTopMils() {
              return mTopMils;
          }
  
          /**
           * Gets the right margin in mils (thousandths of an inch).
           *
           * @return The right margin.
           */
          public int getRightMils() {
              return mRightMils;
          }
  
          /**
           * Gets the bottom margin in mils (thousandths of an inch).
           *
           * @return The bottom margin.
           */
          public int getBottomMils() {
              return mBottomMils;
          }
  
          void writeToParcel(Parcel parcel) {
              parcel.writeInt(mLeftMils);
              parcel.writeInt(mTopMils);
              parcel.writeInt(mRightMils);
              parcel.writeInt(mBottomMils);
          }
  
          static Margins createFromParcel(Parcel parcel) {
              return new Margins(
                      parcel.readInt(),
                      parcel.readInt(),
                      parcel.readInt(),
                      parcel.readInt());
          }
  
          @Override
          public int hashCode() {
              final int prime = 31;
              int result = 1;
              result = prime * result + mBottomMils;
              result = prime * result + mLeftMils;
              result = prime * result + mRightMils;
              result = prime * result + mTopMils;
              return result;
          }
  
          @Override
          public boolean equals(Object obj) {
              if (this == obj) {
                  return true;
              }
              if (obj == null) {
                  return false;
              }
              if (getClass() != obj.getClass()) {
                  return false;
              }
              Margins other = (Margins) obj;
              if (mBottomMils != other.mBottomMils) {
                  return false;
              }
              if (mLeftMils != other.mLeftMils) {
                  return false;
              }
              if (mRightMils != other.mRightMils) {
                  return false;
              }
              if (mTopMils != other.mTopMils) {
                  return false;
              }
              return true;
          }
  
          @Override
          public String toString() {
              StringBuilder builder = new StringBuilder();
              builder.append("Margins{");
              builder.append("leftMils: ").append(mLeftMils);
              builder.append(", topMils: ").append(mTopMils);
              builder.append(", rightMils: ").append(mRightMils);
              builder.append(", bottomMils: ").append(mBottomMils);
              builder.append("}");
              return builder.toString();
          }
      }
  
      static String colorModeToString(int colorMode) {
          switch (colorMode) {
              case COLOR_MODE_MONOCHROME: {
                  return "COLOR_MODE_MONOCHROME";
              }
              case COLOR_MODE_COLOR: {
                  return "COLOR_MODE_COLOR";
              }
              default: {
                  return "COLOR_MODE_UNKNOWN";
              }
          }
      }
  
      static String duplexModeToString(int duplexMode) {
          switch (duplexMode) {
              case DUPLEX_MODE_NONE: {
                  return "DUPLEX_MODE_NONE";
              }
              case DUPLEX_MODE_LONG_EDGE: {
                  return "DUPLEX_MODE_LONG_EDGE";
              }
              case DUPLEX_MODE_SHORT_EDGE: {
                  return "DUPLEX_MODE_SHORT_EDGE";
              }
              default: {
                  return "DUPLEX_MODE_UNKNOWN";
              }
          }
      }
  
      static void enforceValidColorMode(int colorMode) {
          if ((colorMode & VALID_COLOR_MODES) == 0 || Integer.bitCount(colorMode) != 1) {
              throw new IllegalArgumentException("invalid color mode: " + colorMode);
          }
      }
  
      static void enforceValidDuplexMode(int duplexMode) {
          if ((duplexMode & VALID_DUPLEX_MODES) == 0 || Integer.bitCount(duplexMode) != 1) {
              throw new IllegalArgumentException("invalid duplex mode: " + duplexMode);
          }
      }
  
      /**
       * Builder for creating {@link PrintAttributes}.
       */
      public static final class Builder {
          private final PrintAttributes mAttributes = new PrintAttributes();
  
          /**
           * Sets the media size.
           *
           * @param mediaSize The media size.
           * @return This builder.
           */
          public @NonNull Builder setMediaSize(@NonNull MediaSize mediaSize) {
              mAttributes.setMediaSize(mediaSize);
              return this;
          }
  
          /**
           * Sets the resolution.
           *
           * @param resolution The resolution.
           * @return This builder.
           */
          public @NonNull Builder setResolution(@NonNull Resolution resolution) {
              mAttributes.setResolution(resolution);
              return this;
          }
  
          /**
           * Sets the minimal margins. If the content does not fit
           * these margins it will be clipped.
           *
           * @param margins The margins.
           * @return This builder.
           */
          public @NonNull Builder setMinMargins(@NonNull Margins margins) {
              mAttributes.setMinMargins(margins);
              return this;
          }
  
          /**
           * Sets the color mode.
           *
           * @param colorMode A valid color mode or zero.
           * @return This builder.
           *
           * @see PrintAttributes#COLOR_MODE_MONOCHROME
           * @see PrintAttributes#COLOR_MODE_COLOR
           */
          public @NonNull Builder setColorMode(@ColorMode int colorMode) {
              mAttributes.setColorMode(colorMode);
              return this;
          }
  
          /**
           * Sets the duplex mode.
           *
           * @param duplexMode A valid duplex mode or zero.
           * @return This builder.
           *
           * @see PrintAttributes#DUPLEX_MODE_NONE
           * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
           * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
           */
          public @NonNull Builder setDuplexMode(@DuplexMode int duplexMode) {
              mAttributes.setDuplexMode(duplexMode);
              return this;
          }
  
          /**
           * Creates a new {@link PrintAttributes} instance.
           *
           * @return The new instance.
           */
          public @NonNull PrintAttributes build() {
              return mAttributes;
          }
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<PrintAttributes> CREATOR =
              new Creator<PrintAttributes>() {
          @Override
          public PrintAttributes createFromParcel(Parcel parcel) {
              return new PrintAttributes(parcel);
          }
  
          @Override
          public PrintAttributes[] newArray(int size) {
              return new PrintAttributes[size];
          }
      };
  }
  