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.os.Parcel;
21 import android.os.Parcelable;
22 import android.print.PrintAttributes.ColorMode;
23 import android.print.PrintAttributes.DuplexMode;
24 import android.print.PrintAttributes.Margins;
25 import android.print.PrintAttributes.MediaSize;
26 import android.print.PrintAttributes.Resolution;
27 import com.android.internal.util.Preconditions;
28 
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.List;
33 import 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  */
44 public 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      */
PrinterCapabilitiesInfo()72     public PrinterCapabilitiesInfo() {
73         Arrays.fill(mDefaults, DEFAULT_UNDEFINED);
74     }
75 
76     /**
77      * @hide
78      */
PrinterCapabilitiesInfo(PrinterCapabilitiesInfo prototype)79     public PrinterCapabilitiesInfo(PrinterCapabilitiesInfo prototype) {
80         copyFrom(prototype);
81     }
82 
83     /**
84      * @hide
85      */
copyFrom(PrinterCapabilitiesInfo other)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      */
getMediaSizes()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      */
getResolutions()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      */
getMinMargins()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      */
getColorModes()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      */
getDuplexModes()173     public @DuplexMode int getDuplexModes() {
174         return mDuplexModes;
175     }
176 
177     /**
178      * Gets the default print attributes.
179      *
180      * @return The default attributes.
181      */
getDefaults()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      */
enforceValidMask(int mask, IntConsumer enforceSingle)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 
PrinterCapabilitiesInfo(Parcel parcel)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
describeContents()244     public int describeContents() {
245         return 0;
246     }
247 
248     @Override
writeToParcel(Parcel parcel, int flags)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
hashCode()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
equals(Object obj)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
toString()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 
colorModesToString()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 
duplexModesToString()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 
writeMediaSizes(Parcel parcel)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 
readMediaSizes(Parcel parcel)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 
writeResolutions(Parcel parcel)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 
readResolutions(Parcel parcel)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 
writeMargins(Margins margins, Parcel parcel)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 
readMargins(Parcel parcel)416     private Margins readMargins(Parcel parcel) {
417         return (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
418     }
419 
readDefaults(Parcel parcel)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 
writeDefaults(Parcel parcel)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          */
Builder(@onNull PrinterId printerId)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          */
addMediaSize(@onNull MediaSize mediaSize, boolean isDefault)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          */
addResolution(@onNull Resolution resolution, boolean isDefault)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          */
setMinMargins(@onNull Margins margins)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          */
setColorModes(@olorMode int colorModes, @ColorMode int defaultColorMode)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          */
setDuplexModes(@uplexMode int duplexModes, @DuplexMode int defaultDuplexMode)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          */
build()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 
throwIfDefaultAlreadySpecified(int propertyIndex)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 @android.annotation.NonNull 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