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