PrinterCapabilitiesInfo.java revision c43639c3067dda5df189fb3cbf14f256c17e677d
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 17package android.print; 18 19import android.annotation.NonNull; 20import android.os.Parcel; 21import android.os.Parcelable; 22import android.print.PrintAttributes.ColorMode; 23import android.print.PrintAttributes.DuplexMode; 24import android.print.PrintAttributes.Margins; 25import android.print.PrintAttributes.MediaSize; 26import android.print.PrintAttributes.Resolution; 27 28import java.util.ArrayList; 29import java.util.Arrays; 30import java.util.Collections; 31import java.util.List; 32 33/** 34 * This class represents the capabilities of a printer. Instances 35 * of this class are created by a print service to report the 36 * capabilities of a printer it manages. The capabilities of a 37 * printer specify how it can print content. For example, what 38 * are the media sizes supported by the printer, what are the 39 * minimal margins of the printer based on its technical design, 40 * etc. 41 */ 42public final class PrinterCapabilitiesInfo implements Parcelable { 43 /** 44 * Undefined default value. 45 * 46 * @hide 47 */ 48 public static final int DEFAULT_UNDEFINED = -1; 49 50 private static final int PROPERTY_MEDIA_SIZE = 0; 51 private static final int PROPERTY_RESOLUTION = 1; 52 private static final int PROPERTY_COLOR_MODE = 2; 53 private static final int PROPERTY_DUPLEX_MODE = 3; 54 private static final int PROPERTY_COUNT = 4; 55 56 private static final Margins DEFAULT_MARGINS = new Margins(0, 0, 0, 0); 57 58 private Margins mMinMargins = DEFAULT_MARGINS; 59 private List<MediaSize> mMediaSizes; 60 private List<Resolution> mResolutions; 61 62 private int mColorModes; 63 private int mDuplexModes; 64 65 private final int[] mDefaults = new int[PROPERTY_COUNT]; 66 67 /** 68 * @hide 69 */ 70 public PrinterCapabilitiesInfo() { 71 Arrays.fill(mDefaults, DEFAULT_UNDEFINED); 72 } 73 74 /** 75 * @hide 76 */ 77 public PrinterCapabilitiesInfo(PrinterCapabilitiesInfo prototype) { 78 copyFrom(prototype); 79 } 80 81 /** 82 * @hide 83 */ 84 public void copyFrom(PrinterCapabilitiesInfo other) { 85 if (this == other) { 86 return; 87 } 88 89 mMinMargins = other.mMinMargins; 90 91 if (other.mMediaSizes != null) { 92 if (mMediaSizes != null) { 93 mMediaSizes.clear(); 94 mMediaSizes.addAll(other.mMediaSizes); 95 } else { 96 mMediaSizes = new ArrayList<MediaSize>(other.mMediaSizes); 97 } 98 } else { 99 mMediaSizes = null; 100 } 101 102 if (other.mResolutions != null) { 103 if (mResolutions != null) { 104 mResolutions.clear(); 105 mResolutions.addAll(other.mResolutions); 106 } else { 107 mResolutions = new ArrayList<Resolution>(other.mResolutions); 108 } 109 } else { 110 mResolutions = null; 111 } 112 113 mColorModes = other.mColorModes; 114 mDuplexModes = other.mDuplexModes; 115 116 final int defaultCount = other.mDefaults.length; 117 for (int i = 0; i < defaultCount; i++) { 118 mDefaults[i] = other.mDefaults[i]; 119 } 120 } 121 122 /** 123 * Gets the supported media sizes. 124 * 125 * @return The media sizes. 126 */ 127 public @NonNull List<MediaSize> getMediaSizes() { 128 return Collections.unmodifiableList(mMediaSizes); 129 } 130 131 /** 132 * Gets the supported resolutions. 133 * 134 * @return The resolutions. 135 */ 136 public @NonNull List<Resolution> getResolutions() { 137 return Collections.unmodifiableList(mResolutions); 138 } 139 140 /** 141 * Gets the minimal margins. These are the minimal margins 142 * the printer physically supports. 143 * 144 * @return The minimal margins. 145 */ 146 public @NonNull Margins getMinMargins() { 147 return mMinMargins; 148 } 149 150 /** 151 * Gets the bit mask of supported color modes. 152 * 153 * @return The bit mask of supported color modes. 154 * 155 * @see PrintAttributes#COLOR_MODE_COLOR 156 * @see PrintAttributes#COLOR_MODE_MONOCHROME 157 */ 158 public @ColorMode int getColorModes() { 159 return mColorModes; 160 } 161 162 /** 163 * Gets the bit mask of supported duplex modes. 164 * 165 * @return The bit mask of supported duplex modes. 166 * 167 * @see PrintAttributes#DUPLEX_MODE_NONE 168 * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE 169 * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE 170 */ 171 public @DuplexMode int getDuplexModes() { 172 return mDuplexModes; 173 } 174 175 /** 176 * Gets the default print attributes. 177 * 178 * @return The default attributes. 179 */ 180 public @NonNull PrintAttributes getDefaults() { 181 PrintAttributes.Builder builder = new PrintAttributes.Builder(); 182 183 builder.setMinMargins(mMinMargins); 184 185 final int mediaSizeIndex = mDefaults[PROPERTY_MEDIA_SIZE]; 186 if (mediaSizeIndex >= 0) { 187 builder.setMediaSize(mMediaSizes.get(mediaSizeIndex)); 188 } 189 190 final int resolutionIndex = mDefaults[PROPERTY_RESOLUTION]; 191 if (resolutionIndex >= 0) { 192 builder.setResolution(mResolutions.get(resolutionIndex)); 193 } 194 195 final int colorMode = mDefaults[PROPERTY_COLOR_MODE]; 196 if (colorMode > 0) { 197 builder.setColorMode(colorMode); 198 } 199 200 final int duplexMode = mDefaults[PROPERTY_DUPLEX_MODE]; 201 if (duplexMode > 0) { 202 builder.setDuplexMode(duplexMode); 203 } 204 205 return builder.build(); 206 } 207 208 private PrinterCapabilitiesInfo(Parcel parcel) { 209 mMinMargins = readMargins(parcel); 210 readMediaSizes(parcel); 211 readResolutions(parcel); 212 213 mColorModes = parcel.readInt(); 214 mDuplexModes = parcel.readInt(); 215 216 readDefaults(parcel); 217 } 218 219 @Override 220 public int describeContents() { 221 return 0; 222 } 223 224 @Override 225 public void writeToParcel(Parcel parcel, int flags) { 226 writeMargins(mMinMargins, parcel); 227 writeMediaSizes(parcel); 228 writeResolutions(parcel); 229 230 parcel.writeInt(mColorModes); 231 parcel.writeInt(mDuplexModes); 232 233 writeDefaults(parcel); 234 } 235 236 @Override 237 public int hashCode() { 238 final int prime = 31; 239 int result = 1; 240 result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode()); 241 result = prime * result + ((mMediaSizes == null) ? 0 : mMediaSizes.hashCode()); 242 result = prime * result + ((mResolutions == null) ? 0 : mResolutions.hashCode()); 243 result = prime * result + mColorModes; 244 result = prime * result + mDuplexModes; 245 result = prime * result + Arrays.hashCode(mDefaults); 246 return result; 247 } 248 249 @Override 250 public boolean equals(Object obj) { 251 if (this == obj) { 252 return true; 253 } 254 if (obj == null) { 255 return false; 256 } 257 if (getClass() != obj.getClass()) { 258 return false; 259 } 260 PrinterCapabilitiesInfo other = (PrinterCapabilitiesInfo) obj; 261 if (mMinMargins == null) { 262 if (other.mMinMargins != null) { 263 return false; 264 } 265 } else if (!mMinMargins.equals(other.mMinMargins)) { 266 return false; 267 } 268 if (mMediaSizes == null) { 269 if (other.mMediaSizes != null) { 270 return false; 271 } 272 } else if (!mMediaSizes.equals(other.mMediaSizes)) { 273 return false; 274 } 275 if (mResolutions == null) { 276 if (other.mResolutions != null) { 277 return false; 278 } 279 } else if (!mResolutions.equals(other.mResolutions)) { 280 return false; 281 } 282 if (mColorModes != other.mColorModes) { 283 return false; 284 } 285 if (mDuplexModes != other.mDuplexModes) { 286 return false; 287 } 288 if (!Arrays.equals(mDefaults, other.mDefaults)) { 289 return false; 290 } 291 return true; 292 } 293 294 @Override 295 public String toString() { 296 StringBuilder builder = new StringBuilder(); 297 builder.append("PrinterInfo{"); 298 builder.append("minMargins=").append(mMinMargins); 299 builder.append(", mediaSizes=").append(mMediaSizes); 300 builder.append(", resolutions=").append(mResolutions); 301 builder.append(", colorModes=").append(colorModesToString()); 302 builder.append(", duplexModes=").append(duplexModesToString()); 303 builder.append("\"}"); 304 return builder.toString(); 305 } 306 307 private String colorModesToString() { 308 StringBuilder builder = new StringBuilder(); 309 builder.append('['); 310 int colorModes = mColorModes; 311 while (colorModes != 0) { 312 final int colorMode = 1 << Integer.numberOfTrailingZeros(colorModes); 313 colorModes &= ~colorMode; 314 if (builder.length() > 1) { 315 builder.append(", "); 316 } 317 builder.append(PrintAttributes.colorModeToString(colorMode)); 318 } 319 builder.append(']'); 320 return builder.toString(); 321 } 322 323 private String duplexModesToString() { 324 StringBuilder builder = new StringBuilder(); 325 builder.append('['); 326 int duplexModes = mDuplexModes; 327 while (duplexModes != 0) { 328 final int duplexMode = 1 << Integer.numberOfTrailingZeros(duplexModes); 329 duplexModes &= ~duplexMode; 330 if (builder.length() > 1) { 331 builder.append(", "); 332 } 333 builder.append(PrintAttributes.duplexModeToString(duplexMode)); 334 } 335 builder.append(']'); 336 return builder.toString(); 337 } 338 339 private void writeMediaSizes(Parcel parcel) { 340 if (mMediaSizes == null) { 341 parcel.writeInt(0); 342 return; 343 } 344 final int mediaSizeCount = mMediaSizes.size(); 345 parcel.writeInt(mediaSizeCount); 346 for (int i = 0; i < mediaSizeCount; i++) { 347 mMediaSizes.get(i).writeToParcel(parcel); 348 } 349 } 350 351 private void readMediaSizes(Parcel parcel) { 352 final int mediaSizeCount = parcel.readInt(); 353 if (mediaSizeCount > 0 && mMediaSizes == null) { 354 mMediaSizes = new ArrayList<MediaSize>(); 355 } 356 for (int i = 0; i < mediaSizeCount; i++) { 357 mMediaSizes.add(MediaSize.createFromParcel(parcel)); 358 } 359 } 360 361 private void writeResolutions(Parcel parcel) { 362 if (mResolutions == null) { 363 parcel.writeInt(0); 364 return; 365 } 366 final int resolutionCount = mResolutions.size(); 367 parcel.writeInt(resolutionCount); 368 for (int i = 0; i < resolutionCount; i++) { 369 mResolutions.get(i).writeToParcel(parcel); 370 } 371 } 372 373 private void readResolutions(Parcel parcel) { 374 final int resolutionCount = parcel.readInt(); 375 if (resolutionCount > 0 && mResolutions == null) { 376 mResolutions = new ArrayList<Resolution>(); 377 } 378 for (int i = 0; i < resolutionCount; i++) { 379 mResolutions.add(Resolution.createFromParcel(parcel)); 380 } 381 } 382 383 private void writeMargins(Margins margins, Parcel parcel) { 384 if (margins == null) { 385 parcel.writeInt(0); 386 } else { 387 parcel.writeInt(1); 388 margins.writeToParcel(parcel); 389 } 390 } 391 392 private Margins readMargins(Parcel parcel) { 393 return (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null; 394 } 395 396 private void readDefaults(Parcel parcel) { 397 final int defaultCount = parcel.readInt(); 398 for (int i = 0; i < defaultCount; i++) { 399 mDefaults[i] = parcel.readInt(); 400 } 401 } 402 403 private void writeDefaults(Parcel parcel) { 404 final int defaultCount = mDefaults.length; 405 parcel.writeInt(defaultCount); 406 for (int i = 0; i < defaultCount; i++) { 407 parcel.writeInt(mDefaults[i]); 408 } 409 } 410 411 /** 412 * Builder for creating of a {@link PrinterCapabilitiesInfo}. This class is 413 * responsible to enforce that all required attributes have at least one 414 * default value. In other words, this class creates only well-formed {@link 415 * PrinterCapabilitiesInfo}s. 416 * <p> 417 * Look at the individual methods for a reference whether a property is 418 * required or if it is optional. 419 * </p> 420 */ 421 public static final class Builder { 422 private final PrinterCapabilitiesInfo mPrototype; 423 424 /** 425 * Creates a new instance. 426 * 427 * @param printerId The printer id. Cannot be <code>null</code>. 428 * 429 * @throws IllegalArgumentException If the printer id is <code>null</code>. 430 */ 431 public Builder(@NonNull PrinterId printerId) { 432 if (printerId == null) { 433 throw new IllegalArgumentException("printerId cannot be null."); 434 } 435 mPrototype = new PrinterCapabilitiesInfo(); 436 } 437 438 /** 439 * Adds a supported media size. 440 * <p> 441 * <strong>Required:</strong> Yes 442 * </p> 443 * 444 * @param mediaSize A media size. 445 * @param isDefault Whether this is the default. 446 * @return This builder. 447 * @throws IllegalArgumentException If set as default and there 448 * is already a default. 449 * 450 * @see PrintAttributes.MediaSize 451 */ 452 public @NonNull Builder addMediaSize(@NonNull MediaSize mediaSize, boolean isDefault) { 453 if (mPrototype.mMediaSizes == null) { 454 mPrototype.mMediaSizes = new ArrayList<MediaSize>(); 455 } 456 final int insertionIndex = mPrototype.mMediaSizes.size(); 457 mPrototype.mMediaSizes.add(mediaSize); 458 if (isDefault) { 459 throwIfDefaultAlreadySpecified(PROPERTY_MEDIA_SIZE); 460 mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] = insertionIndex; 461 } 462 return this; 463 } 464 465 /** 466 * Adds a supported resolution. 467 * <p> 468 * <strong>Required:</strong> Yes 469 * </p> 470 * 471 * @param resolution A resolution. 472 * @param isDefault Whether this is the default. 473 * @return This builder. 474 * 475 * @throws IllegalArgumentException If set as default and there 476 * is already a default. 477 * 478 * @see PrintAttributes.Resolution 479 */ 480 public @NonNull Builder addResolution(@NonNull Resolution resolution, boolean isDefault) { 481 if (mPrototype.mResolutions == null) { 482 mPrototype.mResolutions = new ArrayList<Resolution>(); 483 } 484 final int insertionIndex = mPrototype.mResolutions.size(); 485 mPrototype.mResolutions.add(resolution); 486 if (isDefault) { 487 throwIfDefaultAlreadySpecified(PROPERTY_RESOLUTION); 488 mPrototype.mDefaults[PROPERTY_RESOLUTION] = insertionIndex; 489 } 490 return this; 491 } 492 493 /** 494 * Sets the minimal margins. These are the minimal margins 495 * the printer physically supports. 496 * 497 * <p> 498 * <strong>Required:</strong> Yes 499 * </p> 500 * 501 * @param margins The margins. 502 * @return This builder. 503 * 504 * @throws IllegalArgumentException If margins are <code>null</code>. 505 * 506 * @see PrintAttributes.Margins 507 */ 508 public @NonNull Builder setMinMargins(@NonNull Margins margins) { 509 if (margins == null) { 510 throw new IllegalArgumentException("margins cannot be null"); 511 } 512 mPrototype.mMinMargins = margins; 513 return this; 514 } 515 516 /** 517 * Sets the color modes. 518 * <p> 519 * <strong>Required:</strong> Yes 520 * </p> 521 * 522 * @param colorModes The color mode bit mask. 523 * @param defaultColorMode The default color mode. 524 * @return This builder. 525 * <p> 526 * <strong>Note:</strong> On platform version 19 (Kitkat) specifying 527 * only PrintAttributes#COLOR_MODE_MONOCHROME leads to a print spooler 528 * crash. Hence, you should declare either both color modes or 529 * PrintAttributes#COLOR_MODE_COLOR. 530 * </p> 531 * 532 * @throws IllegalArgumentException If color modes contains an invalid 533 * mode bit or if the default color mode is invalid. 534 * 535 * @see PrintAttributes#COLOR_MODE_COLOR 536 * @see PrintAttributes#COLOR_MODE_MONOCHROME 537 */ 538 public @NonNull Builder setColorModes(@ColorMode int colorModes, 539 @ColorMode int defaultColorMode) { 540 int currentModes = colorModes; 541 while (currentModes > 0) { 542 final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes)); 543 currentModes &= ~currentMode; 544 PrintAttributes.enforceValidColorMode(currentMode); 545 } 546 PrintAttributes.enforceValidColorMode(defaultColorMode); 547 mPrototype.mColorModes = colorModes; 548 mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode; 549 return this; 550 } 551 552 /** 553 * Sets the duplex modes. 554 * <p> 555 * <strong>Required:</strong> No 556 * </p> 557 * 558 * @param duplexModes The duplex mode bit mask. 559 * @param defaultDuplexMode The default duplex mode. 560 * @return This builder. 561 * 562 * @throws IllegalArgumentException If duplex modes contains an invalid 563 * mode bit or if the default duplex mode is invalid. 564 * 565 * @see PrintAttributes#DUPLEX_MODE_NONE 566 * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE 567 * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE 568 */ 569 public @NonNull Builder setDuplexModes(@DuplexMode int duplexModes, 570 @DuplexMode int defaultDuplexMode) { 571 int currentModes = duplexModes; 572 while (currentModes > 0) { 573 final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes)); 574 currentModes &= ~currentMode; 575 PrintAttributes.enforceValidDuplexMode(currentMode); 576 } 577 PrintAttributes.enforceValidDuplexMode(defaultDuplexMode); 578 mPrototype.mDuplexModes = duplexModes; 579 mPrototype.mDefaults[PROPERTY_DUPLEX_MODE] = defaultDuplexMode; 580 return this; 581 } 582 583 /** 584 * Crates a new {@link PrinterCapabilitiesInfo} enforcing that all 585 * required properties have been specified. See individual methods 586 * in this class for reference about required attributes. 587 * <p> 588 * <strong>Note:</strong> If you do not add supported duplex modes, 589 * {@link android.print.PrintAttributes#DUPLEX_MODE_NONE} will set 590 * as the only supported mode and also as the default duplex mode. 591 * </p> 592 * 593 * @return A new {@link PrinterCapabilitiesInfo}. 594 * 595 * @throws IllegalStateException If a required attribute was not specified. 596 */ 597 public @NonNull PrinterCapabilitiesInfo build() { 598 if (mPrototype.mMediaSizes == null || mPrototype.mMediaSizes.isEmpty()) { 599 throw new IllegalStateException("No media size specified."); 600 } 601 if (mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] == DEFAULT_UNDEFINED) { 602 throw new IllegalStateException("No default media size specified."); 603 } 604 if (mPrototype.mResolutions == null || mPrototype.mResolutions.isEmpty()) { 605 throw new IllegalStateException("No resolution specified."); 606 } 607 if (mPrototype.mDefaults[PROPERTY_RESOLUTION] == DEFAULT_UNDEFINED) { 608 throw new IllegalStateException("No default resolution specified."); 609 } 610 if (mPrototype.mColorModes == 0) { 611 throw new IllegalStateException("No color mode specified."); 612 } 613 if (mPrototype.mDefaults[PROPERTY_COLOR_MODE] == DEFAULT_UNDEFINED) { 614 throw new IllegalStateException("No default color mode specified."); 615 } 616 if (mPrototype.mDuplexModes == 0) { 617 setDuplexModes(PrintAttributes.DUPLEX_MODE_NONE, 618 PrintAttributes.DUPLEX_MODE_NONE); 619 } 620 if (mPrototype.mMinMargins == null) { 621 throw new IllegalArgumentException("margins cannot be null"); 622 } 623 return mPrototype; 624 } 625 626 private void throwIfDefaultAlreadySpecified(int propertyIndex) { 627 if (mPrototype.mDefaults[propertyIndex] != DEFAULT_UNDEFINED) { 628 throw new IllegalArgumentException("Default already specified."); 629 } 630 } 631 } 632 633 public static final Parcelable.Creator<PrinterCapabilitiesInfo> CREATOR = 634 new Parcelable.Creator<PrinterCapabilitiesInfo>() { 635 @Override 636 public PrinterCapabilitiesInfo createFromParcel(Parcel parcel) { 637 return new PrinterCapabilitiesInfo(parcel); 638 } 639 640 @Override 641 public PrinterCapabilitiesInfo[] newArray(int size) { 642 return new PrinterCapabilitiesInfo[size]; 643 } 644 }; 645} 646