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