diff options
Diffstat (limited to 'core/java/android')
35 files changed, 5233 insertions, 3 deletions
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index c6f3fb8..992d8b7 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -91,6 +91,8 @@ import android.os.UserHandle; import android.os.SystemVibrator; import android.os.UserManager; import android.os.storage.StorageManager; +import android.print.IPrintManager; +import android.print.PrintManager; import android.telephony.TelephonyManager; import android.content.ClipboardManager; import android.util.AndroidRuntimeException; @@ -548,6 +550,15 @@ class ContextImpl extends Context { registerService(CAMERA_SERVICE, new StaticServiceFetcher() { public Object createStaticService() { return new CameraManager(); + } + }); + + registerService(PRINT_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE); + IPrintManager service = IPrintManager.Stub.asInterface(iBinder); + return new PrintManager(ctx.getOuterContext(), service, UserHandle.myUserId(), + UserHandle.getAppId(Process.myUid())); }}); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 35e51a6..7c9117c 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -45,7 +45,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.List; /** * Interface to global information about an application environment. This is @@ -2294,6 +2293,15 @@ public abstract class Context { public static final String CAMERA_SERVICE = "camera"; /** + * {@link android.print.PrintManager} for printing and managing + * printers and print taks. + * + * @see #getSystemService + * @see android.print.PrintManager + */ + public static final String PRINT_SERVICE = "print"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 4b022d9..c5d2c77 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -19,7 +19,6 @@ package android.net; import android.os.Environment; import android.os.Parcel; import android.os.Parcelable; -import android.os.Environment.UserEnvironment; import android.os.StrictMode; import android.util.Log; import java.io.File; diff --git a/core/java/android/os/CancellationSignal.java b/core/java/android/os/CancellationSignal.java index dcba9b7..e8053d5 100644 --- a/core/java/android/os/CancellationSignal.java +++ b/core/java/android/os/CancellationSignal.java @@ -17,7 +17,6 @@ package android.os; import android.os.ICancellationSignal; -import android.os.ICancellationSignal.Stub; /** * Provides the ability to cancel an operation in progress. diff --git a/core/java/android/print/IPrintAdapter.aidl b/core/java/android/print/IPrintAdapter.aidl new file mode 100644 index 0000000..a9b4fb7 --- /dev/null +++ b/core/java/android/print/IPrintAdapter.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.ParcelFileDescriptor; +import android.print.IPrintProgressListener; +import android.print.PageRange; +import android.print.PrintAttributes; + +/** + * Interface for communication with the print adapter object. + * + * @hide + */ +oneway interface IPrintAdapter { + void start(); + void printAttributesChanged(in PrintAttributes attributes); + void print(in List<PageRange> pages, in ParcelFileDescriptor fd, + IPrintProgressListener progressListener); + void finish(); +} diff --git a/core/java/android/print/IPrintClient.aidl b/core/java/android/print/IPrintClient.aidl new file mode 100644 index 0000000..3f39d08 --- /dev/null +++ b/core/java/android/print/IPrintClient.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.IntentSender; + +/** + * Interface for communication with a printing app. + * + * @see android.print.IPrintClientCallback + * + * @hide + */ +oneway interface IPrintClient { + void startPrintJobConfigActivity(in IntentSender intent); +} diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl new file mode 100644 index 0000000..ff9877e --- /dev/null +++ b/core/java/android/print/IPrintManager.aidl @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.ICancellationSignal; +import android.print.IPrintAdapter; +import android.print.IPrintClient; +import android.print.IPrinterDiscoveryObserver; +import android.print.PrinterId; +import android.print.PrintJobInfo; +import android.print.PrintAttributes; + +/** + * Interface for communication with the core print manager service. + * + * @hide + */ +interface IPrintManager { + List<PrintJobInfo> getPrintJobs(int appId, int userId); + PrintJobInfo getPrintJob(int printJobId, int appId, int userId); + PrintJobInfo print(String printJobName, in IPrintClient client, in IPrintAdapter printAdapter, + in PrintAttributes attributes, int appId, int userId); + void cancelPrintJob(int printJobId, int appId, int userId); + void onPrintJobQueued(in PrinterId printerId, in PrintJobInfo printJob); + void startDiscoverPrinters(IPrinterDiscoveryObserver observer); + void stopDiscoverPrinters(); +} diff --git a/core/java/android/print/IPrintProgressListener.aidl b/core/java/android/print/IPrintProgressListener.aidl new file mode 100644 index 0000000..2c0d607 --- /dev/null +++ b/core/java/android/print/IPrintProgressListener.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.ICancellationSignal; +import android.print.PageRange; +import android.print.PrintAdapterInfo; + +/** + * Callbacks for observing the print progress (writing of printed content) + * of a PrintAdapter. + * + * @hide + */ +oneway interface IPrintProgressListener { + void onWriteStarted(in PrintAdapterInfo info, ICancellationSignal cancellationSignal); + void onWriteFinished(in List<PageRange> pages); + void onWriteFailed(CharSequence error); +} diff --git a/core/java/android/print/IPrintSpoolerService.aidl b/core/java/android/print/IPrintSpoolerService.aidl new file mode 100644 index 0000000..e84d592 --- /dev/null +++ b/core/java/android/print/IPrintSpoolerService.aidl @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.ComponentName; +import android.os.ParcelFileDescriptor; +import android.print.IPrintAdapter; +import android.print.IPrintClient; +import android.print.IPrintSpoolerServiceCallbacks; +import android.print.PrinterInfo; +import android.print.PrintAttributes; + +/** + * Interface for communication with the print spooler service. + * + * @see android.print.IPrintSpoolerServiceCallbacks + * + * @hide + */ +oneway interface IPrintSpoolerService { + void getPrintJobs(IPrintSpoolerServiceCallbacks callback, in ComponentName componentName, + int state, int appId, int sequence); + void getPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback, + int appId, int sequence); + void createPrintJob(String printJobName, in IPrintClient client, in IPrintAdapter printAdapter, + in PrintAttributes attributes, IPrintSpoolerServiceCallbacks callback, int appId, + int sequence); + void cancelPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback, + int appId, int sequence); + void setPrintJobState(int printJobId, int status, IPrintSpoolerServiceCallbacks callback, + int sequence); + void setPrintJobTag(int printJobId, String tag, IPrintSpoolerServiceCallbacks callback, + int sequence); + void writePrintJobData(in ParcelFileDescriptor fd, int printJobId); +}
\ No newline at end of file diff --git a/core/java/android/print/IPrintSpoolerServiceCallbacks.aidl b/core/java/android/print/IPrintSpoolerServiceCallbacks.aidl new file mode 100644 index 0000000..0c51913 --- /dev/null +++ b/core/java/android/print/IPrintSpoolerServiceCallbacks.aidl @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.print.PrintJobInfo; +import java.util.List; + +/** + * Callbacks for communication with the print spooler service. + * + * @see android.print.IPrintSpoolerService + * + * @hide + */ +oneway interface IPrintSpoolerServiceCallbacks { + void onGetPrintJobsResult(in List<PrintJobInfo> printJob, int sequence); + void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence); + void onCreatePrintJobResult(in PrintJobInfo printJob, int sequence); + void onCancelPrintJobResult(boolean canceled, int sequence); + void onSetPrintJobStateResult(boolean success, int sequence); + void onSetPrintJobTagResult(boolean success, int sequence); +} diff --git a/core/java/android/print/IPrinterDiscoveryObserver.aidl b/core/java/android/print/IPrinterDiscoveryObserver.aidl new file mode 100644 index 0000000..39aeb8c --- /dev/null +++ b/core/java/android/print/IPrinterDiscoveryObserver.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.print.PrinterId; +import android.print.PrinterInfo; + +/** + * Interface for observing printer discovery. + * + * @hide + */ +oneway interface IPrinterDiscoveryObserver { + void addDiscoveredPrinters(in List<PrinterInfo> printers); + void removeDiscoveredPrinters(in List<PrinterId> printers); +} diff --git a/core/java/android/print/PageRange.aidl b/core/java/android/print/PageRange.aidl new file mode 100644 index 0000000..b1ae14f --- /dev/null +++ b/core/java/android/print/PageRange.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +parcelable PageRange; diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java new file mode 100644 index 0000000..044a715 --- /dev/null +++ b/core/java/android/print/PageRange.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents a range of pages. The start and end page indices of + * the range are zero based and are inclusive. + */ +public final class PageRange implements Parcelable { + + /** + * Constant for specifying all pages. + */ + public static final PageRange ALL_PAGES = new PageRange(0, Integer.MAX_VALUE); + + private final int mStart; + private final int mEnd; + + /** + * Creates a new instance. + * + * @param start The start page index (zero based and inclusive). + * @param end The end page index (zero based and inclusive). + * + * @throws IllegalArgumentException If start is less than zero. + * @throws IllegalArgumentException If end is less than zero. + * @throws IllegalArgumentException If start greater than end. + */ + PageRange(int start, int end) { + if (start < 0) { + throw new IllegalArgumentException("start cannot be less than zero."); + } + if (end < 0) { + throw new IllegalArgumentException("end cannot be less than zero."); + } + if (start > end) { + throw new IllegalArgumentException("start must be lesser than end."); + } + mStart = start; + mEnd = end; + } + + private PageRange (Parcel parcel) { + this(parcel.readInt(), parcel.readInt()); + } + + /** + * Gets the start page index (zero based and inclusive). + * + * @return The start page index. + */ + public int getStart() { + return mStart; + } + + /** + * Gets the end page index (zero based and inclusive). + * + * @return The end page index. + */ + public int getEnd() { + return mEnd; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mStart); + parcel.writeInt(mEnd); + } + + @Override + public String toString() { + if (this == ALL_PAGES) { + return "PageRange[<all pages>]"; + } + StringBuilder builder = new StringBuilder(); + builder.append("PageRange[") + .append(mStart) + .append(" - ") + .append(mEnd) + .append("]"); + return builder.toString(); + } + + public static final Parcelable.Creator<PageRange> CREATOR = + new Creator<PageRange>() { + @Override + public PageRange createFromParcel(Parcel parcel) { + return new PageRange(parcel); + } + + @Override + public PageRange[] newArray(int size) { + return new PageRange[size]; + } + }; +} diff --git a/core/java/android/print/PrintAdapter.java b/core/java/android/print/PrintAdapter.java new file mode 100644 index 0000000..a7f809b --- /dev/null +++ b/core/java/android/print/PrintAdapter.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.util.List; + +import android.os.CancellationSignal; + +/** + * Base class that provides data to be printed. + * + * <h3>Lifecycle</h3> + * <p> + * <ul> + * <li> + * You will receive a call on {@link #onStart()} when printing starts. + * This callback can be used to allocate resources. + * </li> + * <li> + * Next you will get one or more calls to the pair + * {@link #onPrintAttributesChanged(PrintAttributes)} and {@link #onPrint(List, + * FileDescriptor, CancellationSignal, PrintProgressCallback)}. The first callback + * informs you that the print attributes (page size, density, etc) changed giving + * you an opportunity to re-layout the content. The second method asks you to write + * a PDF file with the content for specific pages. + * </li> + * <li> + * Finally, you will receive a call on {@link #onFinish()} right after printing. + * You can use this callback to release resources. + * </li> + * <li> + * You can receive calls to {@link #getInfo()} at any point which should return + * a {@link PrintAdapterInfo} describing your {@link PrintAdapter}. + * </li> + * </ul> + * </p> + * <p> + */ +public abstract class PrintAdapter { + + /** + * Called when printing started. You can use this callback to + * allocate resources. + * <p> + * <strong>Note:</strong> Invoked on the main thread. + * </p> + */ + public void onStart() { + /* do nothing - stub */ + } + + /** + * Called when the print job attributes (page size, density, etc) + * changed giving you a chance to re-layout the content such that + * it matches the new constraints. + * <p> + * <strong>Note:</strong> Invoked on the main thread. + * </p> + * + * @param attributes The print job attributes. + * @return Whether the content changed based on the provided attributes. + */ + public boolean onPrintAttributesChanged(PrintAttributes attributes) { + return false; + } + + /** + * Called when specific pages of the content have to be printed in the from of + * a PDF file to the given file descriptor. You should <strong>not</strong> + * close the file descriptor instead you have to invoke {@link PrintProgressCallback + * #onWriteFinished()} or {@link PrintProgressCallback#onPrintFailed(CharSequence)}. + * <p> + * <strong>Note:</strong> If the printed content is large, it is a good + * practice to schedule writing it on a dedicated thread and register a + * callback in the provided {@link CancellationSignal} upon which to stop + * writing data. The cancellation callback will not be made on the main + * thread. + * </p> + * <p> + * <strong>Note:</strong> Invoked on the main thread. + * </p> + * <p> + * + * @param pages The pages whose content to write. + * @param destination The destination file descriptor to which to start writing. + * @param cancellationSignal Signal for observing cancel write requests. + * @param progressListener Callback to inform the system with the write progress. + * + * @see CancellationSignal + */ + public abstract void onPrint(List<PageRange> pages, FileDescriptor destination, + CancellationSignal cancellationSignal, PrintProgressCallback progressListener); + + /** + * Called when printing finished. You can use this callback to release + * resources. + * <p> + * <strong>Note:</strong> Invoked on the main thread. + * </p> + */ + public void onFinish() { + /* do nothing - stub */ + } + + /** + * Gets a {@link PrinterInfo} object that contains metadata about the + * printed content. + * <p> + * <strong>Note:</strong> Invoked on the main thread. + * </p> + * + * @return The info object for this {@link PrintAdapter}. + * + * @see PrintAdapterInfo + */ + public abstract PrintAdapterInfo getInfo(); + + /** + * Base class for implementing a listener for the printing progress + * of a {@link PrintAdapter}. + */ + public static abstract class PrintProgressCallback { + + PrintProgressCallback() { + /* do nothing - hide constructor */ + } + + /** + * Notifies that all the data was printed. + * + * @param pages The pages that were printed. + */ + public void onPrintFinished(List<PageRange> pages) { + /* do nothing - stub */ + } + + /** + * Notifies that an error occurred while printing the data. + * + * @param error Error message. May be null if error is unknown. + */ + public void onPrintFailed(CharSequence error) { + /* do nothing - stub */ + } + } +} diff --git a/core/java/android/print/PrintAdapterInfo.aidl b/core/java/android/print/PrintAdapterInfo.aidl new file mode 100644 index 0000000..27bf717 --- /dev/null +++ b/core/java/android/print/PrintAdapterInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +parcelable PrintAdapterInfo; diff --git a/core/java/android/print/PrintAdapterInfo.java b/core/java/android/print/PrintAdapterInfo.java new file mode 100644 index 0000000..06e6b10 --- /dev/null +++ b/core/java/android/print/PrintAdapterInfo.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class encapsulates information about a {@link PrintAdapter} object. + */ +public final class PrintAdapterInfo implements Parcelable { + + /** + * Constant for unknown page count. + */ + public static final int PAGE_COUNT_UNKNOWN = -1; + + private int mPageCount; + private int mFlags; + + /** + * Creates a new instance. + */ + private PrintAdapterInfo() { + /* do nothing */ + } + + /** + * Creates a new instance. + * + * @param parcel Data from which to initialize. + */ + private PrintAdapterInfo(Parcel parcel) { + mPageCount = parcel.readInt(); + mFlags = parcel.readInt(); + } + + /** + * Gets the total number of pages. + * + * @return The number of pages. + */ + public int getPageCount() { + return mPageCount; + } + + /** + * @return The flags of this printable info. + * + * @see #FLAG_NOTIFY_FOR_ATTRIBUTES_CHANGE + */ + public int getFlags() { + return mFlags; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mPageCount); + parcel.writeInt(mFlags); + } + + /** + * Builder for creating an {@link PrintAdapterInfo}. + */ + public static final class Builder { + private final PrintAdapterInfo mPrintableInfo = new PrintAdapterInfo(); + + /** + * Sets the total number of pages. + * + * @param pageCount The number of pages. Must be + * greater than zero. + */ + public Builder setPageCount(int pageCount) { + if (pageCount < 0) { + throw new IllegalArgumentException("pageCount" + + " must be greater than or euqal to zero!"); + } + mPrintableInfo.mPageCount = pageCount; + return this; + } + + /** + * Sets the flags of this printable info. + * + * @param flags The flags. + * + * @see #FLAG_NOTIFY_FOR_ATTRIBUTES_CHANGE + */ + public Builder setFlags(int flags) { + mPrintableInfo.mFlags = flags; + return this; + } + + /** + * Creates a new {@link PrintAdapterInfo} instance. + * + * @return The new instance. + */ + public PrintAdapterInfo create() { + return mPrintableInfo; + } + } + + public static final Parcelable.Creator<PrintAdapterInfo> CREATOR = + new Creator<PrintAdapterInfo>() { + @Override + public PrintAdapterInfo createFromParcel(Parcel parcel) { + return new PrintAdapterInfo(parcel); + } + + @Override + public PrintAdapterInfo[] newArray(int size) { + return new PrintAdapterInfo[size]; + } + }; +} diff --git a/core/java/android/print/PrintAttributes.aidl b/core/java/android/print/PrintAttributes.aidl new file mode 100644 index 0000000..0b765a2 --- /dev/null +++ b/core/java/android/print/PrintAttributes.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +parcelable PrintAttributes; diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java new file mode 100644 index 0000000..8511d0b --- /dev/null +++ b/core/java/android/print/PrintAttributes.java @@ -0,0 +1,1226 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources.NotFoundException; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.R; + +/** + * This class represents the attributes of a print job. + */ +public final class PrintAttributes implements Parcelable { + /** Duplex mode: No duplexing */ + public static final int DUPLEX_MODE_NONE = 1 << 0; + /** Duplex mode: Turn a page along its long edge, e.g. like a book */ + public static final int DUPLEX_MODE_LONG_EDGE = 1 << 1; + /** Duplex mode: Turn a page along its short edge, e.g. like a notepad */ + public static final int DUPLEX_MODE_SHORT_EDGE = 1 << 2; + + + /** Orientation: Portrait page orientation. */ + public static final int ORIENTATION_PORTRAIT = 1 << 0; + /** Orientation: Landscape page orientation. */ + public static final int ORIENTATION_LANDSCAPE = 1 << 1; + + + /** Color mode: Monochrome color scheme, e.g. one color is used. */ + public static final int COLOR_MODE_MONOCHROME = 1 << 0; + /** Color mode: Color color scheme, e.g. many colors are used. */ + public static final int COLOR_MODE_COLOR = 1 << 1; + + + /** Fitting mode: No fitting. */ + public static final int FITTING_MODE_NONE = 0x00000001; + /** Fitting mode: Fit the content to the page. */ + public static final int FITTING_MODE_FIT_TO_PAGE = 0x00000002; + + + private static final int VALID_DUPLEX_MODES = + DUPLEX_MODE_NONE | DUPLEX_MODE_LONG_EDGE | DUPLEX_MODE_SHORT_EDGE; + + private static final int VALID_COLOR_MODES = + COLOR_MODE_MONOCHROME | COLOR_MODE_COLOR; + + private static final int VALID_FITTING_MODES = + FITTING_MODE_NONE | FITTING_MODE_FIT_TO_PAGE; + + private static final int VALID_ORIENTATIONS = + ORIENTATION_PORTRAIT | ORIENTATION_LANDSCAPE; + + private MediaSize mMediaSize; + private Resolution mResolution; + private Margins mMargins; + private Tray mInputTray; + private Tray mOutputTray; + + private int mDuplexMode; + private int mColorMode; + private int mFittingMode; + private int mOrientation; + private int mCopies; + + PrintAttributes() { + /* hide constructor */ + } + + private PrintAttributes(Parcel parcel) { + mMediaSize = (parcel.readInt() == 1) ? MediaSize.createFromParcel(parcel) : null; + mResolution = (parcel.readInt() == 1) ? Resolution.createFromParcel(parcel) : null; + mMargins = (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null; + mInputTray = (parcel.readInt() == 1) ? Tray.createFromParcel(parcel) : null; + mOutputTray = (parcel.readInt() == 1) ? Tray.createFromParcel(parcel) : null; + mDuplexMode = parcel.readInt(); + mColorMode = parcel.readInt(); + mFittingMode = parcel.readInt(); + mOrientation = parcel.readInt(); + mCopies = parcel.readInt(); + } + + /** + * Gets the media size. + * + * @return The media size or <code>null</code> if not set. + */ + public MediaSize getMediaSize() { + return mMediaSize; + } + + /** + * Sets the media size. + * + * @param The media size. + * + * @hide + */ + public void setMediaSize(MediaSize mediaSize) { + mMediaSize = mediaSize; + } + + /** + * Gets the resolution. + * + * @return The resolution or <code>null</code> if not set. + */ + public Resolution getResolution() { + return mResolution; + } + + /** + * Sets the resolution. + * + * @param The resolution. + * + * @hide + */ + public void setResolution(Resolution resolution) { + mResolution = resolution; + } + + /** + * Gets the margins. + * + * @return The margins or <code>null</code> if not set. + */ + public Margins getMargins() { + return mMargins; + } + + /** + * Sets the margins. + * + * @param The margins. + * + * @hide + */ + public void setMargins(Margins margins) { + mMargins = margins; + } + + /** + * Sets the input tray. + * + * @return The input tray or <code>null</code> if not set. + */ + public Tray getInputTray() { + return mInputTray; + } + + /** + * Gets the input tray. + * + * @param The input tray. + * + * @hide + */ + public void setInputTray(Tray inputTray) { + mInputTray = inputTray; + } + + /** + * Gets the output tray. + * + * @return The output tray or <code>null</code> if not set. + */ + public Tray getOutputTray() { + return mOutputTray; + } + + /** + * Sets the output tray. + * + * @param The output tray. + * + * @hide + */ + public void setOutputTray(Tray outputTray) { + mOutputTray = outputTray; + } + + /** + * Gets the duplex mode. + * + * @return The duplex mode or zero if not set. + * + * @see #DUPLEX_MODE_NONE + * @see #DUPLEX_MODE_SHORT_EDGE + * @see #DUPLEX_MODE_LONG_EDGE + */ + public int getDuplexMode() { + return mDuplexMode; + } + + /** + * Sets the duplex mode. + * + * @param The duplex mode. + * + * @hide + */ + public void setDuplexMode(int duplexMode) { + enforceValidDuplexMode(duplexMode); + mDuplexMode = duplexMode; + } + + /** + * Gets the color mode. + * + * @return The color mode or zero if not set. + * + * @see #COLOR_MODE_COLOR + * @see #COLOR_MODE_MONOCHROME + */ + public int getColorMode() { + return mColorMode; + } + + /** + * Sets the color mode. + * + * @param The color mode. + * + * @see #COLOR_MODE_MONOCHROME + * @see #COLOR_MODE_COLOR + * + * @hide + */ + public void setColorMode(int colorMode) { + enforceValidColorMode(colorMode); + mColorMode = colorMode; + } + + /** + * Gets the fitting mode. + * + * @return The fitting mode or zero if not set. + * + * @see #FITTING_MODE_NONE + * @see #FITTING_MODE_FIT_TO_PAGE + */ + public int getFittingMode() { + return mFittingMode; + } + + /** + * Sets the fitting mode. + * + * @param The fitting mode. + * + * @see #FITTING_MODE_NONE + * @see #FITTING_MODE_FIT_TO_PAGE + * + * @hide + */ + public void setFittingMode(int fittingMode) { + enfoceValidFittingMode(fittingMode); + mFittingMode = fittingMode; + } + + /** + * Gets the orientation. + * + * @return The orientation or zero if not set. + * + * @see #ORIENTATION_PORTRAIT + * @see #ORIENTATION_LANDSCAPE + */ + public int getOrientation() { + return mOrientation; + } + + /** + * Sets the orientation. + * + * @param The orientation. + * + * @see #ORIENTATION_PORTRAIT + * @see #ORIENTATION_LANDSCAPE + * + * @hide + */ + public void setOrientation(int orientation) { + enforceValidOrientation(orientation); + mOrientation = orientation; + } + + /** + * Gets the number of copies. + * + * @return The number of copies or zero if not set. + */ + public int getCopies() { + return mCopies; + } + + /** + * Sets the number of copies. + * + * @param copyCount The number of copies. + * + * @hide + */ + public void setCopies(int copyCount) { + if (copyCount < 1) { + throw new IllegalArgumentException("Copies must be more than one."); + } + mCopies = copyCount; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + if (mMediaSize != null) { + parcel.writeInt(1); + mMediaSize.writeToParcel(parcel); + } else { + parcel.writeInt(0); + } + if (mResolution != null) { + parcel.writeInt(1); + mResolution.writeToParcel(parcel); + } else { + parcel.writeInt(0); + } + if (mMargins != null) { + parcel.writeInt(1); + mMargins.writeToParcel(parcel); + } else { + parcel.writeInt(0); + } + if (mInputTray != null) { + parcel.writeInt(1); + mInputTray.writeToParcel(parcel); + } else { + parcel.writeInt(0); + } + if (mOutputTray != null) { + parcel.writeInt(1); + mOutputTray.writeToParcel(parcel); + } else { + parcel.writeInt(0); + } + parcel.writeInt(mDuplexMode); + parcel.writeInt(mColorMode); + parcel.writeInt(mFittingMode); + parcel.writeInt(mOrientation); + parcel.writeInt(mCopies); + } + + @Override + public int describeContents() { + return 0; + } + + /** hide */ + public void clear() { + mMediaSize = null; + mResolution = null; + mMargins = null; + mInputTray = null; + mOutputTray = null; + mDuplexMode = 0; + mColorMode = 0; + mFittingMode = 0; + mOrientation = 0; + mCopies = 0; + } + + /** + * This class specifies a supported media size. + */ + public static final class MediaSize { + private static final String LOG_TAG = "MediaSize"; + + // TODO: Verify media sizes and add more standard ones. + + // ISO sizes + + /** ISO A0 media size: 841mm x 1189mm (33.11" x 46.81") */ + public static final MediaSize ISO_A0 = + new MediaSize("ISO_A0", "android", R.string.mediaSize_iso_a0, 33110, 46810); + /** ISO A1 media size: 594mm x 841mm (23.39" x 33.11") */ + public static final MediaSize ISO_A1 = + new MediaSize("ISO_A1", "android", R.string.mediaSize_iso_a1, 23390, 33110); + /** ISO A2 media size: 420mm x 594mm (16.54" x 23.39") */ + public static final MediaSize ISO_A2 = + new MediaSize("ISO_A2", "android", R.string.mediaSize_iso_a2, 16540, 23390); + /** ISO A3 media size: 297mm x 420mm (11.69" x 16.54") */ + public static final MediaSize ISO_A3 = + new MediaSize("ISO_A3", "android", R.string.mediaSize_iso_a3, 11690, 16540); + /** ISO A4 media size: 210mm x 297mm (8.27" x 11.69") */ + public static final MediaSize ISO_A4 = + new MediaSize("ISO_A4", "android", R.string.mediaSize_iso_a4, 8270, 11690); + /** ISO A5 media size: 148mm x 210mm (5.83" x 8.27") */ + public static final MediaSize ISO_A5 = + new MediaSize("ISO_A5", "android", R.string.mediaSize_iso_a5, 5830, 8270); + /** ISO A6 media size: 105mm x 148mm (4.13" x 5.83") */ + public static final MediaSize ISO_A6 = + new MediaSize("ISO_A6", "android", R.string.mediaSize_iso_a6, 4130, 5830); + /** ISO A7 media size: 74mm x 105mm (2.91" x 4.13") */ + public static final MediaSize ISO_A7 = + new MediaSize("ISO_A7", "android", R.string.mediaSize_iso_a7, 2910, 4130); + /** ISO A8 media size: 52mm x 74mm (2.05" x 2.91") */ + public static final MediaSize ISO_A8 = + new MediaSize("ISO_A8", "android", R.string.mediaSize_iso_a8, 2050, 2910); + /** ISO A9 media size: 37mm x 52mm (1.46" x 2.05") */ + public static final MediaSize ISO_A9 = + new MediaSize("ISO_A9", "android", R.string.mediaSize_iso_a9, 1460, 2050); + /** ISO A10 media size: 26mm x 37mm (1.02" x 1.46") */ + public static final MediaSize ISO_A10 = + new MediaSize("ISO_A10", "android", R.string.mediaSize_iso_a10, 1020, 1460); + + /** ISO B0 media size: 1000mm x 1414mm (39.37" x 55.67") */ + public static final MediaSize ISO_B0 = + new MediaSize("ISO_B0", "android", R.string.mediaSize_iso_b0, 39370, 55670); + /** ISO B1 media size: 707mm x 1000mm (27.83" x 39.37") */ + public static final MediaSize ISO_B1 = + new MediaSize("ISO_B1", "android", R.string.mediaSize_iso_b1, 27830, 39370); + /** ISO B2 media size: 500mm x 707mm (19.69" x 27.83") */ + public static final MediaSize ISO_B2 = + new MediaSize("ISO_B2", "android", R.string.mediaSize_iso_b2, 19690, 27830); + /** ISO B3 media size: 353mm x 500mm (13.90" x 19.69") */ + public static final MediaSize ISO_B3 = + new MediaSize("ISO_B3", "android", R.string.mediaSize_iso_b3, 13900, 19690); + /** ISO B4 media size: 250mm x 353mm (9.84" x 13.90") */ + public static final MediaSize ISO_B4 = + new MediaSize("ISO_B4", "android", R.string.mediaSize_iso_b4, 9840, 13900); + /** ISO B5 media size: 176mm x 250mm (6.93" x 9.84") */ + public static final MediaSize ISO_B5 = + new MediaSize("ISO_B5", "android", R.string.mediaSize_iso_b5, 6930, 9840); + /** ISO B6 media size: 125mm x 176mm (4.92" x 6.93") */ + public static final MediaSize ISO_B6 = + new MediaSize("ISO_B6", "android", R.string.mediaSize_iso_b6, 4920, 6930); + /** ISO B7 media size: 88mm x 125mm (3.46" x 4.92") */ + public static final MediaSize ISO_B7 = + new MediaSize("ISO_B7", "android", R.string.mediaSize_iso_b7, 3460, 4920); + /** ISO B8 media size: 62mm x 88mm (2.44" x 3.46") */ + public static final MediaSize ISO_B8 = + new MediaSize("ISO_B8", "android", R.string.mediaSize_iso_b8, 2440, 3460); + /** ISO B9 media size: 44mm x 62mm (1.73" x 2.44") */ + public static final MediaSize ISO_B9 = + new MediaSize("ISO_B9", "android", R.string.mediaSize_iso_b9, 1730, 2440); + /** ISO B10 media size: 31mm x 44mm (1.22" x 1.73") */ + public static final MediaSize ISO_B10 = + new MediaSize("ISO_B10", "android", R.string.mediaSize_iso_b10, 1220, 1730); + + /** ISO C0 media size: 917mm x 1297mm (36.10" x 51.06") */ + public static final MediaSize ISO_C0 = + new MediaSize("ISO_C0", "android", R.string.mediaSize_iso_c0, 36100, 51060); + /** ISO C1 media size: 648mm x 917mm (25.51" x 36.10") */ + public static final MediaSize ISO_C1 = + new MediaSize("ISO_C1", "android", R.string.mediaSize_iso_c1, 25510, 36100); + /** ISO C2 media size: 458mm x 648mm (18.03" x 25.51") */ + public static final MediaSize ISO_C2 = + new MediaSize("ISO_C2", "android", R.string.mediaSize_iso_c2, 18030, 25510); + /** ISO C3 media size: 324mm x 458mm (12.76" x 18.03") */ + public static final MediaSize ISO_C3 = + new MediaSize("ISO_C3", "android", R.string.mediaSize_iso_c3, 12760, 18030); + /** ISO C4 media size: 229mm x 324mm (9.02" x 12.76") */ + public static final MediaSize ISO_C4 = + new MediaSize("ISO_C4", "android", R.string.mediaSize_iso_c4, 9020, 12760); + /** ISO C5 media size: 162mm x 229mm (6.38" x 9.02") */ + public static final MediaSize ISO_C5 = + new MediaSize("ISO_C5", "android", R.string.mediaSize_iso_c5, 6380, 9020); + /** ISO C6 media size: 114mm x 162mm (4.49" x 6.38") */ + public static final MediaSize ISO_C6 = + new MediaSize("ISO_C6", "android", R.string.mediaSize_iso_c6, 4490, 6380); + /** ISO C7 media size: 81mm x 114mm (3.19" x 4.49") */ + public static final MediaSize ISO_C7 = + new MediaSize("ISO_C7", "android", R.string.mediaSize_iso_c7, 3190, 4490); + /** ISO C8 media size: 57mm x 81mm (2.24" x 3.19") */ + public static final MediaSize ISO_C8 = + new MediaSize("ISO_C8", "android", R.string.mediaSize_iso_c8, 2240, 3190); + /** ISO C9 media size: 40mm x 57mm (1.57" x 2.24") */ + public static final MediaSize ISO_C9 = + new MediaSize("ISO_C9", "android", R.string.mediaSize_iso_c9, 1570, 2240); + /** ISO C10 media size: 28mm x 40mm (1.10" x 1.57") */ + public static final MediaSize ISO_C10 = + new MediaSize("ISO_C10", "android", R.string.mediaSize_iso_c10, 1100, 1570); + + // North America + + /** North America Letter media size: 8.5" x 11" */ + public static final MediaSize NA_LETTER = + new MediaSize("NA_LETTER", "android", R.string.mediaSize_na_letter, 8500, 11000); + /** North America Government-Letter media size: 8.0" x 10.5" */ + public static final MediaSize NA_GOVT_LETTER = + new MediaSize("NA_GOVT_LETTER", "android", + R.string.mediaSize_na_gvrnmt_letter, 8000, 10500); + /** North America Legal media size: 8.5" x 14" */ + public static final MediaSize NA_LEGAL = + new MediaSize("NA_LEGAL", "android", R.string.mediaSize_na_legal, 8500, 14000); + /** North America Junior Legal media size: 8.0" x 5.0" */ + public static final MediaSize NA_JUNIOR_LEGAL = + new MediaSize("NA_JUNIOR_LEGAL", "android", + R.string.mediaSize_na_junior_legal, 8000, 5000); + /** North America Ledger media size: 17" x 11" */ + public static final MediaSize NA_LEDGER = + new MediaSize("NA_LEDGER", "android", R.string.mediaSize_na_ledger, 17000, 11000); + /** North America Tabloid media size: 11" x 17" */ + public static final MediaSize NA_TBLOID = + new MediaSize("NA_TABLOID", "android", + R.string.mediaSize_na_tabloid, 11000, 17000); + + private final String mId; + private final String mPackageName; + private final int mLabelResId; + private final int mWidthMils; + private final int mHeightMils; + + /** + * Gets the unique media size id. + * + * @return The unique media size id. + */ + public String getId() { + return mId; + } + + /** + * Gets the human readable media size label. + * + * @return The human readable label. + */ + public CharSequence getLabel(PackageManager packageManager) { + try { + return packageManager.getResourcesForApplication( + mPackageName).getString(mLabelResId); + } catch (NotFoundException nfe) { + Log.w(LOG_TAG, "Could not load resouce" + mLabelResId + + " from package " + mPackageName); + } catch (NameNotFoundException nnfee) { + Log.w(LOG_TAG, "Could not load resouce" + mLabelResId + + " from package " + mPackageName); + } + return null; + } + + /** + * Gets the media width in mils (thousands of an inch). + * + * @return The media width. + */ + public int getWidthMils() { + return mWidthMils; + } + + /** + * Gets the media height in mils (thousands of an inch). + * + * @return The media height. + */ + public int getHeightMils() { + return mHeightMils; + } + + /** + * Creates a new instance. + * + * @param id The unique media size id. + * @param packageName The name of the creating package. + * @param labelResId The resource if of a human readable label. + * @param widthMils The width in mils (thousands of an inch). + * @param heightMils The height in mils (thousands of an inch). + * + * @throws IllegalArgumentException If the id is empty. + * @throws IllegalArgumentException If the label is empty. + * @throws IllegalArgumentException If the widthMils is less than or equal to zero. + * @throws IllegalArgumentException If the heightMils is less than or equal to zero. + */ + public MediaSize(String id, String packageName, int labelResId, + int widthMils, int heightMils) { + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id cannot be empty."); + } + if (TextUtils.isEmpty(packageName)) { + throw new IllegalArgumentException("packageName cannot be empty."); + } + if (labelResId <= 0) { + throw new IllegalArgumentException("labelResId must be greater than zero."); + } + if (widthMils <= 0) { + throw new IllegalArgumentException("widthMils " + + "cannot be less than or equal to zero."); + } + if (heightMils <= 0) { + throw new IllegalArgumentException("heightMils " + + "cannot be less than or euqual to zero."); + } + mPackageName = packageName; + mId = id; + mLabelResId = labelResId; + mWidthMils = widthMils; + mHeightMils = heightMils; + } + + void writeToParcel(Parcel parcel) { + parcel.writeString(mId); + parcel.writeString(mPackageName); + parcel.writeInt(mLabelResId); + parcel.writeInt(mWidthMils); + parcel.writeInt(mHeightMils); + } + + static MediaSize createFromParcel(Parcel parcel) { + return new MediaSize( + parcel.readString(), + parcel.readString(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt()); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("MediaSize{"); + builder.append("id: ").append(mId); + builder.append(", packageName: ").append(mPackageName); + builder.append(", labelResId: ").append(mLabelResId); + builder.append(", heightMils: ").append(mHeightMils); + builder.append(", widthMils: ").append(mWidthMils); + builder.append("}"); + return builder.toString(); + } + } + + /** + * This class specifies a supported resolution in dpi (dots per inch). + */ + public static final class Resolution { + private static final String LOG_TAG = "Resolution"; + + private final String mId; + private final String mPackageName; + private final int mLabelResId; + private final int mHorizontalDpi; + private final int mVerticalDpi; + + /** + * Gets the unique resolution id. + * + * @return The unique resolution id. + */ + public String getId() { + return mId; + } + + /** + * Gets the resolution human readable label. + * + * @return The human readable label. + */ + public CharSequence getLabel(PackageManager packageManager) { + try { + return packageManager.getResourcesForApplication( + mPackageName).getString(mLabelResId); + } catch (NotFoundException nfe) { + Log.w(LOG_TAG, "Could not load resouce" + mLabelResId + + " from package " + mPackageName); + } catch (NameNotFoundException nnfee) { + Log.w(LOG_TAG, "Could not load resouce" + mLabelResId + + " from package " + mPackageName); + } + return null; + } + + /** + * Gets the horizontal resolution in dpi. + * + * @return The horizontal resolution. + */ + public int getHorizontalDpi() { + return mHorizontalDpi; + } + + /** + * Gets the vertical resolution in dpi. + * + * @return The vertical resolution. + */ + public int getVerticalDpi() { + return mVerticalDpi; + } + + /** + * Creates a new instance. + * + * @param id The unique resolution id. + * @param packageName The name of the creating package. + * @param labelResId The resource id of a human readable label. + * @param horizontalDpi The horizontal resolution in dpi. + * @param verticalDpi The vertical resolution in dpi. + * + * @throws IllegalArgumentException If the id is empty. + * @throws IllegalArgumentException If the label is empty. + * @throws IllegalArgumentException If the horizontalDpi is less than or equal to zero. + * @throws IllegalArgumentException If the verticalDpi is less than or equal to zero. + */ + public Resolution(String id, String packageName, int labelResId, + int horizontalDpi, int verticalDpi) { + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id cannot be empty."); + } + if (TextUtils.isEmpty(packageName)) { + throw new IllegalArgumentException("packageName cannot be empty."); + } + if (labelResId <= 0) { + throw new IllegalArgumentException("labelResId must be greater than zero."); + } + if (horizontalDpi <= 0) { + throw new IllegalArgumentException("horizontalDpi " + + "cannot be less than or equal to zero."); + } + if (verticalDpi <= 0) { + throw new IllegalArgumentException("verticalDpi" + + " cannot be less than or equal to zero."); + } + mId = id; + mPackageName = packageName; + mLabelResId = labelResId; + mHorizontalDpi = horizontalDpi; + mVerticalDpi = verticalDpi; + } + + void writeToParcel(Parcel parcel) { + parcel.writeString(mId); + parcel.writeString(mPackageName); + parcel.writeInt(mLabelResId); + parcel.writeInt(mHorizontalDpi); + parcel.writeInt(mVerticalDpi); + } + + static Resolution createFromParcel(Parcel parcel) { + return new Resolution( + parcel.readString(), + parcel.readString(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt()); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Resolution{"); + builder.append("id: ").append(mId); + builder.append(", packageName: ").append(mPackageName); + builder.append(", labelResId: ").append(mLabelResId); + builder.append(", horizontalDpi: ").append(mHorizontalDpi); + builder.append(", verticalDpi: ").append(mVerticalDpi); + builder.append("}"); + return builder.toString(); + } + } + + /** + * This class specifies content margins. + */ + public static final class Margins { + private final int mLeftMils; + private final int mTopMils; + private final int mRightMils; + private final int mBottomMils; + + /** + * Gets the left margin in mils (thousands of an inch). + * + * @return The left margin. + */ + public int getLeftMils() { + return mLeftMils; + } + + /** + * Gets the top margin in mils (thousands of an inch). + * + * @return The top margin. + */ + public int getTopMils() { + return mTopMils; + } + + /** + * Gets the right margin in mils (thousands of an inch). + * + * @return The right margin. + */ + public int getRightMils() { + return mRightMils; + } + + /** + * Gets the bottom margin in mils (thousands of an inch). + * + * @return The bottom margin. + */ + public int getBottomMils() { + return mBottomMils; + } + + /** + * Creates a new instance. + * + * @param leftMils The left margin in mils (thousands of an inch). + * @param topMils The top margin in mils (thousands of an inch). + * @param rightMils The right margin in mils (thousands of an inch). + * @param bottomMils The bottom margin in mils (thousands of an inch). + * + * @throws IllegalArgumentException If the leftMils is less than zero. + * @throws IllegalArgumentException If the topMils is less than zero. + * @throws IllegalArgumentException If the rightMils is less than zero. + * @throws IllegalArgumentException If the bottomMils is less than zero. + */ + public Margins(int leftMils, int topMils, int rightMils, int bottomMils) { + if (leftMils < 0) { + throw new IllegalArgumentException("leftMils cannot be less than zero."); + } + if (topMils < 0) { + throw new IllegalArgumentException("topMils cannot be less than zero."); + } + if (rightMils < 0) { + throw new IllegalArgumentException("rightMils cannot be less than zero."); + } + if (bottomMils < 0) { + throw new IllegalArgumentException("bottomMils cannot be less than zero."); + } + mTopMils = topMils; + mLeftMils = leftMils; + mRightMils = rightMils; + mBottomMils = bottomMils; + } + + void writeToParcel(Parcel parcel) { + parcel.writeInt(mLeftMils); + parcel.writeInt(mTopMils); + parcel.writeInt(mRightMils); + parcel.writeInt(mBottomMils); + } + + static Margins createFromParcel(Parcel parcel) { + return new Margins( + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt()); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Margins{"); + builder.append("leftMils: ").append(mLeftMils); + builder.append(", topMils: ").append(mTopMils); + builder.append(", rightMils: ").append(mRightMils); + builder.append(", bottomMils: ").append(mBottomMils); + builder.append("}"); + return builder.toString(); + } + } + + /** + * Represents a printer tray. + */ + public static final class Tray { + private static final String LOG_TAG = "Tray"; + + private final String mId; + private final String mPackageName; + private final int mLabelResId; + + /** + * Gets the unique tray id. + * + * @return The unique tray id. + */ + public String getId() { + return mId; + } + + /** + * Gets the tray human readable label. + * + * @return The human readable label. + */ + public CharSequence getLabel(PackageManager packageManager) { + try { + return packageManager.getResourcesForApplication( + mPackageName).getString(mLabelResId); + } catch (NotFoundException nfe) { + Log.w(LOG_TAG, "Could not load resouce" + mLabelResId + + " from package " + mPackageName); + } catch (NameNotFoundException nnfee) { + Log.w(LOG_TAG, "Could not load resouce" + mLabelResId + + " from package " + mPackageName); + } + return null; + } + + /** + * Creates a new instance. + * + * @param id The unique tray id. + * @param packageName The name of the creating package. + * @param labelResId The resource id of a human readable label. + * + * @throws IllegalArgumentException If the id is empty. + * @throws IllegalArgumentException If the label is empty. + */ + public Tray(String id, String packageName, int labelResId) { + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id cannot be empty."); + } + if (TextUtils.isEmpty(packageName)) { + throw new IllegalArgumentException("packageName cannot be empty."); + } + if (labelResId <= 0) { + throw new IllegalArgumentException("label must be greater than zero."); + } + mId = id; + mPackageName = packageName; + mLabelResId = labelResId; + } + + void writeToParcel(Parcel parcel) { + parcel.writeString(mId); + parcel.writeString(mPackageName); + parcel.writeInt(mLabelResId); + } + + static Tray createFromParcel(Parcel parcel) { + return new Tray( + parcel.readString(), + parcel.readString(), + parcel.readInt()); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Tray{"); + builder.append("id: ").append(mId); + builder.append("id: ").append(mId); + builder.append(", packageName: ").append(mPackageName); + builder.append(", labelResId: ").append(mLabelResId); + builder.append("}"); + return builder.toString(); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrintAttributes{"); + builder.append("mediaSize: ").append(mMediaSize); + builder.append(", resolution: ").append(mResolution); + builder.append(", margins: ").append(mMargins); + builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode)); + builder.append(", colorMode: ").append(colorModeToString(mColorMode)); + builder.append(", fittingMode: ").append(fittingModeToString(mFittingMode)); + builder.append(", orientation: ").append(orientationToString(mOrientation)); + builder.append(", copies: ").append(mCopies); + return builder.toString(); + } + + private static String duplexModeToString(int duplexMode) { + switch (duplexMode) { + case DUPLEX_MODE_NONE: { + return "DUPLEX_MODE_NONE"; + } + case DUPLEX_MODE_LONG_EDGE: { + return "DUPLEX_MODE_LONG_EDGE"; + } + case DUPLEX_MODE_SHORT_EDGE: { + return "DUPLEX_MODE_SHORT_EDGE"; + } + default: + return "DUPLEX_MODE_UNKNOWN"; + } + } + + private static String colorModeToString(int colorMode) { + switch (colorMode) { + case COLOR_MODE_MONOCHROME: { + return "COLOR_MODE_MONOCHROME"; + } + case COLOR_MODE_COLOR: { + return "COLOR_MODE_COLOR"; + } + default: + return "COLOR_MODE_UNKNOWN"; + } + } + + private static String orientationToString(int orientation) { + switch (orientation) { + case ORIENTATION_PORTRAIT: { + return "ORIENTATION_PORTRAIT"; + } + case ORIENTATION_LANDSCAPE: { + return "ORIENTATION_LANDSCAPE"; + } + default: + return "ORIENTATION_UNKNOWN"; + } + } + + private static String fittingModeToString(int fittingMode) { + switch (fittingMode) { + case FITTING_MODE_NONE: { + return "FITTING_MODE_NONE"; + } + case FITTING_MODE_FIT_TO_PAGE: { + return "FITTING_MODE_FIT_TO_PAGE"; + } + default: + return "FITTING_MODE_UNKNOWN"; + } + } + + static void enforceValidDuplexMode(int duplexMode) { + if ((duplexMode & VALID_DUPLEX_MODES) == 0) { + throw new IllegalArgumentException("invalid duplex mode: " + duplexMode); + } + } + + static void enforceValidColorMode(int colorMode) { + if ((colorMode & VALID_COLOR_MODES) == 0) { + throw new IllegalArgumentException("invalid color mode: " + colorMode); + } + } + + static void enfoceValidFittingMode(int fittingMode) { + if ((fittingMode & VALID_FITTING_MODES) == 0) { + throw new IllegalArgumentException("invalid fitting mode: " + fittingMode); + } + } + + static void enforceValidOrientation(int orientation) { + if ((orientation & VALID_ORIENTATIONS) == 0) { + throw new IllegalArgumentException("invalid orientation: " + orientation); + } + } + + /** + * Builder for creating {@link PrintAttributes}. + */ + public static final class Builder { + private final PrintAttributes mAttributes = new PrintAttributes(); + + /** + * Sets the media size. + * + * @param mediaSize The media size. + * @return This builder. + */ + public Builder setMediaSize(MediaSize mediaSize) { + mAttributes.setMediaSize(mediaSize); + return this; + } + + /** + * Sets the resolution. + * + * @param resolution The resolution. + * @return This builder. + */ + public Builder setResolution(Resolution resolution) { + mAttributes.setResolution(resolution); + return this; + } + + /** + * Sets the margins. + * + * @param margins The margins. + * @return This builder. + */ + public Builder setMargins(Margins margins) { + mAttributes.setMargins(margins); + return this; + } + + /** + * Sets the input tray. + * + * @param inputTray The tray. + * @return This builder. + */ + public Builder setInputTray(Tray inputTray) { + mAttributes.setInputTray(inputTray); + return this; + } + + /** + * Sets the output tray. + * + * @param outputTray The tray. + * @return This builder. + */ + public Builder setOutputTray(Tray outputTray) { + mAttributes.setOutputTray(outputTray); + return this; + } + + /** + * Sets the duplex mode. + * + * @param duplexMode A valid duplex mode or zero. + * @return This builder. + * + * @see PrintAttributes#DUPLEX_MODE_NONE + * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE + * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE + */ + public Builder setDuplexMode(int duplexMode) { + if (Integer.bitCount(duplexMode) != 1) { + throw new IllegalArgumentException("can specify at most one duplexMode bit."); + } + mAttributes.setDuplexMode(duplexMode); + return this; + } + + /** + * Sets the color mode. + * + * @param colorMode A valid color mode or zero. + * @return This builder. + * + * @see PrintAttributes#COLOR_MODE_MONOCHROME + * @see PrintAttributes#COLOR_MODE_COLOR + */ + public Builder setColorMode(int colorMode) { + if (Integer.bitCount(colorMode) > 1) { + throw new IllegalArgumentException("can specify at most one colorMode bit."); + } + mAttributes.setColorMode(colorMode); + return this; + } + + /** + * Sets the fitting mode. + * + * @param fittingMode A valid fitting mode or zero. + * @return This builder. + * + * @see PrintAttributes#FITTING_MODE_NONE + * @see PrintAttributes#FITTING_MODE_FIT_TO_PAGE + */ + public Builder setFittingMode(int fittingMode) { + if (Integer.bitCount(fittingMode) > 1) { + throw new IllegalArgumentException("can specify at most one fittingMode bit."); + } + mAttributes.setFittingMode(fittingMode); + return this; + } + + /** + * Sets the orientation. + * + * @param orientation A valid orientation or zero. + * @return This builder. + * + * @see PrintAttributes#ORIENTATION_PORTRAIT + * @see PrintAttributes#ORIENTATION_LANDSCAPE + */ + public Builder setOrientation(int orientation) { + if (Integer.bitCount(orientation) > 1) { + throw new IllegalArgumentException("can specify at most one orientation bit."); + } + mAttributes.setOrientation(orientation); + return this; + } + + /** + * Sets the number of copies. + * + * @param copyCount A greater or equal to zero copy count. + * @return This builder. + */ + public Builder setCopyCount(int copyCount) { + mAttributes.setCopies(copyCount); + return this; + } + + /** + * Creates a new {@link PrintAttributes} instance. + * + * @return The new instance. + */ + public PrintAttributes create() { + return mAttributes; + } + } + + public static final Parcelable.Creator<PrintAttributes> CREATOR = + new Creator<PrintAttributes>() { + @Override + public PrintAttributes createFromParcel(Parcel parcel) { + return new PrintAttributes(parcel); + } + + @Override + public PrintAttributes[] newArray(int size) { + return new PrintAttributes[size]; + } + }; +} diff --git a/core/java/android/print/PrintFileAdapter.java b/core/java/android/print/PrintFileAdapter.java new file mode 100644 index 0000000..bbfc394 --- /dev/null +++ b/core/java/android/print/PrintFileAdapter.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.AsyncTask; +import android.os.CancellationSignal; +import android.os.CancellationSignal.OnCancelListener; +import android.util.Log; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Adapter for printing files. + */ +class PrintFileAdapter extends PrintAdapter { + + private static final String LOG_TAG = "PrintFileAdapter"; + + private final File mFile; + + private WriteFileAsyncTask mWriteFileAsyncTask; + + public PrintFileAdapter(File file) { + if (file == null) { + throw new IllegalArgumentException("File cannot be null!"); + } + mFile = file; + } + + @Override + public void onPrint(List<PageRange> pages, FileDescriptor destination, + CancellationSignal cancellationSignal, PrintProgressCallback progressListener) { + mWriteFileAsyncTask = new WriteFileAsyncTask(mFile, destination, cancellationSignal, + progressListener); + mWriteFileAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, + (Void[]) null); + + } + + @Override + public PrintAdapterInfo getInfo() { + // TODO: When we have PDF render library we should query the page count. + return new PrintAdapterInfo.Builder().create(); + } + + private static final class WriteFileAsyncTask extends AsyncTask<Void, Void, Void> { + + private final File mSource; + + private final FileDescriptor mDestination; + + private final PrintProgressCallback mProgressListener; + + private final CancellationSignal mCancellationSignal; + + public WriteFileAsyncTask(File source, FileDescriptor destination, + CancellationSignal cancellationSignal, PrintProgressCallback progressListener) { + mSource = source; + mDestination = destination; + mProgressListener = progressListener; + mCancellationSignal = cancellationSignal; + mCancellationSignal.setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel() { + cancel(true); + } + }); + } + + @Override + protected Void doInBackground(Void... params) { + InputStream in = null; + OutputStream out = new FileOutputStream(mDestination); + final byte[] buffer = new byte[8192]; + try { + in = new FileInputStream(mSource); + while (true) { + final int readByteCount = in.read(buffer); + if (readByteCount < 0) { + break; + } + out.write(buffer, 0, readByteCount); + } + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error writing data!", ioe); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + if (!isCancelled()) { + List<PageRange> pages = new ArrayList<PageRange>(); + pages.add(PageRange.ALL_PAGES); + mProgressListener.onPrintFinished(pages); + } else { + mProgressListener.onPrintFailed("Cancelled"); + } + } + return null; + } + } +} + diff --git a/core/java/android/print/PrintJob.java b/core/java/android/print/PrintJob.java new file mode 100644 index 0000000..f7cca87 --- /dev/null +++ b/core/java/android/print/PrintJob.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + + +/** + * This class represents a print job from the perspective of + * an application. + */ +public final class PrintJob { + + private final int mId; + + private final PrintManager mPrintManager; + + private PrintJobInfo mCachedInfo; + + PrintJob(PrintJobInfo info, PrintManager printManager) { + mCachedInfo = info; + mPrintManager = printManager; + mId = info.getId(); + } + + /** + * Gets the unique print job id. + * + * @return The id. + */ + public int getId() { + return mId; + } + + /** + * Gets the {@link PrintJobInfo} that describes this job. + * <p> + * <strong>Node:</strong>The returned info object is a snapshot of the + * current print job state. Every call to this method returns a fresh + * info object that reflects the current print job state. + * </p> + * + * @return The print job info. + */ + public PrintJobInfo getInfo() { + PrintJobInfo info = mPrintManager.getPrintJob(mId); + if (info != null) { + mCachedInfo = info; + } + return mCachedInfo; + } + + /** + * Cancels this print job. + */ + public void cancel() { + mPrintManager.cancelPrintJob(mId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PrintJob other = (PrintJob) obj; + return mId == other.mId; + } + + @Override + public int hashCode() { + return mId; + } +} diff --git a/core/java/android/print/PrintJobInfo.aidl b/core/java/android/print/PrintJobInfo.aidl new file mode 100644 index 0000000..fbca99c --- /dev/null +++ b/core/java/android/print/PrintJobInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +parcelable PrintJobInfo; diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java new file mode 100644 index 0000000..1a5d671 --- /dev/null +++ b/core/java/android/print/PrintJobInfo.java @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class represents the description of a print job. + */ +public final class PrintJobInfo implements Parcelable { + + /** Undefined print job id. */ + public static final int PRINT_JOB_ID_UNDEFINED = -1; + + /** + * Constant for matching any print job state. + * + * @hide + */ + public static final int STATE_ANY = -1; + + /** + * Print job state: The print job is being created but not yet + * ready to be printed. + * <p> + * Next valid states: {@link #STATE_QUEUED} + * </p> + */ + public static final int STATE_CREATED = 1; + + /** + * Print job status: The print jobs is created, it is ready + * to be printed and should be processed. + * <p> + * Next valid states: {@link #STATE_STARTED}, {@link #STATE_FAILED}, + * {@link #STATE_CANCELED} + * </p> + */ + public static final int STATE_QUEUED = 2; + + /** + * Print job status: The print job is being printed. + * <p> + * Next valid states: {@link #STATE_COMPLETED}, {@link #STATE_FAILED}, + * {@link #STATE_CANCELED} + * </p> + */ + public static final int STATE_STARTED = 3; + + /** + * Print job status: The print job was successfully printed. + * This is a terminal state. + * <p> + * Next valid states: None + * </p> + */ + public static final int STATE_COMPLETED = 4; + + /** + * Print job status: The print job was printing but printing failed. + * This is a terminal state. + * <p> + * Next valid states: None + * </p> + */ + public static final int STATE_FAILED = 5; + + /** + * Print job status: The print job was canceled. + * This is a terminal state. + * <p> + * Next valid states: None + * </p> + */ + public static final int STATE_CANCELED = 6; + + /** The unique print job id. */ + private int mId; + + /** The human readable print job label. */ + private CharSequence mLabel; + + /** The unique id of the printer. */ + private PrinterId mPrinterId; + + /** The status of the print job. */ + private int mState; + + /** The id of the app that created the job. */ + private int mAppId; + + /** The id of the user that created the job. */ + private int mUserId; + + /** Optional tag assigned by a print service.*/ + private String mTag; + + /** The pages to print */ + private PageRange[] mPageRanges; + + /** The print job attributes size. */ + private PrintAttributes mAttributes; + + /** + * Gets the unique print job id. + * + * @return The id. + */ + public int getId() { + return mId; + } + + /** + * Sets the unique print job id. + * + * @param The job id. + * + * @hide + */ + public void setId(int id) { + this.mId = id; + } + + /** + * Gets the human readable job label. + * + * @return The label. + */ + public CharSequence getLabel() { + return mLabel; + } + + /** + * Sets the human readable job label. + * + * @param label The label. + * + * @hide + */ + public void setLabel(CharSequence label) { + mLabel = label; + } + + /** + * Gets the unique target printer id. + * + * @return The target printer id. + */ + public PrinterId getPrinterId() { + return mPrinterId; + } + + /** + * Sets the unique target pritner id. + * + * @param printerId The target printer id. + * + * @hide + */ + public void setPrinterId(PrinterId printerId) { + mPrinterId = printerId; + } + + /** + * Gets the current job state. + * + * @return The job state. + */ + public int getState() { + return mState; + } + + /** + * Sets the current job state. + * + * @param state The job state. + * + * @hide + */ + public void setState(int state) { + mState = state; + } + + /** + * Sets the owning application id. + * + * @return The owning app id. + * + * @hide + */ + public int getAppId() { + return mAppId; + } + + /** + * Sets the owning application id. + * + * @param appId The owning app id. + * + * @hide + */ + public void setAppId(int appId) { + mAppId = appId; + } + + /** + * Gets the owning user id. + * + * @return The user id. + * + * @hide + */ + public int getUserId() { + return mUserId; + } + + /** + * Sets the owning user id. + * + * @param userId The user id. + * + * @hide + */ + public void setUserId(int userId) { + mUserId = userId; + } + + /** + * Gets the optional tag assigned by a print service. + * + * @return The tag. + */ + public String getTag() { + return mTag; + } + + /** + * Sets the optional tag assigned by a print service. + * + * @param tag The tag. + * + * @hide + */ + public void setTag(String tag) { + mTag = tag; + } + + /** + * Gets the included page ranges. + * + * @return The included page ranges or <code>null</code> if not set. + */ + public PageRange[] getPageRanges() { + return mPageRanges; + } + + /** + * Sets the included page ranges. + * + * @return The included page ranges. + * + * @hide + */ + public void setPageRanges(PageRange[] pageRanges) { + mPageRanges = pageRanges; + } + + /** + * Gets the print job attributes. + * + * @return The attributes. + */ + public PrintAttributes getAttributes() { + return mAttributes; + } + + /** + * Sets the print job attributes. + * + * @param attributes The attributes. + * + * @hide + */ + public void setAttributes(PrintAttributes attributes) { + mAttributes = attributes; + } + + /** @hide*/ + public PrintJobInfo() { + /* do nothing */ + } + + /** @hide */ + public PrintJobInfo(PrintJobInfo other) { + mId = other.mId; + mLabel = other.mLabel; + mPrinterId = other.mPrinterId; + mState = other.mState; + mAppId = other.mAppId; + mUserId = other.mUserId; + mAttributes = other.mAttributes; + } + + private PrintJobInfo(Parcel parcel) { + mId = parcel.readInt(); + mLabel = parcel.readCharSequence(); + mPrinterId = parcel.readParcelable(null); + mState = parcel.readInt(); + mAppId = parcel.readInt(); + mUserId = parcel.readInt(); + if (parcel.readInt() == 1) { + mPageRanges = (PageRange[]) parcel.readParcelableArray(null); + } + if (parcel.readInt() == 1) { + mAttributes = PrintAttributes.CREATOR.createFromParcel(parcel); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mId); + parcel.writeCharSequence(mLabel); + parcel.writeParcelable(mPrinterId, flags); + parcel.writeInt(mState); + parcel.writeInt(mAppId); + parcel.writeInt(mUserId); + if (mPageRanges != null) { + parcel.writeInt(1); + parcel.writeParcelableArray(mPageRanges, flags); + } else { + parcel.writeInt(0); + } + if (mAttributes != null) { + parcel.writeInt(1); + mAttributes.writeToParcel(parcel, flags); + } else { + parcel.writeInt(0); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrintJobInfo{"); + builder.append("label: ").append(mLabel); + builder.append(", id: ").append(mId); + builder.append(", status: ").append(stateToString(mState)); + builder.append(", printer: " + mPrinterId); + builder.append(", attributes: " + (mAttributes != null ? mAttributes.toString() : null)); + builder.append("}"); + return builder.toString(); + } + + /** @hide */ + public static String stateToString(int state) { + switch (state) { + case STATE_CREATED: { + return "STATUS_CREATED"; + } + case STATE_QUEUED: { + return "STATE_QUEUED"; + } + case STATE_STARTED: { + return "STATE_STARTED"; + } + case STATE_FAILED: { + return "STATUS_FAILED"; + } + case STATE_COMPLETED: { + return "STATUS_COMPLETED"; + } + case STATE_CANCELED: { + return "STATUS_CANCELED"; + } + default: { + return "STATUS_UNKNOWN"; + } + } + } + + + public static final Parcelable.Creator<PrintJobInfo> CREATOR = + new Creator<PrintJobInfo>() { + @Override + public PrintJobInfo createFromParcel(Parcel parcel) { + return new PrintJobInfo(parcel); + } + + @Override + public PrintJobInfo[] newArray(int size) { + return new PrintJobInfo[size]; + } + }; +} diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java new file mode 100644 index 0000000..df14a5c --- /dev/null +++ b/core/java/android/print/PrintManager.java @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.Context; +import android.content.IntentSender; +import android.content.IntentSender.SendIntentException; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.ICancellationSignal; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.print.PrintAdapter.PrintProgressCallback; +import android.util.Log; + +import com.android.internal.os.SomeArgs; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileDescriptor; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * System level service for accessing the printing capabilities of the platform. + * <p> + * To obtain a handle to the print manager do the following: + * </p> + * <pre> + * PrintManager printManager = + * (PrintManager) context.getSystemService(Context.PRINT_SERVICE); + * </pre> + */ +public final class PrintManager { + + private static final String LOG_TAG = "PrintManager"; + + /** @hide */ + public static final int APP_ID_ANY = -2; + + private final Context mContext; + + private final IPrintManager mService; + + private final int mUserId; + + private final int mAppId; + + private final PrintClient mPrintClient; + + private final Handler mHandler; + + /** + * Listener for the state of a print job. + */ + public static interface PrintJobStateListener { + public void onStateChanged(int state); + } + + /** + * Creates a new instance. + * + * @param context The current context in which to operate. + * @param service The backing system service. + * + * @hide + */ + public PrintManager(Context context, IPrintManager service, int userId, int appId) { + mContext = context; + mService = service; + mUserId = userId; + mAppId = appId; + mPrintClient = new PrintClient(this); + mHandler = new Handler(context.getMainLooper(), null, false) { + @Override + public void handleMessage(Message message) { + SomeArgs args = (SomeArgs) message.obj; + Context context = (Context) args.arg1; + IntentSender intent = (IntentSender) args.arg2; + args.recycle(); + try { + context.startIntentSender(intent, null, 0, 0, 0); + } catch (SendIntentException sie) { + Log.e(LOG_TAG, "Couldn't start print job config activity.", sie); + } + } + }; + } + + /** + * Creates an instance that can access all print jobs. + * + * @param userId The user id for which to get all print jobs. + * @return An instance of the caller has the permission to access + * all print jobs, null otherwise. + * + * @hide + */ + public PrintManager getGlobalPrintManagerForUser(int userId) { + return new PrintManager(mContext, mService, userId, APP_ID_ANY); + } + + PrintJobInfo getPrintJob(int printJobId) { + try { + return mService.getPrintJob(printJobId, mAppId, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error getting print job:" + printJobId, re); + } + return null; + } + + /** + * Gets the print jobs for this application. + * + * @return The print job list. + * + * @see PrintJob + */ + public List<PrintJob> getPrintJobs() { + try { + List<PrintJobInfo> printJobInfos = mService.getPrintJobs(mAppId, mUserId); + if (printJobInfos == null) { + return Collections.emptyList(); + } + final int printJobCount = printJobInfos.size(); + List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount); + for (int i = 0; i < printJobCount; i++) { + printJobs.add(new PrintJob(printJobInfos.get(i), this)); + } + return printJobs; + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error getting print jobs!", re); + } + return Collections.emptyList(); + } + + ICancellationSignal cancelPrintJob(int printJobId) { + try { + mService.cancelPrintJob(printJobId, mAppId, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error cancleing a print job:" + printJobId, re); + } + return null; + } + + /** + * Creates a print job for printing a file with default print attributes. + * + * @param printJobName A name for the new print job. + * @param pdfFile The PDF file to print. + * @param attributes The default print job attributes. + * @return The created print job. + */ + public PrintJob print(String printJobName, File pdfFile, PrintAttributes attributes) { + PrintFileAdapter printable = new PrintFileAdapter(pdfFile); + return print(printJobName, printable, attributes); + } + + /** + * Creates a print job for printing a {@link PrintAdapter} with default print + * attributes. + * + * @param printJobName A name for the new print job. + * @param printAdapter The printable adapter to print. + * @param attributes The default print job attributes. + * @return The created print job. + */ + public PrintJob print(String printJobName, PrintAdapter printAdapter, + PrintAttributes attributes) { + PrintAdapterDelegate delegate = new PrintAdapterDelegate(printAdapter, + mContext.getMainLooper()); + try { + PrintJobInfo printJob = mService.print(printJobName, mPrintClient, delegate, + attributes, mAppId, mUserId); + if (printJob != null) { + return new PrintJob(printJob, this); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error creating a print job", re); + } + return null; + } + + private static final class PrintClient extends IPrintClient.Stub { + + private final WeakReference<PrintManager> mWeakPrintManager; + + public PrintClient(PrintManager manager) { + mWeakPrintManager = new WeakReference<PrintManager>(manager); + } + + @Override + public void startPrintJobConfigActivity(IntentSender intent) { + PrintManager manager = mWeakPrintManager.get(); + if (manager != null) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = manager.mContext; + args.arg2 = intent; + manager.mHandler.obtainMessage(0, args).sendToTarget(); + } + } + } + + private static final class PrintAdapterDelegate extends IPrintAdapter.Stub { + private final Object mLock = new Object(); + + private PrintAdapter mPrintAdapter; + + private Handler mHandler; + + public PrintAdapterDelegate(PrintAdapter printAdapter, Looper looper) { + mPrintAdapter = printAdapter; + mHandler = new MyHandler(looper); + } + + @Override + public void start() { + synchronized (mLock) { + if (isFinishedLocked()) { + return; + } + mHandler.obtainMessage(MyHandler.MESSAGE_START, + mPrintAdapter).sendToTarget(); + } + } + + @Override + public void printAttributesChanged(PrintAttributes attributes) { + synchronized (mLock) { + if (isFinishedLocked()) { + return; + } + SomeArgs args = SomeArgs.obtain(); + args.arg1 = mPrintAdapter; + args.arg2 = attributes; + mHandler.obtainMessage(MyHandler.MESSAGE_PRINT_ATTRIBUTES_CHANGED, + args).sendToTarget(); + } + } + + @Override + public void print(List<PageRange> pages, ParcelFileDescriptor fd, + IPrintProgressListener progressListener) { + synchronized (mLock) { + if (isFinishedLocked()) { + return; + } + SomeArgs args = SomeArgs.obtain(); + args.arg1 = mPrintAdapter; + args.arg2 = pages; + args.arg3 = fd.getFileDescriptor(); + args.arg4 = progressListener; + mHandler.obtainMessage(MyHandler.MESSAGE_PRINT, args).sendToTarget(); + } + } + + @Override + public void finish() { + synchronized (mLock) { + if (isFinishedLocked()) { + return; + } + mHandler.obtainMessage(MyHandler.MESSAGE_FINIS, + mPrintAdapter).sendToTarget(); + } + } + + private boolean isFinishedLocked() { + return mPrintAdapter == null; + } + + private void finishLocked() { + mPrintAdapter = null; + mHandler = null; + } + + private final class MyHandler extends Handler { + public static final int MESSAGE_START = 1; + public static final int MESSAGE_PRINT_ATTRIBUTES_CHANGED = 2; + public static final int MESSAGE_PRINT = 3; + public static final int MESSAGE_FINIS = 4; + + public MyHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MESSAGE_START: { + PrintAdapter adapter = (PrintAdapter) message.obj; + adapter.onStart(); + } break; + + case MESSAGE_PRINT_ATTRIBUTES_CHANGED: { + SomeArgs args = (SomeArgs) message.obj; + PrintAdapter adapter = (PrintAdapter) args.arg1; + PrintAttributes attributes = (PrintAttributes) args.arg2; + args.recycle(); + adapter.onPrintAttributesChanged(attributes); + } break; + + case MESSAGE_PRINT: { + SomeArgs args = (SomeArgs) message.obj; + PrintAdapter adapter = (PrintAdapter) args.arg1; + @SuppressWarnings("unchecked") + List<PageRange> pages = (List<PageRange>) args.arg2; + final FileDescriptor fd = (FileDescriptor) args.arg3; + IPrintProgressListener listener = (IPrintProgressListener) args.arg4; + args.recycle(); + try { + ICancellationSignal remoteSignal = CancellationSignal.createTransport(); + listener.onWriteStarted(adapter.getInfo(), remoteSignal); + + CancellationSignal localSignal = CancellationSignal.fromTransport( + remoteSignal); + adapter.onPrint(pages, fd, localSignal, + new PrintProgressListenerWrapper(listener) { + @Override + public void onPrintFinished(List<PageRange> pages) { + IoUtils.closeQuietly(fd); + super.onPrintFinished(pages); + } + + @Override + public void onPrintFailed(CharSequence error) { + IoUtils.closeQuietly(fd); + super.onPrintFailed(error); + } + }); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error printing", re); + IoUtils.closeQuietly(fd); + } + } break; + + case MESSAGE_FINIS: { + PrintAdapter adapter = (PrintAdapter) message.obj; + adapter.onFinish(); + synchronized (mLock) { + finishLocked(); + } + } break; + + default: { + throw new IllegalArgumentException("Unknown message: " + + message.what); + } + } + } + } + } + + private static abstract class PrintProgressListenerWrapper extends PrintProgressCallback { + + private final IPrintProgressListener mWrappedListener; + + public PrintProgressListenerWrapper(IPrintProgressListener listener) { + mWrappedListener = listener; + } + + @Override + public void onPrintFinished(List<PageRange> pages) { + try { + mWrappedListener.onWriteFinished(pages); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onWriteFinished", re); + } + } + + @Override + public void onPrintFailed(CharSequence error) { + try { + mWrappedListener.onWriteFailed(error); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onWriteFailed", re); + } + } + } +} diff --git a/core/java/android/print/PrinterId.aidl b/core/java/android/print/PrinterId.aidl new file mode 100644 index 0000000..84f422f --- /dev/null +++ b/core/java/android/print/PrinterId.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +parcelable PrinterId; diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java new file mode 100644 index 0000000..b853eb0 --- /dev/null +++ b/core/java/android/print/PrinterId.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.ComponentName; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class represents the unique id of a printer. + */ +public final class PrinterId implements Parcelable { + + private final ComponentName mServiceComponentName; + + private final String mLocalId; + + /** + * Creates a new instance. + * + * @param serviceComponentName The managing print service. + * @param localId The unique id within the managing service. + * + * @hide + */ + public PrinterId(ComponentName serviceComponentName, String localId) { + mServiceComponentName = serviceComponentName; + mLocalId = localId; + } + + private PrinterId(Parcel parcel) { + mServiceComponentName = parcel.readParcelable(null); + mLocalId = parcel.readString(); + } + + /** + * The id of the print service this printer is managed by. + * + * @return The print service component name. + * + * @hide + */ + public ComponentName getServiceComponentName() { + return mServiceComponentName; + } + + /** + * Gets the local id of this printer in the context + * of the print service that manages it. + * + * @return The local id. + */ + public String getLocalId() { + return mLocalId; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeParcelable(mServiceComponentName, flags); + parcel.writeString(mLocalId); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + PrinterId other = (PrinterId) object; + if (mServiceComponentName == null) { + if (other.mServiceComponentName != null) { + return false; + } + } else if (!mServiceComponentName.equals(other.mServiceComponentName)) { + return false; + } + if (mLocalId != other.mLocalId) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int hashCode = 1; + hashCode = prime * hashCode + ((mServiceComponentName != null) + ? mServiceComponentName.hashCode() : 1); + hashCode = prime * hashCode + mLocalId.hashCode(); + return hashCode; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrinterId{"); + builder.append(mServiceComponentName.flattenToString()); + builder.append(":"); + builder.append(mLocalId); + builder.append('}'); + return builder.toString(); + } + + /** + * @hide + */ + public String flattenToString() { + return mServiceComponentName.flattenToString() + ":" + mLocalId; + } + + /** + * @hide + */ + public static PrinterId unflattenFromString(String string) { + String[] split = string.split(":"); + if (split.length != 2) { + throw new IllegalArgumentException("Not well-formed printer id:" + string); + } + ComponentName component = ComponentName.unflattenFromString(split[0]); + String localId = String.valueOf(split[1]); + return new PrinterId(component, localId); + } + + public static final Parcelable.Creator<PrinterId> CREATOR = + new Creator<PrinterId>() { + @Override + public PrinterId createFromParcel(Parcel parcel) { + return new PrinterId(parcel); + } + + @Override + public PrinterId[] newArray(int size) { + return new PrinterId[size]; + } + }; +} diff --git a/core/java/android/print/PrinterInfo.aidl b/core/java/android/print/PrinterInfo.aidl new file mode 100644 index 0000000..6ec5a58 --- /dev/null +++ b/core/java/android/print/PrinterInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +parcelable PrinterInfo; diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java new file mode 100644 index 0000000..9283472 --- /dev/null +++ b/core/java/android/print/PrinterInfo.java @@ -0,0 +1,798 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.Parcel; +import android.os.Parcelable; +import android.print.PrintAttributes.Margins; +import android.print.PrintAttributes.MediaSize; +import android.print.PrintAttributes.Resolution; +import android.print.PrintAttributes.Tray; +import android.text.TextUtils; +import android.util.SparseIntArray; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents the description of a printer. A description + * contains the printer id, human readable name, status, and available + * options for various printer capabilities, such as media size, etc. + */ +public final class PrinterInfo implements Parcelable { + /** + * Undefined default value. + * + * @hide + */ + public static final int DEFAULT_UNDEFINED = -1; + + private static final int MIN_COPIES = 1; + + private static final int PROPERTY_MEDIA_SIZE = 0; + private static final int PROPERTY_RESOLUTION = 1; + private static final int PROPERTY_INPUT_TRAY = 2; + private static final int PROPERTY_OUTPUT_TRAY = 3; + private static final int PROPERTY_DUPLEX_MODE = 4; + private static final int PROPERTY_COLOR_MODE = 5; + private static final int PROPERTY_FITTING_MODE = 6; + private static final int PROPERTY_ORIENTATION = 7; + + /** Printer status: the printer is ready to print. */ + public static final int STATUS_READY = 1; + + // TODO: Add printer status constants. + + private PrinterId mId; + private CharSequence mLabel; + private int mStatus; + + private Margins mMinMargins; + private final List<MediaSize> mMediaSizes = new ArrayList<MediaSize>(); // required + private final List<Resolution> mResolutions = new ArrayList<Resolution>(); // required + private List<Tray> mInputTrays; + private List<Tray> mOutputTrays; + + private int mDuplexModes; + private int mColorModes; + private int mFittingModes; + private int mOrientations; + + private final SparseIntArray mDefaults = new SparseIntArray(); + private Margins mDefaultMargins; + + private PrinterInfo() { + mDefaults.put(PROPERTY_MEDIA_SIZE, DEFAULT_UNDEFINED); + mDefaults.put(PROPERTY_RESOLUTION, DEFAULT_UNDEFINED); + mDefaults.put(PROPERTY_INPUT_TRAY, DEFAULT_UNDEFINED); + mDefaults.put(PROPERTY_OUTPUT_TRAY, DEFAULT_UNDEFINED); + mDefaults.put(PROPERTY_DUPLEX_MODE, DEFAULT_UNDEFINED); + mDefaults.put(PROPERTY_COLOR_MODE, DEFAULT_UNDEFINED); + mDefaults.put(PROPERTY_FITTING_MODE, DEFAULT_UNDEFINED); + mDefaults.put(PROPERTY_ORIENTATION, DEFAULT_UNDEFINED); + } + + /** + * Get the globally unique printer id. + * + * @return The printer id. + */ + public PrinterId getId() { + return mId; + } + + /** + * Gets the human readable printer label. + * + * @return The human readable label. + */ + public CharSequence getLabel() { + return mLabel; + } + + /** + * Gets the status of the printer. + * + * @return The status. + */ + public int getStatus() { + return mStatus; + } + + /** + * Gets the supported media sizes. + * + * @return The supported media sizes. + */ + public List<MediaSize> getMediaSizes() { + return mMediaSizes; + } + + /** + * Gets the supported resolutions. + * + * @return The supported resolutions. + */ + public List<Resolution> getResolutions() { + return mResolutions; + } + + /** + * Gets the minimal supported margins. + * + * @return The minimal margins. + */ + public Margins getMinMargins() { + return mMinMargins; + } + + /** + * Gets the available input trays. + * + * @return The input trays. + */ + public List<Tray> getInputTrays() { + return mInputTrays; + } + + /** + * Gets the available output trays. + * + * @return The output trays. + */ + public List<Tray> getOutputTrays() { + return mOutputTrays; + } + + /** + * Gets the supported duplex modes. + * + * @return The duplex modes. + * + * @see PrintAttributes#DUPLEX_MODE_NONE + * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE + * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE + */ + public int getDuplexModes() { + return mDuplexModes; + } + + /** + * Gets the supported color modes. + * + * @return The color modes. + * + * @see PrintAttributes#COLOR_MODE_COLOR + * @see PrintAttributes#COLOR_MODE_MONOCHROME + */ + public int getColorModes() { + return mColorModes; + } + + /** + * Gets the supported fitting modes. + * + * @return The fitting modes. + * + * @see PrintAttributes#FITTING_MODE_NONE + * @see PrintAttributes#FITTING_MODE_FIT_TO_PAGE + */ + public int getFittingModes() { + return mFittingModes; + } + + /** + * Gets the supported orientations. + * + * @return The orientations. + * + * @see PrintAttributes#ORIENTATION_PORTRAIT + * @see PrintAttributes#ORIENTATION_LANDSCAPE + */ + public int getOrientations() { + return mOrientations; + } + + /** + * Gets the default print attributes. + * + * @param outAttributes The attributes to populated. + */ + public void getDefaults(PrintAttributes outAttributes) { + outAttributes.clear(); + + // TODO: Do we want a printer to specify default copies? + outAttributes.setCopies(MIN_COPIES); + + outAttributes.setMargins(mDefaultMargins); + + final int mediaSizeIndex = mDefaults.get(PROPERTY_MEDIA_SIZE); + if (mediaSizeIndex >= 0) { + outAttributes.setMediaSize(mMediaSizes.get(mediaSizeIndex)); + } + + final int resolutionIndex = mDefaults.get(PROPERTY_RESOLUTION); + if (resolutionIndex >= 0) { + outAttributes.setResolution(mResolutions.get(resolutionIndex)); + } + + final int inputTrayIndex = mDefaults.get(PROPERTY_INPUT_TRAY); + if (inputTrayIndex >= 0) { + outAttributes.setInputTray(mInputTrays.get(inputTrayIndex)); + } + + final int outputTrayIndex = mDefaults.get(PROPERTY_OUTPUT_TRAY); + if (outputTrayIndex >= 0) { + outAttributes.setOutputTray(mOutputTrays.get(outputTrayIndex)); + } + + final int duplexMode = mDefaults.get(PROPERTY_DUPLEX_MODE); + if (duplexMode > 0) { + outAttributes.setDuplexMode(duplexMode); + } + + final int colorMode = mDefaults.get(PROPERTY_COLOR_MODE); + if (colorMode > 0) { + outAttributes.setColorMode(mColorModes & colorMode); + } + + final int fittingMode = mDefaults.get(PROPERTY_FITTING_MODE); + if (fittingMode > 0) { + outAttributes.setFittingMode(fittingMode); + } + + final int orientation = mDefaults.get(PROPERTY_ORIENTATION); + if (orientation > 0) { + outAttributes.setOrientation(orientation); + } + } + + private PrinterInfo(Parcel parcel) { + mId = parcel.readParcelable(null); + mLabel = parcel.readCharSequence(); + mStatus = parcel.readInt(); + + mMinMargins = readMargins(parcel); + readMediaSizes(parcel); + readResolutions(parcel); + mInputTrays = readInputTrays(parcel); + mOutputTrays = readOutputTrays(parcel); + + mColorModes = parcel.readInt(); + mDuplexModes = parcel.readInt(); + mFittingModes = parcel.readInt(); + mOrientations = parcel.readInt(); + + readDefaults(parcel); + mDefaultMargins = readMargins(parcel); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeParcelable(mId, flags); + parcel.writeCharSequence(mLabel); + parcel.writeInt(mStatus); + + writeMargins(mMinMargins, parcel); + writeMediaSizes(parcel); + writeResolutions(parcel); + writeInputTrays(parcel); + writeOutputTrays(parcel); + + parcel.writeInt(mColorModes); + parcel.writeInt(mDuplexModes); + parcel.writeInt(mFittingModes); + parcel.writeInt(mOrientations); + + writeDefaults(parcel); + writeMargins(mDefaultMargins, parcel); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrinterInfo{"); + builder.append(mId).append(", \""); + builder.append(mLabel); + builder.append("\"}"); + return builder.toString(); + } + + private void writeMediaSizes(Parcel parcel) { + if (mMediaSizes == null) { + parcel.writeInt(0); + return; + } + final int mediaSizeCount = mMediaSizes.size(); + parcel.writeInt(mediaSizeCount); + for (int i = 0; i < mediaSizeCount; i++) { + mMediaSizes.get(i).writeToParcel(parcel); + } + } + + private void readMediaSizes(Parcel parcel) { + final int mediaSizeCount = parcel.readInt(); + for (int i = 0; i < mediaSizeCount; i++) { + mMediaSizes.add(MediaSize.createFromParcel(parcel)); + } + } + + private void writeResolutions(Parcel parcel) { + final int resolutionCount = mResolutions.size(); + parcel.writeInt(resolutionCount); + for (int i = 0; i < resolutionCount; i++) { + mResolutions.get(i).writeToParcel(parcel); + } + } + + private void readResolutions(Parcel parcel) { + final int resolutionCount = parcel.readInt(); + for (int i = 0; i < resolutionCount; i++) { + mResolutions.add(Resolution.createFromParcel(parcel)); + } + } + + private void writeMargins(Margins margins, Parcel parcel) { + if (margins == null) { + parcel.writeInt(0); + } else { + parcel.writeInt(1); + margins.writeToParcel(parcel); + } + } + + private Margins readMargins(Parcel parcel) { + return (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null; + } + + private void writeInputTrays(Parcel parcel) { + if (mInputTrays == null) { + parcel.writeInt(0); + return; + } + final int inputTrayCount = mInputTrays.size(); + parcel.writeInt(inputTrayCount); + for (int i = 0; i < inputTrayCount; i++) { + mInputTrays.get(i).writeToParcel(parcel); + } + } + + private List<Tray> readInputTrays(Parcel parcel) { + final int inputTrayCount = parcel.readInt(); + if (inputTrayCount <= 0) { + return null; + } + List<Tray> inputTrays = new ArrayList<Tray>(inputTrayCount); + for (int i = 0; i < inputTrayCount; i++) { + inputTrays.add(Tray.createFromParcel(parcel)); + } + return inputTrays; + } + + private void writeOutputTrays(Parcel parcel) { + if (mOutputTrays == null) { + parcel.writeInt(0); + return; + } + final int outputTrayCount = mOutputTrays.size(); + parcel.writeInt(outputTrayCount); + for (int i = 0; i < outputTrayCount; i++) { + mOutputTrays.get(i).writeToParcel(parcel); + } + } + + private List<Tray> readOutputTrays(Parcel parcel) { + final int outputTrayCount = parcel.readInt(); + if (outputTrayCount <= 0) { + return null; + } + List<Tray> outputTrays = new ArrayList<Tray>(outputTrayCount); + for (int i = 0; i < outputTrayCount; i++) { + outputTrays.add(Tray.createFromParcel(parcel)); + } + return outputTrays; + } + + private void readDefaults(Parcel parcel) { + final int defaultCount = parcel.readInt(); + for (int i = 0; i < defaultCount; i++) { + mDefaults.append(mDefaults.size(), parcel.readInt()); + } + } + + private void writeDefaults(Parcel parcel) { + final int defaultCount = mDefaults.size(); + parcel.writeInt(defaultCount); + for (int i = 0; i < defaultCount; i++) { + parcel.writeInt(mDefaults.valueAt(i)); + } + } + + /** + * Builder for creating of a {@link PrinterInfo}. This class is responsible + * to enforce that all required attributes have at least one default value. + * In other words, this class creates only well-formed {@link PrinterInfo}s. + * <p> + * Look at the individual methods for a reference whether a property is + * required or if it is optional. + * </p> + */ + public static final class Builder { + private final PrinterInfo mPrinterInfo; + + /** + * Creates a new instance. + * + * @param printerId The printer id. + * @param label The human readable printer label. + * + * @throws IllegalArgumentException IF the printer id is null. + * @throws IllegalArgumentException IF the label is empty. + */ + public Builder(PrinterId printerId, CharSequence label) { + if (printerId == null) { + throw new IllegalArgumentException("printerId cannot be null."); + } + if (TextUtils.isEmpty(label)) { + throw new IllegalArgumentException("label cannot be empty."); + } + mPrinterInfo = new PrinterInfo(); + mPrinterInfo.mLabel = label; + mPrinterInfo.mId = printerId; + } + + /** + * Sets the printer status. + * <p> + * <strong>Required:</strong> Yes + * </p> + * + * @param status The status. + * @return This builder. + */ + public Builder setStatus(int status) { + mPrinterInfo.mStatus = status; + return this; + } + + /** + * Adds a supported media size. + * <p> + * <strong>Required:</strong> Yes + * </p> + * + * @param mediaSize A media size. + * @param isDefault Whether this is the default. + * @return This builder. + * @throws IllegalArgumentException If set as default and there + * is already a default. + * + * @see PrintAttributes.MediaSize + */ + public Builder addMediaSize(MediaSize mediaSize, boolean isDefault) { + final int insertionIndex = mPrinterInfo.mMediaSizes.size(); + mPrinterInfo.mMediaSizes.add(mediaSize); + if (isDefault) { + throwIfDefaultAlreadySpecified(PROPERTY_MEDIA_SIZE); + mPrinterInfo.mDefaults.put(PROPERTY_MEDIA_SIZE, insertionIndex); + } + return this; + } + + /** + * Adds a supported resolution. + * <p> + * <strong>Required:</strong> Yes + * </p> + * + * @param resolution A resolution. + * @param isDefault Whether this is the default. + * @return This builder. + * + * @throws IllegalArgumentException If set as default and there + * is already a default. + * + * @see PrintAttributes.Resolution + */ + public Builder addResolution(Resolution resolution, boolean isDefault) { + final int insertionIndex = mPrinterInfo.mResolutions.size(); + mPrinterInfo.mResolutions.add(resolution); + if (isDefault) { + throwIfDefaultAlreadySpecified(PROPERTY_RESOLUTION); + mPrinterInfo.mDefaults.put(PROPERTY_RESOLUTION, insertionIndex); + } + return this; + } + + /** + * Sets the minimal margins. + * <p> + * <strong>Required:</strong> No + * </p> + * + * @param margins The margins. + * @param defaultMargins The default margins. + * @return This builder. + * + * @see PrintAttributes.Margins + */ + public Builder setMinMargins(Margins margins, Margins defaultMargins) { + if (margins.getLeftMils() > defaultMargins.getLeftMils() + || margins.getTopMils() > defaultMargins.getTopMils() + || margins.getRightMils() < defaultMargins.getRightMils() + || margins.getBottomMils() < defaultMargins.getBottomMils()) { + throw new IllegalArgumentException("Default margins" + + " cannot be outside of the min margins."); + } + mPrinterInfo.mMinMargins = margins; + mPrinterInfo.mDefaultMargins = defaultMargins; + return this; + } + + /** + * Adds an input tray. + * <p> + * <strong>Required:</strong> No + * </p> + * + * @param inputTray A tray. + * @param isDefault Whether this is the default. + * @return This builder. + * + * @throws IllegalArgumentException If set as default and there + * is already a default. + * + * @see PrintAttributes.Tray + */ + public Builder addInputTray(Tray inputTray, boolean isDefault) { + if (mPrinterInfo.mInputTrays == null) { + mPrinterInfo.mInputTrays = new ArrayList<Tray>(); + } + final int insertionIndex = mPrinterInfo.mInputTrays.size(); + mPrinterInfo.mInputTrays.add(inputTray); + if (isDefault) { + throwIfDefaultAlreadySpecified(PROPERTY_INPUT_TRAY); + mPrinterInfo.mDefaults.put(PROPERTY_INPUT_TRAY, insertionIndex); + } + return this; + } + + /** + * Adds an output tray. + * <p> + * <strong>Required:</strong> No + * </p> + * + * @param outputTray A tray. + * @param isDefault Whether this is the default. + * @return This builder. + * + * @throws IllegalArgumentException If set as default and there + * is already a default. + * + * @see PrintAttributes.Tray + */ + public Builder addOutputTray(Tray outputTray, boolean isDefault) { + if (mPrinterInfo.mOutputTrays == null) { + mPrinterInfo.mOutputTrays = new ArrayList<Tray>(); + } + final int insertionIndex = mPrinterInfo.mOutputTrays.size(); + mPrinterInfo.mOutputTrays.add(outputTray); + if (isDefault) { + throwIfDefaultAlreadySpecified(PROPERTY_OUTPUT_TRAY); + mPrinterInfo.mDefaults.put(PROPERTY_OUTPUT_TRAY, insertionIndex); + } + return this; + } + + /** + * Sets the color modes. + * <p> + * <strong>Required:</strong> Yes + * </p> + * + * @param colorModes The color mode bit mask. + * @param defaultColorMode The default color mode. + * @return This builder. + * + * @throws IllegalArgumentException If color modes contains an invalid + * mode bit or if the default color mode is invalid. + * + * @see PrintAttributes#COLOR_MODE_COLOR + * @see PrintAttributes#COLOR_MODE_MONOCHROME + */ + public Builder setColorModes(int colorModes, int defaultColorMode) { + int currentModes = colorModes; + while (currentModes > 0) { + final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes)); + currentModes &= ~currentMode; + PrintAttributes.enforceValidColorMode(currentMode); + } + if ((colorModes & defaultColorMode) == 0) { + throw new IllegalArgumentException("Default color mode not in color modes."); + } + PrintAttributes.enforceValidColorMode(colorModes); + mPrinterInfo.mColorModes = colorModes; + mPrinterInfo.mDefaults.put(PROPERTY_COLOR_MODE, defaultColorMode); + return this; + } + + /** + * Set the duplex modes. + * <p> + * <strong>Required:</strong> No + * </p> + * + * @param duplexModes The duplex mode bit mask. + * @param defaultDuplexMode The default duplex mode. + * @return This builder. + * + * @throws IllegalArgumentException If duplex modes contains an invalid + * mode bit or if the default duplex mode is invalid. + * + * @see PrintAttributes#DUPLEX_MODE_NONE + * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE + * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE + */ + public Builder setDuplexModes(int duplexModes, int defaultDuplexMode) { + int currentModes = duplexModes; + while (currentModes > 0) { + final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes)); + currentModes &= ~currentMode; + PrintAttributes.enforceValidDuplexMode(currentMode); + } + if ((duplexModes & defaultDuplexMode) == 0) { + throw new IllegalArgumentException("Default duplex mode not in duplex modes."); + } + PrintAttributes.enforceValidDuplexMode(defaultDuplexMode); + mPrinterInfo.mDuplexModes = duplexModes; + mPrinterInfo.mDefaults.put(PROPERTY_DUPLEX_MODE, defaultDuplexMode); + return this; + } + + /** + * Sets the fitting modes. + * <p> + * <strong>Required:</strong> No + * </p> + * + * @param fittingModes The fitting mode bit mask. + * @param defaultFittingMode The default fitting mode. + * @return This builder. + * + * @throws IllegalArgumentException If fitting modes contains an invalid + * mode bit or if the default fitting mode is invalid. + * + * @see PrintAttributes#FITTING_MODE_NONE + * @see PrintAttributes#FITTING_MODE_FIT_TO_PAGE + */ + public Builder setFittingModes(int fittingModes, int defaultFittingMode) { + int currentModes = fittingModes; + while (currentModes > 0) { + final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes)); + currentModes &= ~currentMode; + PrintAttributes.enfoceValidFittingMode(currentMode); + } + if ((fittingModes & defaultFittingMode) == 0) { + throw new IllegalArgumentException("Default fitting mode not in fiting modes."); + } + PrintAttributes.enfoceValidFittingMode(defaultFittingMode); + mPrinterInfo.mFittingModes = fittingModes; + mPrinterInfo.mDefaults.put(PROPERTY_FITTING_MODE, defaultFittingMode); + return this; + } + + /** + * Sets the orientations. + * <p> + * <strong>Required:</strong> Yes + * </p> + * + * @param orientations The orientation bit mask. + * @param defaultOrientation The default orientation. + * @return This builder. + * + * @throws IllegalArgumentException If orientations contains an invalid + * mode bit or if the default orientation is invalid. + * + * @see PrintAttributes#ORIENTATION_PORTRAIT + * @see PrintAttributes#ORIENTATION_LANDSCAPE + */ + public Builder setOrientations(int orientations, int defaultOrientation) { + int currentOrientaions = orientations; + while (currentOrientaions > 0) { + final int currentOrnt = (1 << Integer.numberOfTrailingZeros(currentOrientaions)); + currentOrientaions &= ~currentOrnt; + PrintAttributes.enforceValidOrientation(currentOrnt); + } + if ((orientations & defaultOrientation) == 0) { + throw new IllegalArgumentException("Default orientation not in orientations."); + } + PrintAttributes.enforceValidOrientation(defaultOrientation); + mPrinterInfo.mOrientations = orientations; + mPrinterInfo.mDefaults.put(PROPERTY_ORIENTATION, defaultOrientation); + return this; + } + + /** + * Crates a new {@link PrinterInfo} enforcing that all required properties + * have need specified. See individual methods in this class for reference + * about required attributes. + * + * @return A new {@link PrinterInfo}. + * + * @throws IllegalStateException If a required attribute was not specified. + */ + public PrinterInfo create() { + if (mPrinterInfo.mMediaSizes == null || mPrinterInfo.mMediaSizes.isEmpty()) { + throw new IllegalStateException("No media size specified."); + } + if (mPrinterInfo.mDefaults.valueAt(PROPERTY_MEDIA_SIZE) == DEFAULT_UNDEFINED) { + throw new IllegalStateException("No default media size specified."); + } + if (mPrinterInfo.mResolutions == null || mPrinterInfo.mResolutions.isEmpty()) { + throw new IllegalStateException("No resolution specified."); + } + if (mPrinterInfo.mDefaults.valueAt(PROPERTY_RESOLUTION) == DEFAULT_UNDEFINED) { + throw new IllegalStateException("No default resolution specified."); + } + if (mPrinterInfo.mColorModes == 0) { + throw new IllegalStateException("No color mode specified."); + } + if (mPrinterInfo.mDefaults.valueAt(PROPERTY_COLOR_MODE) == DEFAULT_UNDEFINED) { + throw new IllegalStateException("No default color mode specified."); + } + if (mPrinterInfo.mOrientations == 0) { + throw new IllegalStateException("No oprientation specified."); + } + if (mPrinterInfo.mDefaults.valueAt(PROPERTY_ORIENTATION) == DEFAULT_UNDEFINED) { + throw new IllegalStateException("No default orientation specified."); + } + if (mPrinterInfo.mMinMargins == null) { + mPrinterInfo.mMinMargins = new Margins(0, 0, 0, 0); + } + if (mPrinterInfo.mDefaultMargins == null) { + mPrinterInfo.mDefaultMargins = mPrinterInfo.mMinMargins; + } + return mPrinterInfo; + } + + private void throwIfDefaultAlreadySpecified(int propertyIndex) { + if (mPrinterInfo.mDefaults.get(propertyIndex) != DEFAULT_UNDEFINED) { + throw new IllegalArgumentException("Default already specified."); + } + } + } + + public static final Parcelable.Creator<PrinterInfo> CREATOR = + new Parcelable.Creator<PrinterInfo>() { + @Override + public PrinterInfo createFromParcel(Parcel parcel) { + return new PrinterInfo(parcel); + } + + @Override + public PrinterInfo[] newArray(int size) { + return new PrinterInfo[size]; + } + }; +} diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl new file mode 100644 index 0000000..eabd96d --- /dev/null +++ b/core/java/android/printservice/IPrintService.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.printservice; + +import android.os.ICancellationSignal; +import android.print.PrintJobInfo; +import android.print.PrinterId; +import android.printservice.IPrintServiceClient; + +/** + * Top-level interface to a print service component. + * + * @hide + */ +oneway interface IPrintService { + void setClient(IPrintServiceClient client); + void requestCancelPrintJob(in PrintJobInfo printJob); + void onPrintJobQueued(in PrintJobInfo printJob); + void startPrinterDiscovery(); + void stopPrinterDiscovery(); +} diff --git a/core/java/android/printservice/IPrintServiceClient.aidl b/core/java/android/printservice/IPrintServiceClient.aidl new file mode 100644 index 0000000..cff8c02 --- /dev/null +++ b/core/java/android/printservice/IPrintServiceClient.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.printservice; + +import android.os.ParcelFileDescriptor; +import android.print.PrintJobInfo; +import android.print.PrinterId; +import android.print.PrinterInfo; + +/** + * The top-level interface from a print service to the system. + * + * @hide + */ +interface IPrintServiceClient { + List<PrintJobInfo> getPrintJobs(); + PrintJobInfo getPrintJob(int printJobId); + boolean setPrintJobState(int printJobId, int status); + boolean setPrintJobTag(int printJobId, String tag); + oneway void writePrintJobData(in ParcelFileDescriptor fd, int printJobId); + oneway void addDiscoveredPrinters(in List<PrinterInfo> printers); + oneway void removeDiscoveredPrinters(in List<PrinterId> printers); +} diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java new file mode 100644 index 0000000..9688761 --- /dev/null +++ b/core/java/android/printservice/PrintJob.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.printservice; + +import java.io.FileDescriptor; +import java.io.IOException; + +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.print.PrintJobInfo; +import android.util.Log; + +/** + * This class represents a print job from the perspective of a + * print service. It provides APIs for observing the print job + * state and performing operations on the print job. + */ +public final class PrintJob { + + private static final String LOG_TAG = "PrintJob"; + + private final int mId; + + private final IPrintServiceClient mPrintServiceClient; + + private PrintJobInfo mCachedInfo; + + PrintJob(PrintJobInfo info, IPrintServiceClient client) { + if (client == null) { + throw new IllegalStateException("Print serivice not connected!"); + } + mCachedInfo = info; + mId = info.getId(); + mPrintServiceClient = client; + } + + /** + * Gets the unique print job id. + * + * @return The id. + */ + public int getId() { + return mId; + } + + /** + * Gets the {@link PrintJobInfo} that describes this job. + * <p> + * <strong>Node:</strong>The returned info object is a snapshot of the + * current print job state. Every call to this method returns a fresh + * info object that reflects the current print jobs state. + * </p> + * + * @return The print job info. + */ + public PrintJobInfo getInfo() { + PrintJobInfo info = null; + try { + info = mPrintServiceClient.getPrintJob(mId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Couldn't get info for job: " + mId, re); + } + if (info != null) { + mCachedInfo = info; + } + return mCachedInfo; + } + + /** + * Gets whether this print job is queued. Such a print job is + * ready to be printed and can be started. + * + * @return Whether the print job is queued. + * + * @see #start() + */ + public boolean isQueued() { + return getInfo().getState() == PrintJobInfo.STATE_QUEUED; + } + + /** + * Gets whether this print job is started. Such a print job is + * being printed and can be completed or canceled or failed. + * + * @return Whether the print job is started. + * + * @see #complete() + * @see #cancel() + * @see #fail() + */ + public boolean isStarted() { + return getInfo().getState() == PrintJobInfo.STATE_STARTED; + } + + /** + * Starts the print job. You should call this method if {@link + * #isQueued()} returns true and you started printing. + * + * @return Whether the job as started. + * + * @see #isQueued() + */ + public boolean start() { + if (isQueued()) { + return setState(PrintJobInfo.STATE_STARTED); + } + return false; + } + + /** + * Completes the print job. You should call this method if {@link + * #isStarted()} returns true and you are done printing. + * + * @return Whether the job as completed. + * + * @see #isStarted() + */ + public boolean complete() { + if (isStarted()) { + return setState(PrintJobInfo.STATE_COMPLETED); + } + return false; + } + + /** + * Fails the print job. You should call this method if {@link + * #isStarted()} returns true you filed while printing. + * + * @return Whether the job as failed. + * + * @see #isStarted() + */ + public boolean fail(CharSequence error) { + // TODO: Propagate the error message to the UI. + if (isStarted()) { + return setState(PrintJobInfo.STATE_FAILED); + } + return false; + } + + /** + * Cancels the print job. You should call this method if {@link + * #isStarted()} returns true and you canceled the print job as a + * response to a call to {@link PrintService#onRequestCancelPrintJob( + * PrintJob)}. + * + * @return Whether the job as canceled. + * + * @see #isStarted() + */ + public boolean cancel() { + if (isStarted()) { + return setState(PrintJobInfo.STATE_CANCELED); + } + return false; + } + + /** + * Sets a tag that is valid in the context of a {@link PrintService} + * and is not interpreted by the system. For example, a print service + * may set as a tag the key of the print job returned by a remote + * print server, if the printing is off handed to a cloud based service. + * + * @param tag The tag. + * @return True if the tag was set, false otherwise. + */ + public boolean setTag(String tag) { + try { + return mPrintServiceClient.setPrintJobTag(mId, tag); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error setting tag for job:" + mId, re); + } + return false; + } + + /** + * Gets the data associated with this print job. It is a responsibility of + * the print service to open a stream to the returned file descriptor + * and fully read the content. + * + * @return A file descriptor for reading the data or <code>null</code>. + */ + public final FileDescriptor getData() { + ParcelFileDescriptor source = null; + ParcelFileDescriptor sink = null; + try { + ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe(); + source = fds[0]; + sink = fds[1]; + mPrintServiceClient.writePrintJobData(sink, mId); + return source.getFileDescriptor(); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error calling getting print job data!", ioe); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling getting print job data!", re); + } finally { + if (sink != null) { + try { + sink.close(); + } catch (IOException ioe) { + /* ignore */ + } + } + } + return null; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PrintJob other = (PrintJob) obj; + return (mId == other.mId); + } + + @Override + public int hashCode() { + return mId; + } + + private boolean setState(int state) { + // Best effort - update the state of the cached info since + // we may not be able to re-fetch it later if the job gets + // removed from the spooler. + mCachedInfo.setState(state); + try { + return mPrintServiceClient.setPrintJobState(mId, state); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error setting the state of job:" + mId, re); + } + return false; + } +} diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java new file mode 100644 index 0000000..d5cadc0 --- /dev/null +++ b/core/java/android/printservice/PrintService.java @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.printservice; + +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.print.PrintJobInfo; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * <p> + * This is the base class for implementing print services. A print service + * knows how to discover and interact one or more printers via one or more + * protocols. + * </p> + * <h3>Printer discovery</h3> + * <p> + * A print service is responsible for discovering and reporting printers. + * A printer discovery period starts with a call to + * {@link #onStartPrinterDiscovery()} and ends with a call to + * {@link #onStopPrinterDiscovery()}. During a printer discovery + * period the print service reports newly discovered printers by + * calling {@link #addDiscoveredPrinters(List)} and added printers + * that disappeared by calling {@link #removeDiscoveredPrinters(List)}. + * Calls to {@link #addDiscoveredPrinters(List)} and + * {@link #removeDiscoveredPrinters(List)} before a call to + * {@link #onStartPrinterDiscovery()} and after a call to + * {@link #onStopPrinterDiscovery()} is a no-op. + * </p> + * <p> + * For every printer discovery period all printers have to be added. Each + * printer known to this print service should be added only once during a + * discovery period, unless it was added and then removed before that. + * Only an already added printer can be removed. + * </p> + * <h3>Print jobs</h3> + * <p> + * When a new print job targeted to the printers managed by this print + * service is queued, i.e. ready for processing by the print service, + * a call to {@link #onPrintJobQueued(PrintJob)} is made and the print + * service may handle it immediately or schedule that for an appropriate + * time in the future. The list of all print jobs for this service + * are be available by calling {@link #getPrintJobs()}. A queued print + * job is in a {@link PrintJobInfo#STATE_QUEUED} state. + * </p> + * <p> + * A print service is responsible for setting the print job state as + * appropriate while processing it. Initially, a print job is in a + * {@link PrintJobInfo#STATE_QUEUED} state which means that the data to + * be printed is spooled by the system and the print service can obtain + * that data by calling {@link PrintJob#getData()}. After the print + * service starts printing the data it should set the print job state + * to {@link PrintJobInfo#STATE_STARTED}. Upon successful completion, the + * print job state has to be set to {@link PrintJobInfo#STATE_COMPLETED}. + * In a case of a failure, the print job state should be set to + * {@link PrintJobInfo#STATE_FAILED}. If a print job is in a + * {@link PrintJobInfo#STATE_STARTED} state and the user requests to + * cancel it, the print service will receive a call to + * {@link #onRequestCancelPrintJob(PrintJob)} which requests from the + * service to do a best effort in canceling the job. In case the job + * is successfully canceled, its state has to be set to + * {@link PrintJobInfo#STATE_CANCELED}. + * </p> + * <h3>Lifecycle</h3> + * <p> + * The lifecycle of a print service is managed exclusively by the system + * and follows the established service lifecycle. Additionally, starting + * or stopping a print service is triggered exclusively by an explicit + * user action through enabling or disabling it in the device settings. + * After the system binds to a print service, it calls {@link #onConnected()}. + * This method can be overriden by clients to perform post binding setup. + * Also after the system unbinds from a print service, it calls + * {@link #onDisconnected()}. This method can be overriden by clients to + * perform post unbinding cleanup. + * </p> + * <h3>Declaration</h3> + * <p> + * A print service is declared as any other service in an AndroidManifest.xml + * but it must also specify that it handles the {@link android.content.Intent} + * with action {@link #SERVICE_INTERFACE}. Failure to declare this intent + * will cause the system to ignore the print service. Additionally, a print + * service must request the {@link android.Manifest.permission#BIND_PRINT_SERVICE} + * permission to ensure that only the system can bind to it. Failure to + * declare this intent will cause the system to ignore the print service. + * Following is an example declaration: + * </p> + * <pre> + * <service android:name=".MyPrintService" + * android:permission="android.permission.BIND_PRINT_SERVICE"> + * <intent-filter> + * <action android:name="android.printservice.PrintService" /> + * </intent-filter> + * . . . + * </service> + * </pre> + * <h3>Configuration</h3> + * <p> + * A print service can be configured by specifying an optional settings + * activity which exposes service specific options, an optional add + * prints activity which is used for manual addition of printers, etc. + * It is a responsibility of the system to launch the settings and add + * printers activities when appropriate. + * </p> + * <p> + * A print service is configured by providing a + * {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring + * the service. A service declaration with a meta-data tag is presented + * below: + * <pre> <service android:name=".MyPrintService" + * android:permission="android.permission.BIND_PRINT_SERVICE"> + * <intent-filter> + * <action android:name="android.printservice.PrintService" /> + * </intent-filter> + * <meta-data android:name="android.printservice" android:resource="@xml/printservice" /> + * </service></pre> + * </p> + * <p> + * For more details refer to {@link #SERVICE_META_DATA} and + * <code><{@link android.R.styleable#PrintService print-service}></code>. + * </p> + */ +public abstract class PrintService extends Service { + + private static final String LOG_TAG = PrintService.class.getSimpleName(); + + /** + * The {@link Intent} action that must be declared as handled by a service + * in its manifest to allow the system to recognize it as a print service. + */ + public static final String SERVICE_INTERFACE = "android.printservice.PrintService"; + + /** + * Name under which a PrintService component publishes additional information + * about itself. This meta-data must reference an XML resource containing a + * <code><{@link android.R.styleable#PrintService print-service}></code> + * tag. This is a a sample XML file configuring a print service: + * <pre> <print-service + * android:settingsActivity="foo.bar.MySettingsActivity" + * andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity." + * . . . + * /></pre> + */ + public static final String SERVICE_META_DATA = "android.printservice"; + + private final Object mLock = new Object(); + + private Handler mHandler; + + private IPrintServiceClient mClient; + + private boolean mDiscoveringPrinters; + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + mHandler = new MyHandler(base.getMainLooper()); + } + + /** + * The system has connected to this service. + */ + protected void onConnected() { + /* do nothing */ + } + + /** + * The system has disconnected from this service. + */ + protected void onDisconnected() { + /* do nothing */ + } + + /** + * Callback requesting from this service to start printer discovery. + * At the end of the printer discovery period the system will call + * {@link #onStopPrinterDiscovery(). Discovered printers should be + * reported by calling #addDiscoveredPrinters(List) and reported ones + * that disappear should be reported by calling + * {@link #removeDiscoveredPrinters(List)}. + * + * @see #onStopPrinterDiscovery() + * @see #addDiscoveredPrinters(List) + * @see #removeDiscoveredPrinters(List) + */ + protected abstract void onStartPrinterDiscovery(); + + /** + * Callback requesting from this service to stop printer discovery. + * + * @see #onStartPrinterDiscovery() + * @see #addDiscoveredPrinters(List) + * @see #removeDiscoveredPrinters(List) + */ + protected abstract void onStopPrinterDiscovery(); + + /** + * Adds discovered printers. This method should be called during a + * printer discovery period, i.e. after a call to + * {@link #onStartPrinterDiscovery()} and before the corresponding + * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing. + * <p> + * <strong>Note:</strong> For every printer discovery period all + * printers have to be added. You can call this method as many times as + * necessary during the discovery period but should not pass in already + * added printers. If a printer is already added in the same printer + * discovery period, it will be ignored. + * </p> + * + * @param printers A list with discovered printers. + * + * @throws IllegalStateException If this service is not connected. + * + * @see #removeDiscoveredPrinters(List) + * @see #onStartPrinterDiscovery() + * @see #onStopPrinterDiscovery() + */ + public final void addDiscoveredPrinters(List<PrinterInfo> printers) { + synchronized (mLock) { + if (mClient == null) { + throw new IllegalStateException("Print serivice not connected!"); + } + if (mDiscoveringPrinters) { + try { + // Calling with a lock into the system is fine. + mClient.addDiscoveredPrinters(printers); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error adding discovered printers!", re); + } + } + } + } + + /** + * Removes discovered printers given their ids. This method should be called + * during a printer discovery period, i.e. after a call to + * {@link #onStartPrinterDiscovery()} and before the corresponding + * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing. + * <p> + * For every printer discovery period all printers have to be added. You + * should remove only printers that were added in this printer discovery + * period by a call to {@link #addDiscoveredPrinters(List)}. You can call + * this method as many times as necessary during the discovery period + * but should not pass in already removed printer ids. If a printer with + * a given id is already removed in the same discovery period, it will + * be ignored. + * </p> + * + * @param printerIds A list with disappeared printer ids. + * + * @throws IllegalStateException If this service is not connected. + * + * @see #addDiscoveredPrinters(List) + * @see #onStartPrinterDiscovery() + * @see #onStopPrinterDiscovery() + */ + public final void removeDiscoveredPrinters(List<PrinterId> printerIds) { + synchronized (mLock) { + if (mClient == null) { + throw new IllegalStateException("Print serivice not connected!"); + } + if (mDiscoveringPrinters) { + try { + // Calling with a lock into the system is fine. + mClient.removeDiscoveredPrinters(printerIds); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error removing discovered printers!", re); + } + } + } + } + + /** + * Called when canceling of a print job is requested. The service + * should do best effort to fulfill the request. After the print + * job is canceled it state has to be set to + * {@link PrintJobInfo#STATE_CANCELED}. + * + * @param printJob The print job to be canceled. + */ + protected void onRequestCancelPrintJob(PrintJob printJob) { + } + + /** + * Called when there is a queued print job for one of the printers + * managed by this print service. A queued print job is ready for + * processing by a print service which can get the data to be printed + * by calling {@link PrintJob#getData()}. This service may start + * processing the passed in print job or schedule handling of queued + * print jobs at a convenient time. The service can get the print + * jobs by a call to {@link #getPrintJobs()} and examine their state + * to find the ones with state {@link PrintJobInfo#STATE_QUEUED}. + * + * @param printJob The new queued print job. + * + * @see #getPrintJobs() + */ + protected abstract void onPrintJobQueued(PrintJob printJob); + + /** + * Gets the print jobs for the printers managed by this service. + * + * @return The print jobs. + * + * @throws IllegalStateException If this service is not connected. + */ + public final List<PrintJob> getPrintJobs() { + synchronized (mLock) { + if (mClient == null) { + throw new IllegalStateException("Print serivice not connected!"); + } + try { + List<PrintJob> printJobs = null; + List<PrintJobInfo> printJobInfos = mClient.getPrintJobs(); + if (printJobInfos != null) { + final int printJobInfoCount = printJobInfos.size(); + printJobs = new ArrayList<PrintJob>(printJobInfoCount); + for (int i = 0; i < printJobInfoCount; i++) { + printJobs.add(new PrintJob(printJobInfos.get(i), mClient)); + } + } + if (printJobs != null) { + return printJobs; + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling getPrintJobs()", re); + } + return Collections.emptyList(); + } + } + + /** + * Generates a global printer id from a local id. The local id is unique + * only within this print service. + * + * @param localId The local id. + * @return Global printer id. + */ + public final PrinterId generatePrinterId(String localId) { + return new PrinterId(new ComponentName(getPackageName(), + getClass().getName()), localId); + } + + @Override + public final IBinder onBind(Intent intent) { + return new IPrintService.Stub() { + @Override + public void setClient(IPrintServiceClient client) { + mHandler.obtainMessage(MyHandler.MESSAGE_SET_CLEINT, client).sendToTarget(); + } + + @Override + public void startPrinterDiscovery() { + mHandler.sendEmptyMessage(MyHandler.MESSAGE_START_PRINTER_DISCOVERY); + } + + @Override + public void stopPrinterDiscovery() { + mHandler.sendEmptyMessage(MyHandler.MESSAGE_STOP_PRINTER_DISCOVERY); + } + + @Override + public void requestCancelPrintJob(PrintJobInfo printJob) { + mHandler.obtainMessage(MyHandler.MESSAGE_CANCEL_PRINTJOB, printJob).sendToTarget(); + } + + @Override + public void onPrintJobQueued(PrintJobInfo printJob) { + mHandler.obtainMessage(MyHandler.MESSAGE_ON_PRINTJOB_QUEUED, + printJob).sendToTarget(); + } + }; + } + + private final class MyHandler extends Handler { + public static final int MESSAGE_START_PRINTER_DISCOVERY = 1; + public static final int MESSAGE_STOP_PRINTER_DISCOVERY = 2; + public static final int MESSAGE_CANCEL_PRINTJOB = 3; + public static final int MESSAGE_ON_PRINTJOB_QUEUED = 4; + public static final int MESSAGE_SET_CLEINT = 5; + + public MyHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message message) { + final int action = message.what; + switch (action) { + case MESSAGE_START_PRINTER_DISCOVERY: { + synchronized (mLock) { + mDiscoveringPrinters = true; + } + onStartPrinterDiscovery(); + } break; + + case MESSAGE_STOP_PRINTER_DISCOVERY: { + synchronized (mLock) { + mDiscoveringPrinters = false; + } + onStopPrinterDiscovery(); + } break; + + case MESSAGE_CANCEL_PRINTJOB: { + PrintJobInfo printJob = (PrintJobInfo) message.obj; + onRequestCancelPrintJob(new PrintJob(printJob, mClient)); + } break; + + case MESSAGE_ON_PRINTJOB_QUEUED: { + PrintJobInfo printJob = (PrintJobInfo) message.obj; + onPrintJobQueued(new PrintJob(printJob, mClient)); + } break; + + case MESSAGE_SET_CLEINT: { + IPrintServiceClient client = (IPrintServiceClient) message.obj; + synchronized (mLock) { + mClient = client; + if (client == null) { + mDiscoveringPrinters = false; + } + } + if (client != null) { + onConnected(); + } else { + onStopPrinterDiscovery(); + onDisconnected(); + } + } break; + + default: { + throw new IllegalArgumentException("Unknown message: " + action); + } + } + } + } +} diff --git a/core/java/android/printservice/PrintServiceInfo.aidl b/core/java/android/printservice/PrintServiceInfo.aidl new file mode 100644 index 0000000..95b67f2 --- /dev/null +++ b/core/java/android/printservice/PrintServiceInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.printservice; + +parcelable PrintServiceInfo; diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java new file mode 100644 index 0000000..0370a25 --- /dev/null +++ b/core/java/android/printservice/PrintServiceInfo.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.printservice; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * This class describes a {@link PrintService}. A print service knows + * how to communicate with one or more printers over one or more protocols + * and exposes printers for use by the applications via the platform print + * APIs. + * + * @see PrintService + * @see android.print.PrintManager + * + * @hide + */ +public final class PrintServiceInfo implements Parcelable { + + private static final boolean DEBUG = false; + + private static final String LOG_TAG = PrintServiceInfo.class.getSimpleName(); + + private static final String TAG_PRINT_SERVICE = "print-service"; + + private final String mId; + + private final ResolveInfo mResolveInfo; + + private final String mSettingsActivityName; + + private final String mAddPrintersActivityName; + + /** + * Creates a new instance. + * + * @hide + */ + public PrintServiceInfo(Parcel parcel) { + mId = parcel.readString(); + mResolveInfo = parcel.readParcelable(null); + mSettingsActivityName = parcel.readString(); + mAddPrintersActivityName = parcel.readString(); + } + + /** + * Creates a new instance. + * + * @param resolveInfo The service resolve info. + * @param settingsActivityName Optional settings activity name. + * @param addPrintersActivityName Optional add printers activity name. + */ + public PrintServiceInfo(ResolveInfo resolveInfo, String settingsActivityName, + String addPrintersActivityName) { + mId = new ComponentName(resolveInfo.serviceInfo.packageName, + resolveInfo.serviceInfo.name).flattenToString(); + mResolveInfo = resolveInfo; + mSettingsActivityName = settingsActivityName; + mAddPrintersActivityName = addPrintersActivityName; + } + + /** + * Creates a new instance. + * + * @param resolveInfo The service resolve info. + * @param context Context for accessing resources. + * @throws XmlPullParserException If a XML parsing error occurs. + * @throws IOException If a I/O error occurs. + * @hide + */ + public static PrintServiceInfo create(ResolveInfo resolveInfo, Context context) { + String settingsActivityName = null; + String addPrintersActivityName = null; + + XmlResourceParser parser = null; + PackageManager packageManager = context.getPackageManager(); + parser = resolveInfo.serviceInfo.loadXmlMetaData(packageManager, + PrintService.SERVICE_META_DATA); + if (parser != null) { + try { + int type = 0; + while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { + type = parser.next(); + } + + String nodeName = parser.getName(); + if (!TAG_PRINT_SERVICE.equals(nodeName)) { + throw new XmlPullParserException( + "Meta-data does not start with" + TAG_PRINT_SERVICE + " tag"); + } + + Resources resources = packageManager.getResourcesForApplication( + resolveInfo.serviceInfo.applicationInfo); + AttributeSet allAttributes = Xml.asAttributeSet(parser); + TypedArray attributes = resources.obtainAttributes(allAttributes, + com.android.internal.R.styleable.PrintService); + + settingsActivityName = attributes.getString( + com.android.internal.R.styleable.PrintService_settingsActivity); + + addPrintersActivityName = attributes.getString( + com.android.internal.R.styleable.PrintService_addPrintersActivity); + + attributes.recycle(); + } catch (IOException ioe) { + Log.w(LOG_TAG, "Error reading meta-data:" + ioe); + } catch (XmlPullParserException xppe) { + Log.w(LOG_TAG, "Error reading meta-data:" + xppe); + } catch (NameNotFoundException e) { + Log.e(LOG_TAG, "Unable to load resources for: " + + resolveInfo.serviceInfo.packageName); + } finally { + if (parser != null) { + parser.close(); + } + } + } + + return new PrintServiceInfo(resolveInfo, settingsActivityName, addPrintersActivityName); + } + + /** + * The accessibility service id. + * <p> + * <strong>Generated by the system.</strong> + * </p> + * + * @return The id. + */ + public String getId() { + return mId; + } + + /** + * The service {@link ResolveInfo}. + * + * @return The info. + */ + public ResolveInfo getResolveInfo() { + return mResolveInfo; + } + + /** + * The settings activity name. + * <p> + * <strong>Statically set from + * {@link PrintService#SERVICE_META_DATA meta-data}.</strong> + * </p> + * + * @return The settings activity name. + */ + public String getSettingsActivityName() { + return mSettingsActivityName; + } + + /** + * The add printers activity name. + * <p> + * <strong>Statically set from + * {@link PrintService#SERVICE_META_DATA meta-data}.</strong> + * </p> + * + * @return The add printers activity name. + */ + public String getAddPrintersActivityName() { + return mAddPrintersActivityName; + } + + /** + * {@inheritDoc} + */ + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flagz) { + parcel.writeString(mId); + parcel.writeParcelable(mResolveInfo, 0); + parcel.writeString(mSettingsActivityName); + parcel.writeString(mAddPrintersActivityName); + } + + @Override + public int hashCode() { + return 31 * 1 + ((mId == null) ? 0 : mId.hashCode()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PrintServiceInfo other = (PrintServiceInfo) obj; + if (mId == null) { + if (other.mId != null) { + return false; + } + } else if (!mId.equals(other.mId)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrintServiceInfo{"); + builder.append("id:").append(mId).append(", "); + builder.append("resolveInfo:").append(mResolveInfo).append(", "); + if (DEBUG) { + builder.append("settingsActivityName:").append(mSettingsActivityName); + builder.append("addPrintersActivityName:").append(mAddPrintersActivityName); + } else if (mSettingsActivityName != null || mAddPrintersActivityName != null) { + builder.append("<has meta-data>"); + } + builder.append("}"); + return builder.toString(); + } + + public static final Parcelable.Creator<PrintServiceInfo> CREATOR = + new Parcelable.Creator<PrintServiceInfo>() { + public PrintServiceInfo createFromParcel(Parcel parcel) { + return new PrintServiceInfo(parcel); + } + + public PrintServiceInfo[] newArray(int size) { + return new PrintServiceInfo[size]; + } + }; +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1d2fbd8..5dae599 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3508,6 +3508,13 @@ public final class Settings { public static final String LONG_PRESS_TIMEOUT = "long_press_timeout"; /** + * List of the enabled print providers. + * @hide + */ + public static final String ENABLED_PRINT_SERVICES = + "enabled_print_services"; + + /** * Setting to always use the default text-to-speech settings regardless * of the application settings. * 1 = override application settings, diff --git a/core/java/android/util/TimedRemoteCaller.java b/core/java/android/util/TimedRemoteCaller.java new file mode 100644 index 0000000..abb2b64 --- /dev/null +++ b/core/java/android/util/TimedRemoteCaller.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import android.os.SystemClock; + +import java.util.concurrent.TimeoutException; + +/** + * This is a helper class for making an async one way call and + * its async one way response response in a sync fashion within + * a timeout. The key idea is to call the remote method with a + * sequence number and a callback and then starting to wait for + * the response. The remote method calls back with the result and + * the sequence number. If the response comes within the timeout + * and its sequence number is the one sent in the method invocation, + * then the call succeeded. If the response does not come within + * the timeout then the call failed. Older result received when + * waiting for the result are ignored. + * <p> + * Typical usage is: + * </p> + * <p><pre><code> + * public class MyMethodCaller extends TimeoutRemoteCallHelper<Object> { + * // The one way remote method to call. + * private final IRemoteInterface mTarget; + * + * // One way callback invoked when the remote method is done. + * private final IRemoteCallback mCallback = new IRemoteCallback.Stub() { + * public void onCompleted(Object result, int sequence) { + * onRemoteMethodResult(result, sequence); + * } + * }; + * + * public MyMethodCaller(IRemoteInterface target) { + * mTarget = target; + * } + * + * public Object onCallMyMethod(Object arg) throws RemoteException { + * final int sequence = onBeforeRemoteCall(); + * mTarget.myMethod(arg, sequence); + * return getResultTimed(sequence); + * } + * } + * </code></pre></p> + * + * @param <T> The type of the expected result. + * + * @hide + */ +public abstract class TimedRemoteCaller<T> { + + public static final long DEFAULT_CALL_TIMEOUT_MILLIS = 5000; + + private static final int UNDEFINED_SEQUENCE = -1; + + private final Object mLock = new Object(); + + private final long mCallTimeoutMillis; + + private int mSequenceCounter; + + private int mReceivedSequence = UNDEFINED_SEQUENCE; + + private int mAwaitedSequence = UNDEFINED_SEQUENCE; + + private T mResult; + + public TimedRemoteCaller(long callTimeoutMillis) { + mCallTimeoutMillis = callTimeoutMillis; + } + + public final int onBeforeRemoteCall() { + synchronized (mLock) { + mAwaitedSequence = mSequenceCounter++; + return mAwaitedSequence; + } + } + + public final T getResultTimed(int sequence) throws TimeoutException { + synchronized (mLock) { + final boolean success = waitForResultTimedLocked(sequence); + if (!success) { + throw new TimeoutException("No reponse for sequence: " + sequence); + } + T result = mResult; + mResult = null; + return result; + } + } + + public final void onRemoteMethodResult(T result, int sequence) { + synchronized (mLock) { + if (sequence == mAwaitedSequence) { + mReceivedSequence = sequence; + mResult = result; + mLock.notifyAll(); + } + } + } + + private boolean waitForResultTimedLocked(int sequence) { + final long startMillis = SystemClock.uptimeMillis(); + while (true) { + try { + if (mReceivedSequence == sequence) { + return true; + } + final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; + final long waitMillis = mCallTimeoutMillis - elapsedMillis; + if (waitMillis <= 0) { + return false; + } + mLock.wait(waitMillis); + } catch (InterruptedException ie) { + /* ignore */ + } + } + } +} |