Print - platform APIs
Related changes:
Skia (inlcude PDF APIs): https://googleplex-android-review.googlesource.com/#/c/305814/
Canvas to PDF: https://googleplex-android-review.googlesource.com/#/c/319367/
Settings (initial version): https://googleplex-android-review.googlesource.com/#/c/306077/
Build: https://googleplex-android-review.googlesource.com/#/c/292437/
Sample print services: https://googleplex-android-review.googlesource.com/#/c/281785/
Change-Id: I104d12efd12577f05c7b9b2a5e5e49125c0f09da
diff --git a/packages/PrintSpooler/Android.mk b/packages/PrintSpooler/Android.mk
new file mode 100644
index 0000000..a68fcdf
--- /dev/null
+++ b/packages/PrintSpooler/Android.mk
@@ -0,0 +1,32 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := PrintSpooler
+
+LOCAL_JAVA_LIBRARIES := framework
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
new file mode 100644
index 0000000..fbb0060
--- /dev/null
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2013 Google Inc.
+ *
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.printspooler"
+ android:sharedUserId="android.uid.printspooler"
+ android:versionName="1"
+ android:versionCode="1"
+ coreApp="true">
+
+ <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="17"/>
+
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+
+ <permission android:name="android.permission.BIND_PRINT_SPOOLER_SERVICE"
+ android:label="@string/permlab_bindPrintSpoolerService"
+ android:description="@string/permdesc_bindPrintSpoolerService"
+ android:protectionLevel="signature" />
+
+ <application
+ android:allowClearUserData="false"
+ android:label="@string/app_label"
+ android:allowBackup= "false">
+
+ <service
+ android:name=".PrintSpoolerService"
+ android:exported="true"
+ android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE">
+ </service>
+
+ <activity
+ android:name=".PrintJobConfigActivity"
+ android:exported="true">
+ </activity>
+
+ </application>
+
+</manifest>
diff --git a/packages/PrintSpooler/MODULE_LICENSE_APACHE2 b/packages/PrintSpooler/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/PrintSpooler/MODULE_LICENSE_APACHE2
diff --git a/packages/PrintSpooler/NOTICE b/packages/PrintSpooler/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/packages/PrintSpooler/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2008, 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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity.xml b/packages/PrintSpooler/res/layout/print_job_config_activity.xml
new file mode 100644
index 0000000..51e425d
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/print_job_config_activity.xml
@@ -0,0 +1,261 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <GridLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="vertical"
+ android:columnCount="2">
+
+ <EditText
+ android:id="@+id/copies_edittext"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="0"
+ android:layout_column="1"
+ android:minWidth="150dip"
+ android:inputType="number"
+ android:selectAllOnFocus="true">
+ </EditText>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="0"
+ android:layout_column="0"
+ android:text="@string/label_copies"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:labelFor="@id/copies_edittext">
+ </TextView>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="1"
+ android:layout_column="0"
+ android:text="@string/label_destination"
+ android:textAppearance="?android:attr/textAppearanceMedium">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/destination_spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="1"
+ android:layout_column="1"
+ android:minWidth="150dip">
+ </Spinner>
+
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="2"
+ android:layout_column="0"
+ android:text="@string/label_media_size"
+ android:textAppearance="?android:attr/textAppearanceMedium">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/media_size_spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="2"
+ android:layout_column="1"
+ android:minWidth="150dip">
+ </Spinner>
+
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="3"
+ android:layout_column="0"
+ android:text="@string/label_resolution"
+ android:textAppearance="?android:attr/textAppearanceMedium">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/resolution_spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="3"
+ android:layout_column="1"
+ android:minWidth="150dip">
+ </Spinner>
+
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="4"
+ android:layout_column="0"
+ android:text="@string/label_input_tray"
+ android:textAppearance="?android:attr/textAppearanceMedium">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/input_tray_spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="4"
+ android:layout_column="1"
+ android:minWidth="150dip">
+ </Spinner>
+
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="5"
+ android:layout_column="0"
+ android:text="@string/label_output_tray"
+ android:textAppearance="?android:attr/textAppearanceMedium">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/output_tray_spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="5"
+ android:layout_column="1"
+ android:minWidth="150dip">
+ </Spinner>
+
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="6"
+ android:layout_column="0"
+ android:text="@string/label_duplex_mode"
+ android:textAppearance="?android:attr/textAppearanceMedium">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/duplex_mode_spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="6"
+ android:layout_column="1"
+ android:minWidth="150dip">
+ </Spinner>
+
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="7"
+ android:layout_column="0"
+ android:text="@string/label_color_mode"
+ android:textAppearance="?android:attr/textAppearanceMedium">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/color_mode_spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="7"
+ android:layout_column="1"
+ android:minWidth="150dip">
+ </Spinner>
+
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="8"
+ android:layout_column="0"
+ android:text="@string/label_fitting_mode"
+ android:textAppearance="?android:attr/textAppearanceMedium">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/fitting_mode_spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="8"
+ android:layout_column="1"
+ android:minWidth="150dip">
+ </Spinner>
+
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="9"
+ android:layout_column="0"
+ android:text="@string/label_orientation"
+ android:textAppearance="?android:attr/textAppearanceMedium">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/orientation_spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_row="9"
+ android:layout_column="1"
+ android:minWidth="150dip">
+ </Spinner>
+
+ </GridLayout>
+
+</ScrollView>
diff --git a/packages/PrintSpooler/res/menu/print_job_config_activity.xml b/packages/PrintSpooler/res/menu/print_job_config_activity.xml
new file mode 100644
index 0000000..149c274
--- /dev/null
+++ b/packages/PrintSpooler/res/menu/print_job_config_activity.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/print_button"
+ android:title="@string/print_button"
+ android:showAsAction="ifRoom">
+ </item>
+</menu>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
new file mode 100644
index 0000000..8b4b40a
--- /dev/null
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+
+ <!-- Title of the PrintSpooler application. [CHAR LIMIT=16] -->
+ <string name="app_label">Print Spooler</string>
+
+ <!-- Title of the print dialog. [CHAR LIMIT=10] -->
+ <string name="print_job_config_dialog_title">Print</string>
+
+ <!-- Label of the print dialog's print button. [CHAR LIMIT=16] -->
+ <string name="print_button">Print</string>
+
+ <!-- Label of the print dialog's cancel button. [CHAR LIMIT=16] -->
+ <string name="cancel_button">Cancel</string>
+
+ <!-- Label of the destination spinner. [CHAR LIMIT=16] -->
+ <string name="label_destination">Destination</string>
+
+ <!-- Label of the copies count edit text. [CHAR LIMIT=16] -->
+ <string name="label_copies">Copies</string>
+
+ <!-- Label of the media size spinner. [CHAR LIMIT=16] -->
+ <string name="label_media_size">Media size</string>
+
+ <!-- Label of the resolution spinner. [CHAR LIMIT=16] -->
+ <string name="label_resolution">Resolution</string>
+
+ <!-- Label of the input tray spinner. [CHAR LIMIT=16] -->
+ <string name="label_input_tray">Input tray</string>
+
+ <!-- Label of the output tray spinner. [CHAR LIMIT=16] -->
+ <string name="label_output_tray">Output tray</string>
+
+ <!-- Label of the duplex mode spinner. [CHAR LIMIT=16] -->
+ <string name="label_duplex_mode">Duplex mode</string>
+
+ <!-- Label of the color mode spinner. [CHAR LIMIT=16] -->
+ <string name="label_color_mode">Color mode</string>
+
+ <!-- Label of the fitting mode spinner. [CHAR LIMIT=16] -->
+ <string name="label_fitting_mode">Fitting mode</string>
+
+ <!-- Label of the orientation spinner. [CHAR LIMIT=16] -->
+ <string name="label_orientation">Orientation</string>
+
+ <!-- Duplex mode labels. -->
+ <string-array name="duplex_mode_labels">
+ <!-- Duplex mode label: No duplexing. [CHAR LIMIT=20] -->
+ <item>None</item>
+ <!-- Duplex mode label: Turn a page along its long edge, e.g. like a book. [CHAR LIMIT=20] -->
+ <item>Long edge</item>
+ <!-- Duplex mode label: Turn a page along its short edge, e.g. like a notepad. [CHAR LIMIT=20] -->
+ <item>Short edge</item>
+ </string-array>
+
+ <!-- Color mode labels. -->
+ <string-array name="color_mode_labels">
+ <!-- Color modelabel: Monochrome color scheme, e.g. one color is used. [CHAR LIMIT=20] -->
+ <item>Monochrome</item>
+ <!-- Color mode label: Color color scheme, e.g. many colors are used. [CHAR LIMIT=20] -->
+ <item>Color</item>
+ </string-array>
+
+ <!-- Fitting mode labels. -->
+ <string-array name="fitting_mode_labels">
+ <!-- Fitting mode label: No fitting. [CHAR LIMIT=30] -->
+ <item>None</item>
+ <!-- Fitting mode label: Fit the content to the page. [CHAR LIMIT=30] -->
+ <item>Fit to page</item>
+ </string-array>
+
+ <!-- Orientation labels. -->
+ <string-array name="orientation_labels">
+ <!-- Orientation label: Portrait page orientation. [CHAR LIMIT=30] -->
+ <item>Portrait</item>
+ <!-- Orientation label: Landscape page orientation [CHAR LIMIT=30] -->
+ <item>Landscape</item>
+ </string-array>
+
+ <!-- Title of an application permission, listed so the user can choose
+ whether they want to allow the application to do this. -->
+ <string name="permlab_bindPrintSpoolerService">bind to a print spooler service</string>
+ <!-- Description of an application permission, listed so the user can
+ choose whether they want to allow the application to do this. -->
+ <string name="permdesc_bindPrintSpoolerService">Allows the holder to bind to the top-level
+ interface of a print spooler service. Should never be needed for normal apps.</string>
+
+</resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
new file mode 100644
index 0000000..ae2fe5c
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -0,0 +1,794 @@
+/*
+ * 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 com.android.printspooler;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.IBinder.DeathRecipient;
+import android.print.IPrintAdapter;
+import android.print.IPrintManager;
+import android.print.IPrinterDiscoveryObserver;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
+import android.print.PrintAttributes.Tray;
+import android.print.PrintJobInfo;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.Spinner;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Activity for configuring a print job.
+ */
+public class PrintJobConfigActivity extends Activity {
+
+ private static final boolean DEBUG = false;
+
+ private static final String LOG_TAG = PrintJobConfigActivity.class.getSimpleName();
+
+ public static final String EXTRA_PRINTABLE = "printable";
+ public static final String EXTRA_APP_ID = "appId";
+ public static final String EXTRA_ATTRIBUTES = "attributes";
+ public static final String EXTRA_PRINT_JOB_ID = "printJobId";
+
+ private static final int MIN_COPIES = 1;
+
+ private final List<QueuedAsyncTask<?>> mTaskQueue = new ArrayList<QueuedAsyncTask<?>>();
+
+ private IPrintManager mPrintManager;
+
+ private IPrinterDiscoveryObserver mPrinterDiscoveryObserver;
+
+ private int mAppId;
+ private int mPrintJobId;
+
+ private PrintAttributes mPrintAttributes;
+
+ private final PrintSpooler mPrintSpooler = PrintSpooler.getInstance(this);
+
+ private RemotePrintAdapter mRemotePrintAdapter;
+
+ // UI elements
+
+ private EditText mCopiesEditText;
+
+ private Spinner mDestinationSpinner;
+ public ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter;
+
+ private Spinner mMediaSizeSpinner;
+ public ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
+
+ private Spinner mResolutionSpinner;
+ public ArrayAdapter<SpinnerItem<Resolution>> mResolutionSpinnerAdapter;
+
+ private Spinner mInputTraySpinner;
+ public ArrayAdapter<SpinnerItem<Tray>> mInputTraySpinnerAdapter;
+
+ private Spinner mOutputTraySpinner;
+ public ArrayAdapter<SpinnerItem<Tray>> mOutputTraySpinnerAdapter;
+
+ private Spinner mDuplexModeSpinner;
+ public ArrayAdapter<SpinnerItem<Integer>> mDuplexModeSpinnerAdapter;
+
+ private Spinner mColorModeSpinner;
+ public ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
+
+ private Spinner mFittingModeSpinner;
+ public ArrayAdapter<SpinnerItem<Integer>> mFittingModeSpinnerAdapter;
+
+ private Spinner mOrientationSpinner;
+ public ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
+
+ private boolean mPrintStarted;
+
+ private boolean mPrintConfirmed;
+
+ private IBinder mPrinable;
+
+ // TODO: Implement store/restore state.
+
+ private final OnItemSelectedListener mOnItemSelectedListener =
+ new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
+ if (spinner == mDestinationSpinner) {
+ updateUi();
+ notifyPrintableStartIfNeeded();
+ } else if (spinner == mMediaSizeSpinner) {
+ SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
+ mPrintAttributes.setMediaSize(mediaItem.value);
+ updatePrintableContentIfNeeded();
+ } else if (spinner == mResolutionSpinner) {
+ SpinnerItem<Resolution> resolutionItem =
+ mResolutionSpinnerAdapter.getItem(position);
+ mPrintAttributes.setResolution(resolutionItem.value);
+ updatePrintableContentIfNeeded();
+ } else if (spinner == mInputTraySpinner) {
+ SpinnerItem<Tray> inputTrayItem =
+ mInputTraySpinnerAdapter.getItem(position);
+ mPrintAttributes.setInputTray(inputTrayItem.value);
+ } else if (spinner == mOutputTraySpinner) {
+ SpinnerItem<Tray> outputTrayItem =
+ mOutputTraySpinnerAdapter.getItem(position);
+ mPrintAttributes.setOutputTray(outputTrayItem.value);
+ } else if (spinner == mDuplexModeSpinner) {
+ SpinnerItem<Integer> duplexModeItem =
+ mDuplexModeSpinnerAdapter.getItem(position);
+ mPrintAttributes.setDuplexMode(duplexModeItem.value);
+ } else if (spinner == mColorModeSpinner) {
+ SpinnerItem<Integer> colorModeItem =
+ mColorModeSpinnerAdapter.getItem(position);
+ mPrintAttributes.setColorMode(colorModeItem.value);
+ } else if (spinner == mFittingModeSpinner) {
+ SpinnerItem<Integer> fittingModeItem =
+ mFittingModeSpinnerAdapter.getItem(position);
+ mPrintAttributes.setFittingMode(fittingModeItem.value);
+ } else if (spinner == mOrientationSpinner) {
+ SpinnerItem<Integer> orientationItem =
+ mOrientationSpinnerAdapter.getItem(position);
+ mPrintAttributes.setOrientation(orientationItem.value);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ /* do nothing*/
+ }
+ };
+
+ private final TextWatcher mTextWatcher = new TextWatcher() {
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ final int copies = Integer.parseInt(mCopiesEditText.getText().toString());
+ mPrintAttributes.setCopies(copies);
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ /* do nothing */
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ /* do nothing */
+ }
+ };
+
+ private final InputFilter mInputFilter = new InputFilter() {
+ @Override
+ public CharSequence filter(CharSequence source, int start, int end,
+ Spanned dest, int dstart, int dend) {
+ StringBuffer text = new StringBuffer(dest.toString());
+ text.replace(dstart, dend, source.subSequence(start, end).toString());
+ if (TextUtils.isEmpty(text)) {
+ return dest;
+ }
+ final int copies = Integer.parseInt(text.toString());
+ if (copies < MIN_COPIES) {
+ return dest;
+ }
+ return null;
+ }
+ };
+
+ private final DeathRecipient mDeathRecipient = new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ finish();
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.print_job_config_activity);
+
+ getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
+ | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+
+ mPrintManager = (IPrintManager) IPrintManager.Stub.asInterface(
+ ServiceManager.getService(PRINT_SERVICE));
+
+ Bundle extras = getIntent().getExtras();
+
+ mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1);
+ if (mPrintJobId < 0) {
+ throw new IllegalArgumentException("Invalid print job id: " + mPrintJobId);
+ }
+
+ mAppId = extras.getInt(EXTRA_APP_ID, -1);
+ if (mAppId < 0) {
+ throw new IllegalArgumentException("Invalid app id: " + mAppId);
+ }
+
+ mPrintAttributes = getIntent().getParcelableExtra(EXTRA_ATTRIBUTES);
+ if (mPrintAttributes == null) {
+ mPrintAttributes = new PrintAttributes.Builder().create();
+ }
+
+ mPrinable = extras.getBinder(EXTRA_PRINTABLE);
+ if (mPrinable == null) {
+ throw new IllegalArgumentException("Printable cannot be null");
+ }
+ mRemotePrintAdapter = new RemotePrintAdapter(IPrintAdapter.Stub.asInterface(mPrinable),
+ mPrintSpooler.generateFileForPrintJob(mPrintJobId));
+
+ try {
+ mPrinable.linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException re) {
+ finish();
+ }
+
+ mPrinterDiscoveryObserver = new PrintDiscoveryObserver(getMainLooper());
+
+ bindUi();
+ }
+
+ @Override
+ protected void onDestroy() {
+ mPrinable.unlinkToDeath(mDeathRecipient, 0);
+ super.onDestroy();
+ }
+
+ private void bindUi() {
+ // Copies
+ mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
+ mCopiesEditText.setText(String.valueOf(MIN_COPIES));
+ mCopiesEditText.addTextChangedListener(mTextWatcher);
+ mCopiesEditText.setFilters(new InputFilter[] {mInputFilter});
+
+ // Destination.
+ mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
+ mDestinationSpinnerAdapter = new ArrayAdapter<SpinnerItem<PrinterInfo>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
+ mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Media size.
+ mMediaSizeSpinner = (Spinner) findViewById(R.id.media_size_spinner);
+ mMediaSizeSpinnerAdapter = new ArrayAdapter<SpinnerItem<MediaSize>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
+ mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Resolution.
+ mResolutionSpinner = (Spinner) findViewById(R.id.resolution_spinner);
+ mResolutionSpinnerAdapter = new ArrayAdapter<SpinnerItem<Resolution>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mResolutionSpinner.setAdapter(mResolutionSpinnerAdapter);
+ mResolutionSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Input tray.
+ mInputTraySpinner = (Spinner) findViewById(R.id.input_tray_spinner);
+ mInputTraySpinnerAdapter = new ArrayAdapter<SpinnerItem<Tray>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mInputTraySpinner.setAdapter(mInputTraySpinnerAdapter);
+
+ // Output tray.
+ mOutputTraySpinner = (Spinner) findViewById(R.id.output_tray_spinner);
+ mOutputTraySpinnerAdapter = new ArrayAdapter<SpinnerItem<Tray>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mOutputTraySpinner.setAdapter(mOutputTraySpinnerAdapter);
+ mOutputTraySpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Duplex mode.
+ mDuplexModeSpinner = (Spinner) findViewById(R.id.duplex_mode_spinner);
+ mDuplexModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mDuplexModeSpinner.setAdapter(mDuplexModeSpinnerAdapter);
+ mDuplexModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Color mode.
+ mColorModeSpinner = (Spinner) findViewById(R.id.color_mode_spinner);
+ mColorModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
+ mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Color mode.
+ mFittingModeSpinner = (Spinner) findViewById(R.id.fitting_mode_spinner);
+ mFittingModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mFittingModeSpinner.setAdapter(mFittingModeSpinnerAdapter);
+ mFittingModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Orientation
+ mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
+ mOrientationSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
+ mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ }
+
+ private void updateUi() {
+ final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
+ PrinterInfo printer = mDestinationSpinnerAdapter.getItem(selectedIndex).value;
+ printer.getDefaults(mPrintAttributes);
+
+ // Copies.
+ mCopiesEditText.setText(String.valueOf(
+ Math.max(mPrintAttributes.getCopies(), MIN_COPIES)));
+
+ // Media size.
+ mMediaSizeSpinnerAdapter.clear();
+ List<MediaSize> mediaSizes = printer.getMediaSizes();
+ final int mediaSizeCount = mediaSizes.size();
+ for (int i = 0; i < mediaSizeCount; i++) {
+ MediaSize mediaSize = mediaSizes.get(i);
+ mMediaSizeSpinnerAdapter.add(new SpinnerItem<MediaSize>(
+ mediaSize, mediaSize.getLabel(getPackageManager())));
+ }
+ final int selectedMediaSizeIndex = mediaSizes.indexOf(
+ mPrintAttributes.getMediaSize());
+ mMediaSizeSpinner.setOnItemSelectedListener(null);
+ mMediaSizeSpinner.setSelection(selectedMediaSizeIndex);
+ mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Resolution.
+ mResolutionSpinnerAdapter.clear();
+ List<Resolution> resolutions = printer.getResolutions();
+ final int resolutionCount = resolutions.size();
+ for (int i = 0; i < resolutionCount; i++) {
+ Resolution resolution = resolutions.get(i);
+ mResolutionSpinnerAdapter.add(new SpinnerItem<Resolution>(
+ resolution, resolution.getLabel(getPackageManager())));
+ }
+ final int selectedResolutionIndex = resolutions.indexOf(
+ mPrintAttributes.getResolution());
+ mResolutionSpinner.setOnItemSelectedListener(null);
+ mResolutionSpinner.setSelection(selectedResolutionIndex);
+ mResolutionSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Input tray.
+ mInputTraySpinnerAdapter.clear();
+ List<Tray> inputTrays = printer.getInputTrays();
+ final int inputTrayCount = inputTrays.size();
+ for (int i = 0; i < inputTrayCount; i++) {
+ Tray inputTray = inputTrays.get(i);
+ mInputTraySpinnerAdapter.add(new SpinnerItem<Tray>(
+ inputTray, inputTray.getLabel(getPackageManager())));
+ }
+ final int selectedInputTrayIndex = inputTrays.indexOf(
+ mPrintAttributes.getInputTray());
+ mInputTraySpinner.setSelection(selectedInputTrayIndex);
+
+ // Output tray.
+ mOutputTraySpinnerAdapter.clear();
+ List<Tray> outputTrays = printer.getOutputTrays();
+ final int outputTrayCount = outputTrays.size();
+ for (int i = 0; i < outputTrayCount; i++) {
+ Tray outputTray = outputTrays.get(i);
+ mOutputTraySpinnerAdapter.add(new SpinnerItem<Tray>(
+ outputTray, outputTray.getLabel(getPackageManager())));
+ }
+ final int selectedOutputTrayIndex = outputTrays.indexOf(
+ mPrintAttributes.getOutputTray());
+ mOutputTraySpinner.setSelection(selectedOutputTrayIndex);
+
+ // Duplex mode.
+ final int duplexModes = printer.getDuplexModes();
+ mDuplexModeSpinnerAdapter.clear();
+ String[] duplexModeLabels = getResources().getStringArray(
+ R.array.duplex_mode_labels);
+ int remainingDuplexModes = duplexModes;
+ while (remainingDuplexModes != 0) {
+ final int duplexBitOffset = Integer.numberOfTrailingZeros(remainingDuplexModes);
+ final int duplexMode = 1 << duplexBitOffset;
+ remainingDuplexModes &= ~duplexMode;
+ mDuplexModeSpinnerAdapter.add(new SpinnerItem<Integer>(duplexMode,
+ duplexModeLabels[duplexBitOffset]));
+ }
+ final int selectedDuplexModeIndex = Integer.numberOfTrailingZeros(
+ (duplexModes & mPrintAttributes.getDuplexMode()));
+ mDuplexModeSpinner.setSelection(selectedDuplexModeIndex);
+
+ // Color mode.
+ final int colorModes = printer.getColorModes();
+ mColorModeSpinnerAdapter.clear();
+ String[] colorModeLabels = getResources().getStringArray(
+ R.array.color_mode_labels);
+ int remainingColorModes = colorModes;
+ while (remainingColorModes != 0) {
+ final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
+ final int colorMode = 1 << colorBitOffset;
+ remainingColorModes &= ~colorMode;
+ mColorModeSpinnerAdapter.add(new SpinnerItem<Integer>(colorMode,
+ colorModeLabels[colorBitOffset]));
+ }
+ final int selectedColorModeIndex = Integer.numberOfTrailingZeros(
+ (colorModes & mPrintAttributes.getColorMode()));
+ mColorModeSpinner.setSelection(selectedColorModeIndex);
+
+ // Fitting mode.
+ final int fittingModes = printer.getFittingModes();
+ mFittingModeSpinnerAdapter.clear();
+ String[] fittingModeLabels = getResources().getStringArray(
+ R.array.fitting_mode_labels);
+ int remainingFittingModes = fittingModes;
+ while (remainingFittingModes != 0) {
+ final int fittingBitOffset = Integer.numberOfTrailingZeros(remainingFittingModes);
+ final int fittingMode = 1 << fittingBitOffset;
+ remainingFittingModes &= ~fittingMode;
+ mFittingModeSpinnerAdapter.add(new SpinnerItem<Integer>(fittingMode,
+ fittingModeLabels[fittingBitOffset]));
+ }
+ final int selectedFittingModeIndex = Integer.numberOfTrailingZeros(
+ (fittingModes & mPrintAttributes.getFittingMode()));
+ mFittingModeSpinner.setSelection(selectedFittingModeIndex);
+
+ // Orientation.
+ final int orientations = printer.getOrientations();
+ mOrientationSpinnerAdapter.clear();
+ String[] orientationLabels = getResources().getStringArray(
+ R.array.orientation_labels);
+ int remainingOrientations = orientations;
+ while (remainingOrientations != 0) {
+ final int orientationBitOffset = Integer.numberOfTrailingZeros(remainingOrientations);
+ final int orientation = 1 << orientationBitOffset;
+ remainingOrientations &= ~orientation;
+ mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>(orientation,
+ orientationLabels[orientationBitOffset]));
+ }
+ final int selectedOrientationIndex = Integer.numberOfTrailingZeros(
+ (orientations & mPrintAttributes.getOrientation()));
+ mOrientationSpinner.setSelection(selectedOrientationIndex);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ try {
+ mPrintManager.startDiscoverPrinters(mPrinterDiscoveryObserver);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error starting printer discovery!", re);
+ }
+ notifyPrintableStartIfNeeded();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ try {
+ mPrintManager.stopDiscoverPrinters();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error starting printer discovery!", re);
+ }
+ notifyPrintableFinishIfNeeded();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.print_job_config_activity, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.print_button) {
+ mPrintConfirmed = true;
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void notifyPrintableStartIfNeeded() {
+ if (mDestinationSpinner.getSelectedItemPosition() < 0
+ || mPrintStarted) {
+ return;
+ }
+ mPrintStarted = true;
+ new QueuedAsyncTask<Void>(mTaskQueue) {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ mRemotePrintAdapter.start();
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error reading printed data!", ioe);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ updatePrintableContentIfNeeded();
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ private void updatePrintableContentIfNeeded() {
+ if (!mPrintStarted) {
+ return;
+ }
+
+ mPrintSpooler.setPrintJobAttributes(mPrintJobId, mPrintAttributes);
+
+ // TODO: Implement page selector.
+ final List<PageRange> pages = new ArrayList<PageRange>();
+ pages.add(PageRange.ALL_PAGES);
+
+ new QueuedAsyncTask<File>(mTaskQueue) {
+ @Override
+ protected File doInBackground(Void... params) {
+ try {
+ mRemotePrintAdapter.printAttributesChanged(mPrintAttributes);
+ mRemotePrintAdapter.cancelPrint();
+ mRemotePrintAdapter.print(pages);
+ return mRemotePrintAdapter.getFile();
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error reading printed data!", ioe);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(File file) {
+ super.onPostExecute(file);
+ updatePrintPreview(file);
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ private void notifyPrintableFinishIfNeeded() {
+ if (!mPrintStarted) {
+ return;
+ }
+ mPrintStarted = false;
+
+ // Cancel all pending async tasks if the activity was canceled.
+ if (!mPrintConfirmed) {
+ final int taskCount = mTaskQueue.size();
+ for (int i = taskCount - 1; i >= 0; i--) {
+ mTaskQueue.remove(i).cancel();
+ }
+ }
+
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ // Notify the app that printing completed.
+ try {
+ mRemotePrintAdapter.finish();
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error reading printed data!", ioe);
+ }
+
+ // If canceled, nothing to do.
+ if (!mPrintConfirmed) {
+ mPrintSpooler.setPrintJobState(mPrintJobId,
+ PrintJobInfo.STATE_CANCELED);
+ return null;
+ }
+
+ // No printer, nothing to do.
+ final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
+ if (selectedIndex < 0) {
+ // Update the print job's status.
+ mPrintSpooler.setPrintJobState(mPrintJobId,
+ PrintJobInfo.STATE_CANCELED);
+ return null;
+ }
+
+ // Update the print job's printer.
+ SpinnerItem<PrinterInfo> printerItem =
+ mDestinationSpinnerAdapter.getItem(selectedIndex);
+ PrinterId printerId = printerItem.value.getId();
+ mPrintSpooler.setPrintJobPrinterId(mPrintJobId, printerId);
+
+ // Update the print job's status.
+ mPrintSpooler.setPrintJobState(mPrintJobId,
+ PrintJobInfo.STATE_QUEUED);
+ return null;
+ }
+
+ // Important: If we are canceling, then we do not wait for the write
+ // to complete since the result will be discarded anyway, we simply
+ // execute the finish immediately which will interrupt the write.
+ }.executeOnExecutor(mPrintConfirmed ? AsyncTask.SERIAL_EXECUTOR
+ : AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+
+ if (DEBUG) {
+ if (mPrintConfirmed) {
+ File file = mRemotePrintAdapter.getFile();
+ if (file.exists()) {
+ new ViewSpooledFileAsyncTask(file).executeOnExecutor(
+ AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+ }
+ }
+ }
+
+ private void updatePrintPreview(File file) {
+ // TODO: Implement
+ }
+
+ private void addPrinters(List<PrinterInfo> addedPrinters) {
+ final int addedPrinterCount = addedPrinters.size();
+ for (int i = 0; i < addedPrinterCount; i++) {
+ PrinterInfo addedPrinter = addedPrinters.get(i);
+ boolean duplicate = false;
+ final int existingPrinterCount = mDestinationSpinnerAdapter.getCount();
+ for (int j = 0; j < existingPrinterCount; j++) {
+ PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value;
+ if (addedPrinter.getId().equals(existingPrinter.getId())) {
+ duplicate = true;
+ break;
+ }
+ }
+ if (!duplicate) {
+ mDestinationSpinnerAdapter.add(new SpinnerItem<PrinterInfo>(
+ addedPrinter, addedPrinter.getLabel()));
+ } else {
+ Log.w(LOG_TAG, "Skipping a duplicate printer: " + addedPrinter);
+ }
+ }
+ }
+
+ private void removePrinters(List<PrinterId> pritnerIds) {
+ final int printerIdCount = pritnerIds.size();
+ for (int i = 0; i < printerIdCount; i++) {
+ PrinterId removedPrinterId = pritnerIds.get(i);
+ boolean removed = false;
+ final int existingPrinterCount = mDestinationSpinnerAdapter.getCount();
+ for (int j = 0; j < existingPrinterCount; j++) {
+ PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value;
+ if (removedPrinterId.equals(existingPrinter.getId())) {
+ mDestinationSpinnerAdapter.remove(mDestinationSpinnerAdapter.getItem(j));
+ removed = true;
+ break;
+ }
+ }
+ if (!removed) {
+ Log.w(LOG_TAG, "Ignoring not added printer with id: " + removedPrinterId);
+ }
+ }
+ }
+
+ private abstract class QueuedAsyncTask<T> extends AsyncTask<Void, Void, T> {
+
+ private final List<QueuedAsyncTask<?>> mPendingOrRunningTasks;
+
+ public QueuedAsyncTask(List<QueuedAsyncTask<?>> pendingOrRunningTasks) {
+ mPendingOrRunningTasks = pendingOrRunningTasks;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mPendingOrRunningTasks.add(this);
+ }
+
+ @Override
+ protected void onPostExecute(T result) {
+ mPendingOrRunningTasks.remove(this);
+ }
+
+ public void cancel() {
+ super.cancel(true);
+ mPendingOrRunningTasks.remove(this);
+ }
+ }
+
+ private final class ViewSpooledFileAsyncTask extends AsyncTask<Void, Void, Void> {
+
+ private final File mFile;
+
+ public ViewSpooledFileAsyncTask(File file) {
+ mFile = file;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ mFile.setExecutable(true, false);
+ mFile.setWritable(true, false);
+ mFile.setReadable(true, false);
+
+ final long identity = Binder.clearCallingIdentity();
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(Uri.fromFile(mFile), "application/pdf");
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivityAsUser(intent, null, UserHandle.CURRENT);
+ Binder.restoreCallingIdentity(identity);
+ return null;
+ }
+ }
+
+ private final class PrintDiscoveryObserver extends IPrinterDiscoveryObserver.Stub {
+ private static final int MESSAGE_ADD_DICOVERED_PRINTERS = 1;
+ private static final int MESSAGE_REMOVE_DICOVERED_PRINTERS = 2;
+
+ private final Handler mHandler;
+
+ @SuppressWarnings("unchecked")
+ public PrintDiscoveryObserver(Looper looper) {
+ mHandler = new Handler(looper, null, true) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MESSAGE_ADD_DICOVERED_PRINTERS: {
+ List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
+ addPrinters(printers);
+ // Just added the first printer, so select it and start printing.
+ if (mDestinationSpinnerAdapter.getCount() == 1) {
+ mDestinationSpinner.setSelection(0);
+ }
+ } break;
+ case MESSAGE_REMOVE_DICOVERED_PRINTERS: {
+ List<PrinterId> printerIds = (List<PrinterId>) message.obj;
+ removePrinters(printerIds);
+ // TODO: Handle removing the last printer.
+ } break;
+ }
+ }
+ };
+ }
+
+ @Override
+ public void addDiscoveredPrinters(List<PrinterInfo> printers) {
+ mHandler.obtainMessage(MESSAGE_ADD_DICOVERED_PRINTERS, printers).sendToTarget();
+ }
+
+ @Override
+ public void removeDiscoveredPrinters(List<PrinterId> printers) {
+ mHandler.obtainMessage(MESSAGE_REMOVE_DICOVERED_PRINTERS, printers).sendToTarget();
+ }
+ }
+
+ private final class SpinnerItem<T> {
+ final T value;
+ CharSequence label;
+
+ public SpinnerItem(T value, CharSequence label) {
+ this.value = value;
+ this.label = label;
+ }
+
+ public String toString() {
+ return label.toString();
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
new file mode 100644
index 0000000..2b27b69
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
@@ -0,0 +1,734 @@
+/*
+ * 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 com.android.printspooler;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.print.IPrintClient;
+import android.print.IPrintManager;
+import android.print.PrintAttributes;
+import android.print.PrintJobInfo;
+import android.print.PrintManager;
+import android.print.PrinterId;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+public class PrintSpooler {
+
+ private static final String LOG_TAG = PrintSpooler.class.getSimpleName();
+
+ private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
+
+ private static final boolean DEBUG_PERSISTENCE = false;
+
+ private static final boolean PERSISTNECE_MANAGER_ENABLED = false;
+
+ private static final String PRINT_FILE_EXTENSION = "pdf";
+
+ private static int sPrintJobIdCounter;
+
+ private static final Object sLock = new Object();
+
+ private final Object mLock = new Object();
+
+ private static PrintSpooler sInstance;
+
+ private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
+
+ private final PersistenceManager mPersistanceManager;
+
+ private final Context mContext;
+
+ private final IPrintManager mPrintManager;
+
+ public static PrintSpooler getInstance(Context context) {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new PrintSpooler(context);
+ }
+ return sInstance;
+ }
+ }
+
+ private PrintSpooler(Context context) {
+ mContext = context;
+ mPersistanceManager = new PersistenceManager();
+ mPersistanceManager.readStateLocked();
+ mPrintManager = IPrintManager.Stub.asInterface(
+ ServiceManager.getService("print"));
+ }
+
+ public List<PrintJobInfo> getPrintJobs(ComponentName componentName, int state, int appId) {
+ synchronized (mLock) {
+ List<PrintJobInfo> foundPrintJobs = null;
+ final int printJobCount = mPrintJobs.size();
+ for (int i = 0; i < printJobCount; i++) {
+ PrintJobInfo printJob = mPrintJobs.get(i);
+ PrinterId printerId = printJob.getPrinterId();
+ final boolean sameComponent = (componentName == null
+ || (printerId != null
+ && componentName.equals(printerId.getServiceComponentName())));
+ final boolean sameAppId = appId == PrintManager.APP_ID_ANY
+ || printJob.getAppId() == appId;
+ final boolean sameState = state == PrintJobInfo.STATE_ANY
+ || state == printJob.getState();
+ if (sameComponent && sameAppId && sameState) {
+ if (foundPrintJobs == null) {
+ foundPrintJobs = new ArrayList<PrintJobInfo>();
+ }
+ foundPrintJobs.add(printJob);
+ }
+ }
+ return foundPrintJobs;
+ }
+ }
+
+ public PrintJobInfo getPrintJob(int printJobId, int appId) {
+ synchronized (mLock) {
+ final int printJobCount = mPrintJobs.size();
+ for (int i = 0; i < printJobCount; i++) {
+ PrintJobInfo printJob = mPrintJobs.get(i);
+ if (printJob.getId() == printJobId
+ && (appId == PrintManager.APP_ID_ANY || appId == printJob.getAppId())) {
+ return printJob;
+ }
+ }
+ return null;
+ }
+ }
+
+ public boolean cancelPrintJob(int printJobId, int appId) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJob(printJobId, appId);
+ if (printJob != null) {
+ switch (printJob.getState()) {
+ case PrintJobInfo.STATE_CREATED: {
+ removePrintJobLocked(printJob);
+ } return true;
+ case PrintJobInfo.STATE_QUEUED: {
+ removePrintJobLocked(printJob);
+ } return true;
+ default: {
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ public PrintJobInfo createPrintJob(CharSequence label, IPrintClient client,
+ PrintAttributes attributes, int appId) {
+ synchronized (mLock) {
+ final int printJobId = generatePrintJobIdLocked();
+ PrintJobInfo printJob = new PrintJobInfo();
+ printJob.setId(printJobId);
+ printJob.setAppId(appId);
+ printJob.setLabel(label);
+ printJob.setAttributes(attributes);
+
+ addPrintJobLocked(printJob);
+ setPrintJobState(printJobId, PrintJobInfo.STATE_CREATED);
+
+ return printJob;
+ }
+ }
+
+ private int generatePrintJobIdLocked() {
+ int printJobId = sPrintJobIdCounter++;
+ while (isDuplicatePrintJobId(printJobId)) {
+ printJobId = sPrintJobIdCounter++;
+ }
+ return printJobId;
+ }
+
+ private boolean isDuplicatePrintJobId(int printJobId) {
+ final int printJobCount = mPrintJobs.size();
+ for (int j = 0; j < printJobCount; j++) {
+ PrintJobInfo printJob = mPrintJobs.get(j);
+ if (printJob.getId() == printJobId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @SuppressWarnings("resource")
+ public boolean writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
+ synchronized (mLock) {
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ try {
+ PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ File file = generateFileForPrintJob(printJobId);
+ in = new FileInputStream(file);
+ out = new FileOutputStream(fd.getFileDescriptor());
+ final byte[] buffer = new byte[8192];
+ while (true) {
+ final int readByteCount = in.read(buffer);
+ if (readByteCount < 0) {
+ return true;
+ }
+ out.write(buffer, 0, readByteCount);
+ }
+ }
+ } catch (FileNotFoundException fnfe) {
+ Log.e(LOG_TAG, "Error writing print job data!", fnfe);
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error writing print job data!", ioe);
+ } finally {
+ closeIfNotNullNoException(in);
+ closeIfNotNullNoException(out);
+ closeIfNotNullNoException(fd);
+ }
+ }
+ return false;
+ }
+
+ private void closeIfNotNullNoException(Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (IOException ioe) {
+ /* ignore */;
+ }
+ }
+ }
+
+ public File generateFileForPrintJob(int printJobId) {
+ return new File(mContext.getFilesDir(), "print_job_"
+ + printJobId + "." + PRINT_FILE_EXTENSION);
+ }
+
+ private void addPrintJobLocked(PrintJobInfo printJob) {
+ mPrintJobs.add(printJob);
+ if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ Slog.i(LOG_TAG, "[ADD] " + printJob);
+ }
+ }
+
+ private void removePrintJobLocked(PrintJobInfo printJob) {
+ if (mPrintJobs.remove(printJob)) {
+ generateFileForPrintJob(printJob.getId()).delete();
+ if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ Slog.i(LOG_TAG, "[REMOVE] " + printJob);
+ }
+ }
+ }
+
+ public boolean setPrintJobState(int printJobId, int state) {
+ boolean success = false;
+ PrintJobInfo queuedPrintJob = null;
+
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null && printJob.getState() < state) {
+ success = true;
+ printJob.setState(state);
+ // TODO: Update notifications.
+ switch (state) {
+ case PrintJobInfo.STATE_COMPLETED:
+ case PrintJobInfo.STATE_CANCELED: {
+ removePrintJobLocked(printJob);
+ } break;
+ case PrintJobInfo.STATE_QUEUED: {
+ queuedPrintJob = new PrintJobInfo(printJob);
+ } break;
+ }
+ if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ Slog.i(LOG_TAG, "[STATUS CHANGED] " + printJob);
+ }
+ mPersistanceManager.writeStateLocked();
+ }
+ }
+
+ if (queuedPrintJob != null) {
+ try {
+ mPrintManager.onPrintJobQueued(queuedPrintJob.getPrinterId(),
+ queuedPrintJob);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+
+ return success;
+ }
+
+ public boolean setPrintJobTag(int printJobId, String tag) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setTag(tag);
+ mPersistanceManager.writeStateLocked();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void setPrintJobAttributes(int printJobId, PrintAttributes attributes) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setAttributes(attributes);
+ mPersistanceManager.writeStateLocked();
+ }
+ }
+ }
+
+ public void setPrintJobPrinterId(int printJobId, PrinterId printerId) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setPrinterId(printerId);
+ mPersistanceManager.writeStateLocked();
+ }
+ }
+ }
+
+ private final class PersistenceManager {
+ private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
+
+ private static final String TAG_SPOOLER = "spooler";
+ private static final String TAG_JOB = "job";
+ private static final String TAG_ID = "id";
+ private static final String TAG_TAG = "tag";
+ private static final String TAG_APP_ID = "app-id";
+ private static final String TAG_STATE = "state";
+ private static final String TAG_ATTRIBUTES = "attributes";
+ private static final String TAG_LABEL = "label";
+ private static final String TAG_PRINTER = "printer";
+
+ private static final String ATTRIBUTE_MEDIA_SIZE = "mediaSize";
+ private static final String ATTRIBUTE_RESOLUTION = "resolution";
+ private static final String ATTRIBUTE_MARGINS = "margins";
+ private static final String ATTRIBUTE_INPUT_TRAY = "inputTray";
+ private static final String ATTRIBUTE_OUTPUT_TRAY = "outputTray";
+ private static final String ATTRIBUTE_DUPLEX_MODE = "duplexMode";
+ private static final String ATTRIBUTE_COLOR_MODE = "colorMode";
+ private static final String ATTRIBUTE_FITTING_MODE = "fittingMode";
+ private static final String ATTRIBUTE_ORIENTATION = "orientation";
+
+ private final AtomicFile mStatePersistFile;
+
+ private boolean mWriteStateScheduled;
+
+ private PersistenceManager() {
+ mStatePersistFile = new AtomicFile(new File(mContext.getFilesDir(),
+ PERSIST_FILE_NAME));
+ }
+
+ public void writeStateLocked() {
+ // TODO: Implement persistence of PrintableInfo
+ if (!PERSISTNECE_MANAGER_ENABLED) {
+ return;
+ }
+ if (mWriteStateScheduled) {
+ return;
+ }
+ mWriteStateScheduled = true;
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ synchronized (mLock) {
+ mWriteStateScheduled = false;
+ doWriteStateLocked();
+ }
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ private void doWriteStateLocked() {
+ FileOutputStream out = null;
+ try {
+ out = mStatePersistFile.startWrite();
+
+ XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(out, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_SPOOLER);
+
+ List<PrintJobInfo> printJobs = mPrintJobs;
+
+ final int printJobCount = printJobs.size();
+ for (int j = 0; j < printJobCount; j++) {
+ PrintJobInfo printJob = printJobs.get(j);
+
+ final int state = printJob.getState();
+ if (state < PrintJobInfo.STATE_QUEUED
+ || state > PrintJobInfo.STATE_FAILED) {
+ continue;
+ }
+
+ serializer.startTag(null, TAG_JOB);
+
+ serializer.startTag(null, TAG_ID);
+ serializer.text(String.valueOf(printJob.getId()));
+ serializer.endTag(null, TAG_ID);
+
+ serializer.startTag(null, TAG_TAG);
+ serializer.text(printJob.getTag());
+ serializer.endTag(null, TAG_TAG);
+
+ serializer.startTag(null, TAG_APP_ID);
+ serializer.text(String.valueOf(printJob.getAppId()));
+ serializer.endTag(null, TAG_APP_ID);
+
+ serializer.startTag(null, TAG_LABEL);
+ serializer.text(printJob.getLabel().toString());
+ serializer.endTag(null, TAG_LABEL);
+
+ serializer.startTag(null, TAG_STATE);
+ serializer.text(String.valueOf(printJob.getState()));
+ serializer.endTag(null, TAG_STATE);
+
+ serializer.startTag(null, TAG_PRINTER);
+ serializer.text(printJob.getPrinterId().flattenToString());
+ serializer.endTag(null, TAG_PRINTER);
+
+ PrintAttributes attributes = printJob.getAttributes();
+ if (attributes != null) {
+ serializer.startTag(null, TAG_ATTRIBUTES);
+
+ //TODO: Implement persistence of the attributes below.
+
+// MediaSize mediaSize = attributes.getMediaSize();
+// if (mediaSize != null) {
+// serializer.attribute(null, ATTRIBUTE_MEDIA_SIZE,
+// mediaSize.flattenToString());
+// }
+//
+// Resolution resolution = attributes.getResolution();
+// if (resolution != null) {
+// serializer.attribute(null, ATTRIBUTE_RESOLUTION,
+// resolution.flattenToString());
+// }
+//
+// Margins margins = attributes.getMargins();
+// if (margins != null) {
+// serializer.attribute(null, ATTRIBUTE_MARGINS,
+// margins.flattenToString());
+// }
+//
+// Tray inputTray = attributes.getInputTray();
+// if (inputTray != null) {
+// serializer.attribute(null, ATTRIBUTE_INPUT_TRAY,
+// inputTray.flattenToString());
+// }
+//
+// Tray outputTray = attributes.getOutputTray();
+// if (outputTray != null) {
+// serializer.attribute(null, ATTRIBUTE_OUTPUT_TRAY,
+// outputTray.flattenToString());
+// }
+
+ final int duplexMode = attributes.getDuplexMode();
+ if (duplexMode > 0) {
+ serializer.attribute(null, ATTRIBUTE_DUPLEX_MODE,
+ String.valueOf(duplexMode));
+ }
+
+ final int colorMode = attributes.getColorMode();
+ if (colorMode > 0) {
+ serializer.attribute(null, ATTRIBUTE_COLOR_MODE,
+ String.valueOf(colorMode));
+ }
+
+ final int fittingMode = attributes.getFittingMode();
+ if (fittingMode > 0) {
+ serializer.attribute(null, ATTRIBUTE_FITTING_MODE,
+ String.valueOf(fittingMode));
+ }
+
+ final int orientation = attributes.getOrientation();
+ if (orientation > 0) {
+ serializer.attribute(null, ATTRIBUTE_ORIENTATION,
+ String.valueOf(orientation));
+ }
+
+ serializer.endTag(null, TAG_ATTRIBUTES);
+ }
+
+ serializer.endTag(null, TAG_JOB);
+
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[PERSISTED] " + printJob);
+ }
+ }
+
+ serializer.endTag(null, TAG_SPOOLER);
+ serializer.endDocument();
+ mStatePersistFile.finishWrite(out);
+ } catch (IOException e) {
+ Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
+ mStatePersistFile.failWrite(out);
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException ioe) {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ public void readStateLocked() {
+ if (!PERSISTNECE_MANAGER_ENABLED) {
+ return;
+ }
+ FileInputStream in = null;
+ try {
+ in = mStatePersistFile.openRead();
+ } catch (FileNotFoundException e) {
+ Log.i(LOG_TAG, "No existing print spooler state.");
+ return;
+ }
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parseState(parser);
+ } catch (IllegalStateException ise) {
+ Slog.w(LOG_TAG, "Failed parsing " + ise);
+ } catch (NullPointerException npe) {
+ Slog.w(LOG_TAG, "Failed parsing " + npe);
+ } catch (NumberFormatException nfe) {
+ Slog.w(LOG_TAG, "Failed parsing " + nfe);
+ } catch (XmlPullParserException xppe) {
+ Slog.w(LOG_TAG, "Failed parsing " + xppe);
+ } catch (IOException ioe) {
+ Slog.w(LOG_TAG, "Failed parsing " + ioe);
+ } catch (IndexOutOfBoundsException iobe) {
+ Slog.w(LOG_TAG, "Failed parsing " + iobe);
+ } finally {
+ try {
+ in.close();
+ } catch (IOException ioe) {
+ /* ignore */
+ }
+ }
+ }
+
+ private void parseState(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
+ parser.next();
+
+ while (parsePrintJob(parser)) {
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
+ }
+
+ private boolean parsePrintJob(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ skipEmptyTextTags(parser);
+ if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
+ return false;
+ }
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_ID);
+ parser.next();
+ final int printJobId = Integer.parseInt(parser.getText());
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_ID);
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_TAG);
+ parser.next();
+ String tag = parser.getText();
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_TAG);
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_APP_ID);
+ parser.next();
+ final int appId = Integer.parseInt(parser.getText());
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_APP_ID);
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_LABEL);
+ parser.next();
+ String label = parser.getText();
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_LABEL);
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_STATE);
+ parser.next();
+ final int state = Integer.parseInt(parser.getText());
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_STATE);
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_PRINTER);
+ parser.next();
+ PrinterId printerId = PrinterId.unflattenFromString(parser.getText());
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_PRINTER);
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES);
+
+ final int attributeCount = parser.getAttributeCount();
+ PrintAttributes attributes = null;
+ if (attributeCount > 0) {
+ PrintAttributes.Builder builder = new PrintAttributes.Builder();
+
+ // TODO: Implement reading of the attributes below.
+
+// String mediaSize = parser.getAttributeValue(null, ATTRIBUTE_MEDIA_SIZE);
+// if (mediaSize != null) {
+// builder.setMediaSize(MediaSize.unflattenFromString(mediaSize));
+// }
+//
+// String resolution = parser.getAttributeValue(null, ATTRIBUTE_RESOLUTION);
+// if (resolution != null) {
+// builder.setMediaSize(Resolution.unflattenFromString(resolution));
+// }
+//
+// String margins = parser.getAttributeValue(null, ATTRIBUTE_MARGINS);
+// if (margins != null) {
+// builder.setMediaSize(Margins.unflattenFromString(margins));
+// }
+//
+// String inputTray = parser.getAttributeValue(null, ATTRIBUTE_INPUT_TRAY);
+// if (inputTray != null) {
+// builder.setMediaSize(Tray.unflattenFromString(inputTray));
+// }
+//
+// String outputTray = parser.getAttributeValue(null, ATTRIBUTE_OUTPUT_TRAY);
+// if (outputTray != null) {
+// builder.setMediaSize(Tray.unflattenFromString(outputTray));
+// }
+//
+// String duplexMode = parser.getAttributeValue(null, ATTRIBUTE_DUPLEX_MODE);
+// if (duplexMode != null) {
+// builder.setDuplexMode(Integer.parseInt(duplexMode));
+// }
+
+ String colorMode = parser.getAttributeValue(null, ATTRIBUTE_COLOR_MODE);
+ if (colorMode != null) {
+ builder.setColorMode(Integer.parseInt(colorMode));
+ }
+
+ String fittingMode = parser.getAttributeValue(null, ATTRIBUTE_COLOR_MODE);
+ if (fittingMode != null) {
+ builder.setFittingMode(Integer.parseInt(fittingMode));
+ }
+ }
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
+ parser.next();
+
+ PrintJobInfo printJob = new PrintJobInfo();
+ printJob.setId(printJobId);
+ printJob.setTag(tag);
+ printJob.setAppId(appId);
+ printJob.setLabel(label);
+ printJob.setState(state);
+ printJob.setAttributes(attributes);
+ printJob.setPrinterId(printerId);
+
+ mPrintJobs.add(printJob);
+
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[RESTORED] " + printJob);
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_JOB);
+
+ return true;
+ }
+
+ private void expect(XmlPullParser parser, int type, String tag)
+ throws IOException, XmlPullParserException {
+ if (!accept(parser, type, tag)) {
+ throw new XmlPullParserException("Exepected event: " + type
+ + " and tag: " + tag + " but got event: " + parser.getEventType()
+ + " and tag:" + parser.getName());
+ }
+ }
+
+ private void skipEmptyTextTags(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ while (accept(parser, XmlPullParser.TEXT, null)
+ && "\n".equals(parser.getText())) {
+ parser.next();
+ }
+ }
+
+ private boolean accept(XmlPullParser parser, int type, String tag)
+ throws IOException, XmlPullParserException {
+ if (parser.getEventType() != type) {
+ return false;
+ }
+ if (tag != null) {
+ if (!tag.equals(parser.getName())) {
+ return false;
+ }
+ } else if (parser.getName() != null) {
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
new file mode 100644
index 0000000..57c4557
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
@@ -0,0 +1,187 @@
+/*
+ * 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 com.android.printspooler;
+
+import java.util.List;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.print.IPrintAdapter;
+import android.print.IPrintClient;
+import android.print.IPrintSpoolerService;
+import android.print.IPrintSpoolerServiceCallbacks;
+import android.print.PrintAttributes;
+import android.print.PrintJobInfo;
+import android.util.Slog;
+
+import com.android.internal.os.SomeArgs;
+
+/**
+ * Service for exposing some of the {@link PrintSpooler} functionality to
+ * another process.
+ */
+public final class PrintSpoolerService extends Service {
+
+ private static final String LOG_TAG = "PrintSpoolerService";
+
+ private Intent mStartPrintJobConfigActivityIntent;
+
+ private PrintSpooler mSpooler;
+
+ private Handler mHanlder;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mStartPrintJobConfigActivityIntent = new Intent(PrintSpoolerService.this,
+ PrintJobConfigActivity.class);
+ mSpooler = PrintSpooler.getInstance(this);
+ mHanlder = new MyHandler(getMainLooper());
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new IPrintSpoolerService.Stub() {
+ @Override
+ public void getPrintJobs(IPrintSpoolerServiceCallbacks callback,
+ ComponentName componentName, int state, int appId, int sequence)
+ throws RemoteException {
+ List<PrintJobInfo> printJobs = null;
+ try {
+ printJobs = mSpooler.getPrintJobs(componentName, state, appId);
+ } finally {
+ callback.onGetPrintJobsResult(printJobs, sequence);
+ }
+ }
+
+ @Override
+ public void getPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback,
+ int appId, int sequence) throws RemoteException {
+ PrintJobInfo printJob = null;
+ try {
+ printJob = mSpooler.getPrintJob(printJobId, appId);
+ } finally {
+ callback.onGetPrintJobInfoResult(printJob, sequence);
+ }
+ }
+
+ @Override
+ public void cancelPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback,
+ int appId, int sequence) throws RemoteException {
+ boolean success = false;
+ try {
+ success = mSpooler.cancelPrintJob(printJobId, appId);
+ } finally {
+ callback.onCancelPrintJobResult(success, sequence);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void createPrintJob(String printJobName, IPrintClient client,
+ IPrintAdapter printAdapter, PrintAttributes attributes,
+ IPrintSpoolerServiceCallbacks callback, int appId, int sequence)
+ throws RemoteException {
+ PrintJobInfo printJob = null;
+ try {
+ printJob = mSpooler.createPrintJob(printJobName, client,
+ attributes, appId);
+ if (printJob != null) {
+ Intent intent = mStartPrintJobConfigActivityIntent;
+ intent.putExtra(PrintJobConfigActivity.EXTRA_PRINTABLE,
+ printAdapter.asBinder());
+ intent.putExtra(PrintJobConfigActivity.EXTRA_APP_ID, appId);
+ intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_JOB_ID,
+ printJob.getId());
+ intent.putExtra(PrintJobConfigActivity.EXTRA_ATTRIBUTES, attributes);
+
+ IntentSender sender = PendingIntent.getActivity(
+ PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = client;
+ args.arg2 = sender;
+ mHanlder.obtainMessage(0, args).sendToTarget();
+ }
+ } finally {
+ callback.onCreatePrintJobResult(printJob, sequence);
+ }
+ }
+
+ @Override
+ public void setPrintJobState(int printJobId, int state,
+ IPrintSpoolerServiceCallbacks callback, int sequece)
+ throws RemoteException {
+ boolean success = false;
+ try {
+ // TODO: Make sure the clients (print services) can set the state
+ // only to acceptable ones, e.g. not settings STATE_CREATED.
+ success = mSpooler.setPrintJobState(printJobId, state);
+ } finally {
+ callback.onSetPrintJobStateResult(success, sequece);
+ }
+ }
+
+ @Override
+ public void setPrintJobTag(int printJobId, String tag,
+ IPrintSpoolerServiceCallbacks callback, int sequece)
+ throws RemoteException {
+ boolean success = false;
+ try {
+ success = mSpooler.setPrintJobTag(printJobId, tag);
+ } finally {
+ callback.onSetPrintJobTagResult(success, sequece);
+ }
+ }
+
+ @Override
+ public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
+ mSpooler.writePrintJobData(fd, printJobId);
+ }
+ };
+ }
+
+ private static final class MyHandler extends Handler {
+
+ public MyHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ SomeArgs args = (SomeArgs) message.obj;
+ IPrintClient client = (IPrintClient) args.arg1;
+ IntentSender sender = (IntentSender) args.arg2;
+ args.recycle();
+ try {
+ client.startPrintJobConfigActivity(sender);
+ } catch (RemoteException re) {
+ Slog.i(LOG_TAG, "Error starting print job config activity!", re);
+ }
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java
new file mode 100644
index 0000000..7537218
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java
@@ -0,0 +1,219 @@
+/*
+ * 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 com.android.printspooler;
+
+import android.os.ICancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.print.IPrintAdapter;
+import android.print.IPrintProgressListener;
+import android.print.PageRange;
+import android.print.PrintAdapterInfo;
+import android.print.PrintAttributes;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * This class represents a remote print adapter instance.
+ */
+final class RemotePrintAdapter {
+ private static final String LOG_TAG = "RemotePrintAdapter";
+
+ private static final boolean DEBUG = true;
+
+ private final Object mLock = new Object();
+
+ private final IPrintAdapter mRemoteInterface;
+
+ private final File mFile;
+
+ private final IPrintProgressListener mIPrintProgressListener;
+
+ private PrintAdapterInfo mInfo;
+
+ private ICancellationSignal mCancellationSignal;
+
+ private Thread mWriteThread;
+
+ public RemotePrintAdapter(IPrintAdapter printAdatper, File file) {
+ mRemoteInterface = printAdatper;
+ mFile = file;
+ mIPrintProgressListener = new IPrintProgressListener.Stub() {
+ @Override
+ public void onWriteStarted(PrintAdapterInfo info,
+ ICancellationSignal cancellationSignal) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "IPrintProgressListener#onWriteStarted()");
+ }
+ synchronized (mLock) {
+ mInfo = info;
+ mCancellationSignal = cancellationSignal;
+ }
+ }
+
+ @Override
+ public void onWriteFinished(List<PageRange> pages) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "IPrintProgressListener#onWriteFinished(" + pages + ")");
+ }
+ synchronized (mLock) {
+ if (isPrintingLocked()) {
+ mWriteThread.interrupt();
+ mCancellationSignal = null;
+ }
+ }
+ }
+
+ @Override
+ public void onWriteFailed(CharSequence error) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "IPrintProgressListener#onWriteFailed(" + error + ")");
+ }
+ synchronized (mLock) {
+ if (isPrintingLocked()) {
+ mWriteThread.interrupt();
+ mCancellationSignal = null;
+ }
+ }
+ }
+ };
+ }
+
+ public File getFile() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "getFile()");
+ }
+ return mFile;
+ }
+
+ public void start() throws IOException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "start()");
+ }
+ try {
+ mRemoteInterface.start();
+ } catch (RemoteException re) {
+ throw new IOException("Error reading file", re);
+ }
+ }
+
+ public void printAttributesChanged(PrintAttributes attributes) throws IOException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "printAttributesChanged(" + attributes +")");
+ }
+ try {
+ mRemoteInterface.printAttributesChanged(attributes);
+ } catch (RemoteException re) {
+ throw new IOException("Error reading file", re);
+ }
+ }
+
+ public void print(List<PageRange> pages) throws IOException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "print(" + pages +")");
+ }
+ InputStream in = null;
+ OutputStream out = null;
+ ParcelFileDescriptor source = null;
+ ParcelFileDescriptor sink = null;
+ synchronized (mLock) {
+ mWriteThread = Thread.currentThread();
+ }
+ try {
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ source = pipe[0];
+ sink = pipe[1];
+
+ in = new FileInputStream(source.getFileDescriptor());
+ out = new FileOutputStream(mFile);
+
+ // Async call to initiate the other process writing the data.
+ mRemoteInterface.print(pages, sink, mIPrintProgressListener);
+
+ // Close the source. It is now held by the client.
+ sink.close();
+ sink = null;
+
+ final byte[] buffer = new byte[8192];
+ while (true) {
+ if (Thread.currentThread().isInterrupted()) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ final int readByteCount = in.read(buffer);
+ if (readByteCount < 0) {
+ break;
+ }
+ out.write(buffer, 0, readByteCount);
+ }
+ } catch (RemoteException re) {
+ throw new IOException("Error reading file", re);
+ } catch (IOException ioe) {
+ throw new IOException("Error reading file", ioe);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ IoUtils.closeQuietly(sink);
+ IoUtils.closeQuietly(source);
+ }
+ }
+
+ public void cancelPrint() throws IOException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "cancelPrint()");
+ }
+ synchronized (mLock) {
+ if (isPrintingLocked()) {
+ try {
+ mCancellationSignal.cancel();
+ } catch (RemoteException re) {
+ throw new IOException("Error cancelling print", re);
+ }
+ }
+ }
+ }
+
+ public void finish() throws IOException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "finish()");
+ }
+ try {
+ mRemoteInterface.finish();
+ } catch (RemoteException re) {
+ throw new IOException("Error reading file", re);
+ }
+ }
+
+ public PrintAdapterInfo getInfo() {
+ synchronized (mLock) {
+ return mInfo;
+ }
+ }
+
+ private boolean isPrintingLocked() {
+ return mCancellationSignal != null;
+ }
+}