Home | History | Annotate | Download | only in print
      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      */
     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