PrinterInfo.java revision 55b409a97cf6376399a0940313ea852368727d6f
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.os.Parcel;
20import android.os.Parcelable;
21import android.print.PrintAttributes.Margins;
22import android.print.PrintAttributes.MediaSize;
23import android.print.PrintAttributes.Resolution;
24import android.print.PrintAttributes.Tray;
25import android.text.TextUtils;
26
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.List;
30
31/**
32 * This class represents the description of a printer. A description
33 * contains the printer id, human readable name, status, and available
34 * options for various printer capabilities, such as media size, etc.
35 */
36public final class PrinterInfo implements Parcelable {
37    /**
38     * Undefined default value.
39     *
40     * @hide
41     */
42    public static final int DEFAULT_UNDEFINED = -1;
43
44    private static final int PROPERTY_MEDIA_SIZE = 0;
45    private static final int PROPERTY_RESOLUTION = 1;
46    private static final int PROPERTY_INPUT_TRAY = 2;
47    private static final int PROPERTY_OUTPUT_TRAY = 3;
48    private static final int PROPERTY_DUPLEX_MODE = 4;
49    private static final int PROPERTY_COLOR_MODE = 5;
50    private static final int PROPERTY_FITTING_MODE = 6;
51    private static final int PROPERTY_ORIENTATION = 7;
52    private static final int PROPERTY_COUNT = 8;
53
54    /** Printer status: the printer is ready to print. */
55    public static final int STATUS_READY = 1;
56
57    private static final Margins DEFAULT_MARGINS = new Margins(0,  0,  0,  0);
58
59    // TODO: Add printer status constants.
60
61    private PrinterId mId;
62    private CharSequence mLabel;
63    private int mStatus;
64
65    private Margins mMinMargins = DEFAULT_MARGINS;
66    private List<MediaSize> mMediaSizes;
67    private List<Resolution> mResolutions;
68    private List<Tray> mInputTrays;
69    private List<Tray> mOutputTrays;
70
71    private int mDuplexModes;
72    private int mColorModes;
73    private int mFittingModes;
74    private int mOrientations;
75
76    private final int[] mDefaults = new int[PROPERTY_COUNT];
77    private Margins mDefaultMargins = DEFAULT_MARGINS;
78
79    /**
80     * @hide
81     */
82    public PrinterInfo() {
83        Arrays.fill(mDefaults, DEFAULT_UNDEFINED);
84    }
85
86    private PrinterInfo(PrinterInfo prototype) {
87        copyFrom(prototype);
88    }
89
90    /**
91     * @hide
92     */
93    public void copyFrom(PrinterInfo other) {
94        mId = other.mId;
95        mLabel = other.mLabel;
96        mStatus = other.mStatus;
97
98        mMinMargins = other.mMinMargins;
99        if (other.mMediaSizes != null) {
100            if (mMediaSizes != null) {
101                mMediaSizes.clear();
102                mMediaSizes.addAll(other.mMediaSizes);
103            } else {
104                mMediaSizes = new ArrayList<MediaSize>(other.mMediaSizes);
105            }
106        } else {
107            mMediaSizes = null;
108        }
109
110        if (other.mResolutions != null) {
111            if (mResolutions != null) {
112                mResolutions.clear();
113                mResolutions.addAll(other.mResolutions);
114            } else {
115                mResolutions = new ArrayList<Resolution>(other.mResolutions);
116            }
117        } else {
118            mResolutions = null;
119        }
120
121        if (other.mInputTrays != null) {
122            if (mInputTrays != null) {
123                mInputTrays.clear();
124                mInputTrays.addAll(other.mInputTrays);
125            } else {
126                mInputTrays = new ArrayList<Tray>(other.mInputTrays);
127            }
128        } else {
129            mInputTrays = null;
130        }
131
132        if (other.mOutputTrays != null) {
133            if (mOutputTrays != null) {
134                mOutputTrays.clear();
135                mOutputTrays.addAll(other.mOutputTrays);
136            } else {
137                mOutputTrays = new ArrayList<Tray>(other.mOutputTrays);
138            }
139        } else {
140            mOutputTrays = null;
141        }
142
143        mDuplexModes = other.mDuplexModes;
144        mColorModes = other.mColorModes;
145        mFittingModes = other.mFittingModes;
146        mOrientations = other.mOrientations;
147
148        final int defaultCount = other.mDefaults.length;
149        for (int i = 0; i < defaultCount; i++) {
150            mDefaults[i] = other.mDefaults[i];
151        }
152        mDefaultMargins = other.mDefaultMargins;
153    }
154
155    /**
156     * Get the globally unique printer id.
157     *
158     * @return The printer id.
159     */
160    public PrinterId getId() {
161        return mId;
162    }
163
164    /**
165     * Gets the human readable printer label.
166     *
167     * @return The human readable label.
168     */
169    public CharSequence getLabel() {
170        return mLabel;
171    }
172
173    /**
174     * Gets the status of the printer.
175     *
176     * @return The status.
177     */
178    public int getStatus() {
179        return mStatus;
180    }
181
182    /**
183     * Gets the supported media sizes.
184     *
185     * @return The supported media sizes.
186     */
187    public List<MediaSize> getMediaSizes() {
188        return mMediaSizes;
189    }
190
191    /**
192     * Gets the supported resolutions.
193     *
194     * @return The supported resolutions.
195     */
196    public List<Resolution> getResolutions() {
197        return mResolutions;
198    }
199
200    /**
201     * Gets the minimal supported margins.
202     *
203     * @return The minimal margins.
204     */
205    public Margins getMinMargins() {
206        return mMinMargins;
207    }
208
209    /**
210     * Gets the available input trays.
211     *
212     * @return The input trays.
213     */
214    public List<Tray> getInputTrays() {
215        return mInputTrays;
216    }
217
218    /**
219     * Gets the available output trays.
220     *
221     * @return The output trays.
222     */
223    public List<Tray> getOutputTrays() {
224        return mOutputTrays;
225    }
226
227    /**
228     * Gets the supported duplex modes.
229     *
230     * @return The duplex modes.
231     *
232     * @see PrintAttributes#DUPLEX_MODE_NONE
233     * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
234     * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
235     */
236    public int getDuplexModes() {
237        return mDuplexModes;
238    }
239
240    /**
241     * Gets the supported color modes.
242     *
243     * @return The color modes.
244     *
245     * @see PrintAttributes#COLOR_MODE_COLOR
246     * @see PrintAttributes#COLOR_MODE_MONOCHROME
247     */
248    public int getColorModes() {
249        return mColorModes;
250    }
251
252    /**
253     * Gets the supported fitting modes.
254     *
255     * @return The fitting modes.
256     *
257     * @see PrintAttributes#FITTING_MODE_NONE
258     * @see PrintAttributes#FITTING_MODE_FIT_TO_PAGE
259     */
260    public int getFittingModes() {
261        return mFittingModes;
262    }
263
264    /**
265     * Gets the supported orientations.
266     *
267     * @return The orientations.
268     *
269     * @see PrintAttributes#ORIENTATION_PORTRAIT
270     * @see PrintAttributes#ORIENTATION_LANDSCAPE
271     */
272    public int getOrientations() {
273        return mOrientations;
274    }
275
276    /**
277     * Gets the default print attributes.
278     *
279     * @param outAttributes The attributes to populated.
280     */
281    public void getDefaults(PrintAttributes outAttributes) {
282        outAttributes.clear();
283
284        outAttributes.setMargins(mDefaultMargins);
285
286        final int mediaSizeIndex = mDefaults[PROPERTY_MEDIA_SIZE];
287        if (mediaSizeIndex >= 0) {
288            outAttributes.setMediaSize(mMediaSizes.get(mediaSizeIndex));
289        }
290
291        final int resolutionIndex = mDefaults[PROPERTY_RESOLUTION];
292        if (resolutionIndex >= 0) {
293            outAttributes.setResolution(mResolutions.get(resolutionIndex));
294        }
295
296        final int inputTrayIndex = mDefaults[PROPERTY_INPUT_TRAY];
297        if (inputTrayIndex >= 0) {
298            outAttributes.setInputTray(mInputTrays.get(inputTrayIndex));
299        }
300
301        final int outputTrayIndex = mDefaults[PROPERTY_OUTPUT_TRAY];
302        if (outputTrayIndex >= 0) {
303            outAttributes.setOutputTray(mOutputTrays.get(outputTrayIndex));
304        }
305
306        final int duplexMode = mDefaults[PROPERTY_DUPLEX_MODE];
307        if (duplexMode > 0) {
308            outAttributes.setDuplexMode(duplexMode);
309        }
310
311        final int colorMode = mDefaults[PROPERTY_COLOR_MODE];
312        if (colorMode > 0) {
313            outAttributes.setColorMode(mColorModes & colorMode);
314        }
315
316        final int fittingMode = mDefaults[PROPERTY_FITTING_MODE];
317        if (fittingMode > 0) {
318            outAttributes.setFittingMode(fittingMode);
319        }
320
321        final int orientation = mDefaults[PROPERTY_ORIENTATION];
322        if (orientation > 0) {
323            outAttributes.setOrientation(orientation);
324        }
325    }
326
327    /**
328     * Gets whether this printer info is fully-populated, i.e. whether
329     * all required attributes are specified. See the {@link Builder}
330     * documentation for which attributes are required.
331     *
332     * @return Whether this info has all required attributes.
333     */
334    public boolean hasAllRequiredAttributes() {
335        return (mMediaSizes != null && !mMediaSizes.isEmpty()
336                && mResolutions != null && !mResolutions.isEmpty()
337                && mColorModes != 0 || mOrientations != 0
338                && mDefaults[PROPERTY_MEDIA_SIZE] != DEFAULT_UNDEFINED
339                && mDefaults[PROPERTY_RESOLUTION] != DEFAULT_UNDEFINED
340                && mDefaults[PROPERTY_COLOR_MODE] != DEFAULT_UNDEFINED
341                && mDefaults[PROPERTY_ORIENTATION] != DEFAULT_UNDEFINED);
342    }
343
344    private PrinterInfo(Parcel parcel) {
345        mId = parcel.readParcelable(null);
346        mLabel = parcel.readCharSequence();
347        mStatus = parcel.readInt();
348
349        mMinMargins = readMargins(parcel);
350        readMediaSizes(parcel);
351        readResolutions(parcel);
352        mInputTrays = readInputTrays(parcel);
353        mOutputTrays = readOutputTrays(parcel);
354
355        mColorModes = parcel.readInt();
356        mDuplexModes = parcel.readInt();
357        mFittingModes = parcel.readInt();
358        mOrientations = parcel.readInt();
359
360        readDefaults(parcel);
361        mDefaultMargins = readMargins(parcel);
362    }
363
364    @Override
365    public int describeContents() {
366        return 0;
367    }
368
369    @Override
370    public void writeToParcel(Parcel parcel, int flags) {
371        parcel.writeParcelable(mId, flags);
372        parcel.writeCharSequence(mLabel);
373        parcel.writeInt(mStatus);
374
375        writeMargins(mMinMargins, parcel);
376        writeMediaSizes(parcel);
377        writeResolutions(parcel);
378        writeInputTrays(parcel);
379        writeOutputTrays(parcel);
380
381        parcel.writeInt(mColorModes);
382        parcel.writeInt(mDuplexModes);
383        parcel.writeInt(mFittingModes);
384        parcel.writeInt(mOrientations);
385
386        writeDefaults(parcel);
387        writeMargins(mDefaultMargins, parcel);
388    }
389
390    @Override
391    public int hashCode() {
392        final int prime = 31;
393        int result = 1;
394        result = prime * result + ((mId == null) ? 0 : mId.hashCode());
395        result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
396        result = prime * result + mStatus;
397        result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode());
398        result = prime * result + ((mMediaSizes == null) ? 0 : mMediaSizes.hashCode());
399        result = prime * result + ((mResolutions == null) ? 0 : mResolutions.hashCode());
400        result = prime * result + ((mInputTrays == null) ? 0 : mInputTrays.hashCode());
401        result = prime * result + ((mOutputTrays == null) ? 0 : mOutputTrays.hashCode());
402        result = prime * result + mDuplexModes;
403        result = prime * result + mColorModes;
404        result = prime * result + mFittingModes;
405        result = prime * result + mOrientations;
406        result = prime * result + Arrays.hashCode(mDefaults);
407        result = prime * result + ((mDefaultMargins == null) ? 0 : mDefaultMargins.hashCode());
408        return result;
409    }
410
411    @Override
412    public boolean equals(Object obj) {
413        if (this == obj) {
414            return true;
415        }
416        if (obj == null) {
417            return false;
418        }
419        if (getClass() != obj.getClass()) {
420            return false;
421        }
422        PrinterInfo other = (PrinterInfo) obj;
423        if (mId == null) {
424            if (other.mId != null) {
425                return false;
426            }
427        } else if (!mId.equals(other.mId)) {
428            return false;
429        }
430        if (!TextUtils.equals(mLabel, other.mLabel)) {
431            return false;
432        }
433        if (mStatus != other.mStatus) {
434            return false;
435        }
436        if (mMinMargins == null) {
437            if (other.mMinMargins != null) {
438                return false;
439            }
440        } else if (!mMinMargins.equals(other.mMinMargins)) {
441            return false;
442        }
443        if (mMediaSizes == null) {
444            if (other.mMediaSizes != null) {
445                return false;
446            }
447        } else if (!mMediaSizes.equals(other.mMediaSizes)) {
448            return false;
449        }
450        if (mResolutions == null) {
451            if (other.mResolutions != null) {
452                return false;
453            }
454        } else if (!mResolutions.equals(other.mResolutions)) {
455            return false;
456        }
457        if (mInputTrays == null) {
458            if (other.mInputTrays != null) {
459                return false;
460            }
461        } else if (!mInputTrays.equals(other.mInputTrays)) {
462            return false;
463        }
464        if (mOutputTrays == null) {
465            if (other.mOutputTrays != null) {
466                return false;
467            }
468        } else if (!mOutputTrays.equals(other.mOutputTrays)) {
469            return false;
470        }
471        if (mDuplexModes != other.mDuplexModes) {
472            return false;
473        }
474        if (mColorModes != other.mColorModes) {
475            return false;
476        }
477        if (mFittingModes != other.mFittingModes) {
478            return false;
479        }
480        if (mOrientations != other.mOrientations) {
481            return false;
482        }
483        if (!Arrays.equals(mDefaults, other.mDefaults)) {
484            return false;
485        }
486        if (mDefaultMargins == null) {
487            if (other.mDefaultMargins != null) {
488                return false;
489            }
490        } else if (!mDefaultMargins.equals(other.mDefaultMargins)) {
491            return false;
492        }
493        return true;
494    }
495
496    @Override
497    public String toString() {
498        StringBuilder builder = new StringBuilder();
499        builder.append("PrinterInfo{");
500        builder.append(mId).append(", \"");
501        builder.append(mLabel);
502        builder.append("\"}");
503        return builder.toString();
504    }
505
506    private void writeMediaSizes(Parcel parcel) {
507        if (mMediaSizes == null) {
508            parcel.writeInt(0);
509            return;
510        }
511        final int mediaSizeCount = mMediaSizes.size();
512        parcel.writeInt(mediaSizeCount);
513        for (int i = 0; i < mediaSizeCount; i++) {
514            mMediaSizes.get(i).writeToParcel(parcel);
515        }
516    }
517
518    private void readMediaSizes(Parcel parcel) {
519        final int mediaSizeCount = parcel.readInt();
520        if (mediaSizeCount > 0 && mMediaSizes == null) {
521            mMediaSizes = new ArrayList<MediaSize>();
522        }
523        for (int i = 0; i < mediaSizeCount; i++) {
524            mMediaSizes.add(MediaSize.createFromParcel(parcel));
525        }
526    }
527
528    private void writeResolutions(Parcel parcel) {
529        if (mResolutions == null) {
530            parcel.writeInt(0);
531            return;
532        }
533        final int resolutionCount = mResolutions.size();
534        parcel.writeInt(resolutionCount);
535        for (int i = 0; i < resolutionCount; i++) {
536            mResolutions.get(i).writeToParcel(parcel);
537        }
538    }
539
540    private void readResolutions(Parcel parcel) {
541        final int resolutionCount = parcel.readInt();
542        if (resolutionCount > 0 && mResolutions == null) {
543            mResolutions = new ArrayList<Resolution>();
544        }
545        for (int i = 0; i < resolutionCount; i++) {
546            mResolutions.add(Resolution.createFromParcel(parcel));
547        }
548    }
549
550    private void writeMargins(Margins margins, Parcel parcel) {
551        if (margins == null) {
552            parcel.writeInt(0);
553        } else {
554            parcel.writeInt(1);
555            margins.writeToParcel(parcel);
556        }
557    }
558
559    private Margins readMargins(Parcel parcel) {
560        return (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
561    }
562
563    private void writeInputTrays(Parcel parcel) {
564        if (mInputTrays == null) {
565            parcel.writeInt(0);
566            return;
567        }
568        final int inputTrayCount = mInputTrays.size();
569        parcel.writeInt(inputTrayCount);
570        for (int i = 0; i < inputTrayCount; i++) {
571            mInputTrays.get(i).writeToParcel(parcel);
572        }
573    }
574
575    private List<Tray> readInputTrays(Parcel parcel) {
576        final int inputTrayCount = parcel.readInt();
577        if (inputTrayCount <= 0) {
578            return null;
579        }
580        List<Tray> inputTrays = new ArrayList<Tray>(inputTrayCount);
581        for (int i = 0; i < inputTrayCount; i++) {
582            inputTrays.add(Tray.createFromParcel(parcel));
583        }
584        return inputTrays;
585    }
586
587    private void writeOutputTrays(Parcel parcel) {
588        if (mOutputTrays == null) {
589            parcel.writeInt(0);
590            return;
591        }
592        final int outputTrayCount = mOutputTrays.size();
593        parcel.writeInt(outputTrayCount);
594        for (int i = 0; i < outputTrayCount; i++) {
595            mOutputTrays.get(i).writeToParcel(parcel);
596        }
597    }
598
599    private List<Tray> readOutputTrays(Parcel parcel) {
600        final int outputTrayCount = parcel.readInt();
601        if (outputTrayCount <= 0) {
602            return null;
603        }
604        List<Tray> outputTrays = new ArrayList<Tray>(outputTrayCount);
605        for (int i = 0; i < outputTrayCount; i++) {
606            outputTrays.add(Tray.createFromParcel(parcel));
607        }
608        return outputTrays;
609    }
610
611    private void readDefaults(Parcel parcel) {
612        final int defaultCount = parcel.readInt();
613        for (int i = 0; i < defaultCount; i++) {
614            mDefaults[i] = parcel.readInt();
615        }
616    }
617
618    private void writeDefaults(Parcel parcel) {
619        final int defaultCount = mDefaults.length;
620        parcel.writeInt(defaultCount);
621        for (int i = 0; i < defaultCount; i++) {
622            parcel.writeInt(mDefaults[i]);
623        }
624    }
625
626    /**
627     * Builder for creating of a {@link PrinterInfo}. This class is responsible
628     * to enforce that all required attributes have at least one default value.
629     * In other words, this class creates only well-formed {@link PrinterInfo}s.
630     * <p>
631     * Look at the individual methods for a reference whether a property is
632     * required or if it is optional.
633     * </p>
634     */
635    public static final class Builder {
636        private final PrinterInfo mPrototype;
637
638        /**
639         * Creates a new instance.
640         *
641         * @param printerId The printer id. Cannot be null.
642         * @param label The human readable printer label. Cannot be null or empty.
643         *
644         * @throws IllegalArgumentException If the printer id is null.
645         * @throws IllegalArgumentException If the label is empty.
646         */
647        public Builder(PrinterId printerId, CharSequence label) {
648            if (printerId == null) {
649                throw new IllegalArgumentException("printerId cannot be null.");
650            }
651            if (TextUtils.isEmpty(label)) {
652                throw new IllegalArgumentException("label cannot be empty.");
653            }
654            mPrototype = new PrinterInfo();
655            mPrototype.mLabel = label;
656            mPrototype.mId = printerId;
657        }
658
659        /**
660         * Sets the printer status.
661         * <p>
662         * <strong>Required:</strong> Yes
663         * </p>
664         *
665         * @param status The status.
666         * @return This builder.
667         */
668        public Builder setStatus(int status) {
669            mPrototype.mStatus = status;
670            return this;
671        }
672
673        /**
674         * Adds a supported media size.
675         * <p>
676         * <strong>Required:</strong> Yes
677         * </p>
678         *
679         * @param mediaSize A media size.
680         * @param isDefault Whether this is the default.
681         * @return This builder.
682         * @throws IllegalArgumentException If set as default and there
683         *     is already a default.
684         *
685         * @see PrintAttributes.MediaSize
686         */
687        public Builder addMediaSize(MediaSize mediaSize, boolean isDefault) {
688            if (mPrototype.mMediaSizes == null) {
689                mPrototype.mMediaSizes = new ArrayList<MediaSize>();
690            }
691            final int insertionIndex = mPrototype.mMediaSizes.size();
692            mPrototype.mMediaSizes.add(mediaSize);
693            if (isDefault) {
694                throwIfDefaultAlreadySpecified(PROPERTY_MEDIA_SIZE);
695                mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] = insertionIndex;
696            }
697            return this;
698        }
699
700        /**
701         * Adds a supported resolution.
702         * <p>
703         * <strong>Required:</strong> Yes
704         * </p>
705         *
706         * @param resolution A resolution.
707         * @param isDefault Whether this is the default.
708         * @return This builder.
709         *
710         * @throws IllegalArgumentException If set as default and there
711         *     is already a default.
712         *
713         * @see PrintAttributes.Resolution
714         */
715        public Builder addResolution(Resolution resolution, boolean isDefault) {
716            if (mPrototype.mResolutions == null) {
717                mPrototype.mResolutions = new ArrayList<Resolution>();
718            }
719            final int insertionIndex = mPrototype.mResolutions.size();
720            mPrototype.mResolutions.add(resolution);
721            if (isDefault) {
722                throwIfDefaultAlreadySpecified(PROPERTY_RESOLUTION);
723                mPrototype.mDefaults[PROPERTY_RESOLUTION] = insertionIndex;
724            }
725            return this;
726        }
727
728        /**
729         * Sets the minimal margins.
730         * <p>
731         * <strong>Required:</strong> No
732         * </p>
733         *
734         * @param margins The margins.
735         * @param defaultMargins The default margins.
736         * @return This builder.
737         *
738         * @see PrintAttributes.Margins
739         */
740        public Builder setMinMargins(Margins margins, Margins defaultMargins) {
741            if (margins.getLeftMils() > defaultMargins.getLeftMils()
742                    || margins.getTopMils() > defaultMargins.getTopMils()
743                    || margins.getRightMils() < defaultMargins.getRightMils()
744                    || margins.getBottomMils() < defaultMargins.getBottomMils()) {
745                throw new IllegalArgumentException("Default margins"
746                    + " cannot be outside of the min margins.");
747            }
748            mPrototype.mMinMargins = margins;
749            mPrototype.mDefaultMargins = defaultMargins;
750            return this;
751        }
752
753        /**
754         * Adds an input tray.
755         * <p>
756         * <strong>Required:</strong> No
757         * </p>
758         *
759         * @param inputTray A tray.
760         * @param isDefault Whether this is the default.
761         * @return This builder.
762         *
763         * @throws IllegalArgumentException If set as default and there
764         *     is already a default.
765         *
766         * @see PrintAttributes.Tray
767         */
768        public Builder addInputTray(Tray inputTray, boolean isDefault) {
769            if (mPrototype.mInputTrays == null) {
770                mPrototype.mInputTrays = new ArrayList<Tray>();
771            }
772            final int insertionIndex = mPrototype.mInputTrays.size();
773            mPrototype.mInputTrays.add(inputTray);
774            if (isDefault) {
775                throwIfDefaultAlreadySpecified(PROPERTY_INPUT_TRAY);
776                mPrototype.mDefaults[PROPERTY_INPUT_TRAY] = insertionIndex;
777            }
778            return this;
779        }
780
781        /**
782         * Adds an output tray.
783         * <p>
784         * <strong>Required:</strong> No
785         * </p>
786         *
787         * @param outputTray A tray.
788         * @param isDefault Whether this is the default.
789         * @return This builder.
790         *
791         * @throws IllegalArgumentException If set as default and there
792         *     is already a default.
793         *
794         * @see PrintAttributes.Tray
795         */
796        public Builder addOutputTray(Tray outputTray, boolean isDefault) {
797            if (mPrototype.mOutputTrays == null) {
798                mPrototype.mOutputTrays = new ArrayList<Tray>();
799            }
800            final int insertionIndex = mPrototype.mOutputTrays.size();
801            mPrototype.mOutputTrays.add(outputTray);
802            if (isDefault) {
803                throwIfDefaultAlreadySpecified(PROPERTY_OUTPUT_TRAY);
804                mPrototype.mDefaults[PROPERTY_OUTPUT_TRAY] = insertionIndex;
805            }
806            return this;
807        }
808
809        /**
810         * Sets the color modes.
811         * <p>
812         * <strong>Required:</strong> Yes
813         * </p>
814         *
815         * @param colorModes The color mode bit mask.
816         * @param defaultColorMode The default color mode.
817         * @return This builder.
818         *
819         * @throws IllegalArgumentException If color modes contains an invalid
820         *         mode bit or if the default color mode is invalid.
821         *
822         * @see PrintAttributes#COLOR_MODE_COLOR
823         * @see PrintAttributes#COLOR_MODE_MONOCHROME
824         */
825        public Builder setColorModes(int colorModes, int defaultColorMode) {
826            int currentModes = colorModes;
827            while (currentModes > 0) {
828                final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
829                currentModes &= ~currentMode;
830                PrintAttributes.enforceValidColorMode(currentMode);
831            }
832            if ((colorModes & defaultColorMode) == 0) {
833                throw new IllegalArgumentException("Default color mode not in color modes.");
834            }
835            PrintAttributes.enforceValidColorMode(colorModes);
836            mPrototype.mColorModes = colorModes;
837            mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode;
838            return this;
839        }
840
841        /**
842         * Set the duplex modes.
843         * <p>
844         * <strong>Required:</strong> No
845         * </p>
846         *
847         * @param duplexModes The duplex mode bit mask.
848         * @param defaultDuplexMode The default duplex mode.
849         * @return This builder.
850         *
851         * @throws IllegalArgumentException If duplex modes contains an invalid
852         *         mode bit or if the default duplex mode is invalid.
853         *
854         * @see PrintAttributes#DUPLEX_MODE_NONE
855         * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
856         * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
857         */
858        public Builder setDuplexModes(int duplexModes, int defaultDuplexMode) {
859            int currentModes = duplexModes;
860            while (currentModes > 0) {
861                final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
862                currentModes &= ~currentMode;
863                PrintAttributes.enforceValidDuplexMode(currentMode);
864            }
865            if ((duplexModes & defaultDuplexMode) == 0) {
866                throw new IllegalArgumentException("Default duplex mode not in duplex modes.");
867            }
868            PrintAttributes.enforceValidDuplexMode(defaultDuplexMode);
869            mPrototype.mDuplexModes = duplexModes;
870            mPrototype.mDefaults[PROPERTY_DUPLEX_MODE] = defaultDuplexMode;
871            return this;
872        }
873
874        /**
875         * Sets the fitting modes.
876         * <p>
877         * <strong>Required:</strong> No
878         * </p>
879         *
880         * @param fittingModes The fitting mode bit mask.
881         * @param defaultFittingMode The default fitting mode.
882         * @return This builder.
883         *
884         * @throws IllegalArgumentException If fitting modes contains an invalid
885         *         mode bit or if the default fitting mode is invalid.
886         *
887         * @see PrintAttributes#FITTING_MODE_NONE
888         * @see PrintAttributes#FITTING_MODE_FIT_TO_PAGE
889         */
890        public Builder setFittingModes(int fittingModes, int defaultFittingMode) {
891            int currentModes = fittingModes;
892            while (currentModes > 0) {
893                final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
894                currentModes &= ~currentMode;
895                PrintAttributes.enfoceValidFittingMode(currentMode);
896            }
897            if ((fittingModes & defaultFittingMode) == 0) {
898                throw new IllegalArgumentException("Default fitting mode not in fiting modes.");
899            }
900            PrintAttributes.enfoceValidFittingMode(defaultFittingMode);
901            mPrototype.mFittingModes = fittingModes;
902            mPrototype.mDefaults[PROPERTY_FITTING_MODE] = defaultFittingMode;
903            return this;
904        }
905
906        /**
907         * Sets the orientations.
908         * <p>
909         * <strong>Required:</strong> Yes
910         * </p>
911         *
912         * @param orientations The orientation bit mask.
913         * @param defaultOrientation The default orientation.
914         * @return This builder.
915         *
916         * @throws IllegalArgumentException If orientations contains an invalid
917         *         mode bit or if the default orientation is invalid.
918         *
919         * @see PrintAttributes#ORIENTATION_PORTRAIT
920         * @see PrintAttributes#ORIENTATION_LANDSCAPE
921         */
922        public Builder setOrientations(int orientations, int defaultOrientation) {
923            int currentOrientaions = orientations;
924            while (currentOrientaions > 0) {
925                final int currentOrnt = (1 << Integer.numberOfTrailingZeros(currentOrientaions));
926                currentOrientaions &= ~currentOrnt;
927                PrintAttributes.enforceValidOrientation(currentOrnt);
928            }
929            if ((orientations & defaultOrientation) == 0) {
930                throw new IllegalArgumentException("Default orientation not in orientations.");
931            }
932            PrintAttributes.enforceValidOrientation(defaultOrientation);
933            mPrototype.mOrientations = orientations;
934            mPrototype.mDefaults[PROPERTY_ORIENTATION] = defaultOrientation;
935            return this;
936        }
937
938        /**
939         * Crates a new {@link PrinterInfo}.
940         *
941         * @return A new {@link PrinterInfo}.
942         */
943        public PrinterInfo create() {
944            return new PrinterInfo(mPrototype);
945        }
946
947        private void throwIfDefaultAlreadySpecified(int propertyIndex) {
948            if (mPrototype.mDefaults[propertyIndex] != DEFAULT_UNDEFINED) {
949                throw new IllegalArgumentException("Default already specified.");
950            }
951        }
952    }
953
954    public static final Parcelable.Creator<PrinterInfo> CREATOR =
955            new Parcelable.Creator<PrinterInfo>() {
956        @Override
957        public PrinterInfo createFromParcel(Parcel parcel) {
958            return new PrinterInfo(parcel);
959        }
960
961        @Override
962        public PrinterInfo[] newArray(int size) {
963            return new PrinterInfo[size];
964        }
965    };
966}
967