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.service.print.PrinterInfoProto;
35import android.text.TextUtils;
36
37import com.android.internal.util.Preconditions;
38
39import java.lang.annotation.Retention;
40import 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 */
53public 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
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     */
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
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     */
175    public boolean getHasCustomPrinterIcon() {
176        return mHasCustomPrinterIcon;
177    }
178
179    /**
180     * Get the printer name.
181     *
182     * @return The printer name.
183     */
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     */
197    public @Status int getStatus() {
198        return mStatus;
199    }
200
201    /**
202     * Gets the  printer description.
203     *
204     * @return The description.
205     */
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     */
218    public @Nullable PendingIntent getInfoIntent() {
219        return mInfoIntent;
220    }
221
222    /**
223     * Gets the printer capabilities.
224     *
225     * @return The capabilities.
226     */
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     */
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     */
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     */
266    private static @NonNull String checkName(String name) {
267        return Preconditions.checkStringNotEmpty(name, "name cannot be empty.");
268    }
269
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
285    public int describeContents() {
286        return 0;
287    }
288
289    @Override
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
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     */
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
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
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
560        public @NonNull Builder incCustomPrinterIconGen() {
561            mCustomPrinterIconGen++;
562            return this;
563        }
564    }
565
566    public static final 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