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