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