| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.print; |
| |
| import android.annotation.NonNull; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.print.PrintAttributes.ColorMode; |
| import android.print.PrintAttributes.DuplexMode; |
| import android.print.PrintAttributes.Margins; |
| import android.print.PrintAttributes.MediaSize; |
| import android.print.PrintAttributes.Resolution; |
| import com.android.internal.util.Preconditions; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.function.IntConsumer; |
| |
| /** |
| * This class represents the capabilities of a printer. Instances |
| * of this class are created by a print service to report the |
| * capabilities of a printer it manages. The capabilities of a |
| * printer specify how it can print content. For example, what |
| * are the media sizes supported by the printer, what are the |
| * minimal margins of the printer based on its technical design, |
| * etc. |
| */ |
| public final class PrinterCapabilitiesInfo implements Parcelable { |
| /** |
| * Undefined default value. |
| * |
| * @hide |
| */ |
| public static final int DEFAULT_UNDEFINED = -1; |
| |
| private static final int PROPERTY_MEDIA_SIZE = 0; |
| private static final int PROPERTY_RESOLUTION = 1; |
| private static final int PROPERTY_COLOR_MODE = 2; |
| private static final int PROPERTY_DUPLEX_MODE = 3; |
| private static final int PROPERTY_COUNT = 4; |
| |
| private static final Margins DEFAULT_MARGINS = new Margins(0, 0, 0, 0); |
| |
| private @NonNull Margins mMinMargins = DEFAULT_MARGINS; |
| private @NonNull List<MediaSize> mMediaSizes; |
| private @NonNull List<Resolution> mResolutions; |
| |
| private int mColorModes; |
| private int mDuplexModes; |
| |
| private final int[] mDefaults = new int[PROPERTY_COUNT]; |
| |
| /** |
| * @hide |
| */ |
| public PrinterCapabilitiesInfo() { |
| Arrays.fill(mDefaults, DEFAULT_UNDEFINED); |
| } |
| |
| /** |
| * @hide |
| */ |
| public PrinterCapabilitiesInfo(PrinterCapabilitiesInfo prototype) { |
| copyFrom(prototype); |
| } |
| |
| /** |
| * @hide |
| */ |
| public void copyFrom(PrinterCapabilitiesInfo other) { |
| if (this == other) { |
| return; |
| } |
| |
| mMinMargins = other.mMinMargins; |
| |
| if (other.mMediaSizes != null) { |
| if (mMediaSizes != null) { |
| mMediaSizes.clear(); |
| mMediaSizes.addAll(other.mMediaSizes); |
| } else { |
| mMediaSizes = new ArrayList<MediaSize>(other.mMediaSizes); |
| } |
| } else { |
| mMediaSizes = null; |
| } |
| |
| if (other.mResolutions != null) { |
| if (mResolutions != null) { |
| mResolutions.clear(); |
| mResolutions.addAll(other.mResolutions); |
| } else { |
| mResolutions = new ArrayList<Resolution>(other.mResolutions); |
| } |
| } else { |
| mResolutions = null; |
| } |
| |
| mColorModes = other.mColorModes; |
| mDuplexModes = other.mDuplexModes; |
| |
| final int defaultCount = other.mDefaults.length; |
| for (int i = 0; i < defaultCount; i++) { |
| mDefaults[i] = other.mDefaults[i]; |
| } |
| } |
| |
| /** |
| * Gets the supported media sizes. |
| * |
| * @return The media sizes. |
| */ |
| public @NonNull List<MediaSize> getMediaSizes() { |
| return Collections.unmodifiableList(mMediaSizes); |
| } |
| |
| /** |
| * Gets the supported resolutions. |
| * |
| * @return The resolutions. |
| */ |
| public @NonNull List<Resolution> getResolutions() { |
| return Collections.unmodifiableList(mResolutions); |
| } |
| |
| /** |
| * Gets the minimal margins. These are the minimal margins |
| * the printer physically supports. |
| * |
| * @return The minimal margins. |
| */ |
| public @NonNull Margins getMinMargins() { |
| return mMinMargins; |
| } |
| |
| /** |
| * Gets the bit mask of supported color modes. |
| * |
| * @return The bit mask of supported color modes. |
| * |
| * @see PrintAttributes#COLOR_MODE_COLOR |
| * @see PrintAttributes#COLOR_MODE_MONOCHROME |
| */ |
| public @ColorMode int getColorModes() { |
| return mColorModes; |
| } |
| |
| /** |
| * Gets the bit mask of supported duplex modes. |
| * |
| * @return The bit mask of supported duplex modes. |
| * |
| * @see PrintAttributes#DUPLEX_MODE_NONE |
| * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE |
| * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE |
| */ |
| public @DuplexMode int getDuplexModes() { |
| return mDuplexModes; |
| } |
| |
| /** |
| * Gets the default print attributes. |
| * |
| * @return The default attributes. |
| */ |
| public @NonNull PrintAttributes getDefaults() { |
| PrintAttributes.Builder builder = new PrintAttributes.Builder(); |
| |
| builder.setMinMargins(mMinMargins); |
| |
| final int mediaSizeIndex = mDefaults[PROPERTY_MEDIA_SIZE]; |
| if (mediaSizeIndex >= 0) { |
| builder.setMediaSize(mMediaSizes.get(mediaSizeIndex)); |
| } |
| |
| final int resolutionIndex = mDefaults[PROPERTY_RESOLUTION]; |
| if (resolutionIndex >= 0) { |
| builder.setResolution(mResolutions.get(resolutionIndex)); |
| } |
| |
| final int colorMode = mDefaults[PROPERTY_COLOR_MODE]; |
| if (colorMode > 0) { |
| builder.setColorMode(colorMode); |
| } |
| |
| final int duplexMode = mDefaults[PROPERTY_DUPLEX_MODE]; |
| if (duplexMode > 0) { |
| builder.setDuplexMode(duplexMode); |
| } |
| |
| return builder.build(); |
| } |
| |
| /** |
| * Call enforceSingle for each bit in the mask. |
| * |
| * @param mask The mask |
| * @param enforceSingle The function to call |
| */ |
| private static void enforceValidMask(int mask, IntConsumer enforceSingle) { |
| int current = mask; |
| while (current > 0) { |
| final int currentMode = (1 << Integer.numberOfTrailingZeros(current)); |
| current &= ~currentMode; |
| enforceSingle.accept(currentMode); |
| } |
| } |
| |
| private PrinterCapabilitiesInfo(Parcel parcel) { |
| mMinMargins = Preconditions.checkNotNull(readMargins(parcel)); |
| readMediaSizes(parcel); |
| readResolutions(parcel); |
| |
| mColorModes = parcel.readInt(); |
| enforceValidMask(mColorModes, |
| (currentMode) -> PrintAttributes.enforceValidColorMode(currentMode)); |
| |
| mDuplexModes = parcel.readInt(); |
| enforceValidMask(mDuplexModes, |
| (currentMode) -> PrintAttributes.enforceValidDuplexMode(currentMode)); |
| |
| readDefaults(parcel); |
| Preconditions.checkArgument(mMediaSizes.size() > mDefaults[PROPERTY_MEDIA_SIZE]); |
| Preconditions.checkArgument(mResolutions.size() > mDefaults[PROPERTY_RESOLUTION]); |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel parcel, int flags) { |
| writeMargins(mMinMargins, parcel); |
| writeMediaSizes(parcel); |
| writeResolutions(parcel); |
| |
| parcel.writeInt(mColorModes); |
| parcel.writeInt(mDuplexModes); |
| |
| writeDefaults(parcel); |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode()); |
| result = prime * result + ((mMediaSizes == null) ? 0 : mMediaSizes.hashCode()); |
| result = prime * result + ((mResolutions == null) ? 0 : mResolutions.hashCode()); |
| result = prime * result + mColorModes; |
| result = prime * result + mDuplexModes; |
| result = prime * result + Arrays.hashCode(mDefaults); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null) { |
| return false; |
| } |
| if (getClass() != obj.getClass()) { |
| return false; |
| } |
| PrinterCapabilitiesInfo other = (PrinterCapabilitiesInfo) obj; |
| if (mMinMargins == null) { |
| if (other.mMinMargins != null) { |
| return false; |
| } |
| } else if (!mMinMargins.equals(other.mMinMargins)) { |
| return false; |
| } |
| if (mMediaSizes == null) { |
| if (other.mMediaSizes != null) { |
| return false; |
| } |
| } else if (!mMediaSizes.equals(other.mMediaSizes)) { |
| return false; |
| } |
| if (mResolutions == null) { |
| if (other.mResolutions != null) { |
| return false; |
| } |
| } else if (!mResolutions.equals(other.mResolutions)) { |
| return false; |
| } |
| if (mColorModes != other.mColorModes) { |
| return false; |
| } |
| if (mDuplexModes != other.mDuplexModes) { |
| return false; |
| } |
| if (!Arrays.equals(mDefaults, other.mDefaults)) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("PrinterInfo{"); |
| builder.append("minMargins=").append(mMinMargins); |
| builder.append(", mediaSizes=").append(mMediaSizes); |
| builder.append(", resolutions=").append(mResolutions); |
| builder.append(", colorModes=").append(colorModesToString()); |
| builder.append(", duplexModes=").append(duplexModesToString()); |
| builder.append("\"}"); |
| return builder.toString(); |
| } |
| |
| private String colorModesToString() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append('['); |
| int colorModes = mColorModes; |
| while (colorModes != 0) { |
| final int colorMode = 1 << Integer.numberOfTrailingZeros(colorModes); |
| colorModes &= ~colorMode; |
| if (builder.length() > 1) { |
| builder.append(", "); |
| } |
| builder.append(PrintAttributes.colorModeToString(colorMode)); |
| } |
| builder.append(']'); |
| return builder.toString(); |
| } |
| |
| private String duplexModesToString() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append('['); |
| int duplexModes = mDuplexModes; |
| while (duplexModes != 0) { |
| final int duplexMode = 1 << Integer.numberOfTrailingZeros(duplexModes); |
| duplexModes &= ~duplexMode; |
| if (builder.length() > 1) { |
| builder.append(", "); |
| } |
| builder.append(PrintAttributes.duplexModeToString(duplexMode)); |
| } |
| builder.append(']'); |
| return builder.toString(); |
| } |
| |
| private void writeMediaSizes(Parcel parcel) { |
| if (mMediaSizes == null) { |
| parcel.writeInt(0); |
| return; |
| } |
| final int mediaSizeCount = mMediaSizes.size(); |
| parcel.writeInt(mediaSizeCount); |
| for (int i = 0; i < mediaSizeCount; i++) { |
| mMediaSizes.get(i).writeToParcel(parcel); |
| } |
| } |
| |
| private void readMediaSizes(Parcel parcel) { |
| final int mediaSizeCount = parcel.readInt(); |
| if (mediaSizeCount > 0 && mMediaSizes == null) { |
| mMediaSizes = new ArrayList<MediaSize>(); |
| } |
| for (int i = 0; i < mediaSizeCount; i++) { |
| mMediaSizes.add(MediaSize.createFromParcel(parcel)); |
| } |
| } |
| |
| private void writeResolutions(Parcel parcel) { |
| if (mResolutions == null) { |
| parcel.writeInt(0); |
| return; |
| } |
| final int resolutionCount = mResolutions.size(); |
| parcel.writeInt(resolutionCount); |
| for (int i = 0; i < resolutionCount; i++) { |
| mResolutions.get(i).writeToParcel(parcel); |
| } |
| } |
| |
| private void readResolutions(Parcel parcel) { |
| final int resolutionCount = parcel.readInt(); |
| if (resolutionCount > 0 && mResolutions == null) { |
| mResolutions = new ArrayList<Resolution>(); |
| } |
| for (int i = 0; i < resolutionCount; i++) { |
| mResolutions.add(Resolution.createFromParcel(parcel)); |
| } |
| } |
| |
| private void writeMargins(Margins margins, Parcel parcel) { |
| if (margins == null) { |
| parcel.writeInt(0); |
| } else { |
| parcel.writeInt(1); |
| margins.writeToParcel(parcel); |
| } |
| } |
| |
| private Margins readMargins(Parcel parcel) { |
| return (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null; |
| } |
| |
| private void readDefaults(Parcel parcel) { |
| final int defaultCount = parcel.readInt(); |
| for (int i = 0; i < defaultCount; i++) { |
| mDefaults[i] = parcel.readInt(); |
| } |
| } |
| |
| private void writeDefaults(Parcel parcel) { |
| final int defaultCount = mDefaults.length; |
| parcel.writeInt(defaultCount); |
| for (int i = 0; i < defaultCount; i++) { |
| parcel.writeInt(mDefaults[i]); |
| } |
| } |
| |
| /** |
| * Builder for creating of a {@link PrinterCapabilitiesInfo}. This class is |
| * responsible to enforce that all required attributes have at least one |
| * default value. In other words, this class creates only well-formed {@link |
| * PrinterCapabilitiesInfo}s. |
| * <p> |
| * Look at the individual methods for a reference whether a property is |
| * required or if it is optional. |
| * </p> |
| */ |
| public static final class Builder { |
| private final PrinterCapabilitiesInfo mPrototype; |
| |
| /** |
| * Creates a new instance. |
| * |
| * @param printerId The printer id. Cannot be <code>null</code>. |
| * |
| * @throws IllegalArgumentException If the printer id is <code>null</code>. |
| */ |
| public Builder(@NonNull PrinterId printerId) { |
| if (printerId == null) { |
| throw new IllegalArgumentException("printerId cannot be null."); |
| } |
| mPrototype = new PrinterCapabilitiesInfo(); |
| } |
| |
| /** |
| * Adds a supported media size. |
| * <p> |
| * <strong>Required:</strong> Yes |
| * </p> |
| * |
| * @param mediaSize A media size. |
| * @param isDefault Whether this is the default. |
| * @return This builder. |
| * @throws IllegalArgumentException If set as default and there |
| * is already a default. |
| * |
| * @see PrintAttributes.MediaSize |
| */ |
| public @NonNull Builder addMediaSize(@NonNull MediaSize mediaSize, boolean isDefault) { |
| if (mPrototype.mMediaSizes == null) { |
| mPrototype.mMediaSizes = new ArrayList<MediaSize>(); |
| } |
| final int insertionIndex = mPrototype.mMediaSizes.size(); |
| mPrototype.mMediaSizes.add(mediaSize); |
| if (isDefault) { |
| throwIfDefaultAlreadySpecified(PROPERTY_MEDIA_SIZE); |
| mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] = insertionIndex; |
| } |
| return this; |
| } |
| |
| /** |
| * Adds a supported resolution. |
| * <p> |
| * <strong>Required:</strong> Yes |
| * </p> |
| * |
| * @param resolution A resolution. |
| * @param isDefault Whether this is the default. |
| * @return This builder. |
| * |
| * @throws IllegalArgumentException If set as default and there |
| * is already a default. |
| * |
| * @see PrintAttributes.Resolution |
| */ |
| public @NonNull Builder addResolution(@NonNull Resolution resolution, boolean isDefault) { |
| if (mPrototype.mResolutions == null) { |
| mPrototype.mResolutions = new ArrayList<Resolution>(); |
| } |
| final int insertionIndex = mPrototype.mResolutions.size(); |
| mPrototype.mResolutions.add(resolution); |
| if (isDefault) { |
| throwIfDefaultAlreadySpecified(PROPERTY_RESOLUTION); |
| mPrototype.mDefaults[PROPERTY_RESOLUTION] = insertionIndex; |
| } |
| return this; |
| } |
| |
| /** |
| * Sets the minimal margins. These are the minimal margins |
| * the printer physically supports. |
| * |
| * <p> |
| * <strong>Required:</strong> Yes |
| * </p> |
| * |
| * @param margins The margins. |
| * @return This builder. |
| * |
| * @throws IllegalArgumentException If margins are <code>null</code>. |
| * |
| * @see PrintAttributes.Margins |
| */ |
| public @NonNull Builder setMinMargins(@NonNull Margins margins) { |
| if (margins == null) { |
| throw new IllegalArgumentException("margins cannot be null"); |
| } |
| mPrototype.mMinMargins = margins; |
| return this; |
| } |
| |
| /** |
| * Sets the color modes. |
| * <p> |
| * <strong>Required:</strong> Yes |
| * </p> |
| * |
| * @param colorModes The color mode bit mask. |
| * @param defaultColorMode The default color mode. |
| * @return This builder. |
| * <p> |
| * <strong>Note:</strong> On platform version 19 (Kitkat) specifying |
| * only PrintAttributes#COLOR_MODE_MONOCHROME leads to a print spooler |
| * crash. Hence, you should declare either both color modes or |
| * PrintAttributes#COLOR_MODE_COLOR. |
| * </p> |
| * |
| * @throws IllegalArgumentException If color modes contains an invalid |
| * mode bit or if the default color mode is invalid. |
| * |
| * @see PrintAttributes#COLOR_MODE_COLOR |
| * @see PrintAttributes#COLOR_MODE_MONOCHROME |
| */ |
| public @NonNull Builder setColorModes(@ColorMode int colorModes, |
| @ColorMode int defaultColorMode) { |
| enforceValidMask(colorModes, |
| (currentMode) -> PrintAttributes.enforceValidColorMode(currentMode)); |
| PrintAttributes.enforceValidColorMode(defaultColorMode); |
| mPrototype.mColorModes = colorModes; |
| mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode; |
| return this; |
| } |
| |
| /** |
| * Sets the duplex modes. |
| * <p> |
| * <strong>Required:</strong> No |
| * </p> |
| * |
| * @param duplexModes The duplex mode bit mask. |
| * @param defaultDuplexMode The default duplex mode. |
| * @return This builder. |
| * |
| * @throws IllegalArgumentException If duplex modes contains an invalid |
| * mode bit or if the default duplex mode is invalid. |
| * |
| * @see PrintAttributes#DUPLEX_MODE_NONE |
| * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE |
| * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE |
| */ |
| public @NonNull Builder setDuplexModes(@DuplexMode int duplexModes, |
| @DuplexMode int defaultDuplexMode) { |
| enforceValidMask(duplexModes, |
| (currentMode) -> PrintAttributes.enforceValidDuplexMode(currentMode)); |
| PrintAttributes.enforceValidDuplexMode(defaultDuplexMode); |
| mPrototype.mDuplexModes = duplexModes; |
| mPrototype.mDefaults[PROPERTY_DUPLEX_MODE] = defaultDuplexMode; |
| return this; |
| } |
| |
| /** |
| * Crates a new {@link PrinterCapabilitiesInfo} enforcing that all |
| * required properties have been specified. See individual methods |
| * in this class for reference about required attributes. |
| * <p> |
| * <strong>Note:</strong> If you do not add supported duplex modes, |
| * {@link android.print.PrintAttributes#DUPLEX_MODE_NONE} will set |
| * as the only supported mode and also as the default duplex mode. |
| * </p> |
| * |
| * @return A new {@link PrinterCapabilitiesInfo}. |
| * |
| * @throws IllegalStateException If a required attribute was not specified. |
| */ |
| public @NonNull PrinterCapabilitiesInfo build() { |
| if (mPrototype.mMediaSizes == null || mPrototype.mMediaSizes.isEmpty()) { |
| throw new IllegalStateException("No media size specified."); |
| } |
| if (mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] == DEFAULT_UNDEFINED) { |
| throw new IllegalStateException("No default media size specified."); |
| } |
| if (mPrototype.mResolutions == null || mPrototype.mResolutions.isEmpty()) { |
| throw new IllegalStateException("No resolution specified."); |
| } |
| if (mPrototype.mDefaults[PROPERTY_RESOLUTION] == DEFAULT_UNDEFINED) { |
| throw new IllegalStateException("No default resolution specified."); |
| } |
| if (mPrototype.mColorModes == 0) { |
| throw new IllegalStateException("No color mode specified."); |
| } |
| if (mPrototype.mDefaults[PROPERTY_COLOR_MODE] == DEFAULT_UNDEFINED) { |
| throw new IllegalStateException("No default color mode specified."); |
| } |
| if (mPrototype.mDuplexModes == 0) { |
| setDuplexModes(PrintAttributes.DUPLEX_MODE_NONE, |
| PrintAttributes.DUPLEX_MODE_NONE); |
| } |
| if (mPrototype.mMinMargins == null) { |
| throw new IllegalArgumentException("margins cannot be null"); |
| } |
| return mPrototype; |
| } |
| |
| private void throwIfDefaultAlreadySpecified(int propertyIndex) { |
| if (mPrototype.mDefaults[propertyIndex] != DEFAULT_UNDEFINED) { |
| throw new IllegalArgumentException("Default already specified."); |
| } |
| } |
| } |
| |
| public static final @android.annotation.NonNull Parcelable.Creator<PrinterCapabilitiesInfo> CREATOR = |
| new Parcelable.Creator<PrinterCapabilitiesInfo>() { |
| @Override |
| public PrinterCapabilitiesInfo createFromParcel(Parcel parcel) { |
| return new PrinterCapabilitiesInfo(parcel); |
| } |
| |
| @Override |
| public PrinterCapabilitiesInfo[] newArray(int size) { |
| return new PrinterCapabilitiesInfo[size]; |
| } |
| }; |
| } |