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