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.DrawableRes; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.TestApi; 24 import android.app.PendingIntent; 25 import android.content.Context; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.PackageInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.graphics.drawable.Drawable; 31 import android.graphics.drawable.Icon; 32 import android.os.Parcel; 33 import android.os.Parcelable; 34 import android.text.TextUtils; 35 36 import com.android.internal.util.Preconditions; 37 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 40 41 /** 42 * This class represents the description of a printer. Instances of 43 * this class are created by print services to report to the system 44 * the printers they manage. The information of this class has two 45 * major components, printer properties such as name, id, status, 46 * description and printer capabilities which describe the various 47 * print modes a printer supports such as media sizes, margins, etc. 48 * <p> 49 * Once {@link PrinterInfo.Builder#build() built} the objects are immutable. 50 * </p> 51 */ 52 public final class PrinterInfo implements Parcelable { 53 54 /** @hide */ 55 @IntDef({ 56 STATUS_IDLE, STATUS_BUSY, STATUS_UNAVAILABLE 57 }) 58 @Retention(RetentionPolicy.SOURCE) 59 public @interface Status { 60 } 61 /** Printer status: the printer is idle and ready to print. */ 62 public static final int STATUS_IDLE = 1; 63 64 /** Printer status: the printer is busy printing. */ 65 public static final int STATUS_BUSY = 2; 66 67 /** Printer status: the printer is not available. */ 68 public static final int STATUS_UNAVAILABLE = 3; 69 70 private final @NonNull PrinterId mId; 71 72 /** Resource inside the printer's services's package to be used as an icon */ 73 private final int mIconResourceId; 74 75 /** If a custom icon can be loaded for the printer */ 76 private final boolean mHasCustomPrinterIcon; 77 78 /** The generation of the icon in the cache. */ 79 private final int mCustomPrinterIconGen; 80 81 /** Intent that launches the activity showing more information about the printer. */ 82 private final @Nullable PendingIntent mInfoIntent; 83 84 private final @NonNull String mName; 85 86 private final @Status int mStatus; 87 88 private final @Nullable String mDescription; 89 90 private final @Nullable PrinterCapabilitiesInfo mCapabilities; 91 92 private PrinterInfo(@NonNull PrinterId printerId, @NonNull String name, @Status int status, 93 int iconResourceId, boolean hasCustomPrinterIcon, String description, 94 PendingIntent infoIntent, PrinterCapabilitiesInfo capabilities, 95 int customPrinterIconGen) { 96 mId = printerId; 97 mName = name; 98 mStatus = status; 99 mIconResourceId = iconResourceId; 100 mHasCustomPrinterIcon = hasCustomPrinterIcon; 101 mDescription = description; 102 mInfoIntent = infoIntent; 103 mCapabilities = capabilities; 104 mCustomPrinterIconGen = customPrinterIconGen; 105 } 106 107 /** 108 * Get the globally unique printer id. 109 * 110 * @return The printer id. 111 */ 112 public @NonNull PrinterId getId() { 113 return mId; 114 } 115 116 /** 117 * Get the icon to be used for this printer. If no per printer icon is available, the printer's 118 * service's icon is returned. If the printer has a custom icon this icon might get requested 119 * asynchronously. Once the icon is loaded the discovery sessions will be notified that the 120 * printer changed. 121 * 122 * @param context The context that will be using the icons 123 * @return The icon to be used for the printer or null if no icon could be found. 124 * @hide 125 */ 126 @TestApi 127 public @Nullable Drawable loadIcon(@NonNull Context context) { 128 Drawable drawable = null; 129 PackageManager packageManager = context.getPackageManager(); 130 131 if (mHasCustomPrinterIcon) { 132 PrintManager printManager = (PrintManager) context 133 .getSystemService(Context.PRINT_SERVICE); 134 135 Icon icon = printManager.getCustomPrinterIcon(mId); 136 137 if (icon != null) { 138 drawable = icon.loadDrawable(context); 139 } 140 } 141 142 if (drawable == null) { 143 try { 144 String packageName = mId.getServiceName().getPackageName(); 145 PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0); 146 ApplicationInfo appInfo = packageInfo.applicationInfo; 147 148 // If no custom icon is available, try the icon from the resources 149 if (mIconResourceId != 0) { 150 drawable = packageManager.getDrawable(packageName, mIconResourceId, appInfo); 151 } 152 153 // Fall back to the printer's service's icon if no per printer icon could be found 154 if (drawable == null) { 155 drawable = appInfo.loadIcon(packageManager); 156 } 157 } catch (NameNotFoundException e) { 158 } 159 } 160 161 return drawable; 162 } 163 164 /** 165 * Get the printer name. 166 * 167 * @return The printer name. 168 */ 169 public @NonNull String getName() { 170 return mName; 171 } 172 173 /** 174 * Gets the printer status. 175 * 176 * @return The status. 177 * 178 * @see #STATUS_BUSY 179 * @see #STATUS_IDLE 180 * @see #STATUS_UNAVAILABLE 181 */ 182 public @Status int getStatus() { 183 return mStatus; 184 } 185 186 /** 187 * Gets the printer description. 188 * 189 * @return The description. 190 */ 191 public @Nullable String getDescription() { 192 return mDescription; 193 } 194 195 /** 196 * Get the {@link PendingIntent} that launches the activity showing more information about the 197 * printer. 198 * 199 * @return the {@link PendingIntent} that launches the activity showing more information about 200 * the printer or null if it is not configured 201 * @hide 202 */ 203 public @Nullable PendingIntent getInfoIntent() { 204 return mInfoIntent; 205 } 206 207 /** 208 * Gets the printer capabilities. 209 * 210 * @return The capabilities. 211 */ 212 public @Nullable PrinterCapabilitiesInfo getCapabilities() { 213 return mCapabilities; 214 } 215 216 /** 217 * Check if printerId is valid. 218 * 219 * @param printerId The printerId that might be valid 220 * @return The valid printerId 221 * @throws IllegalArgumentException if printerId is not valid. 222 */ 223 private static @NonNull PrinterId checkPrinterId(PrinterId printerId) { 224 return Preconditions.checkNotNull(printerId, "printerId cannot be null."); 225 } 226 227 /** 228 * Check if status is valid. 229 * 230 * @param status The status that might be valid 231 * @return The valid status 232 * @throws IllegalArgumentException if status is not valid. 233 */ 234 private static @Status int checkStatus(int status) { 235 if (!(status == STATUS_IDLE 236 || status == STATUS_BUSY 237 || status == STATUS_UNAVAILABLE)) { 238 throw new IllegalArgumentException("status is invalid."); 239 } 240 241 return status; 242 } 243 244 /** 245 * Check if name is valid. 246 * 247 * @param name The name that might be valid 248 * @return The valid name 249 * @throws IllegalArgumentException if name is not valid. 250 */ 251 private static @NonNull String checkName(String name) { 252 return Preconditions.checkStringNotEmpty(name, "name cannot be empty."); 253 } 254 255 private PrinterInfo(Parcel parcel) { 256 // mName can be null due to unchecked set in Builder.setName and status can be invalid 257 // due to unchecked set in Builder.setStatus, hence we can only check mId for a valid state 258 mId = checkPrinterId((PrinterId) parcel.readParcelable(null)); 259 mName = checkName(parcel.readString()); 260 mStatus = checkStatus(parcel.readInt()); 261 mDescription = parcel.readString(); 262 mCapabilities = parcel.readParcelable(null); 263 mIconResourceId = parcel.readInt(); 264 mHasCustomPrinterIcon = parcel.readByte() != 0; 265 mCustomPrinterIconGen = parcel.readInt(); 266 mInfoIntent = parcel.readParcelable(null); 267 } 268 269 @Override 270 public int describeContents() { 271 return 0; 272 } 273 274 @Override 275 public void writeToParcel(Parcel parcel, int flags) { 276 parcel.writeParcelable(mId, flags); 277 parcel.writeString(mName); 278 parcel.writeInt(mStatus); 279 parcel.writeString(mDescription); 280 parcel.writeParcelable(mCapabilities, flags); 281 parcel.writeInt(mIconResourceId); 282 parcel.writeByte((byte) (mHasCustomPrinterIcon ? 1 : 0)); 283 parcel.writeInt(mCustomPrinterIconGen); 284 parcel.writeParcelable(mInfoIntent, flags); 285 } 286 287 @Override 288 public int hashCode() { 289 final int prime = 31; 290 int result = 1; 291 result = prime * result + mId.hashCode(); 292 result = prime * result + mName.hashCode(); 293 result = prime * result + mStatus; 294 result = prime * result + ((mDescription != null) ? mDescription.hashCode() : 0); 295 result = prime * result + ((mCapabilities != null) ? mCapabilities.hashCode() : 0); 296 result = prime * result + mIconResourceId; 297 result = prime * result + (mHasCustomPrinterIcon ? 1 : 0); 298 result = prime * result + mCustomPrinterIconGen; 299 result = prime * result + ((mInfoIntent != null) ? mInfoIntent.hashCode() : 0); 300 return result; 301 } 302 303 /** 304 * Compare two {@link PrinterInfo printerInfos} in all aspects beside being null and the 305 * {@link #mStatus}. 306 * 307 * @param other the other {@link PrinterInfo} 308 * @return true iff the infos are equivalent 309 * @hide 310 */ 311 public boolean equalsIgnoringStatus(PrinterInfo other) { 312 if (!mId.equals(other.mId)) { 313 return false; 314 } 315 if (!mName.equals(other.mName)) { 316 return false; 317 } 318 if (!TextUtils.equals(mDescription, other.mDescription)) { 319 return false; 320 } 321 if (mCapabilities == null) { 322 if (other.mCapabilities != null) { 323 return false; 324 } 325 } else if (!mCapabilities.equals(other.mCapabilities)) { 326 return false; 327 } 328 if (mIconResourceId != other.mIconResourceId) { 329 return false; 330 } 331 if (mHasCustomPrinterIcon != other.mHasCustomPrinterIcon) { 332 return false; 333 } 334 if (mCustomPrinterIconGen != other.mCustomPrinterIconGen) { 335 return false; 336 } 337 if (mInfoIntent == null) { 338 if (other.mInfoIntent != null) { 339 return false; 340 } 341 } else if (!mInfoIntent.equals(other.mInfoIntent)) { 342 return false; 343 } 344 return true; 345 } 346 347 @Override 348 public boolean equals(Object obj) { 349 if (this == obj) { 350 return true; 351 } 352 if (obj == null) { 353 return false; 354 } 355 if (getClass() != obj.getClass()) { 356 return false; 357 } 358 PrinterInfo other = (PrinterInfo) obj; 359 if (!equalsIgnoringStatus(other)) { 360 return false; 361 } 362 if (mStatus != other.mStatus) { 363 return false; 364 } 365 return true; 366 } 367 368 @Override 369 public String toString() { 370 StringBuilder builder = new StringBuilder(); 371 builder.append("PrinterInfo{"); 372 builder.append("id=").append(mId); 373 builder.append(", name=").append(mName); 374 builder.append(", status=").append(mStatus); 375 builder.append(", description=").append(mDescription); 376 builder.append(", capabilities=").append(mCapabilities); 377 builder.append(", iconResId=").append(mIconResourceId); 378 builder.append(", hasCustomPrinterIcon=").append(mHasCustomPrinterIcon); 379 builder.append(", customPrinterIconGen=").append(mCustomPrinterIconGen); 380 builder.append(", infoIntent=").append(mInfoIntent); 381 builder.append("\"}"); 382 return builder.toString(); 383 } 384 385 /** 386 * Builder for creating of a {@link PrinterInfo}. 387 */ 388 public static final class Builder { 389 private @NonNull PrinterId mPrinterId; 390 private @NonNull String mName; 391 private @Status int mStatus; 392 private int mIconResourceId; 393 private boolean mHasCustomPrinterIcon; 394 private String mDescription; 395 private PendingIntent mInfoIntent; 396 private PrinterCapabilitiesInfo mCapabilities; 397 private int mCustomPrinterIconGen; 398 399 /** 400 * Constructor. 401 * 402 * @param printerId The printer id. Cannot be null. 403 * @param name The printer name. Cannot be empty. 404 * @param status The printer status. Must be a valid status. 405 * @throws IllegalArgumentException If the printer id is null, or the 406 * printer name is empty or the status is not a valid one. 407 */ 408 public Builder(@NonNull PrinterId printerId, @NonNull String name, @Status int status) { 409 mPrinterId = checkPrinterId(printerId); 410 mName = checkName(name); 411 mStatus = checkStatus(status); 412 } 413 414 /** 415 * Constructor. 416 * 417 * @param other Other info from which to start building. 418 */ 419 public Builder(@NonNull PrinterInfo other) { 420 mPrinterId = other.mId; 421 mName = other.mName; 422 mStatus = other.mStatus; 423 mIconResourceId = other.mIconResourceId; 424 mHasCustomPrinterIcon = other.mHasCustomPrinterIcon; 425 mDescription = other.mDescription; 426 mInfoIntent = other.mInfoIntent; 427 mCapabilities = other.mCapabilities; 428 mCustomPrinterIconGen = other.mCustomPrinterIconGen; 429 } 430 431 /** 432 * Sets the printer status. 433 * 434 * @param status The status. 435 * @return This builder. 436 * @see PrinterInfo#STATUS_IDLE 437 * @see PrinterInfo#STATUS_BUSY 438 * @see PrinterInfo#STATUS_UNAVAILABLE 439 */ 440 public @NonNull Builder setStatus(@Status int status) { 441 mStatus = checkStatus(status); 442 return this; 443 } 444 445 /** 446 * Set a drawable resource as icon for this printer. If no icon is set the printer's 447 * service's icon is used for the printer. 448 * 449 * @param iconResourceId The resource ID of the icon. 450 * @return This builder. 451 * @see PrinterInfo.Builder#setHasCustomPrinterIcon 452 */ 453 public @NonNull Builder setIconResourceId(@DrawableRes int iconResourceId) { 454 mIconResourceId = Preconditions.checkArgumentNonnegative(iconResourceId, 455 "iconResourceId can't be negative"); 456 return this; 457 } 458 459 /** 460 * Declares that the print service can load a custom per printer's icon. If both 461 * {@link PrinterInfo.Builder#setIconResourceId} and a custom icon are set the resource icon 462 * is shown while the custom icon loads but then the custom icon is used. If 463 * {@link PrinterInfo.Builder#setIconResourceId} is not set the printer's service's icon is 464 * shown while loading. 465 * <p> 466 * The icon is requested asynchronously and only when needed via 467 * {@link android.printservice.PrinterDiscoverySession#onRequestCustomPrinterIcon}. 468 * </p> 469 * 470 * @param hasCustomPrinterIcon If the printer has a custom icon or not. 471 * 472 * @return This builder. 473 */ 474 public @NonNull Builder setHasCustomPrinterIcon(boolean hasCustomPrinterIcon) { 475 mHasCustomPrinterIcon = hasCustomPrinterIcon; 476 return this; 477 } 478 479 /** 480 * Sets the <strong>localized</strong> printer name which 481 * is shown to the user 482 * 483 * @param name The name. 484 * @return This builder. 485 */ 486 public @NonNull Builder setName(@NonNull String name) { 487 mName = checkName(name); 488 return this; 489 } 490 491 /** 492 * Sets the <strong>localized</strong> printer description 493 * which is shown to the user 494 * 495 * @param description The description. 496 * @return This builder. 497 */ 498 public @NonNull Builder setDescription(@NonNull String description) { 499 mDescription = description; 500 return this; 501 } 502 503 /** 504 * Sets the {@link PendingIntent} that launches an activity showing more information about 505 * the printer. 506 * 507 * @param infoIntent The {@link PendingIntent intent}. 508 * @return This builder. 509 */ 510 public @NonNull Builder setInfoIntent(@NonNull PendingIntent infoIntent) { 511 mInfoIntent = infoIntent; 512 return this; 513 } 514 515 /** 516 * Sets the printer capabilities. 517 * 518 * @param capabilities The capabilities. 519 * @return This builder. 520 */ 521 public @NonNull Builder setCapabilities(@NonNull PrinterCapabilitiesInfo capabilities) { 522 mCapabilities = capabilities; 523 return this; 524 } 525 526 /** 527 * Creates a new {@link PrinterInfo}. 528 * 529 * @return A new {@link PrinterInfo}. 530 */ 531 public @NonNull PrinterInfo build() { 532 return new PrinterInfo(mPrinterId, mName, mStatus, mIconResourceId, 533 mHasCustomPrinterIcon, mDescription, mInfoIntent, mCapabilities, 534 mCustomPrinterIconGen); 535 } 536 537 /** 538 * Increments the generation number of the custom printer icon. As the {@link PrinterInfo} 539 * does not match the previous one anymore, users of the {@link PrinterInfo} will reload the 540 * icon if needed. 541 * 542 * @return This builder. 543 * @hide 544 */ 545 public @NonNull Builder incCustomPrinterIconGen() { 546 mCustomPrinterIconGen++; 547 return this; 548 } 549 } 550 551 public static final Parcelable.Creator<PrinterInfo> CREATOR = 552 new Parcelable.Creator<PrinterInfo>() { 553 @Override 554 public PrinterInfo createFromParcel(Parcel parcel) { 555 return new PrinterInfo(parcel); 556 } 557 558 @Override 559 public PrinterInfo[] newArray(int size) { 560 return new PrinterInfo[size]; 561 } 562 }; 563 } 564