PrinterCapabilitiesInfo.java revision 948c9a6393e0995a4d988d5d164998aa0e12b25d
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;
24
25import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.Collections;
28import java.util.List;
29
30/**
31 * This class represents the capabilities of a printer. Instances
32 * of this class are created by a print service to report the
33 * capabilities of a printer it manages. The capabilities of a
34 * printer specify how it can print content. For example, what
35 * are the media sizes supported by the printer, what are the
36 * minimal margins of the printer based on its technical design,
37 * etc.
38 */
39public final class PrinterCapabilitiesInfo implements Parcelable {
40    /**
41     * Undefined default value.
42     *
43     * @hide
44     */
45    public static final int DEFAULT_UNDEFINED = -1;
46
47    private static final int PROPERTY_MEDIA_SIZE = 0;
48    private static final int PROPERTY_RESOLUTION = 1;
49    private static final int PROPERTY_COLOR_MODE = 2;
50    private static final int PROPERTY_DUPLEX_MODE = 3;
51    private static final int PROPERTY_COUNT = 4;
52
53    private static final Margins DEFAULT_MARGINS = new Margins(0,  0,  0,  0);
54
55    private Margins mMinMargins = DEFAULT_MARGINS;
56    private List<MediaSize> mMediaSizes;
57    private List<Resolution> mResolutions;
58
59    private int mColorModes;
60    private int mDuplexModes;
61
62    private final int[] mDefaults = new int[PROPERTY_COUNT];
63
64    /**
65     * @hide
66     */
67    public PrinterCapabilitiesInfo() {
68        Arrays.fill(mDefaults, DEFAULT_UNDEFINED);
69    }
70
71    /**
72     * @hide
73     */
74    public PrinterCapabilitiesInfo(PrinterCapabilitiesInfo prototype) {
75        copyFrom(prototype);
76    }
77
78    /**
79     * @hide
80     */
81    public void copyFrom(PrinterCapabilitiesInfo other) {
82        if (this == other) {
83            return;
84        }
85
86        mMinMargins = other.mMinMargins;
87
88        if (other.mMediaSizes != null) {
89            if (mMediaSizes != null) {
90                mMediaSizes.clear();
91                mMediaSizes.addAll(other.mMediaSizes);
92            } else {
93                mMediaSizes = new ArrayList<MediaSize>(other.mMediaSizes);
94            }
95        } else {
96            mMediaSizes = null;
97        }
98
99        if (other.mResolutions != null) {
100            if (mResolutions != null) {
101                mResolutions.clear();
102                mResolutions.addAll(other.mResolutions);
103            } else {
104                mResolutions = new ArrayList<Resolution>(other.mResolutions);
105            }
106        } else {
107            mResolutions = null;
108        }
109
110        mColorModes = other.mColorModes;
111        mDuplexModes = other.mDuplexModes;
112
113        final int defaultCount = other.mDefaults.length;
114        for (int i = 0; i < defaultCount; i++) {
115            mDefaults[i] = other.mDefaults[i];
116        }
117    }
118
119    /**
120     * Gets the supported media sizes.
121     *
122     * @return The media sizes.
123     */
124    public List<MediaSize> getMediaSizes() {
125        return Collections.unmodifiableList(mMediaSizes);
126    }
127
128    /**
129     * Gets the supported resolutions.
130     *
131     * @return The resolutions.
132     */
133    public List<Resolution> getResolutions() {
134        return Collections.unmodifiableList(mResolutions);
135    }
136
137    /**
138     * Gets the minimal margins. These are the minimal margins
139     * the printer physically supports.
140     *
141     * @return The minimal margins.
142     */
143    public Margins getMinMargins() {
144        return mMinMargins;
145    }
146
147    /**
148     * Gets the bit mask of supported color modes.
149     *
150     * @return The bit mask of supported color modes.
151     *
152     * @see PrintAttributes#COLOR_MODE_COLOR
153     * @see PrintAttributes#COLOR_MODE_MONOCHROME
154     */
155    public int getColorModes() {
156        return mColorModes;
157    }
158
159    /**
160     * Gets the bit mask of supported duplex modes.
161     *
162     * @return The bit mask of supported duplex modes.
163     *
164     * @see PrintAttributes#DUPLEX_MODE_NONE
165     * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
166     * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
167     */
168    public int getDuplexModes() {
169        return mDuplexModes;
170    }
171
172    /**
173     * Gets the default print attributes.
174     *
175     * @return The default attributes.
176     */
177    public PrintAttributes getDefaults() {
178        PrintAttributes.Builder builder = new PrintAttributes.Builder();
179
180        builder.setMinMargins(mMinMargins);
181
182        final int mediaSizeIndex = mDefaults[PROPERTY_MEDIA_SIZE];
183        if (mediaSizeIndex >= 0) {
184            builder.setMediaSize(mMediaSizes.get(mediaSizeIndex));
185        }
186
187        final int resolutionIndex = mDefaults[PROPERTY_RESOLUTION];
188        if (resolutionIndex >= 0) {
189            builder.setResolution(mResolutions.get(resolutionIndex));
190        }
191
192        final int colorMode = mDefaults[PROPERTY_COLOR_MODE];
193        if (colorMode > 0) {
194            builder.setColorMode(colorMode);
195        }
196
197        final int duplexMode = mDefaults[PROPERTY_DUPLEX_MODE];
198        if (duplexMode > 0) {
199            builder.setDuplexMode(duplexMode);
200        }
201
202        return builder.build();
203    }
204
205    private PrinterCapabilitiesInfo(Parcel parcel) {
206        mMinMargins = readMargins(parcel);
207        readMediaSizes(parcel);
208        readResolutions(parcel);
209
210        mColorModes = parcel.readInt();
211        mDuplexModes = parcel.readInt();
212
213        readDefaults(parcel);
214    }
215
216    @Override
217    public int describeContents() {
218        return 0;
219    }
220
221    @Override
222    public void writeToParcel(Parcel parcel, int flags) {
223        writeMargins(mMinMargins, parcel);
224        writeMediaSizes(parcel);
225        writeResolutions(parcel);
226
227        parcel.writeInt(mColorModes);
228        parcel.writeInt(mDuplexModes);
229
230        writeDefaults(parcel);
231    }
232
233    @Override
234    public int hashCode() {
235        final int prime = 31;
236        int result = 1;
237        result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode());
238        result = prime * result + ((mMediaSizes == null) ? 0 : mMediaSizes.hashCode());
239        result = prime * result + ((mResolutions == null) ? 0 : mResolutions.hashCode());
240        result = prime * result + mColorModes;
241        result = prime * result + mDuplexModes;
242        result = prime * result + Arrays.hashCode(mDefaults);
243        return result;
244    }
245
246    @Override
247    public boolean equals(Object obj) {
248        if (this == obj) {
249            return true;
250        }
251        if (obj == null) {
252            return false;
253        }
254        if (getClass() != obj.getClass()) {
255            return false;
256        }
257        PrinterCapabilitiesInfo other = (PrinterCapabilitiesInfo) obj;
258        if (mMinMargins == null) {
259            if (other.mMinMargins != null) {
260                return false;
261            }
262        } else if (!mMinMargins.equals(other.mMinMargins)) {
263            return false;
264        }
265        if (mMediaSizes == null) {
266            if (other.mMediaSizes != null) {
267                return false;
268            }
269        } else if (!mMediaSizes.equals(other.mMediaSizes)) {
270            return false;
271        }
272        if (mResolutions == null) {
273            if (other.mResolutions != null) {
274                return false;
275            }
276        } else if (!mResolutions.equals(other.mResolutions)) {
277            return false;
278        }
279        if (mColorModes != other.mColorModes) {
280            return false;
281        }
282        if (mDuplexModes != other.mDuplexModes) {
283            return false;
284        }
285        if (!Arrays.equals(mDefaults, other.mDefaults)) {
286            return false;
287        }
288        return true;
289    }
290
291    @Override
292    public String toString() {
293        StringBuilder builder = new StringBuilder();
294        builder.append("PrinterInfo{");
295        builder.append("minMargins=").append(mMinMargins);
296        builder.append(", mediaSizes=").append(mMediaSizes);
297        builder.append(", resolutions=").append(mResolutions);
298        builder.append(", colorModes=").append(colorModesToString());
299        builder.append(", duplexModes=").append(duplexModesToString());
300        builder.append("\"}");
301        return builder.toString();
302    }
303
304    private String colorModesToString() {
305        StringBuilder builder = new StringBuilder();
306        builder.append('[');
307        int colorModes = mColorModes;
308        while (colorModes != 0) {
309            final int colorMode = 1 << Integer.numberOfTrailingZeros(colorModes);
310            colorModes &= ~colorMode;
311            if (builder.length() > 1) {
312                builder.append(", ");
313            }
314            builder.append(PrintAttributes.colorModeToString(colorMode));
315        }
316        builder.append(']');
317        return builder.toString();
318    }
319
320    private String duplexModesToString() {
321        StringBuilder builder = new StringBuilder();
322        builder.append('[');
323        int duplexModes = mDuplexModes;
324        while (duplexModes != 0) {
325            final int duplexMode = 1 << Integer.numberOfTrailingZeros(duplexModes);
326            duplexModes &= ~duplexMode;
327            if (builder.length() > 1) {
328                builder.append(", ");
329            }
330            builder.append(PrintAttributes.duplexModeToString(duplexMode));
331        }
332        builder.append(']');
333        return builder.toString();
334    }
335
336    private void writeMediaSizes(Parcel parcel) {
337        if (mMediaSizes == null) {
338            parcel.writeInt(0);
339            return;
340        }
341        final int mediaSizeCount = mMediaSizes.size();
342        parcel.writeInt(mediaSizeCount);
343        for (int i = 0; i < mediaSizeCount; i++) {
344            mMediaSizes.get(i).writeToParcel(parcel);
345        }
346    }
347
348    private void readMediaSizes(Parcel parcel) {
349        final int mediaSizeCount = parcel.readInt();
350        if (mediaSizeCount > 0 && mMediaSizes == null) {
351            mMediaSizes = new ArrayList<MediaSize>();
352        }
353        for (int i = 0; i < mediaSizeCount; i++) {
354            mMediaSizes.add(MediaSize.createFromParcel(parcel));
355        }
356    }
357
358    private void writeResolutions(Parcel parcel) {
359        if (mResolutions == null) {
360            parcel.writeInt(0);
361            return;
362        }
363        final int resolutionCount = mResolutions.size();
364        parcel.writeInt(resolutionCount);
365        for (int i = 0; i < resolutionCount; i++) {
366            mResolutions.get(i).writeToParcel(parcel);
367        }
368    }
369
370    private void readResolutions(Parcel parcel) {
371        final int resolutionCount = parcel.readInt();
372        if (resolutionCount > 0 && mResolutions == null) {
373            mResolutions = new ArrayList<Resolution>();
374        }
375        for (int i = 0; i < resolutionCount; i++) {
376            mResolutions.add(Resolution.createFromParcel(parcel));
377        }
378    }
379
380    private void writeMargins(Margins margins, Parcel parcel) {
381        if (margins == null) {
382            parcel.writeInt(0);
383        } else {
384            parcel.writeInt(1);
385            margins.writeToParcel(parcel);
386        }
387    }
388
389    private Margins readMargins(Parcel parcel) {
390        return (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
391    }
392
393    private void readDefaults(Parcel parcel) {
394        final int defaultCount = parcel.readInt();
395        for (int i = 0; i < defaultCount; i++) {
396            mDefaults[i] = parcel.readInt();
397        }
398    }
399
400    private void writeDefaults(Parcel parcel) {
401        final int defaultCount = mDefaults.length;
402        parcel.writeInt(defaultCount);
403        for (int i = 0; i < defaultCount; i++) {
404            parcel.writeInt(mDefaults[i]);
405        }
406    }
407
408    /**
409     * Builder for creating of a {@link PrinterCapabilitiesInfo}. This class is
410     * responsible to enforce that all required attributes have at least one
411     * default value. In other words, this class creates only well-formed {@link
412     * PrinterCapabilitiesInfo}s.
413     * <p>
414     * Look at the individual methods for a reference whether a property is
415     * required or if it is optional.
416     * </p>
417     */
418    public static final class Builder {
419        private final PrinterCapabilitiesInfo mPrototype;
420
421        /**
422         * Creates a new instance.
423         *
424         * @param printerId The printer id. Cannot be <code>null</code>.
425         *
426         * @throws IllegalArgumentException If the printer id is <code>null</code>.
427         */
428        public Builder(PrinterId printerId) {
429            if (printerId == null) {
430                throw new IllegalArgumentException("printerId cannot be null.");
431            }
432            mPrototype = new PrinterCapabilitiesInfo();
433        }
434
435        /**
436         * Adds a supported media size.
437         * <p>
438         * <strong>Required:</strong> Yes
439         * </p>
440         *
441         * @param mediaSize A media size.
442         * @param isDefault Whether this is the default.
443         * @return This builder.
444         * @throws IllegalArgumentException If set as default and there
445         *     is already a default.
446         *
447         * @see PrintAttributes.MediaSize
448         */
449        public Builder addMediaSize(MediaSize mediaSize, boolean isDefault) {
450            if (mPrototype.mMediaSizes == null) {
451                mPrototype.mMediaSizes = new ArrayList<MediaSize>();
452            }
453            final int insertionIndex = mPrototype.mMediaSizes.size();
454            mPrototype.mMediaSizes.add(mediaSize);
455            if (isDefault) {
456                throwIfDefaultAlreadySpecified(PROPERTY_MEDIA_SIZE);
457                mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] = insertionIndex;
458            }
459            return this;
460        }
461
462        /**
463         * Adds a supported resolution.
464         * <p>
465         * <strong>Required:</strong> Yes
466         * </p>
467         *
468         * @param resolution A resolution.
469         * @param isDefault Whether this is the default.
470         * @return This builder.
471         *
472         * @throws IllegalArgumentException If set as default and there
473         *     is already a default.
474         *
475         * @see PrintAttributes.Resolution
476         */
477        public Builder addResolution(Resolution resolution, boolean isDefault) {
478            if (mPrototype.mResolutions == null) {
479                mPrototype.mResolutions = new ArrayList<Resolution>();
480            }
481            final int insertionIndex = mPrototype.mResolutions.size();
482            mPrototype.mResolutions.add(resolution);
483            if (isDefault) {
484                throwIfDefaultAlreadySpecified(PROPERTY_RESOLUTION);
485                mPrototype.mDefaults[PROPERTY_RESOLUTION] = insertionIndex;
486            }
487            return this;
488        }
489
490        /**
491         * Sets the minimal margins. These are the minimal margins
492         * the printer physically supports.
493         *
494         * <p>
495         * <strong>Required:</strong> Yes
496         * </p>
497         *
498         * @param margins The margins.
499         * @return This builder.
500         *
501         * @throws IllegalArgumentException If margins are <code>null</code>.
502         *
503         * @see PrintAttributes.Margins
504         */
505        public Builder setMinMargins(Margins margins) {
506            if (margins == null) {
507                throw new IllegalArgumentException("margins cannot be null");
508            }
509            mPrototype.mMinMargins = margins;
510            return this;
511        }
512
513        /**
514         * Sets the color modes.
515         * <p>
516         * <strong>Required:</strong> Yes
517         * </p>
518         *
519         * @param colorModes The color mode bit mask.
520         * @param defaultColorMode The default color mode.
521         * @return This builder.
522         * <p>
523         * <strong>Note:</strong> On platform version 19 (Kitkat) specifying
524         * only PrintAttributes#COLOR_MODE_MONOCHROME leads to a print spooler
525         * crash. Hence, you should declare either both color modes or
526         * PrintAttributes#COLOR_MODE_COLOR.
527         * </p>
528         *
529         * @throws IllegalArgumentException If color modes contains an invalid
530         *         mode bit or if the default color mode is invalid.
531         *
532         * @see PrintAttributes#COLOR_MODE_COLOR
533         * @see PrintAttributes#COLOR_MODE_MONOCHROME
534         */
535        public Builder setColorModes(int colorModes, int defaultColorMode) {
536            int currentModes = colorModes;
537            while (currentModes > 0) {
538                final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
539                currentModes &= ~currentMode;
540                PrintAttributes.enforceValidColorMode(currentMode);
541            }
542            PrintAttributes.enforceValidColorMode(defaultColorMode);
543            mPrototype.mColorModes = colorModes;
544            mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode;
545            return this;
546        }
547
548        /**
549         * Sets the duplex modes.
550         * <p>
551         * <strong>Required:</strong> No
552         * </p>
553         *
554         * @param duplexModes The duplex mode bit mask.
555         * @param defaultDuplexMode The default duplex mode.
556         * @return This builder.
557         *
558         * @throws IllegalArgumentException If duplex modes contains an invalid
559         *         mode bit or if the default duplex mode is invalid.
560         *
561         * @see PrintAttributes#DUPLEX_MODE_NONE
562         * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
563         * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
564         */
565        public Builder setDuplexModes(int duplexModes, int defaultDuplexMode) {
566            int currentModes = duplexModes;
567            while (currentModes > 0) {
568                final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
569                currentModes &= ~currentMode;
570                PrintAttributes.enforceValidDuplexMode(currentMode);
571            }
572            PrintAttributes.enforceValidDuplexMode(defaultDuplexMode);
573            mPrototype.mDuplexModes = duplexModes;
574            mPrototype.mDefaults[PROPERTY_DUPLEX_MODE] = defaultDuplexMode;
575            return this;
576        }
577
578        /**
579         * Crates a new {@link PrinterCapabilitiesInfo} enforcing that all
580         * required properties have been specified. See individual methods
581         * in this class for reference about required attributes.
582         * <p>
583         * <strong>Note:</strong> If you do not add supported duplex modes,
584         * {@link android.print.PrintAttributes#DUPLEX_MODE_NONE} will set
585         * as the only supported mode and also as the default duplex mode.
586         * </p>
587         *
588         * @return A new {@link PrinterCapabilitiesInfo}.
589         *
590         * @throws IllegalStateException If a required attribute was not specified.
591         */
592        public PrinterCapabilitiesInfo build() {
593            if (mPrototype.mMediaSizes == null || mPrototype.mMediaSizes.isEmpty()) {
594                throw new IllegalStateException("No media size specified.");
595            }
596            if (mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] == DEFAULT_UNDEFINED) {
597                throw new IllegalStateException("No default media size specified.");
598            }
599            if (mPrototype.mResolutions == null || mPrototype.mResolutions.isEmpty()) {
600                throw new IllegalStateException("No resolution specified.");
601            }
602            if (mPrototype.mDefaults[PROPERTY_RESOLUTION] == DEFAULT_UNDEFINED) {
603                throw new IllegalStateException("No default resolution specified.");
604            }
605            if (mPrototype.mColorModes == 0) {
606                throw new IllegalStateException("No color mode specified.");
607            }
608            if (mPrototype.mDefaults[PROPERTY_COLOR_MODE] == DEFAULT_UNDEFINED) {
609                throw new IllegalStateException("No default color mode specified.");
610            }
611            if (mPrototype.mDuplexModes == 0) {
612                setDuplexModes(PrintAttributes.DUPLEX_MODE_NONE,
613                        PrintAttributes.DUPLEX_MODE_NONE);
614            }
615            if (mPrototype.mMinMargins == null) {
616                throw new IllegalArgumentException("margins cannot be null");
617            }
618            return mPrototype;
619        }
620
621        private void throwIfDefaultAlreadySpecified(int propertyIndex) {
622            if (mPrototype.mDefaults[propertyIndex] != DEFAULT_UNDEFINED) {
623                throw new IllegalArgumentException("Default already specified.");
624            }
625        }
626    }
627
628    public static final Parcelable.Creator<PrinterCapabilitiesInfo> CREATOR =
629            new Parcelable.Creator<PrinterCapabilitiesInfo>() {
630        @Override
631        public PrinterCapabilitiesInfo createFromParcel(Parcel parcel) {
632            return new PrinterCapabilitiesInfo(parcel);
633        }
634
635        @Override
636        public PrinterCapabilitiesInfo[] newArray(int size) {
637            return new PrinterCapabilitiesInfo[size];
638        }
639    };
640}
641