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.DrawableRes;
20import android.annotation.IntDef;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.annotation.TestApi;
24import android.app.PendingIntent;
25import android.content.Context;
26import android.content.pm.ApplicationInfo;
27import android.content.pm.PackageInfo;
28import android.content.pm.PackageManager;
29import android.content.pm.PackageManager.NameNotFoundException;
30import android.graphics.drawable.Drawable;
31import android.graphics.drawable.Icon;
32import android.os.Parcel;
33import android.os.Parcelable;
34import android.text.TextUtils;
35
36import com.android.internal.util.Preconditions;
37
38import java.lang.annotation.Retention;
39import 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 */
52public 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     * Check if the printer has a custom printer icon.
166     *
167     * @return {@code true} iff the printer has a custom printer icon.
168     *
169     * @hide
170     */
171    public boolean getHasCustomPrinterIcon() {
172        return mHasCustomPrinterIcon;
173    }
174
175    /**
176     * Get the printer name.
177     *
178     * @return The printer name.
179     */
180    public @NonNull String getName() {
181        return mName;
182    }
183
184    /**
185     * Gets the printer status.
186     *
187     * @return The status.
188     *
189     * @see #STATUS_BUSY
190     * @see #STATUS_IDLE
191     * @see #STATUS_UNAVAILABLE
192     */
193    public @Status int getStatus() {
194        return mStatus;
195    }
196
197    /**
198     * Gets the  printer description.
199     *
200     * @return The description.
201     */
202    public @Nullable String getDescription() {
203        return mDescription;
204    }
205
206    /**
207     * Get the {@link PendingIntent} that launches the activity showing more information about the
208     * printer.
209     *
210     * @return the {@link PendingIntent} that launches the activity showing more information about
211     *         the printer or null if it is not configured
212     * @hide
213     */
214    public @Nullable PendingIntent getInfoIntent() {
215        return mInfoIntent;
216    }
217
218    /**
219     * Gets the printer capabilities.
220     *
221     * @return The capabilities.
222     */
223    public @Nullable PrinterCapabilitiesInfo getCapabilities() {
224        return mCapabilities;
225    }
226
227    /**
228     * Check if printerId is valid.
229     *
230     * @param printerId The printerId that might be valid
231     * @return The valid printerId
232     * @throws IllegalArgumentException if printerId is not valid.
233     */
234    private static @NonNull PrinterId checkPrinterId(PrinterId printerId) {
235        return Preconditions.checkNotNull(printerId, "printerId cannot be null.");
236    }
237
238    /**
239     * Check if status is valid.
240     *
241     * @param status The status that might be valid
242     * @return The valid status
243     * @throws IllegalArgumentException if status is not valid.
244     */
245    private static @Status int checkStatus(int status) {
246        if (!(status == STATUS_IDLE
247                || status == STATUS_BUSY
248                || status == STATUS_UNAVAILABLE)) {
249            throw new IllegalArgumentException("status is invalid.");
250        }
251
252        return status;
253    }
254
255    /**
256     * Check if name is valid.
257     *
258     * @param name The name that might be valid
259     * @return The valid name
260     * @throws IllegalArgumentException if name is not valid.
261     */
262    private static @NonNull String checkName(String name) {
263        return Preconditions.checkStringNotEmpty(name, "name cannot be empty.");
264    }
265
266    private PrinterInfo(Parcel parcel) {
267        // mName can be null due to unchecked set in Builder.setName and status can be invalid
268        // due to unchecked set in Builder.setStatus, hence we can only check mId for a valid state
269        mId = checkPrinterId((PrinterId) parcel.readParcelable(null));
270        mName = checkName(parcel.readString());
271        mStatus = checkStatus(parcel.readInt());
272        mDescription = parcel.readString();
273        mCapabilities = parcel.readParcelable(null);
274        mIconResourceId = parcel.readInt();
275        mHasCustomPrinterIcon = parcel.readByte() != 0;
276        mCustomPrinterIconGen = parcel.readInt();
277        mInfoIntent = parcel.readParcelable(null);
278    }
279
280    @Override
281    public int describeContents() {
282        return 0;
283    }
284
285    @Override
286    public void writeToParcel(Parcel parcel, int flags) {
287        parcel.writeParcelable(mId, flags);
288        parcel.writeString(mName);
289        parcel.writeInt(mStatus);
290        parcel.writeString(mDescription);
291        parcel.writeParcelable(mCapabilities, flags);
292        parcel.writeInt(mIconResourceId);
293        parcel.writeByte((byte) (mHasCustomPrinterIcon ? 1 : 0));
294        parcel.writeInt(mCustomPrinterIconGen);
295        parcel.writeParcelable(mInfoIntent, flags);
296    }
297
298    @Override
299    public int hashCode() {
300        final int prime = 31;
301        int result = 1;
302        result = prime * result + mId.hashCode();
303        result = prime * result + mName.hashCode();
304        result = prime * result + mStatus;
305        result = prime * result + ((mDescription != null) ? mDescription.hashCode() : 0);
306        result = prime * result + ((mCapabilities != null) ? mCapabilities.hashCode() : 0);
307        result = prime * result + mIconResourceId;
308        result = prime * result + (mHasCustomPrinterIcon ? 1 : 0);
309        result = prime * result + mCustomPrinterIconGen;
310        result = prime * result + ((mInfoIntent != null) ? mInfoIntent.hashCode() : 0);
311        return result;
312    }
313
314    /**
315     * Compare two {@link PrinterInfo printerInfos} in all aspects beside being null and the
316     * {@link #mStatus}.
317     *
318     * @param other the other {@link PrinterInfo}
319     * @return true iff the infos are equivalent
320     * @hide
321     */
322    public boolean equalsIgnoringStatus(PrinterInfo other) {
323        if (!mId.equals(other.mId)) {
324            return false;
325        }
326        if (!mName.equals(other.mName)) {
327           return false;
328        }
329        if (!TextUtils.equals(mDescription, other.mDescription)) {
330            return false;
331        }
332        if (mCapabilities == null) {
333            if (other.mCapabilities != null) {
334                return false;
335            }
336        } else if (!mCapabilities.equals(other.mCapabilities)) {
337            return false;
338        }
339        if (mIconResourceId != other.mIconResourceId) {
340            return false;
341        }
342        if (mHasCustomPrinterIcon != other.mHasCustomPrinterIcon) {
343            return false;
344        }
345        if (mCustomPrinterIconGen != other.mCustomPrinterIconGen) {
346            return false;
347        }
348        if (mInfoIntent == null) {
349            if (other.mInfoIntent != null) {
350                return false;
351            }
352        } else if (!mInfoIntent.equals(other.mInfoIntent)) {
353            return false;
354        }
355        return true;
356    }
357
358    @Override
359    public boolean equals(Object obj) {
360        if (this == obj) {
361            return true;
362        }
363        if (obj == null) {
364            return false;
365        }
366        if (getClass() != obj.getClass()) {
367            return false;
368        }
369        PrinterInfo other = (PrinterInfo) obj;
370        if (!equalsIgnoringStatus(other)) {
371            return false;
372        }
373        if (mStatus != other.mStatus) {
374            return false;
375        }
376        return true;
377    }
378
379    @Override
380    public String toString() {
381        StringBuilder builder = new StringBuilder();
382        builder.append("PrinterInfo{");
383        builder.append("id=").append(mId);
384        builder.append(", name=").append(mName);
385        builder.append(", status=").append(mStatus);
386        builder.append(", description=").append(mDescription);
387        builder.append(", capabilities=").append(mCapabilities);
388        builder.append(", iconResId=").append(mIconResourceId);
389        builder.append(", hasCustomPrinterIcon=").append(mHasCustomPrinterIcon);
390        builder.append(", customPrinterIconGen=").append(mCustomPrinterIconGen);
391        builder.append(", infoIntent=").append(mInfoIntent);
392        builder.append("\"}");
393        return builder.toString();
394    }
395
396    /**
397     * Builder for creating of a {@link PrinterInfo}.
398     */
399    public static final class Builder {
400        private @NonNull PrinterId mPrinterId;
401        private @NonNull String mName;
402        private @Status int mStatus;
403        private int mIconResourceId;
404        private boolean mHasCustomPrinterIcon;
405        private String mDescription;
406        private PendingIntent mInfoIntent;
407        private PrinterCapabilitiesInfo mCapabilities;
408        private int mCustomPrinterIconGen;
409
410        /**
411         * Constructor.
412         *
413         * @param printerId The printer id. Cannot be null.
414         * @param name The printer name. Cannot be empty.
415         * @param status The printer status. Must be a valid status.
416         * @throws IllegalArgumentException If the printer id is null, or the
417         * printer name is empty or the status is not a valid one.
418         */
419        public Builder(@NonNull PrinterId printerId, @NonNull String name, @Status int status) {
420            mPrinterId = checkPrinterId(printerId);
421            mName = checkName(name);
422            mStatus = checkStatus(status);
423        }
424
425        /**
426         * Constructor.
427         *
428         * @param other Other info from which to start building.
429         */
430        public Builder(@NonNull PrinterInfo other) {
431            mPrinterId = other.mId;
432            mName = other.mName;
433            mStatus = other.mStatus;
434            mIconResourceId = other.mIconResourceId;
435            mHasCustomPrinterIcon = other.mHasCustomPrinterIcon;
436            mDescription = other.mDescription;
437            mInfoIntent = other.mInfoIntent;
438            mCapabilities = other.mCapabilities;
439            mCustomPrinterIconGen = other.mCustomPrinterIconGen;
440        }
441
442        /**
443         * Sets the printer status.
444         *
445         * @param status The status.
446         * @return This builder.
447         * @see PrinterInfo#STATUS_IDLE
448         * @see PrinterInfo#STATUS_BUSY
449         * @see PrinterInfo#STATUS_UNAVAILABLE
450         */
451        public @NonNull Builder setStatus(@Status int status) {
452            mStatus = checkStatus(status);
453            return this;
454        }
455
456        /**
457         * Set a drawable resource as icon for this printer. If no icon is set the printer's
458         * service's icon is used for the printer.
459         *
460         * @param iconResourceId The resource ID of the icon.
461         * @return This builder.
462         * @see PrinterInfo.Builder#setHasCustomPrinterIcon
463         */
464        public @NonNull Builder setIconResourceId(@DrawableRes int iconResourceId) {
465            mIconResourceId = Preconditions.checkArgumentNonnegative(iconResourceId,
466                    "iconResourceId can't be negative");
467            return this;
468        }
469
470        /**
471         * Declares that the print service can load a custom per printer's icon. If both
472         * {@link PrinterInfo.Builder#setIconResourceId} and a custom icon are set the resource icon
473         * is shown while the custom icon loads but then the custom icon is used. If
474         * {@link PrinterInfo.Builder#setIconResourceId} is not set the printer's service's icon is
475         * shown while loading.
476         * <p>
477         * The icon is requested asynchronously and only when needed via
478         * {@link android.printservice.PrinterDiscoverySession#onRequestCustomPrinterIcon}.
479         * </p>
480         *
481         * @param hasCustomPrinterIcon If the printer has a custom icon or not.
482         *
483         * @return This builder.
484         */
485        public @NonNull Builder setHasCustomPrinterIcon(boolean hasCustomPrinterIcon) {
486            mHasCustomPrinterIcon = hasCustomPrinterIcon;
487            return this;
488        }
489
490        /**
491         * Sets the <strong>localized</strong> printer name which
492         * is shown to the user
493         *
494         * @param name The name.
495         * @return This builder.
496         */
497        public @NonNull Builder setName(@NonNull String name) {
498            mName = checkName(name);
499            return this;
500        }
501
502        /**
503         * Sets the <strong>localized</strong> printer description
504         * which is shown to the user
505         *
506         * @param description The description.
507         * @return This builder.
508         */
509        public @NonNull Builder setDescription(@NonNull String description) {
510            mDescription = description;
511            return this;
512        }
513
514        /**
515         * Sets the {@link PendingIntent} that launches an activity showing more information about
516         * the printer.
517         *
518         * @param infoIntent The {@link PendingIntent intent}.
519         * @return This builder.
520         */
521        public @NonNull Builder setInfoIntent(@NonNull PendingIntent infoIntent) {
522            mInfoIntent = infoIntent;
523            return this;
524        }
525
526        /**
527         * Sets the printer capabilities.
528         *
529         * @param capabilities The capabilities.
530         * @return This builder.
531         */
532        public @NonNull Builder setCapabilities(@NonNull PrinterCapabilitiesInfo capabilities) {
533            mCapabilities = capabilities;
534            return this;
535        }
536
537        /**
538         * Creates a new {@link PrinterInfo}.
539         *
540         * @return A new {@link PrinterInfo}.
541         */
542        public @NonNull PrinterInfo build() {
543            return new PrinterInfo(mPrinterId, mName, mStatus, mIconResourceId,
544                    mHasCustomPrinterIcon, mDescription, mInfoIntent, mCapabilities,
545                    mCustomPrinterIconGen);
546        }
547
548        /**
549         * Increments the generation number of the custom printer icon. As the {@link PrinterInfo}
550         * does not match the previous one anymore, users of the {@link PrinterInfo} will reload the
551         * icon if needed.
552         *
553         * @return This builder.
554         * @hide
555         */
556        public @NonNull Builder incCustomPrinterIconGen() {
557            mCustomPrinterIconGen++;
558            return this;
559        }
560    }
561
562    public static final Parcelable.Creator<PrinterInfo> CREATOR =
563            new Parcelable.Creator<PrinterInfo>() {
564        @Override
565        public PrinterInfo createFromParcel(Parcel parcel) {
566            return new PrinterInfo(parcel);
567        }
568
569        @Override
570        public PrinterInfo[] newArray(int size) {
571            return new PrinterInfo[size];
572        }
573    };
574}
575