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