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