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