1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.print;
18
19import android.annotation.IntDef;
20import android.annotation.IntRange;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.annotation.StringRes;
24import android.content.pm.PackageManager;
25import android.content.pm.PackageManager.NameNotFoundException;
26import android.content.res.Resources.NotFoundException;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.text.TextUtils;
30import android.util.ArrayMap;
31import android.util.ArraySet;
32import android.util.Log;
33
34import com.android.internal.R;
35import com.android.internal.util.Preconditions;
36
37import java.lang.annotation.Retention;
38import java.lang.annotation.RetentionPolicy;
39import java.util.Map;
40
41/**
42 * This class represents the attributes of a print job. These attributes
43 * describe how the printed content should be laid out. For example, the
44 * print attributes may state that the content should be laid out on a
45 * letter size with 300 DPI (dots per inch) resolution, have a margin of
46 * 10 mills (thousand of an inch) on all sides, and be black and white.
47 */
48public final class PrintAttributes implements Parcelable {
49    /** @hide */
50    @Retention(RetentionPolicy.SOURCE)
51    @IntDef(flag = true, value = {
52            COLOR_MODE_MONOCHROME, COLOR_MODE_COLOR
53    })
54    @interface ColorMode {
55    }
56    /** Color mode: Monochrome color scheme, for example one color is used. */
57    public static final int COLOR_MODE_MONOCHROME = 1 << 0;
58    /** Color mode: Color color scheme, for example many colors are used. */
59    public static final int COLOR_MODE_COLOR = 1 << 1;
60
61    private static final int VALID_COLOR_MODES =
62            COLOR_MODE_MONOCHROME | COLOR_MODE_COLOR;
63
64    /** @hide */
65    @Retention(RetentionPolicy.SOURCE)
66    @IntDef(flag = true, value = {
67            DUPLEX_MODE_NONE, DUPLEX_MODE_LONG_EDGE, DUPLEX_MODE_SHORT_EDGE
68    })
69    @interface DuplexMode {
70    }
71    /** Duplex mode: No duplexing. */
72    public static final int DUPLEX_MODE_NONE = 1 << 0;
73    /** Duplex mode: Pages are turned sideways along the long edge - like a book. */
74    public static final int DUPLEX_MODE_LONG_EDGE = 1 << 1;
75    /** Duplex mode: Pages are turned upwards along the short edge - like a notpad. */
76    public static final int DUPLEX_MODE_SHORT_EDGE = 1 << 2;
77
78    private static final int VALID_DUPLEX_MODES =
79            DUPLEX_MODE_NONE | DUPLEX_MODE_LONG_EDGE | DUPLEX_MODE_SHORT_EDGE;
80
81    private @Nullable MediaSize mMediaSize;
82    private @Nullable Resolution mResolution;
83    private @Nullable Margins mMinMargins;
84
85    private @IntRange(from = 0) int mColorMode;
86    private @IntRange(from = 0) int mDuplexMode;
87
88    PrintAttributes() {
89        /* hide constructor */
90    }
91
92    private PrintAttributes(@NonNull Parcel parcel) {
93        mMediaSize = (parcel.readInt() == 1) ? MediaSize.createFromParcel(parcel) : null;
94        mResolution = (parcel.readInt() == 1) ? Resolution.createFromParcel(parcel) : null;
95        mMinMargins = (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
96        mColorMode = parcel.readInt();
97        if (mColorMode != 0) {
98            enforceValidColorMode(mColorMode);
99        }
100        mDuplexMode = parcel.readInt();
101        if (mDuplexMode != 0) {
102            enforceValidDuplexMode(mDuplexMode);
103        }
104    }
105
106    /**
107     * Gets the media size.
108     *
109     * @return The media size or <code>null</code> if not set.
110     */
111    public @Nullable MediaSize getMediaSize() {
112        return mMediaSize;
113    }
114
115    /**
116     * Sets the media size.
117     *
118     * @param mediaSize The media size.
119     *
120     * @hide
121     */
122    public void setMediaSize(MediaSize mediaSize) {
123        mMediaSize = mediaSize;
124    }
125
126    /**
127     * Gets the resolution.
128     *
129     * @return The resolution or <code>null</code> if not set.
130     */
131    public @Nullable Resolution getResolution() {
132        return mResolution;
133    }
134
135    /**
136     * Sets the resolution.
137     *
138     * @param resolution The resolution.
139     *
140     * @hide
141     */
142    public void setResolution(Resolution resolution) {
143        mResolution = resolution;
144    }
145
146    /**
147     * Gets the minimal margins. If the content does not fit
148     * these margins it will be clipped.
149     * <p>
150     * <strong>These margins are physically imposed by the printer and they
151     * are <em>not</em> rotated, i.e. they are the same for both portrait and
152     * landscape. For example, a printer may not be able to print in a stripe
153     * on both left and right sides of the page.
154     * </strong>
155     * </p>
156     *
157     * @return The margins or <code>null</code> if not set.
158     */
159    public @Nullable Margins getMinMargins() {
160        return mMinMargins;
161    }
162
163    /**
164     * Sets the minimal margins. If the content does not fit
165     * these margins it will be clipped.
166     * <p>
167     * <strong>These margins are physically imposed by the printer and they
168     * are <em>not</em> rotated, i.e. they are the same for both portrait and
169     * landscape. For example, a printer may not be able to print in a stripe
170     * on both left and right sides of the page.
171     * </strong>
172     * </p>
173     *
174     * @param margins The margins.
175     *
176     * @hide
177     */
178    public void setMinMargins(Margins margins) {
179        mMinMargins = margins;
180    }
181
182    /**
183     * Gets the color mode.
184     *
185     * @return The color mode or zero if not set.
186     *
187     * @see #COLOR_MODE_COLOR
188     * @see #COLOR_MODE_MONOCHROME
189     */
190    public @IntRange(from = 0) int getColorMode() {
191        return mColorMode;
192    }
193
194    /**
195     * Sets the color mode.
196     *
197     * @param colorMode The color mode.
198     *
199     * @see #COLOR_MODE_MONOCHROME
200     * @see #COLOR_MODE_COLOR
201     *
202     * @hide
203     */
204    public void setColorMode(int colorMode) {
205        enforceValidColorMode(colorMode);
206        mColorMode = colorMode;
207    }
208
209    /**
210     * Gets whether this print attributes are in portrait orientation,
211     * which is the media size is in portrait and all orientation dependent
212     * attributes such as resolution and margins are properly adjusted.
213     *
214     * @return Whether this print attributes are in portrait.
215     *
216     * @hide
217     */
218    public boolean isPortrait() {
219        return mMediaSize.isPortrait();
220    }
221
222    /**
223     * Gets the duplex mode.
224     *
225     * @return The duplex mode or zero if not set.
226     *
227     * @see #DUPLEX_MODE_NONE
228     * @see #DUPLEX_MODE_LONG_EDGE
229     * @see #DUPLEX_MODE_SHORT_EDGE
230     */
231    public @IntRange(from = 0) int getDuplexMode() {
232        return mDuplexMode;
233    }
234
235    /**
236     * Sets the duplex mode.
237     *
238     * @param duplexMode The duplex mode.
239     *
240     * @see #DUPLEX_MODE_NONE
241     * @see #DUPLEX_MODE_LONG_EDGE
242     * @see #DUPLEX_MODE_SHORT_EDGE
243     *
244     * @hide
245     */
246    public void setDuplexMode(int duplexMode) {
247        enforceValidDuplexMode(duplexMode);
248        mDuplexMode = duplexMode;
249    }
250
251    /**
252     * Gets a new print attributes instance which is in portrait orientation,
253     * which is the media size is in portrait and all orientation dependent
254     * attributes such as resolution and margins are properly adjusted.
255     *
256     * @return New instance in portrait orientation if this one is in
257     * landscape, otherwise this instance.
258     *
259     * @hide
260     */
261    public PrintAttributes asPortrait() {
262        if (isPortrait()) {
263            return this;
264        }
265
266        PrintAttributes attributes = new PrintAttributes();
267
268        // Rotate the media size.
269        attributes.setMediaSize(getMediaSize().asPortrait());
270
271        // Rotate the resolution.
272        Resolution oldResolution = getResolution();
273        Resolution newResolution = new Resolution(
274                oldResolution.getId(),
275                oldResolution.getLabel(),
276                oldResolution.getVerticalDpi(),
277                oldResolution.getHorizontalDpi());
278        attributes.setResolution(newResolution);
279
280        // Do not rotate the physical margins.
281        attributes.setMinMargins(getMinMargins());
282
283        attributes.setColorMode(getColorMode());
284        attributes.setDuplexMode(getDuplexMode());
285
286        return attributes;
287    }
288
289    /**
290     * Gets a new print attributes instance which is in landscape orientation,
291     * which is the media size is in landscape and all orientation dependent
292     * attributes such as resolution and margins are properly adjusted.
293     *
294     * @return New instance in landscape orientation if this one is in
295     * portrait, otherwise this instance.
296     *
297     * @hide
298     */
299    public PrintAttributes asLandscape() {
300        if (!isPortrait()) {
301            return this;
302        }
303
304        PrintAttributes attributes = new PrintAttributes();
305
306        // Rotate the media size.
307        attributes.setMediaSize(getMediaSize().asLandscape());
308
309        // Rotate the resolution.
310        Resolution oldResolution = getResolution();
311        Resolution newResolution = new Resolution(
312                oldResolution.getId(),
313                oldResolution.getLabel(),
314                oldResolution.getVerticalDpi(),
315                oldResolution.getHorizontalDpi());
316        attributes.setResolution(newResolution);
317
318        // Do not rotate the physical margins.
319        attributes.setMinMargins(getMinMargins());
320
321        attributes.setColorMode(getColorMode());
322        attributes.setDuplexMode(getDuplexMode());
323
324        return attributes;
325    }
326
327    @Override
328    public void writeToParcel(Parcel parcel, int flags) {
329        if (mMediaSize != null) {
330            parcel.writeInt(1);
331            mMediaSize.writeToParcel(parcel);
332        } else {
333            parcel.writeInt(0);
334        }
335        if (mResolution != null) {
336            parcel.writeInt(1);
337            mResolution.writeToParcel(parcel);
338        } else {
339            parcel.writeInt(0);
340        }
341        if (mMinMargins != null) {
342            parcel.writeInt(1);
343            mMinMargins.writeToParcel(parcel);
344        } else {
345            parcel.writeInt(0);
346        }
347        parcel.writeInt(mColorMode);
348        parcel.writeInt(mDuplexMode);
349    }
350
351    @Override
352    public int describeContents() {
353        return 0;
354    }
355
356    @Override
357    public int hashCode() {
358        final int prime = 31;
359        int result = 1;
360        result = prime * result + mColorMode;
361        result = prime * result + mDuplexMode;
362        result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode());
363        result = prime * result + ((mMediaSize == null) ? 0 : mMediaSize.hashCode());
364        result = prime * result + ((mResolution == null) ? 0 : mResolution.hashCode());
365        return result;
366    }
367
368    @Override
369    public boolean equals(Object obj) {
370        if (this == obj) {
371            return true;
372        }
373        if (obj == null) {
374            return false;
375        }
376        if (getClass() != obj.getClass()) {
377            return false;
378        }
379        PrintAttributes other = (PrintAttributes) obj;
380        if (mColorMode != other.mColorMode) {
381            return false;
382        }
383        if (mDuplexMode != other.mDuplexMode) {
384            return false;
385        }
386        if (mMinMargins == null) {
387            if (other.mMinMargins != null) {
388                return false;
389            }
390        } else if (!mMinMargins.equals(other.mMinMargins)) {
391            return false;
392        }
393        if (mMediaSize == null) {
394            if (other.mMediaSize != null) {
395                return false;
396            }
397        } else if (!mMediaSize.equals(other.mMediaSize)) {
398            return false;
399        }
400        if (mResolution == null) {
401            if (other.mResolution != null) {
402                return false;
403            }
404        } else if (!mResolution.equals(other.mResolution)) {
405            return false;
406        }
407        return true;
408    }
409
410    @Override
411    public String toString() {
412        StringBuilder builder = new StringBuilder();
413        builder.append("PrintAttributes{");
414        builder.append("mediaSize: ").append(mMediaSize);
415        if (mMediaSize != null) {
416            builder.append(", orientation: ").append(mMediaSize.isPortrait()
417                    ? "portrait" : "landscape");
418        } else {
419            builder.append(", orientation: ").append("null");
420        }
421        builder.append(", resolution: ").append(mResolution);
422        builder.append(", minMargins: ").append(mMinMargins);
423        builder.append(", colorMode: ").append(colorModeToString(mColorMode));
424        builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode));
425        builder.append("}");
426        return builder.toString();
427    }
428
429    /** @hide */
430    public void clear() {
431        mMediaSize = null;
432        mResolution = null;
433        mMinMargins = null;
434        mColorMode = 0;
435        mDuplexMode = 0;
436    }
437
438    /**
439     * @hide
440     */
441    public void copyFrom(PrintAttributes other) {
442        mMediaSize = other.mMediaSize;
443        mResolution = other.mResolution;
444        mMinMargins = other.mMinMargins;
445        mColorMode = other.mColorMode;
446        mDuplexMode = other.mDuplexMode;
447    }
448
449    /**
450     * This class specifies a supported media size. Media size is the
451     * dimension of the media on which the content is printed. For
452     * example, the {@link #NA_LETTER} media size designates a page
453     * with size 8.5" x 11".
454     */
455    public static final class MediaSize {
456        private static final String LOG_TAG = "MediaSize";
457
458        private static final Map<String, MediaSize> sIdToMediaSizeMap =
459                new ArrayMap<>();
460
461        /**
462         * Unknown media size in portrait mode.
463         * <p>
464         * <strong>Note: </strong>This is for specifying orientation without media
465         * size. You should not use the dimensions reported by this instance.
466         * </p>
467         */
468        public static final MediaSize UNKNOWN_PORTRAIT =
469                new MediaSize("UNKNOWN_PORTRAIT", "android",
470                        R.string.mediasize_unknown_portrait, 1, Integer.MAX_VALUE);
471
472        /**
473         * Unknown media size in landscape mode.
474         * <p>
475         * <strong>Note: </strong>This is for specifying orientation without media
476         * size. You should not use the dimensions reported by this instance.
477         * </p>
478         */
479        public static final MediaSize UNKNOWN_LANDSCAPE =
480                new MediaSize("UNKNOWN_LANDSCAPE", "android",
481                        R.string.mediasize_unknown_landscape, Integer.MAX_VALUE, 1);
482
483        // ISO sizes
484
485        /** ISO A0 media size: 841mm x 1189mm (33.11" x 46.81") */
486        public static final MediaSize ISO_A0 =
487                new MediaSize("ISO_A0", "android", R.string.mediasize_iso_a0, 33110, 46810);
488        /** ISO A1 media size: 594mm x 841mm (23.39" x 33.11") */
489        public static final MediaSize ISO_A1 =
490                new MediaSize("ISO_A1", "android", R.string.mediasize_iso_a1, 23390, 33110);
491        /** ISO A2 media size: 420mm x 594mm (16.54" x 23.39") */
492        public static final MediaSize ISO_A2 =
493                new MediaSize("ISO_A2", "android", R.string.mediasize_iso_a2, 16540, 23390);
494        /** ISO A3 media size: 297mm x 420mm (11.69" x 16.54") */
495        public static final MediaSize ISO_A3 =
496                new MediaSize("ISO_A3", "android", R.string.mediasize_iso_a3, 11690, 16540);
497        /** ISO A4 media size: 210mm x 297mm (8.27" x 11.69") */
498        public static final MediaSize ISO_A4 =
499                new MediaSize("ISO_A4", "android", R.string.mediasize_iso_a4, 8270, 11690);
500        /** ISO A5 media size: 148mm x 210mm (5.83" x 8.27") */
501        public static final MediaSize ISO_A5 =
502                new MediaSize("ISO_A5", "android", R.string.mediasize_iso_a5, 5830, 8270);
503        /** ISO A6 media size: 105mm x 148mm (4.13" x 5.83") */
504        public static final MediaSize ISO_A6 =
505                new MediaSize("ISO_A6", "android", R.string.mediasize_iso_a6, 4130, 5830);
506        /** ISO A7 media size: 74mm x 105mm (2.91" x 4.13") */
507        public static final MediaSize ISO_A7 =
508                new MediaSize("ISO_A7", "android", R.string.mediasize_iso_a7, 2910, 4130);
509        /** ISO A8 media size: 52mm x 74mm (2.05" x 2.91") */
510        public static final MediaSize ISO_A8 =
511                new MediaSize("ISO_A8", "android", R.string.mediasize_iso_a8, 2050, 2910);
512        /** ISO A9 media size: 37mm x 52mm (1.46" x 2.05") */
513        public static final MediaSize ISO_A9 =
514                new MediaSize("ISO_A9", "android", R.string.mediasize_iso_a9, 1460, 2050);
515        /** ISO A10 media size: 26mm x 37mm (1.02" x 1.46") */
516        public static final MediaSize ISO_A10 =
517                new MediaSize("ISO_A10", "android", R.string.mediasize_iso_a10, 1020, 1460);
518
519        /** ISO B0 media size: 1000mm x 1414mm (39.37" x 55.67") */
520        public static final MediaSize ISO_B0 =
521                new MediaSize("ISO_B0", "android", R.string.mediasize_iso_b0, 39370, 55670);
522        /** ISO B1 media size: 707mm x 1000mm (27.83" x 39.37") */
523        public static final MediaSize ISO_B1 =
524                new MediaSize("ISO_B1", "android", R.string.mediasize_iso_b1, 27830, 39370);
525        /** ISO B2 media size: 500mm x 707mm (19.69" x 27.83") */
526        public static final MediaSize ISO_B2 =
527                new MediaSize("ISO_B2", "android", R.string.mediasize_iso_b2, 19690, 27830);
528        /** ISO B3 media size: 353mm x 500mm (13.90" x 19.69") */
529        public static final MediaSize ISO_B3 =
530                new MediaSize("ISO_B3", "android", R.string.mediasize_iso_b3, 13900, 19690);
531        /** ISO B4 media size: 250mm x 353mm (9.84" x 13.90") */
532        public static final MediaSize ISO_B4 =
533                new MediaSize("ISO_B4", "android", R.string.mediasize_iso_b4, 9840, 13900);
534        /** ISO B5 media size: 176mm x 250mm (6.93" x 9.84") */
535        public static final MediaSize ISO_B5 =
536                new MediaSize("ISO_B5", "android", R.string.mediasize_iso_b5, 6930, 9840);
537        /** ISO B6 media size: 125mm x 176mm (4.92" x 6.93") */
538        public static final MediaSize ISO_B6 =
539                new MediaSize("ISO_B6", "android", R.string.mediasize_iso_b6, 4920, 6930);
540        /** ISO B7 media size: 88mm x 125mm (3.46" x 4.92") */
541        public static final MediaSize ISO_B7 =
542                new MediaSize("ISO_B7", "android", R.string.mediasize_iso_b7, 3460, 4920);
543        /** ISO B8 media size: 62mm x 88mm (2.44" x 3.46") */
544        public static final MediaSize ISO_B8 =
545                new MediaSize("ISO_B8", "android", R.string.mediasize_iso_b8, 2440, 3460);
546        /** ISO B9 media size: 44mm x 62mm (1.73" x 2.44") */
547        public static final MediaSize ISO_B9 =
548                new MediaSize("ISO_B9", "android", R.string.mediasize_iso_b9, 1730, 2440);
549        /** ISO B10 media size: 31mm x 44mm (1.22" x 1.73") */
550        public static final MediaSize ISO_B10 =
551                new MediaSize("ISO_B10", "android", R.string.mediasize_iso_b10, 1220, 1730);
552
553        /** ISO C0 media size: 917mm x 1297mm (36.10" x 51.06") */
554        public static final MediaSize ISO_C0 =
555                new MediaSize("ISO_C0", "android", R.string.mediasize_iso_c0, 36100, 51060);
556        /** ISO C1 media size: 648mm x 917mm (25.51" x 36.10") */
557        public static final MediaSize ISO_C1 =
558                new MediaSize("ISO_C1", "android", R.string.mediasize_iso_c1, 25510, 36100);
559        /** ISO C2 media size: 458mm x 648mm (18.03" x 25.51") */
560        public static final MediaSize ISO_C2 =
561                new MediaSize("ISO_C2", "android", R.string.mediasize_iso_c2, 18030, 25510);
562        /** ISO C3 media size: 324mm x 458mm (12.76" x 18.03") */
563        public static final MediaSize ISO_C3 =
564                new MediaSize("ISO_C3", "android", R.string.mediasize_iso_c3, 12760, 18030);
565        /** ISO C4 media size: 229mm x 324mm (9.02" x 12.76") */
566        public static final MediaSize ISO_C4 =
567                new MediaSize("ISO_C4", "android", R.string.mediasize_iso_c4, 9020, 12760);
568        /** ISO C5 media size: 162mm x 229mm (6.38" x 9.02") */
569        public static final MediaSize ISO_C5 =
570                new MediaSize("ISO_C5", "android", R.string.mediasize_iso_c5, 6380, 9020);
571        /** ISO C6 media size: 114mm x 162mm (4.49" x 6.38") */
572        public static final MediaSize ISO_C6 =
573                new MediaSize("ISO_C6", "android", R.string.mediasize_iso_c6, 4490, 6380);
574        /** ISO C7 media size: 81mm x 114mm (3.19" x 4.49") */
575        public static final MediaSize ISO_C7 =
576                new MediaSize("ISO_C7", "android", R.string.mediasize_iso_c7, 3190, 4490);
577        /** ISO C8 media size: 57mm x 81mm (2.24" x 3.19") */
578        public static final MediaSize ISO_C8 =
579                new MediaSize("ISO_C8", "android", R.string.mediasize_iso_c8, 2240, 3190);
580        /** ISO C9 media size: 40mm x 57mm (1.57" x 2.24") */
581        public static final MediaSize ISO_C9 =
582                new MediaSize("ISO_C9", "android", R.string.mediasize_iso_c9, 1570, 2240);
583        /** ISO C10 media size: 28mm x 40mm (1.10" x 1.57") */
584        public static final MediaSize ISO_C10 =
585                new MediaSize("ISO_C10", "android", R.string.mediasize_iso_c10, 1100, 1570);
586
587        // North America
588
589        /** North America Letter media size: 8.5" x 11" (279mm x 216mm) */
590        public static final MediaSize NA_LETTER =
591                new MediaSize("NA_LETTER", "android", R.string.mediasize_na_letter, 8500, 11000);
592        /** North America Government-Letter media size: 8.0" x 10.5" (203mm x 267mm) */
593        public static final MediaSize NA_GOVT_LETTER =
594                new MediaSize("NA_GOVT_LETTER", "android",
595                        R.string.mediasize_na_gvrnmt_letter, 8000, 10500);
596        /** North America Legal media size: 8.5" x 14" (216mm x 356mm) */
597        public static final MediaSize NA_LEGAL =
598                new MediaSize("NA_LEGAL", "android", R.string.mediasize_na_legal, 8500, 14000);
599        /** North America Junior Legal media size: 8.0" x 5.0" (203mm × 127mm) */
600        public static final MediaSize NA_JUNIOR_LEGAL =
601                new MediaSize("NA_JUNIOR_LEGAL", "android",
602                        R.string.mediasize_na_junior_legal, 8000, 5000);
603        /** North America Ledger media size: 17" x 11" (432mm × 279mm) */
604        public static final MediaSize NA_LEDGER =
605                new MediaSize("NA_LEDGER", "android", R.string.mediasize_na_ledger, 17000, 11000);
606        /** North America Tabloid media size: 11" x 17" (279mm × 432mm) */
607        public static final MediaSize NA_TABLOID =
608                new MediaSize("NA_TABLOID", "android",
609                        R.string.mediasize_na_tabloid, 11000, 17000);
610        /** North America Index Card 3x5 media size: 3" x 5" (76mm x 127mm) */
611        public static final MediaSize NA_INDEX_3X5 =
612                new MediaSize("NA_INDEX_3X5", "android",
613                        R.string.mediasize_na_index_3x5, 3000, 5000);
614        /** North America Index Card 4x6 media size: 4" x 6" (102mm x 152mm) */
615        public static final MediaSize NA_INDEX_4X6 =
616                new MediaSize("NA_INDEX_4X6", "android",
617                        R.string.mediasize_na_index_4x6, 4000, 6000);
618        /** North America Index Card 5x8 media size: 5" x 8" (127mm x 203mm) */
619        public static final MediaSize NA_INDEX_5X8 =
620                new MediaSize("NA_INDEX_5X8", "android",
621                        R.string.mediasize_na_index_5x8, 5000, 8000);
622        /** North America Monarch media size: 7.25" x 10.5" (184mm x 267mm) */
623        public static final MediaSize NA_MONARCH =
624                new MediaSize("NA_MONARCH", "android",
625                        R.string.mediasize_na_monarch, 7250, 10500);
626        /** North America Quarto media size: 8" x 10" (203mm x 254mm) */
627        public static final MediaSize NA_QUARTO =
628                new MediaSize("NA_QUARTO", "android",
629                        R.string.mediasize_na_quarto, 8000, 10000);
630        /** North America Foolscap media size: 8" x 13" (203mm x 330mm) */
631        public static final MediaSize NA_FOOLSCAP =
632                new MediaSize("NA_FOOLSCAP", "android",
633                        R.string.mediasize_na_foolscap, 8000, 13000);
634
635        // Chinese
636
637        /** Chinese ROC 8K media size: 270mm x 390mm (10.629" x 15.3543") */
638        public static final MediaSize ROC_8K =
639                new MediaSize("ROC_8K", "android",
640                        R.string.mediasize_chinese_roc_8k, 10629, 15354);
641        /** Chinese ROC 16K media size: 195mm x 270mm (7.677" x 10.629") */
642        public static final MediaSize ROC_16K =
643                new MediaSize("ROC_16K", "android",
644                        R.string.mediasize_chinese_roc_16k, 7677, 10629);
645
646        /** Chinese PRC 1 media size: 102mm x 165mm (4.015" x 6.496") */
647        public static final MediaSize PRC_1 =
648                new MediaSize("PRC_1", "android",
649                        R.string.mediasize_chinese_prc_1, 4015, 6496);
650        /** Chinese PRC 2 media size: 102mm x 176mm (4.015" x 6.929") */
651        public static final MediaSize PRC_2 =
652                new MediaSize("PRC_2", "android",
653                        R.string.mediasize_chinese_prc_2, 4015, 6929);
654        /** Chinese PRC 3 media size: 125mm x 176mm (4.921" x 6.929") */
655        public static final MediaSize PRC_3 =
656                new MediaSize("PRC_3", "android",
657                        R.string.mediasize_chinese_prc_3, 4921, 6929);
658        /** Chinese PRC 4 media size: 110mm x 208mm (4.330" x 8.189") */
659        public static final MediaSize PRC_4 =
660                new MediaSize("PRC_4", "android",
661                        R.string.mediasize_chinese_prc_4, 4330, 8189);
662        /** Chinese PRC 5 media size: 110mm x 220mm (4.330" x 8.661") */
663        public static final MediaSize PRC_5 =
664                new MediaSize("PRC_5", "android",
665                        R.string.mediasize_chinese_prc_5, 4330, 8661);
666        /** Chinese PRC 6 media size: 120mm x 320mm (4.724" x 12.599") */
667        public static final MediaSize PRC_6 =
668                new MediaSize("PRC_6", "android",
669                        R.string.mediasize_chinese_prc_6, 4724, 12599);
670        /** Chinese PRC 7 media size: 160mm x 230mm (6.299" x 9.055") */
671        public static final MediaSize PRC_7 =
672                new MediaSize("PRC_7", "android",
673                        R.string.mediasize_chinese_prc_7, 6299, 9055);
674        /** Chinese PRC 8 media size: 120mm x 309mm (4.724" x 12.165") */
675        public static final MediaSize PRC_8 =
676                new MediaSize("PRC_8", "android",
677                        R.string.mediasize_chinese_prc_8, 4724, 12165);
678        /** Chinese PRC 9 media size: 229mm x 324mm (9.016" x 12.756") */
679        public static final MediaSize PRC_9 =
680                new MediaSize("PRC_9", "android",
681                        R.string.mediasize_chinese_prc_9, 9016, 12756);
682        /** Chinese PRC 10 media size: 324mm x 458mm (12.756" x 18.032") */
683        public static final MediaSize PRC_10 =
684                new MediaSize("PRC_10", "android",
685                        R.string.mediasize_chinese_prc_10, 12756, 18032);
686
687        /** Chinese PRC 16k media size: 146mm x 215mm (5.749" x 8.465") */
688        public static final MediaSize PRC_16K =
689                new MediaSize("PRC_16K", "android",
690                        R.string.mediasize_chinese_prc_16k, 5749, 8465);
691        /** Chinese Pa Kai media size: 267mm x 389mm (10.512" x 15.315") */
692        public static final MediaSize OM_PA_KAI =
693                new MediaSize("OM_PA_KAI", "android",
694                        R.string.mediasize_chinese_om_pa_kai, 10512, 15315);
695        /** Chinese Dai Pa Kai media size: 275mm x 395mm (10.827" x 15.551") */
696        public static final MediaSize OM_DAI_PA_KAI =
697                new MediaSize("OM_DAI_PA_KAI", "android",
698                        R.string.mediasize_chinese_om_dai_pa_kai, 10827, 15551);
699        /** Chinese Jurro Ku Kai media size: 198mm x 275mm (7.796" x 10.827") */
700        public static final MediaSize OM_JUURO_KU_KAI =
701                new MediaSize("OM_JUURO_KU_KAI", "android",
702                        R.string.mediasize_chinese_om_jurro_ku_kai, 7796, 10827);
703
704        // Japanese
705
706        /** Japanese JIS B10 media size: 32mm x 45mm (1.259" x 1.772") */
707        public static final MediaSize JIS_B10 =
708                new MediaSize("JIS_B10", "android",
709                        R.string.mediasize_japanese_jis_b10, 1259, 1772);
710        /** Japanese JIS B9 media size: 45mm x 64mm (1.772" x 2.52") */
711        public static final MediaSize JIS_B9 =
712                new MediaSize("JIS_B9", "android",
713                        R.string.mediasize_japanese_jis_b9, 1772, 2520);
714        /** Japanese JIS B8 media size: 64mm x 91mm (2.52" x 3.583") */
715        public static final MediaSize JIS_B8 =
716                new MediaSize("JIS_B8", "android",
717                        R.string.mediasize_japanese_jis_b8, 2520, 3583);
718        /** Japanese JIS B7 media size: 91mm x 128mm (3.583" x 5.049") */
719        public static final MediaSize JIS_B7 =
720                new MediaSize("JIS_B7", "android",
721                        R.string.mediasize_japanese_jis_b7, 3583, 5049);
722        /** Japanese JIS B6 media size: 128mm x 182mm (5.049" x 7.165") */
723        public static final MediaSize JIS_B6 =
724                new MediaSize("JIS_B6", "android",
725                        R.string.mediasize_japanese_jis_b6, 5049, 7165);
726        /** Japanese JIS B5 media size: 182mm x 257mm (7.165" x 10.118") */
727        public static final MediaSize JIS_B5 =
728                new MediaSize("JIS_B5", "android",
729                        R.string.mediasize_japanese_jis_b5, 7165, 10118);
730        /** Japanese JIS B4 media size: 257mm x 364mm (10.118" x 14.331") */
731        public static final MediaSize JIS_B4 =
732                new MediaSize("JIS_B4", "android",
733                        R.string.mediasize_japanese_jis_b4, 10118, 14331);
734        /** Japanese JIS B3 media size: 364mm x 515mm (14.331" x 20.276") */
735        public static final MediaSize JIS_B3 =
736                new MediaSize("JIS_B3", "android",
737                        R.string.mediasize_japanese_jis_b3, 14331, 20276);
738        /** Japanese JIS B2 media size: 515mm x 728mm (20.276" x 28.661") */
739        public static final MediaSize JIS_B2 =
740                new MediaSize("JIS_B2", "android",
741                        R.string.mediasize_japanese_jis_b2, 20276, 28661);
742        /** Japanese JIS B1 media size: 728mm x 1030mm (28.661" x 40.551") */
743        public static final MediaSize JIS_B1 =
744                new MediaSize("JIS_B1", "android",
745                        R.string.mediasize_japanese_jis_b1, 28661, 40551);
746        /** Japanese JIS B0 media size: 1030mm x 1456mm (40.551" x 57.323") */
747        public static final MediaSize JIS_B0 =
748                new MediaSize("JIS_B0", "android",
749                        R.string.mediasize_japanese_jis_b0, 40551, 57323);
750
751        /** Japanese JIS Exec media size: 216mm x 330mm (8.504" x 12.992") */
752        public static final MediaSize JIS_EXEC =
753                new MediaSize("JIS_EXEC", "android",
754                        R.string.mediasize_japanese_jis_exec, 8504, 12992);
755
756        /** Japanese Chou4 media size: 90mm x 205mm (3.543" x 8.071") */
757        public static final MediaSize JPN_CHOU4 =
758                new MediaSize("JPN_CHOU4", "android",
759                        R.string.mediasize_japanese_chou4, 3543, 8071);
760        /** Japanese Chou3 media size: 120mm x 235mm (4.724" x 9.252") */
761        public static final MediaSize JPN_CHOU3 =
762                new MediaSize("JPN_CHOU3", "android",
763                        R.string.mediasize_japanese_chou3, 4724, 9252);
764        /** Japanese Chou2 media size: 111.1mm x 146mm (4.374" x 5.748") */
765        public static final MediaSize JPN_CHOU2 =
766                new MediaSize("JPN_CHOU2", "android",
767                        R.string.mediasize_japanese_chou2, 4374, 5748);
768
769        /** Japanese Hagaki media size: 100mm x 148mm (3.937" x 5.827") */
770        public static final MediaSize JPN_HAGAKI =
771                new MediaSize("JPN_HAGAKI", "android",
772                        R.string.mediasize_japanese_hagaki, 3937, 5827);
773        /** Japanese Oufuku media size: 148mm x 200mm (5.827" x 7.874") */
774        public static final MediaSize JPN_OUFUKU =
775                new MediaSize("JPN_OUFUKU", "android",
776                        R.string.mediasize_japanese_oufuku, 5827, 7874);
777
778        /** Japanese Kahu media size: 240mm x 322.1mm (9.449" x 12.681") */
779        public static final MediaSize JPN_KAHU =
780                new MediaSize("JPN_KAHU", "android",
781                        R.string.mediasize_japanese_kahu, 9449, 12681);
782        /** Japanese Kaku2 media size: 240mm x 332mm (9.449" x 13.071") */
783        public static final MediaSize JPN_KAKU2 =
784                new MediaSize("JPN_KAKU2", "android",
785                        R.string.mediasize_japanese_kaku2, 9449, 13071);
786
787        /** Japanese You4 media size: 105mm x 235mm (4.134" x 9.252") */
788        public static final MediaSize JPN_YOU4 =
789                new MediaSize("JPN_YOU4", "android",
790                        R.string.mediasize_japanese_you4, 4134, 9252);
791
792        private final @NonNull String mId;
793        /**@hide */
794        public final @NonNull String mLabel;
795        /**@hide */
796        public final @Nullable String mPackageName;
797        /**@hide */
798        public final @StringRes int mLabelResId;
799        private final @IntRange(from = 1) int mWidthMils;
800        private final @IntRange(from = 1) int mHeightMils;
801
802        /**
803         * Creates a new instance.
804         *
805         * @param id The unique media size id.
806         * @param packageName The name of the creating package.
807         * @param labelResId The resource if of a human readable label.
808         * @param widthMils The width in mils (thousandths of an inch).
809         * @param heightMils The height in mils (thousandths of an inch).
810         *
811         * @throws IllegalArgumentException If the id is empty or the label
812         * is empty or the widthMils is less than or equal to zero or the
813         * heightMils is less than or equal to zero.
814         *
815         * @hide
816         */
817        public MediaSize(String id, String packageName, int labelResId,
818                int widthMils, int heightMils) {
819            this(id, null, packageName, widthMils, heightMils, labelResId);
820
821            // Build this mapping only for predefined media sizes.
822            sIdToMediaSizeMap.put(mId, this);
823        }
824
825        /**
826         * Creates a new instance.
827         *
828         * @param id The unique media size id. It is unique amongst other media sizes
829         *        supported by the printer.
830         * @param label The <strong>localized</strong> human readable label.
831         * @param widthMils The width in mils (thousandths of an inch).
832         * @param heightMils The height in mils (thousandths of an inch).
833         *
834         * @throws IllegalArgumentException If the id is empty or the label is empty
835         * or the widthMils is less than or equal to zero or the heightMils is less
836         * than or equal to zero.
837         */
838        public MediaSize(@NonNull String id, @NonNull String label,
839                @IntRange(from = 1) int widthMils, @IntRange(from = 1) int heightMils) {
840            this(id, label, null, widthMils, heightMils, 0);
841        }
842
843        /**
844         * Get the Id of all predefined media sizes beside the {@link #UNKNOWN_PORTRAIT} and
845         * {@link #UNKNOWN_LANDSCAPE}.
846         *
847         * @return List of all predefined media sizes
848         *
849         * @hide
850         */
851        public static @NonNull ArraySet<MediaSize> getAllPredefinedSizes() {
852            ArraySet<MediaSize> definedMediaSizes = new ArraySet<>(sIdToMediaSizeMap.values());
853
854            definedMediaSizes.remove(UNKNOWN_PORTRAIT);
855            definedMediaSizes.remove(UNKNOWN_LANDSCAPE);
856
857            return definedMediaSizes;
858        }
859
860        /**
861         * Creates a new instance.
862         *
863         * @param id The unique media size id. It is unique amongst other media sizes
864         *        supported by the printer.
865         * @param label The <strong>localized</strong> human readable label.
866         * @param packageName The name of the creating package.
867         * @param widthMils The width in mils (thousandths of an inch).
868         * @param heightMils The height in mils (thousandths of an inch).
869         * @param labelResId The resource if of a human readable label.
870         *
871         * @throws IllegalArgumentException If the id is empty or the label is unset
872         * or the widthMils is less than or equal to zero or the heightMils is less
873         * than or equal to zero.
874         *
875         * @hide
876         */
877        public MediaSize(String id, String label, String packageName, int widthMils, int heightMils,
878                int labelResId) {
879            mPackageName = packageName;
880            mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty.");
881            mLabelResId = labelResId;
882            mWidthMils = Preconditions.checkArgumentPositive(widthMils, "widthMils cannot be " +
883                    "less than or equal to zero.");
884            mHeightMils = Preconditions.checkArgumentPositive(heightMils, "heightMils cannot be " +
885                    "less than or equal to zero.");
886            mLabel = label;
887
888            // The label has to be either a string ot a StringRes
889            Preconditions.checkArgument(!TextUtils.isEmpty(label) !=
890                    (!TextUtils.isEmpty(packageName) && labelResId != 0), "label cannot be empty.");
891        }
892
893        /**
894         * Gets the unique media size id. It is unique amongst other media sizes
895         * supported by the printer.
896         * <p>
897         * This id is defined by the client that generated the media size
898         * instance and should not be interpreted by other parties.
899         * </p>
900         *
901         * @return The unique media size id.
902         */
903        public @NonNull String getId() {
904            return mId;
905        }
906
907        /**
908         * Gets the human readable media size label.
909         *
910         * @param packageManager The package manager for loading the label.
911         * @return The human readable label.
912         */
913        public @NonNull String getLabel(@NonNull PackageManager packageManager) {
914            if (!TextUtils.isEmpty(mPackageName) && mLabelResId > 0) {
915                try {
916                    return packageManager.getResourcesForApplication(
917                            mPackageName).getString(mLabelResId);
918                } catch (NotFoundException | NameNotFoundException e) {
919                    Log.w(LOG_TAG, "Could not load resouce" + mLabelResId
920                            + " from package " + mPackageName);
921                }
922            }
923            return mLabel;
924        }
925
926        /**
927         * Gets the media width in mils (thousandths of an inch).
928         *
929         * @return The media width.
930         */
931        public @IntRange(from = 1) int getWidthMils() {
932            return mWidthMils;
933        }
934
935        /**
936         * Gets the media height in mils (thousandths of an inch).
937         *
938         * @return The media height.
939         */
940        public @IntRange(from = 1) int getHeightMils() {
941            return mHeightMils;
942        }
943
944        /**
945         * Gets whether this media size is in portrait which is the
946         * height is greater or equal to the width.
947         *
948         * @return True if the media size is in portrait, false if
949         * it is in landscape.
950         */
951        public boolean isPortrait() {
952            return mHeightMils >= mWidthMils;
953        }
954
955        /**
956         * Returns a new media size instance in a portrait orientation,
957         * which is the height is the greater dimension.
958         *
959         * @return New instance in landscape orientation if this one
960         * is in landscape, otherwise this instance.
961         */
962        public @NonNull MediaSize asPortrait() {
963            if (isPortrait()) {
964                return this;
965            }
966            return new MediaSize(mId, mLabel, mPackageName,
967                    Math.min(mWidthMils, mHeightMils),
968                    Math.max(mWidthMils, mHeightMils),
969                    mLabelResId);
970        }
971
972        /**
973         * Returns a new media size instance in a landscape orientation,
974         * which is the height is the lesser dimension.
975         *
976         * @return New instance in landscape orientation if this one
977         * is in portrait, otherwise this instance.
978         */
979        public @NonNull MediaSize asLandscape() {
980            if (!isPortrait()) {
981                return this;
982            }
983            return new MediaSize(mId, mLabel, mPackageName,
984                    Math.max(mWidthMils, mHeightMils),
985                    Math.min(mWidthMils, mHeightMils),
986                    mLabelResId);
987        }
988
989        void writeToParcel(Parcel parcel) {
990            parcel.writeString(mId);
991            parcel.writeString(mLabel);
992            parcel.writeString(mPackageName);
993            parcel.writeInt(mWidthMils);
994            parcel.writeInt(mHeightMils);
995            parcel.writeInt(mLabelResId);
996        }
997
998        static MediaSize createFromParcel(Parcel parcel) {
999            return new MediaSize(
1000                    parcel.readString(),
1001                    parcel.readString(),
1002                    parcel.readString(),
1003                    parcel.readInt(),
1004                    parcel.readInt(),
1005                    parcel.readInt());
1006        }
1007
1008        @Override
1009        public int hashCode() {
1010            final int prime = 31;
1011            int result = 1;
1012            result = prime * result + mWidthMils;
1013            result = prime * result + mHeightMils;
1014            return result;
1015        }
1016
1017        @Override
1018        public boolean equals(Object obj) {
1019            if (this == obj) {
1020                return true;
1021            }
1022            if (obj == null) {
1023                return false;
1024            }
1025            if (getClass() != obj.getClass()) {
1026                return false;
1027            }
1028            MediaSize other = (MediaSize) obj;
1029            if (mWidthMils != other.mWidthMils) {
1030                return false;
1031            }
1032            if (mHeightMils != other.mHeightMils) {
1033                return false;
1034            }
1035            return true;
1036        }
1037
1038        @Override
1039        public String toString() {
1040            StringBuilder builder = new StringBuilder();
1041            builder.append("MediaSize{");
1042            builder.append("id: ").append(mId);
1043            builder.append(", label: ").append(mLabel);
1044            builder.append(", packageName: ").append(mPackageName);
1045            builder.append(", heightMils: ").append(mHeightMils);
1046            builder.append(", widthMils: ").append(mWidthMils);
1047            builder.append(", labelResId: ").append(mLabelResId);
1048            builder.append("}");
1049            return builder.toString();
1050        }
1051
1052        /**
1053         * Gets a standard media size given its id.
1054         *
1055         * @param id The media size id.
1056         * @return The media size for the given id or null.
1057         *
1058         * @hide
1059         */
1060        public static MediaSize getStandardMediaSizeById(String id) {
1061            return sIdToMediaSizeMap.get(id);
1062        }
1063    }
1064
1065    /**
1066     * This class specifies a supported resolution in DPI (dots per inch).
1067     * Resolution defines how many points with different color can be placed
1068     * on one inch in horizontal or vertical direction of the target media.
1069     * For example, a printer with 600 DPI can produce higher quality images
1070     * the one with 300 DPI resolution.
1071     */
1072    public static final class Resolution {
1073        private final @NonNull String mId;
1074        private final @NonNull String mLabel;
1075        private final @IntRange(from = 1) int mHorizontalDpi;
1076        private final @IntRange(from = 1) int mVerticalDpi;
1077
1078        /**
1079         * Creates a new instance.
1080         *
1081         * @param id The unique resolution id. It is unique amongst other resolutions
1082         *        supported by the printer.
1083         * @param label The <strong>localized</strong> human readable label.
1084         * @param horizontalDpi The horizontal resolution in DPI (dots per inch).
1085         * @param verticalDpi The vertical resolution in DPI (dots per inch).
1086         *
1087         * @throws IllegalArgumentException If the id is empty or the label is empty
1088         * or the horizontalDpi is less than or equal to zero or the verticalDpi is
1089         * less than or equal to zero.
1090         */
1091        public Resolution(@NonNull String id, @NonNull String label,
1092                @IntRange(from = 1) int horizontalDpi, @IntRange(from = 1) int verticalDpi) {
1093            if (TextUtils.isEmpty(id)) {
1094                throw new IllegalArgumentException("id cannot be empty.");
1095            }
1096            if (TextUtils.isEmpty(label)) {
1097                throw new IllegalArgumentException("label cannot be empty.");
1098            }
1099            if (horizontalDpi <= 0) {
1100                throw new IllegalArgumentException("horizontalDpi "
1101                        + "cannot be less than or equal to zero.");
1102            }
1103            if (verticalDpi <= 0) {
1104                throw new IllegalArgumentException("verticalDpi"
1105                       + " cannot be less than or equal to zero.");
1106            }
1107            mId = id;
1108            mLabel = label;
1109            mHorizontalDpi = horizontalDpi;
1110            mVerticalDpi = verticalDpi;
1111        }
1112
1113        /**
1114         * Gets the unique resolution id. It is unique amongst other resolutions
1115         * supported by the printer.
1116         * <p>
1117         * This id is defined by the client that generated the resolution
1118         * instance and should not be interpreted by other parties.
1119         * </p>
1120         *
1121         * @return The unique resolution id.
1122         */
1123        public @NonNull String getId() {
1124            return mId;
1125        }
1126
1127        /**
1128         * Gets the resolution human readable label.
1129         *
1130         * @return The human readable label.
1131         */
1132        public @NonNull String getLabel() {
1133            return mLabel;
1134        }
1135
1136        /**
1137         * Gets the horizontal resolution in DPI (dots per inch).
1138         *
1139         * @return The horizontal resolution.
1140         */
1141        public @IntRange(from = 1) int getHorizontalDpi() {
1142            return mHorizontalDpi;
1143        }
1144
1145        /**
1146         * Gets the vertical resolution in DPI (dots per inch).
1147         *
1148         * @return The vertical resolution.
1149         */
1150        public @IntRange(from = 1) int getVerticalDpi() {
1151            return mVerticalDpi;
1152        }
1153
1154        void writeToParcel(Parcel parcel) {
1155            parcel.writeString(mId);
1156            parcel.writeString(mLabel);
1157            parcel.writeInt(mHorizontalDpi);
1158            parcel.writeInt(mVerticalDpi);
1159        }
1160
1161        static Resolution createFromParcel(Parcel parcel) {
1162            return new Resolution(
1163                    parcel.readString(),
1164                    parcel.readString(),
1165                    parcel.readInt(),
1166                    parcel.readInt());
1167        }
1168
1169        @Override
1170        public int hashCode() {
1171            final int prime = 31;
1172            int result = 1;
1173            result = prime * result + mHorizontalDpi;
1174            result = prime * result + mVerticalDpi;
1175            return result;
1176        }
1177
1178        @Override
1179        public boolean equals(Object obj) {
1180            if (this == obj) {
1181                return true;
1182            }
1183            if (obj == null) {
1184                return false;
1185            }
1186            if (getClass() != obj.getClass()) {
1187                return false;
1188            }
1189            Resolution other = (Resolution) obj;
1190            if (mHorizontalDpi != other.mHorizontalDpi) {
1191                return false;
1192            }
1193            if (mVerticalDpi != other.mVerticalDpi) {
1194                return false;
1195            }
1196            return true;
1197        }
1198
1199        @Override
1200        public String toString() {
1201            StringBuilder builder = new StringBuilder();
1202            builder.append("Resolution{");
1203            builder.append("id: ").append(mId);
1204            builder.append(", label: ").append(mLabel);
1205            builder.append(", horizontalDpi: ").append(mHorizontalDpi);
1206            builder.append(", verticalDpi: ").append(mVerticalDpi);
1207            builder.append("}");
1208            return builder.toString();
1209        }
1210    }
1211
1212    /**
1213     * This class specifies content margins. Margins define the white space
1214     * around the content where the left margin defines the amount of white
1215     * space on the left of the content and so on.
1216     */
1217    public static final class Margins {
1218        public static final Margins NO_MARGINS = new Margins(0,  0,  0,  0);
1219
1220        private final int mLeftMils;
1221        private final int mTopMils;
1222        private final int mRightMils;
1223        private final int mBottomMils;
1224
1225        /**
1226         * Creates a new instance.
1227         *
1228         * @param leftMils The left margin in mils (thousandths of an inch).
1229         * @param topMils The top margin in mils (thousandths of an inch).
1230         * @param rightMils The right margin in mils (thousandths of an inch).
1231         * @param bottomMils The bottom margin in mils (thousandths of an inch).
1232         */
1233        public Margins(int leftMils, int topMils, int rightMils, int bottomMils) {
1234            mTopMils = topMils;
1235            mLeftMils = leftMils;
1236            mRightMils = rightMils;
1237            mBottomMils = bottomMils;
1238        }
1239
1240        /**
1241         * Gets the left margin in mils (thousandths of an inch).
1242         *
1243         * @return The left margin.
1244         */
1245        public int getLeftMils() {
1246            return mLeftMils;
1247        }
1248
1249        /**
1250         * Gets the top margin in mils (thousandths of an inch).
1251         *
1252         * @return The top margin.
1253         */
1254        public int getTopMils() {
1255            return mTopMils;
1256        }
1257
1258        /**
1259         * Gets the right margin in mils (thousandths of an inch).
1260         *
1261         * @return The right margin.
1262         */
1263        public int getRightMils() {
1264            return mRightMils;
1265        }
1266
1267        /**
1268         * Gets the bottom margin in mils (thousandths of an inch).
1269         *
1270         * @return The bottom margin.
1271         */
1272        public int getBottomMils() {
1273            return mBottomMils;
1274        }
1275
1276        void writeToParcel(Parcel parcel) {
1277            parcel.writeInt(mLeftMils);
1278            parcel.writeInt(mTopMils);
1279            parcel.writeInt(mRightMils);
1280            parcel.writeInt(mBottomMils);
1281        }
1282
1283        static Margins createFromParcel(Parcel parcel) {
1284            return new Margins(
1285                    parcel.readInt(),
1286                    parcel.readInt(),
1287                    parcel.readInt(),
1288                    parcel.readInt());
1289        }
1290
1291        @Override
1292        public int hashCode() {
1293            final int prime = 31;
1294            int result = 1;
1295            result = prime * result + mBottomMils;
1296            result = prime * result + mLeftMils;
1297            result = prime * result + mRightMils;
1298            result = prime * result + mTopMils;
1299            return result;
1300        }
1301
1302        @Override
1303        public boolean equals(Object obj) {
1304            if (this == obj) {
1305                return true;
1306            }
1307            if (obj == null) {
1308                return false;
1309            }
1310            if (getClass() != obj.getClass()) {
1311                return false;
1312            }
1313            Margins other = (Margins) obj;
1314            if (mBottomMils != other.mBottomMils) {
1315                return false;
1316            }
1317            if (mLeftMils != other.mLeftMils) {
1318                return false;
1319            }
1320            if (mRightMils != other.mRightMils) {
1321                return false;
1322            }
1323            if (mTopMils != other.mTopMils) {
1324                return false;
1325            }
1326            return true;
1327        }
1328
1329        @Override
1330        public String toString() {
1331            StringBuilder builder = new StringBuilder();
1332            builder.append("Margins{");
1333            builder.append("leftMils: ").append(mLeftMils);
1334            builder.append(", topMils: ").append(mTopMils);
1335            builder.append(", rightMils: ").append(mRightMils);
1336            builder.append(", bottomMils: ").append(mBottomMils);
1337            builder.append("}");
1338            return builder.toString();
1339        }
1340    }
1341
1342    static String colorModeToString(int colorMode) {
1343        switch (colorMode) {
1344            case COLOR_MODE_MONOCHROME: {
1345                return "COLOR_MODE_MONOCHROME";
1346            }
1347            case COLOR_MODE_COLOR: {
1348                return "COLOR_MODE_COLOR";
1349            }
1350            default: {
1351                return "COLOR_MODE_UNKNOWN";
1352            }
1353        }
1354    }
1355
1356    static String duplexModeToString(int duplexMode) {
1357        switch (duplexMode) {
1358            case DUPLEX_MODE_NONE: {
1359                return "DUPLEX_MODE_NONE";
1360            }
1361            case DUPLEX_MODE_LONG_EDGE: {
1362                return "DUPLEX_MODE_LONG_EDGE";
1363            }
1364            case DUPLEX_MODE_SHORT_EDGE: {
1365                return "DUPLEX_MODE_SHORT_EDGE";
1366            }
1367            default: {
1368                return "DUPLEX_MODE_UNKNOWN";
1369            }
1370        }
1371    }
1372
1373    static void enforceValidColorMode(int colorMode) {
1374        if ((colorMode & VALID_COLOR_MODES) == 0 || Integer.bitCount(colorMode) != 1) {
1375            throw new IllegalArgumentException("invalid color mode: " + colorMode);
1376        }
1377    }
1378
1379    static void enforceValidDuplexMode(int duplexMode) {
1380        if ((duplexMode & VALID_DUPLEX_MODES) == 0 || Integer.bitCount(duplexMode) != 1) {
1381            throw new IllegalArgumentException("invalid duplex mode: " + duplexMode);
1382        }
1383    }
1384
1385    /**
1386     * Builder for creating {@link PrintAttributes}.
1387     */
1388    public static final class Builder {
1389        private final PrintAttributes mAttributes = new PrintAttributes();
1390
1391        /**
1392         * Sets the media size.
1393         *
1394         * @param mediaSize The media size.
1395         * @return This builder.
1396         */
1397        public @NonNull Builder setMediaSize(@NonNull MediaSize mediaSize) {
1398            mAttributes.setMediaSize(mediaSize);
1399            return this;
1400        }
1401
1402        /**
1403         * Sets the resolution.
1404         *
1405         * @param resolution The resolution.
1406         * @return This builder.
1407         */
1408        public @NonNull Builder setResolution(@NonNull Resolution resolution) {
1409            mAttributes.setResolution(resolution);
1410            return this;
1411        }
1412
1413        /**
1414         * Sets the minimal margins. If the content does not fit
1415         * these margins it will be clipped.
1416         *
1417         * @param margins The margins.
1418         * @return This builder.
1419         */
1420        public @NonNull Builder setMinMargins(@NonNull Margins margins) {
1421            mAttributes.setMinMargins(margins);
1422            return this;
1423        }
1424
1425        /**
1426         * Sets the color mode.
1427         *
1428         * @param colorMode A valid color mode or zero.
1429         * @return This builder.
1430         *
1431         * @see PrintAttributes#COLOR_MODE_MONOCHROME
1432         * @see PrintAttributes#COLOR_MODE_COLOR
1433         */
1434        public @NonNull Builder setColorMode(@ColorMode int colorMode) {
1435            mAttributes.setColorMode(colorMode);
1436            return this;
1437        }
1438
1439        /**
1440         * Sets the duplex mode.
1441         *
1442         * @param duplexMode A valid duplex mode or zero.
1443         * @return This builder.
1444         *
1445         * @see PrintAttributes#DUPLEX_MODE_NONE
1446         * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
1447         * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
1448         */
1449        public @NonNull Builder setDuplexMode(@DuplexMode int duplexMode) {
1450            mAttributes.setDuplexMode(duplexMode);
1451            return this;
1452        }
1453
1454        /**
1455         * Creates a new {@link PrintAttributes} instance.
1456         *
1457         * @return The new instance.
1458         */
1459        public @NonNull PrintAttributes build() {
1460            return mAttributes;
1461        }
1462    }
1463
1464    public static final Parcelable.Creator<PrintAttributes> CREATOR =
1465            new Creator<PrintAttributes>() {
1466        @Override
1467        public PrintAttributes createFromParcel(Parcel parcel) {
1468            return new PrintAttributes(parcel);
1469        }
1470
1471        @Override
1472        public PrintAttributes[] newArray(int size) {
1473            return new PrintAttributes[size];
1474        }
1475    };
1476}
1477