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 
17 package android.print;
18 
19 import android.os.Parcel;
20 import android.os.Parcelable;
21 import android.print.PrintAttributes.Margins;
22 import android.print.PrintAttributes.MediaSize;
23 import android.print.PrintAttributes.Resolution;
24 
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import 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  */
39 public 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      */
PrinterCapabilitiesInfo()67     public PrinterCapabilitiesInfo() {
68         Arrays.fill(mDefaults, DEFAULT_UNDEFINED);
69     }
70 
71     /**
72      * @hide
73      */
PrinterCapabilitiesInfo(PrinterCapabilitiesInfo prototype)74     public PrinterCapabilitiesInfo(PrinterCapabilitiesInfo prototype) {
75         copyFrom(prototype);
76     }
77 
78     /**
79      * @hide
80      */
copyFrom(PrinterCapabilitiesInfo other)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      */
getMediaSizes()124     public List<MediaSize> getMediaSizes() {
125         return Collections.unmodifiableList(mMediaSizes);
126     }
127 
128     /**
129      * Gets the supported resolutions.
130      *
131      * @return The resolutions.
132      */
getResolutions()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      */
getMinMargins()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      */
getColorModes()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      */
getDuplexModes()168     public int getDuplexModes() {
169         return mDuplexModes;
170     }
171 
172     /**
173      * Gets the default print attributes.
174      *
175      * @return The default attributes.
176      */
getDefaults()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 
PrinterCapabilitiesInfo(Parcel parcel)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
describeContents()217     public int describeContents() {
218         return 0;
219     }
220 
221     @Override
writeToParcel(Parcel parcel, int flags)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
hashCode()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
equals(Object obj)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
toString()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 
colorModesToString()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 
duplexModesToString()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 
writeMediaSizes(Parcel parcel)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 
readMediaSizes(Parcel parcel)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 
writeResolutions(Parcel parcel)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 
readResolutions(Parcel parcel)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 
writeMargins(Margins margins, Parcel parcel)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 
readMargins(Parcel parcel)389     private Margins readMargins(Parcel parcel) {
390         return (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
391     }
392 
readDefaults(Parcel parcel)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 
writeDefaults(Parcel parcel)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          */
Builder(PrinterId printerId)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          */
addMediaSize(MediaSize mediaSize, boolean isDefault)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          */
addResolution(Resolution resolution, boolean isDefault)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          */
setMinMargins(Margins margins)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          */
setColorModes(int colorModes, int defaultColorMode)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          */
setDuplexModes(int duplexModes, int defaultDuplexMode)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          */
build()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 
throwIfDefaultAlreadySpecified(int propertyIndex)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