summaryrefslogtreecommitdiffstats
path: root/packages/PrintSpooler
diff options
context:
space:
mode:
Diffstat (limited to 'packages/PrintSpooler')
-rw-r--r--packages/PrintSpooler/AndroidManifest.xml9
-rw-r--r--packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.pngbin0 -> 667 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.pngbin0 -> 596 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.pngbin0 -> 761 bytes
-rw-r--r--packages/PrintSpooler/res/layout/print_job_config_activity_container.xml5
-rw-r--r--packages/PrintSpooler/res/layout/select_printer_activity.xml29
-rw-r--r--packages/PrintSpooler/res/layout/spinner_dropdown_item.xml2
-rw-r--r--packages/PrintSpooler/res/menu/select_printer_activity.xml37
-rw-r--r--packages/PrintSpooler/res/values/strings.xml26
-rw-r--r--packages/PrintSpooler/res/values/themes.xml8
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java575
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java993
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java979
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java1258
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java41
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java401
16 files changed, 2928 insertions, 1435 deletions
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index 74fd7a8..1f10af8 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -18,7 +18,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.printspooler"
- android:sharedUserId="android.uid.printspooler"
+ android:sharedUserId="android.uid.system"
android:versionName="1"
android:versionCode="1"
coreApp="true">
@@ -50,6 +50,13 @@
android:theme="@style/PrintJobConfigActivityTheme">
</activity>
+ <activity
+ android:name=".SelectPrinterActivity"
+ android:label="@string/all_printers_label"
+ android:theme="@style/SelectPrinterActivityTheme"
+ android:exported="false">
+ </activity>
+
<receiver
android:name=".NotificationController$NotificationBroadcastReceiver"
android:exported="false" >
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.png
new file mode 100644
index 0000000..4b68f52
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png
new file mode 100644
index 0000000..15ffadd
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png
new file mode 100644
index 0000000..420510e
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png
Binary files differ
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
index a0c111b..7817094 100644
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
+++ b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
@@ -20,9 +20,4 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/container_background">
-
- <include
- layout="@layout/print_job_config_activity_content_editing">
- </include>
-
</FrameLayout>
diff --git a/packages/PrintSpooler/res/layout/select_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml
new file mode 100644
index 0000000..f4e1853
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <fragment
+ android:name="com.android.printspooler.SelectPrinterFragment"
+ android:id="@+id/select_printer_fragment"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ </fragment>
+
+</FrameLayout> \ No newline at end of file
diff --git a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
index 002cc14..d14c064 100644
--- a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
+++ b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
@@ -15,7 +15,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingStart="8dip"
android:paddingEnd="8dip"
diff --git a/packages/PrintSpooler/res/menu/select_printer_activity.xml b/packages/PrintSpooler/res/menu/select_printer_activity.xml
new file mode 100644
index 0000000..28fbd35
--- /dev/null
+++ b/packages/PrintSpooler/res/menu/select_printer_activity.xml
@@ -0,0 +1,37 @@
+<?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/action_search"
+ android:title="@string/search"
+ android:icon="@*android:drawable/ic_menu_search_holo_light"
+ android:actionViewClass="android.widget.SearchView"
+ android:showAsAction="ifRoom"
+ android:alphabeticShortcut="f"
+ android:imeOptions="actionSearch">
+ </item>
+
+ <item
+ android:id="@+id/action_add_printer"
+ android:title="@null"
+ android:icon="@drawable/ic_menu_add"
+ android:showAsAction="ifRoom"
+ android:alphabeticShortcut="a">
+ </item>
+
+</menu>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 2086f58..41fc516 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -58,6 +58,32 @@
<!-- Title for the temporary dialog show while an app is generating a print job. [CHAR LIMIT=30] -->
<string name="generating_print_job">Generating print job</string>
+ <!-- Title for the save as PDF option in the printer list. [CHAR LIMIT=30] -->
+ <string name="save_as_pdf">Save as PDF</string>
+
+ <!-- Title for the open all printers UI option in the printer list. [CHAR LIMIT=30] -->
+ <string name="all_printers">All printers\.\.\.</string>
+
+ <!-- Title for the searching for printers option in the printer list
+ (only option if not printers are available). [CHAR LIMIT=40] -->
+ <string name="searching_for_printers">Searching for printers\.\.\.</string>
+
+ <!-- Select printer activity -->
+
+ <!-- Title for the share action bar menu item. [CHAR LIMIT=20] -->
+ <string name="search">Search</string>
+
+ <!-- Title for the select printer activity. [CHAR LIMIT=30] -->
+ <string name="all_printers_label">All printers</string>
+
+ <!-- Add printer dialog -->
+
+ <!-- Title for the alert dialog for selecting a print service. [CHAR LIMIT=50] -->
+ <string name="choose_print_service">Choose print service</string>
+
+ <!-- Title for the button to search the play store for print services. [CHAR LIMIT=50] -->
+ <string name="search_play_store">Search in play store</string>
+
<!-- Notifications -->
<!-- Template for the notificaiton label for a printing print job. [CHAR LIMIT=25] -->
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index ab16c65..831b0ec 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -24,4 +24,12 @@
<item name="android:colorBackgroundCacheHint">@android:color/transparent</item>
</style>
+ <style name="SelectPrinterActivityTheme" parent="@android:style/Theme.Holo.Light">
+ <item name="android:actionBarStyle">@style/SelectPrinterActivityActionBarStyle</item>
+ </style>
+
+ <style name="SelectPrinterActivityActionBarStyle" parent="@android:style/Widget.Holo.ActionBar">
+ <item name="android:displayOptions">showTitle</item>
+ </style>
+
</resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
new file mode 100644
index 0000000..6bad5b3
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
@@ -0,0 +1,575 @@
+/*
+ * 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.content.ComponentName;
+import android.content.Context;
+import android.content.Loader;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.printspooler.PrintSpoolerService.PrinterDiscoverySession;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+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.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class is responsible for loading printers by doing discovery
+ * and merging the discovered printers with the previously used ones.
+ */
+public class FusedPrintersProvider extends Loader<List<PrinterInfo>> {
+ private static final String LOG_TAG = "FusedPrintersProvider";
+
+ private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
+
+ private static final double WEIGHT_DECAY_COEFFICIENT = 0.95f;
+
+ private static final int MAX_HISTORY_LENGTH = 50;
+
+ private static final int MAX_HISTORICAL_PRINTER_COUNT = 4;
+
+ private final Map<PrinterId, PrinterInfo> mPrinters =
+ new LinkedHashMap<PrinterId, PrinterInfo>();
+
+ private final PersistenceManager mPersistenceManager;
+
+ private PrinterDiscoverySession mDiscoverySession;
+
+ private List<PrinterInfo> mFavoritePrinters;
+
+ public FusedPrintersProvider(Context context) {
+ super(context);
+ mPersistenceManager = new PersistenceManager(context);
+ }
+
+ public void addHistoricalPrinter(PrinterInfo printer) {
+ mPersistenceManager.addPrinterAndWritePrinterHistory(printer);
+ }
+
+ public List<PrinterInfo> getPrinters() {
+ return new ArrayList<PrinterInfo>(mPrinters.values());
+ }
+
+ @Override
+ public void deliverResult(List<PrinterInfo> printers) {
+ if (isStarted()) {
+ super.deliverResult(printers);
+ }
+ }
+
+ @Override
+ protected void onStartLoading() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onStartLoading()");
+ }
+ // The contract is that if we already have a valid,
+ // result the we have to deliver it immediately.
+ if (!mPrinters.isEmpty()) {
+ deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
+ }
+ // If the data has changed since the last load
+ // or is not available, start a load.
+ if (takeContentChanged() || mPrinters.isEmpty()) {
+ onForceLoad();
+ }
+ }
+
+ @Override
+ protected void onStopLoading() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onStopLoading()");
+ }
+ onCancelLoad();
+ }
+
+ @Override
+ protected void onForceLoad() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onForceLoad()");
+ }
+ onCancelLoad();
+ loadInternal();
+ }
+
+ private void loadInternal() {
+ if (mDiscoverySession == null) {
+ mDiscoverySession = new MyPrinterDiscoverySession();
+ mPersistenceManager.readPrinterHistory();
+ }
+ if (mPersistenceManager.isReadHistoryCompleted()
+ && !mDiscoverySession.isStarted()) {
+ final int favoriteCount = Math.min(MAX_HISTORICAL_PRINTER_COUNT,
+ mFavoritePrinters.size());
+ List<PrinterId> printerIds = new ArrayList<PrinterId>(favoriteCount);
+ for (int i = 0; i < favoriteCount; i++) {
+ printerIds.add(mFavoritePrinters.get(i).getId());
+ }
+ mDiscoverySession.startPrinterDisovery(printerIds);
+ }
+ }
+
+ @Override
+ protected boolean onCancelLoad() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onCancelLoad()");
+ }
+ return cancelInternal();
+ }
+
+ private boolean cancelInternal() {
+ if (mDiscoverySession != null && mDiscoverySession.isStarted()) {
+ mDiscoverySession.stopPrinterDiscovery();
+ return true;
+ } else if (mPersistenceManager.isReadHistoryInProgress()) {
+ return mPersistenceManager.stopReadPrinterHistory();
+ }
+ return false;
+ }
+
+ @Override
+ protected void onReset() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onReset()");
+ }
+ onStopLoading();
+ mPrinters.clear();
+ if (mDiscoverySession != null) {
+ mDiscoverySession.destroy();
+ mDiscoverySession = null;
+ }
+ }
+
+ @Override
+ protected void onAbandon() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onAbandon()");
+ }
+ onStopLoading();
+ }
+
+ public void refreshPrinter(PrinterId printerId) {
+ if (isStarted() && mDiscoverySession != null && mDiscoverySession.isStarted()) {
+ mDiscoverySession.requestPrinterUpdated(printerId);
+ }
+ }
+
+ private final class MyPrinterDiscoverySession extends PrinterDiscoverySession {
+
+ @Override
+ public void onPrintersAdded(List<PrinterInfo> printers) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "MyPrinterDiscoverySession#onPrintersAdded()");
+ }
+ boolean printersAdded = false;
+ final int addedPrinterCount = printers.size();
+ for (int i = 0; i < addedPrinterCount; i++) {
+ PrinterInfo printer = printers.get(i);
+ if (!mPrinters.containsKey(printer.getId())) {
+ mPrinters.put(printer.getId(), printer);
+ printersAdded = true;
+ }
+ }
+ if (printersAdded) {
+ deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
+ }
+ }
+
+ @Override
+ public void onPrintersRemoved(List<PrinterId> printerIds) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "MyPrinterDiscoverySession#onPrintersRemoved()");
+ }
+ boolean removedPrinters = false;
+ final int removedPrinterCount = printerIds.size();
+ for (int i = 0; i < removedPrinterCount; i++) {
+ PrinterId removedPrinterId = printerIds.get(i);
+ if (mPrinters.remove(removedPrinterId) != null) {
+ removedPrinters = true;
+ }
+ }
+ if (removedPrinters) {
+ deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
+ }
+ }
+
+ @Override
+ public void onPrintersUpdated(List<PrinterInfo> printers) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "MyPrinterDiscoverySession#onPrintersUpdated()");
+ }
+ boolean updatedPrinters = false;
+ final int updatedPrinterCount = printers.size();
+ for (int i = 0; i < updatedPrinterCount; i++) {
+ PrinterInfo updatedPrinter = printers.get(i);
+ if (mPrinters.containsKey(updatedPrinter.getId())) {
+ mPrinters.put(updatedPrinter.getId(), updatedPrinter);
+ updatedPrinters = true;
+ }
+ }
+ if (updatedPrinters) {
+ deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
+ }
+ }
+ }
+
+ private final class PersistenceManager {
+ private static final String PERSIST_FILE_NAME = "printer_history.xml";
+
+ private static final String TAG_PRINTERS = "printers";
+
+ private static final String TAG_PRINTER = "printer";
+ private static final String TAG_PRINTER_ID = "printerId";
+
+ private static final String ATTR_LOCAL_ID = "localId";
+ private static final String ATTR_SERVICE_NAME = "serviceName";
+
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_DESCRIPTION = "description";
+ private static final String ATTR_STATUS = "status";
+
+ private final AtomicFile mStatePersistFile;
+
+ private List<PrinterInfo> mHistoricalPrinters;
+
+ private boolean mReadHistoryCompleted;
+ private boolean mReadHistoryInProgress;
+
+ private final AsyncTask<Void, Void, List<PrinterInfo>> mReadTask =
+ new AsyncTask<Void, Void, List<PrinterInfo>>() {
+ @Override
+ protected List<PrinterInfo> doInBackground(Void... args) {
+ return doReadPrinterHistory();
+ }
+
+ @Override
+ protected void onPostExecute(List<PrinterInfo> printers) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "read history completed");
+ }
+
+ mHistoricalPrinters = printers;
+
+ // Compute the favorite printers.
+ mFavoritePrinters = computeFavoritePrinters(printers);
+
+ // We want the first few favorite printers on top of the list.
+ final int favoriteCount = Math.min(mFavoritePrinters.size(),
+ MAX_HISTORICAL_PRINTER_COUNT);
+ for (int i = 0; i < favoriteCount; i++) {
+ PrinterInfo favoritePrinter = mFavoritePrinters.get(i);
+ mPrinters.put(favoritePrinter.getId(), favoritePrinter);
+ }
+
+ mReadHistoryInProgress = false;
+ mReadHistoryCompleted = true;
+
+ loadInternal();
+ }
+
+ private List<PrinterInfo> doReadPrinterHistory() {
+ FileInputStream in = null;
+ try {
+ in = mStatePersistFile.openRead();
+ } catch (FileNotFoundException fnfe) {
+ Log.i(LOG_TAG, "No existing printer history.");
+ return new ArrayList<PrinterInfo>();
+ }
+ try {
+ List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parseState(parser, printers);
+ return printers;
+ } 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 {
+ IoUtils.closeQuietly(in);
+ }
+
+ return Collections.emptyList();
+ }
+
+ private void parseState(XmlPullParser parser, List<PrinterInfo> outPrinters)
+ throws IOException, XmlPullParserException {
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS);
+ parser.next();
+
+ while (parsePrinter(parser, outPrinters)) {
+ // Be nice and respond to cancellation
+ if (isCancelled()) {
+ return;
+ }
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);
+ }
+
+ private boolean parsePrinter(XmlPullParser parser, List<PrinterInfo> outPrinters)
+ throws IOException, XmlPullParserException {
+ skipEmptyTextTags(parser);
+ if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) {
+ return false;
+ }
+
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+ String description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
+ final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS));
+
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID);
+ String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
+ ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
+ null, ATTR_SERVICE_NAME));
+ PrinterId printerId = new PrinterId(service, localId);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
+ parser.next();
+
+ PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status);
+ builder.setDescription(description);
+ PrinterInfo printer = builder.create();
+
+ outPrinters.add(printer);
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "[RESTORED] " + printer);
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_PRINTER);
+
+ 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;
+ }
+ };
+
+ private final AsyncTask<List<PrinterInfo>, Void, Void> mWriteTask =
+ new AsyncTask<List<PrinterInfo>, Void, Void>() {
+ @Override
+ protected Void doInBackground(List<PrinterInfo>... printers) {
+ doWritePrinterHistory(printers[0]);
+ return null;
+ }
+
+ private void doWritePrinterHistory(List<PrinterInfo> printers) {
+ FileOutputStream out = null;
+ try {
+ out = mStatePersistFile.startWrite();
+
+ XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(out, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_PRINTERS);
+
+ final int printerCount = printers.size();
+ for (int i = 0; i < printerCount; i++) {
+ PrinterInfo printer = printers.get(i);
+
+ serializer.startTag(null, TAG_PRINTER);
+
+ serializer.attribute(null, ATTR_NAME, printer.getName());
+ serializer.attribute(null, ATTR_STATUS, String.valueOf(
+ printer.getStatus()));
+ String description = printer.getDescription();
+ if (description != null) {
+ serializer.attribute(null, ATTR_DESCRIPTION, description);
+ }
+
+ PrinterId printerId = printer.getId();
+ serializer.startTag(null, TAG_PRINTER_ID);
+ serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
+ serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
+ .flattenToString());
+ serializer.endTag(null, TAG_PRINTER_ID);
+
+ serializer.endTag(null, TAG_PRINTER);
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "[PERSISTED] " + printer);
+ }
+ }
+
+ serializer.endTag(null, TAG_PRINTERS);
+ serializer.endDocument();
+ mStatePersistFile.finishWrite(out);
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "[PERSIST END]");
+ }
+ } catch (IOException ioe) {
+ Slog.w(LOG_TAG, "Failed to write printer history, restoring backup.", ioe);
+ mStatePersistFile.failWrite(out);
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+ };
+
+ private PersistenceManager(Context context) {
+ mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
+ PERSIST_FILE_NAME));
+ }
+
+ public boolean isReadHistoryInProgress() {
+ return mReadHistoryInProgress;
+ }
+
+ public boolean isReadHistoryCompleted() {
+ return mReadHistoryCompleted;
+ }
+
+ public boolean stopReadPrinterHistory() {
+ return mReadTask.cancel(true);
+ }
+
+ public void readPrinterHistory() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "read history started");
+ }
+ mReadHistoryInProgress = true;
+ mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void addPrinterAndWritePrinterHistory(PrinterInfo printer) {
+ if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
+ mHistoricalPrinters.remove(0);
+ }
+ mHistoricalPrinters.add(printer);
+ mWriteTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, mHistoricalPrinters);
+ }
+
+ private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) {
+ Map<PrinterId, PrinterRecord> recordMap =
+ new ArrayMap<PrinterId, PrinterRecord>();
+
+ // Recompute the weights.
+ float currentWeight = 1.0f;
+ final int printerCount = printers.size();
+ for (int i = printerCount - 1; i >= 0; i--) {
+ PrinterInfo printer = printers.get(i);
+ // Aggregate weight for the same printer
+ PrinterRecord record = recordMap.get(printer.getId());
+ if (record == null) {
+ record = new PrinterRecord(printer);
+ recordMap.put(printer.getId(), record);
+ }
+ record.weight += currentWeight;
+ currentWeight *= WEIGHT_DECAY_COEFFICIENT;
+ }
+
+ // Soft the favorite printers.
+ List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>(
+ recordMap.values());
+ Collections.sort(favoriteRecords);
+
+ // Write the favorites to the output.
+ final int favoriteCount = favoriteRecords.size();
+ List<PrinterInfo> favoritePrinters = new ArrayList<PrinterInfo>(favoriteCount);
+ for (int i = 0; i < favoriteCount; i++) {
+ PrinterInfo printer = favoriteRecords.get(i).printer;
+ favoritePrinters.add(printer);
+ }
+
+ return favoritePrinters;
+ }
+
+ private final class PrinterRecord implements Comparable<PrinterRecord> {
+ public final PrinterInfo printer;
+ public float weight;
+
+ public PrinterRecord(PrinterInfo printer) {
+ this.printer = printer;
+ }
+
+ @Override
+ public int compareTo(PrinterRecord another) {
+ return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
+ }
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
index 9160b7d..d3dd8c9 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -18,12 +18,17 @@ package com.android.printspooler;
import android.app.Activity;
import android.app.Dialog;
+import android.app.LoaderManager;
import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -34,15 +39,15 @@ import android.os.Message;
import android.os.RemoteException;
import android.print.ILayoutResultCallback;
import android.print.IPrintDocumentAdapter;
-import android.print.IPrinterDiscoverySessionController;
-import android.print.IPrinterDiscoverySessionObserver;
import android.print.IWriteResultCallback;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.PrintJobInfo;
+import android.print.PrintManager;
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
@@ -64,15 +69,23 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@@ -94,15 +107,32 @@ public class PrintJobConfigActivity extends Activity {
public static final String EXTRA_PRINT_ATTRIBUTES = "printAttributes";
public static final String EXTRA_PRINT_JOB_ID = "printJobId";
- private static final int CONTROLLER_STATE_INITIALIZED = 1;
- private static final int CONTROLLER_STATE_STARTED = 2;
- private static final int CONTROLLER_STATE_LAYOUT_STARTED = 3;
- private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 4;
- private static final int CONTROLLER_STATE_WRITE_STARTED = 5;
- private static final int CONTROLLER_STATE_WRITE_COMPLETED = 6;
- private static final int CONTROLLER_STATE_FINISHED = 7;
- private static final int CONTROLLER_STATE_FAILED = 8;
- private static final int CONTROLLER_STATE_CANCELLED = 9;
+ public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
+
+ private static final int LOADER_ID_PRINTERS_LOADER = 1;
+
+ private static final int DEST_ADAPTER_MIN_ITEM_COUNT = 2;
+ private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
+
+ private static final int DEST_ADAPTER_POSITION_SEARCHING_FOR_PRINTERS = 0;
+ private static final int DEST_ADAPTER_POSITION_SAVE_AS_PDF = 1;
+
+ private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
+ private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1;
+ private static final int DEST_ADAPTER_ITEM_ID_SEARCHING_FOR_PRINTERS = Integer.MAX_VALUE - 2;
+
+ private static final int ACTIVITY_REQUEST_CREATE_FILE = 1;
+ private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2;
+
+ private static final int CONTROLLER_STATE_FINISHED = 1;
+ private static final int CONTROLLER_STATE_FAILED = 2;
+ private static final int CONTROLLER_STATE_CANCELLED = 3;
+ private static final int CONTROLLER_STATE_INITIALIZED = 4;
+ private static final int CONTROLLER_STATE_STARTED = 5;
+ private static final int CONTROLLER_STATE_LAYOUT_STARTED = 6;
+ private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 7;
+ private static final int CONTROLLER_STATE_WRITE_STARTED = 8;
+ private static final int CONTROLLER_STATE_WRITE_COMPLETED = 9;
private static final int EDITOR_STATE_INITIALIZED = 1;
private static final int EDITOR_STATE_CONFIRMED_PRINT = 2;
@@ -110,6 +140,7 @@ public class PrintJobConfigActivity extends Activity {
private static final int EDITOR_STATE_CANCELLED = 4;
private static final int MIN_COPIES = 1;
+ private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES);
private static final Pattern PATTERN_DIGITS = Pattern.compile("\\d");
@@ -132,11 +163,9 @@ public class PrintJobConfigActivity extends Activity {
}
};
- private PrintSpooler mSpooler;
private Editor mEditor;
private Document mDocument;
private PrintController mController;
- private PrinterDiscoverySessionObserver mPrinterDiscoverySessionObserver;
private int mPrintJobId;
@@ -147,9 +176,6 @@ public class PrintJobConfigActivity extends Activity {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
- setContentView(R.layout.print_job_config_activity_container);
-
- getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
Bundle extras = getIntent().getExtras();
@@ -168,12 +194,17 @@ public class PrintJobConfigActivity extends Activity {
mCurrPrintAttributes.copyFrom(attributes);
}
- mSpooler = PrintSpooler.peekInstance();
+ setContentView(R.layout.print_job_config_activity_container);
+
+ // TODO: This should be on the style
+ getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+
+
mEditor = new Editor();
mDocument = new Document();
mController = new PrintController(new RemotePrintDocumentAdapter(
IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
- mSpooler.generateFileForPrintJob(mPrintJobId)));
+ PrintSpoolerService.peekInstance().generateFileForPrintJob(mPrintJobId)));
try {
mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
@@ -184,9 +215,6 @@ public class PrintJobConfigActivity extends Activity {
mController.initialize();
mEditor.initialize();
- mPrinterDiscoverySessionObserver = new PrinterDiscoverySessionObserver(mEditor,
- getMainLooper());
- mSpooler.createPrinterDiscoverySession(mPrinterDiscoverySessionObserver);
}
@Override
@@ -194,17 +222,14 @@ public class PrintJobConfigActivity extends Activity {
// We can safely do the work in here since at this point
// the system is bound to our (spooler) process which
// guarantees that this process will not be killed.
- mPrinterDiscoverySessionObserver.close();
- mPrinterDiscoverySessionObserver.destroy();
- mPrinterDiscoverySessionObserver = null;
if (mController.hasStarted()) {
mController.finish();
}
if (mEditor.isPrintConfirmed() && mController.isFinished()) {
- mSpooler.setPrintJobState(mPrintJobId,
+ PrintSpoolerService.peekInstance().setPrintJobState(mPrintJobId,
PrintJobInfo.STATE_QUEUED, null);
} else {
- mSpooler.setPrintJobState(mPrintJobId,
+ PrintSpoolerService.peekInstance().setPrintJobState(mPrintJobId,
PrintJobInfo.STATE_CANCELED, null);
}
mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
@@ -315,12 +340,13 @@ public class PrintJobConfigActivity extends Activity {
public void update() {
if (!printAttributesChanged()) {
- // If the attributes changes, then we do not do a layout but may
+ // If the attributes changed, then we do not do a layout but may
// have to ask the app to write some pages. Hence, pretend layout
// completed and nothing changed, so we handle writing as usual.
handleOnLayoutFinished(mDocument.info, false, mRequestCounter.get());
} else {
- mSpooler.setPrintJobAttributesNoPersistence(mPrintJobId, mCurrPrintAttributes);
+ PrintSpoolerService.peekInstance().setPrintJobAttributesNoPersistence(
+ mPrintJobId, mCurrPrintAttributes);
mMetadata.putBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW,
!mEditor.isPrintConfirmed());
@@ -353,15 +379,14 @@ public class PrintJobConfigActivity extends Activity {
}
mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED;
+ mEditor.updateUi();
- // If the info changed, we update the document and the print job,
- // and update the UI since the the page range selection may have
- // become invalid.
+ // If the info changed, we update the document and the print job.
final boolean infoChanged = !info.equals(mDocument.info);
if (infoChanged) {
mDocument.info = info;
- mSpooler.setPrintJobPrintDocumentInfoNoPersistence(mPrintJobId, info);
- mEditor.updateUi();
+ PrintSpoolerService.peekInstance().setPrintJobPrintDocumentInfoNoPersistence(
+ mPrintJobId, info);
}
// If the document info or the layout changed, then
@@ -447,11 +472,13 @@ public class PrintJobConfigActivity extends Activity {
if (Arrays.equals(mDocument.pages, mRequestedPages)) {
// We got a document with exactly the pages we wanted. Hence,
// the printer has to print all pages in the data.
- mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, ALL_PAGES_ARRAY);
+ PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+ ALL_PAGES_ARRAY);
} else if (Arrays.equals(mDocument.pages, ALL_PAGES_ARRAY)) {
// We requested specific pages but got all of them. Hence,
// the printer has to print only the requested pages.
- mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mRequestedPages);
+ PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+ mRequestedPages);
} else if (PageRangeUtils.contains(mDocument.pages, mRequestedPages)) {
// We requested specific pages and got more but not all pages.
// Hence, we have to offset appropriately the printed pages to
@@ -460,14 +487,16 @@ public class PrintJobConfigActivity extends Activity {
final int offset = mDocument.pages[0].getStart() - pages[0].getStart();
PageRange[] offsetPages = Arrays.copyOf(mDocument.pages, mDocument.pages.length);
PageRangeUtils.offsetStart(offsetPages, offset);
- mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, offsetPages);
+ PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+ offsetPages);
} else if (Arrays.equals(mRequestedPages, ALL_PAGES_ARRAY)
&& mDocument.pages.length == 1 && mDocument.pages[0].getStart() == 0
&& mDocument.pages[0].getEnd() == mDocument.info.getPageCount() - 1) {
// We requested all pages via the special constant and got all
// of them as an explicit enumeration. Hence, the printer has
// to print only the requested pages.
- mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mDocument.pages);
+ PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+ mDocument.pages);
} else {
// We did not get the pages we requested, then the application
// misbehaves, so we fail quickly.
@@ -478,7 +507,16 @@ public class PrintJobConfigActivity extends Activity {
}
if (mEditor.isDone()) {
- PrintJobConfigActivity.this.finish();
+ if (mEditor.isPrintingToPdf()) {
+ PrintJobInfo printJob = PrintSpoolerService.peekInstance()
+ .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
+ Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ intent.setType("application/pdf");
+ intent.putExtra(Intent.EXTRA_TITLE, printJob.getLabel());
+ startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
+ } else {
+ PrintJobConfigActivity.this.finish();
+ }
}
}
@@ -585,33 +623,111 @@ public class PrintJobConfigActivity extends Activity {
}
}
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case ACTIVITY_REQUEST_CREATE_FILE: {
+ if (data != null) {
+ Uri uri = data.getData();
+ writePrintJobDataAndFinish(uri);
+ } else {
+ mEditor.showUi(Editor.UI_EDITING_PRINT_JOB,
+ new Runnable() {
+ @Override
+ public void run() {
+ mEditor.initialize();
+ mEditor.bindUi();
+ mEditor.updateUi();
+ }
+ });
+ }
+ } break;
+
+ case ACTIVITY_REQUEST_SELECT_PRINTER: {
+ if (resultCode == RESULT_OK) {
+ PrinterId printerId = (PrinterId) data.getParcelableExtra(
+ INTENT_EXTRA_PRINTER_ID);
+ // TODO: Make sure the selected printer is in the shown list.
+ mEditor.selectPrinter(printerId);
+ }
+ } break;
+ }
+ }
+
+ private void writePrintJobDataAndFinish(final Uri uri) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ PrintJobInfo printJob = PrintSpoolerService.peekInstance()
+ .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
+ if (printJob == null) {
+ return null;
+ }
+ File file = PrintSpoolerService.peekInstance()
+ .generateFileForPrintJob(mPrintJobId);
+ in = new FileInputStream(file);
+ out = getContentResolver().openOutputStream(uri);
+ final byte[] buffer = new byte[8192];
+ while (true) {
+ final int readByteCount = in.read(buffer);
+ if (readByteCount < 0) {
+ break;
+ }
+ 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 {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ }
+ return null;
+ }
+
+ @Override
+ public void onPostExecute(Void result) {
+ mEditor.cancel();
+ PrintJobConfigActivity.this.finish();
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
private final class Editor {
- private final EditText mCopiesEditText;
+ private static final int UI_NONE = 0;
+ private static final int UI_EDITING_PRINT_JOB = 1;
+ private static final int UI_GENERATING_PRINT_JOB = 2;
+
+ private EditText mCopiesEditText;
- private final TextView mRangeTitle;
- private final EditText mRangeEditText;
+ private TextView mRangeTitle;
+ private EditText mRangeEditText;
- private final Spinner mDestinationSpinner;
- private final ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter;
+ private Spinner mDestinationSpinner;
+ private final DestinationAdapter mDestinationSpinnerAdapter;
- private final Spinner mMediaSizeSpinner;
+ private Spinner mMediaSizeSpinner;
private final ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
- private final Spinner mColorModeSpinner;
+ private Spinner mColorModeSpinner;
private final ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
- private final Spinner mOrientationSpinner;
+ private Spinner mOrientationSpinner;
private final ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
- private final Spinner mRangeOptionsSpinner;
+ private Spinner mRangeOptionsSpinner;
private final ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
private final SimpleStringSplitter mStringCommaSplitter =
new SimpleStringSplitter(',');
- private final View mContentContainer;
+ private View mContentContainer;
- private final Button mPrintButton;
+ private Button mPrintButton;
private final OnItemSelectedListener mOnItemSelectedListener =
new AdapterView.OnItemSelectedListener() {
@@ -622,16 +738,32 @@ public class PrintJobConfigActivity extends Activity {
mIgnoreNextDestinationChange = false;
return;
}
+ if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) {
+ mIgnoreNextDestinationChange = true;
+ mDestinationSpinner.setSelection(0);
+ Intent intent = new Intent(PrintJobConfigActivity.this,
+ SelectPrinterActivity.class);
+ startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER);
+ return;
+ }
+ mWaitingForPrinterCapabilities = false;
mCurrPrintAttributes.clear();
- SpinnerItem<PrinterInfo> dstItem = mDestinationSpinnerAdapter.getItem(position);
- if (dstItem != null) {
- PrinterInfo printer = dstItem.value;
- mSpooler.setPrintJobPrinterNoPersistence(mPrintJobId, printer);
+ PrinterInfo printer = (PrinterInfo) mDestinationSpinnerAdapter
+ .getItem(position);
+ if (printer != null) {
+ PrintSpoolerService.peekInstance().setPrintJobPrinterNoPersistence(
+ mPrintJobId, printer);
PrinterCapabilitiesInfo capabilities = printer.getCapabilities();
if (capabilities == null) {
List<PrinterId> printerIds = new ArrayList<PrinterId>();
printerIds.add(printer.getId());
- mPrinterDiscoverySessionObserver.requestPrinterUpdate(printer.getId());
+ FusedPrintersProvider printersLoader = (FusedPrintersProvider)
+ (Loader<?>) getLoaderManager().getLoader(
+ LOADER_ID_PRINTERS_LOADER);
+ if (printersLoader != null) {
+ printersLoader.refreshPrinter(printer.getId());
+ }
+ mWaitingForPrinterCapabilities = true;
//TODO: We need a timeout for the update.
} else {
capabilities.getDefaults(mCurrPrintAttributes);
@@ -643,6 +775,31 @@ public class PrintJobConfigActivity extends Activity {
}
}
}
+
+ // The printer changed so we want to start with a clean slate
+ // for the print options and let them be populated from the
+ // printer capabilities and use the printer defaults.
+ if (!mMediaSizeSpinnerAdapter.isEmpty()) {
+ mIgnoreNextMediaSizeChange = true;
+ mMediaSizeSpinnerAdapter.clear();
+ }
+ if (!mColorModeSpinnerAdapter.isEmpty()) {
+ mIgnoreNextColorModeChange = true;
+ mColorModeSpinnerAdapter.clear();
+ }
+ if (!mOrientationSpinnerAdapter.isEmpty()) {
+ mIgnoreNextOrientationChange = true;
+ mOrientationSpinnerAdapter.clear();
+ }
+ if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
+ mIgnoreNextRangeOptionChange = true;
+ mRangeOptionsSpinner.setSelection(0);
+ }
+ if (!TextUtils.isEmpty(mCopiesEditText.getText())) {
+ mIgnoreNextCopiesChange = true;
+ mCopiesEditText.setText(MIN_COPIES_STRING);
+ }
+
updateUi();
} else if (spinner == mMediaSizeSpinner) {
if (mIgnoreNextMediaSizeChange) {
@@ -728,7 +885,8 @@ public class PrintJobConfigActivity extends Activity {
}
mCopiesEditText.setError(null);
- mSpooler.setPrintJobCopiesNoPersistence(mPrintJobId, copies);
+ PrintSpoolerService.peekInstance().setPrintJobCopiesNoPersistence(
+ mPrintJobId, copies);
updateUi();
if (hadErrors && !hasErrors() && printAttributesChanged()) {
@@ -805,54 +963,64 @@ public class PrintJobConfigActivity extends Activity {
private boolean mIgnoreNextCopiesChange;
private boolean mIgnoreNextRangeChange;
- public Editor() {
- // Content container
- mContentContainer = findViewById(R.id.content_container);
+ private boolean mWaitingForPrinterCapabilities;
- // Copies
- mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
- mCopiesEditText.setText(String.valueOf(MIN_COPIES));
- mSpooler.setPrintJobCopiesNoPersistence(mPrintJobId, MIN_COPIES);
- mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
- mCopiesEditText.selectAll();
+ private int mCurrentUi = UI_NONE;
+ public Editor() {
// Destination.
- mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
mDestinationSpinnerAdapter = new DestinationAdapter();
- mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
- mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ mDestinationSpinnerAdapter.registerDataSetObserver(new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ final int selectedPosition = mDestinationSpinner.getSelectedItemPosition();
+ if (mDestinationSpinnerAdapter.getCount() > 0) {
+ // Make sure we select the first printer if we have data.
+ if (selectedPosition == AdapterView.INVALID_POSITION) {
+ mDestinationSpinner.setSelection(0);
+ }
+ } else {
+ // Make sure we select no printer if we have no data.
+ mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
+ }
+
+ // Maybe we did not have capabilities when the current printer was
+ // selected, but now the selected printer has capabilities. Generate
+ // a fake selection so the code in the selection change handling takes
+ // care of updating everything. This way the logic is in one place.
+ if (mWaitingForPrinterCapabilities) {
+ mWaitingForPrinterCapabilities = false;
+ PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
+ if (printer != null && printer.getCapabilities() != null) {
+ mOnItemSelectedListener.onItemSelected(mDestinationSpinner, null,
+ selectedPosition, selectedPosition);
+ }
+ }
+ updateUi();
+ }
+
+ @Override
+ public void onInvalidated() {
+ updateUi();
+ }
+ });
// Media size.
- mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
mMediaSizeSpinnerAdapter = new ArrayAdapter<SpinnerItem<MediaSize>>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
- mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
- mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Color mode.
- mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
mColorModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
- mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
- mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Orientation
- mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
mOrientationSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
- mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
- mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-
- // Range
- mRangeTitle = (TextView) findViewById(R.id.page_range_title);
- mRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
- mRangeEditText.addTextChangedListener(mRangeTextWatcher);
// Range options
- mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
mRangeOptionsSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
@@ -865,25 +1033,26 @@ public class PrintJobConfigActivity extends Activity {
mRangeOptionsSpinnerAdapter.add(new SpinnerItem<Integer>(
rangeOptionsValues[i], rangeOptionsLabels[i]));
}
- mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
- if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
- mIgnoreNextRangeOptionChange = true;
- mRangeOptionsSpinner.setSelection(0);
- }
- // Print button
- mPrintButton = (Button) findViewById(R.id.print_button);
- mPrintButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mEditor.confirmPrint();
- updateUi();
- mController.update();
- showGeneratingPrintJobUi();
+ showUi(UI_EDITING_PRINT_JOB, null);
+ bindUi();
+ updateUi();
+ }
+
+ public void selectPrinter(PrinterId printerId) {
+ final int printerCount = mDestinationSpinnerAdapter.getCount();
+ for (int i = 0; i < printerCount; i++) {
+ PrinterInfo printer = (PrinterInfo) mDestinationSpinnerAdapter.getItem(i);
+ if (printer.getId().equals(printerId)) {
+ mDestinationSpinner.setSelection(i);
+ return;
}
- });
+ }
+ }
- updateUi();
+ public boolean isPrintingToPdf() {
+ return mDestinationSpinner.getSelectedItem()
+ == mDestinationSpinnerAdapter.mFakePdfPrinter;
}
public boolean shouldCloseOnTouch(MotionEvent event) {
@@ -913,19 +1082,104 @@ public class PrintJobConfigActivity extends Activity {
}
public boolean isShwoingGeneratingPrintJobUi() {
- return (findViewById(R.id.content_generating) != null);
+ return (mCurrentUi == UI_GENERATING_PRINT_JOB);
}
- private void showGeneratingPrintJobUi() {
- // Find everything we will shuffle around.
- final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
- final View contentEditing = contentContainer.findViewById(R.id.content_editing);
- final View contentGenerating = getLayoutInflater().inflate(
- R.layout.print_job_config_activity_content_generating,
- contentContainer, false);
+ public void showUi(int ui, final Runnable postSwitchCallback) {
+ if (ui == UI_NONE) {
+ throw new IllegalStateException("cannot remove the ui");
+ }
+
+ if (mCurrentUi == ui) {
+ return;
+ }
+
+ switch (mCurrentUi) {
+ case UI_NONE: {
+ switch (ui) {
+ case UI_EDITING_PRINT_JOB: {
+ doUiSwitch(R.layout.print_job_config_activity_content_editing);
+ registerPrintButtonClickListener();
+ if (postSwitchCallback != null) {
+ postSwitchCallback.run();
+ }
+ } break;
- // Wire the cancel action.
- Button cancelButton = (Button) contentGenerating.findViewById(R.id.cancel_button);
+ case UI_GENERATING_PRINT_JOB: {
+ doUiSwitch(R.layout.print_job_config_activity_content_generating);
+ registerCancelButtonClickListener();
+ if (postSwitchCallback != null) {
+ postSwitchCallback.run();
+ }
+ } break;
+ }
+ } break;
+
+ case UI_EDITING_PRINT_JOB: {
+ switch (ui) {
+ case UI_GENERATING_PRINT_JOB: {
+ animateUiSwitch(R.layout.print_job_config_activity_content_generating,
+ new Runnable() {
+ @Override
+ public void run() {
+ registerCancelButtonClickListener();
+ if (postSwitchCallback != null) {
+ postSwitchCallback.run();
+ }
+ }
+ });
+ } break;
+ }
+ } break;
+
+ case UI_GENERATING_PRINT_JOB: {
+ switch (ui) {
+ case UI_EDITING_PRINT_JOB: {
+ animateUiSwitch(R.layout.print_job_config_activity_content_editing,
+ new Runnable() {
+ @Override
+ public void run() {
+ registerPrintButtonClickListener();
+ if (postSwitchCallback != null) {
+ postSwitchCallback.run();
+ }
+ }
+ });
+ } break;
+ }
+ } break;
+ }
+
+ mCurrentUi = ui;
+ }
+
+ private void registerPrintButtonClickListener() {
+ Button printButton = (Button) findViewById(R.id.print_button);
+ printButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
+ if (printer != null) {
+ mEditor.confirmPrint();
+ mController.update();
+ if (!printer.equals(mDestinationSpinnerAdapter.mFakePdfPrinter)) {
+ FusedPrintersProvider printersLoader = (FusedPrintersProvider)
+ (Loader<?>) getLoaderManager().getLoader(
+ LOADER_ID_PRINTERS_LOADER);
+ if (printersLoader != null) {
+ printersLoader.addHistoricalPrinter(printer);
+ }
+ }
+ } else {
+ mEditor.cancel();
+ PrintJobConfigActivity.this.finish();
+ }
+ }
+ });
+ }
+
+ private void registerCancelButtonClickListener() {
+ Button cancelButton = (Button) findViewById(R.id.cancel_button);
cancelButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -935,24 +1189,38 @@ public class PrintJobConfigActivity extends Activity {
mEditor.cancel();
}
});
+ }
+
+ private void doUiSwitch(int showLayoutId) {
+ ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
+ contentContainer.removeAllViews();
+ getLayoutInflater().inflate(showLayoutId, contentContainer, true);
+ }
+
+ private void animateUiSwitch(int showLayoutId, final Runnable postAnimateCommand) {
+ // Find everything we will shuffle around.
+ final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
+ final View hidingView = contentContainer.getChildAt(0);
+ final View showingView = getLayoutInflater().inflate(showLayoutId,
+ null, false);
// First animation - fade out the old content.
- contentEditing.animate().alpha(0.0f).withLayer().withEndAction(new Runnable() {
+ hidingView.animate().alpha(0.0f).withLayer().withEndAction(new Runnable() {
@Override
public void run() {
- contentEditing.setVisibility(View.INVISIBLE);
+ hidingView.setVisibility(View.INVISIBLE);
// Prepare the new content with correct size and alpha.
- contentGenerating.setMinimumWidth(contentContainer.getWidth());
- contentGenerating.setAlpha(0.0f);
+ showingView.setMinimumWidth(contentContainer.getWidth());
+ showingView.setAlpha(0.0f);
- // Compute how to much shrink the container to fit around the new content.
+ // Compute how to much shrink /stretch the content.
final int widthSpec = MeasureSpec.makeMeasureSpec(
- contentContainer.getWidth(), MeasureSpec.AT_MOST);
+ contentContainer.getWidth(), MeasureSpec.UNSPECIFIED);
final int heightSpec = MeasureSpec.makeMeasureSpec(
- contentContainer.getHeight(), MeasureSpec.AT_MOST);
- contentGenerating.measure(widthSpec, heightSpec);
- final float scaleY = (float) contentGenerating.getMeasuredHeight()
+ contentContainer.getHeight(), MeasureSpec.UNSPECIFIED);
+ showingView.measure(widthSpec, heightSpec);
+ final float scaleY = (float) showingView.getMeasuredHeight()
/ (float) contentContainer.getHeight();
// Second animation - resize the container.
@@ -963,10 +1231,16 @@ public class PrintJobConfigActivity extends Activity {
// Swap the old and the new content.
contentContainer.removeAllViews();
contentContainer.setScaleY(1.0f);
- contentContainer.addView(contentGenerating);
+ contentContainer.addView(showingView);
// Third animation - show the new content.
- contentGenerating.animate().withLayer().alpha(1.0f);
+ showingView.animate().withLayer().alpha(1.0f).withEndAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ postAnimateCommand.run();
+ }
+ });
}
});
}
@@ -975,10 +1249,6 @@ public class PrintJobConfigActivity extends Activity {
public void initialize() {
mEditorState = EDITOR_STATE_INITIALIZED;
- if (mDestinationSpinner.getSelectedItemPosition() != AdapterView.INVALID_POSITION) {
- mIgnoreNextDestinationChange = true;
- mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
- }
}
public boolean isCancelled() {
@@ -1001,6 +1271,7 @@ public class PrintJobConfigActivity extends Activity {
public void confirmPrint() {
mEditorState = EDITOR_STATE_CONFIRMED_PRINT;
+ showUi(UI_GENERATING_PRINT_JOB, null);
}
public boolean isPreviewConfirmed() {
@@ -1046,7 +1317,79 @@ public class PrintJobConfigActivity extends Activity {
return ALL_PAGES_ARRAY;
}
+ private void bindUi() {
+ if (mCurrentUi != UI_EDITING_PRINT_JOB) {
+ return;
+ }
+
+ // Content container
+ mContentContainer = findViewById(R.id.content_container);
+
+ // Copies
+ mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
+ mCopiesEditText.setText(MIN_COPIES_STRING);
+ mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
+ mCopiesEditText.selectAll();
+ if (!TextUtils.equals(mCopiesEditText.getText(), MIN_COPIES_STRING)) {
+ mIgnoreNextCopiesChange = true;
+ }
+ PrintSpoolerService.peekInstance().setPrintJobCopiesNoPersistence(
+ mPrintJobId, MIN_COPIES);
+
+ // Destination.
+ mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
+ mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
+ mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ if (mDestinationSpinnerAdapter.getCount() > 0) {
+ mIgnoreNextDestinationChange = true;
+ }
+
+ // Media size.
+ mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
+ mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
+ mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ if (mMediaSizeSpinnerAdapter.getCount() > 0) {
+ mIgnoreNextMediaSizeChange = true;
+ }
+
+ // Color mode.
+ mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
+ mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
+ mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ if (mColorModeSpinnerAdapter.getCount() > 0) {
+ mIgnoreNextColorModeChange = true;
+ }
+
+ // Orientation
+ mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
+ mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
+ mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ if (mOrientationSpinnerAdapter.getCount() > 0) {
+ mIgnoreNextOrientationChange = true;
+ }
+
+ // Range
+ mRangeTitle = (TextView) findViewById(R.id.page_range_title);
+ mRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
+ mRangeEditText.addTextChangedListener(mRangeTextWatcher);
+
+ // Range options
+ mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
+ mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
+ mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ if (mRangeOptionsSpinnerAdapter.getCount() > 0) {
+ mIgnoreNextRangeOptionChange = true;
+ }
+
+ // Print button
+ mPrintButton = (Button) findViewById(R.id.print_button);
+ registerPrintButtonClickListener();
+ }
+
public void updateUi() {
+ if (mCurrentUi != UI_EDITING_PRINT_JOB) {
+ return;
+ }
if (isPrintConfirmed() || isPreviewConfirmed() || isCancelled()) {
mDestinationSpinner.setEnabled(false);
mCopiesEditText.setEnabled(false);
@@ -1061,14 +1404,20 @@ public class PrintJobConfigActivity extends Activity {
return;
}
+ // If a printer with capabilities is selected, then we enabled all options.
+ boolean allOptionsEnabled = false;
final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
+ if (selectedIndex >= 0) {
+ Object item = mDestinationSpinnerAdapter.getItem(selectedIndex);
+ if (item instanceof PrinterInfo) {
+ PrinterInfo printer = (PrinterInfo) item;
+ if (printer.getCapabilities() != null) {
+ allOptionsEnabled = true;
+ }
+ }
+ }
- if (selectedIndex < 0 || mDestinationSpinnerAdapter.getItem(
- selectedIndex).value.getCapabilities() == null) {
-
- // Destination
- mDestinationSpinner.setEnabled(false);
-
+ if (!allOptionsEnabled) {
String minCopiesString = String.valueOf(MIN_COPIES);
if (!TextUtils.equals(mCopiesEditText.getText(), minCopiesString)) {
mIgnoreNextCopiesChange = true;
@@ -1121,17 +1470,10 @@ public class PrintJobConfigActivity extends Activity {
mPrintButton.setEnabled(false);
} else {
PrintAttributes defaultAttributes = mTempPrintAttributes;
- PrinterInfo printer = mDestinationSpinnerAdapter.getItem(selectedIndex).value;
+ PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
PrinterCapabilitiesInfo capabilities = printer.getCapabilities();
printer.getCapabilities().getDefaults(defaultAttributes);
- // Destination
- if (mDestinationSpinnerAdapter.getCount() > 1) {
- mDestinationSpinner.setEnabled(true);
- } else {
- mDestinationSpinner.setEnabled(false);
- }
-
// Copies
mCopiesEditText.setEnabled(true);
@@ -1159,9 +1501,6 @@ public class PrintJobConfigActivity extends Activity {
if (mediaSizeCount <= 0) {
mMediaSizeSpinner.setEnabled(false);
mMediaSizeSpinner.setSelection(AdapterView.INVALID_POSITION);
- } else if (mediaSizeCount == 1) {
- mMediaSizeSpinner.setEnabled(false);
- mMediaSizeSpinner.setSelection(0);
} else {
mMediaSizeSpinner.setEnabled(true);
final int selectedMediaSizeIndex = Math.max(mediaSizes.indexOf(
@@ -1172,6 +1511,7 @@ public class PrintJobConfigActivity extends Activity {
}
}
}
+ mMediaSizeSpinner.setEnabled(true);
// Color mode.
final int colorModes = capabilities.getColorModes();
@@ -1210,9 +1550,6 @@ public class PrintJobConfigActivity extends Activity {
if (colorModeCount <= 0) {
mColorModeSpinner.setEnabled(false);
mColorModeSpinner.setSelection(AdapterView.INVALID_POSITION);
- } else if (colorModeCount == 1) {
- mColorModeSpinner.setEnabled(false);
- mColorModeSpinner.setSelection(0);
} else {
mColorModeSpinner.setEnabled(true);
final int selectedColorModeIndex = Integer.numberOfTrailingZeros(
@@ -1223,6 +1560,7 @@ public class PrintJobConfigActivity extends Activity {
}
}
}
+ mColorModeSpinner.setEnabled(true);
// Orientation.
final int orientations = capabilities.getOrientations();
@@ -1262,9 +1600,6 @@ public class PrintJobConfigActivity extends Activity {
if (orientationCount <= 0) {
mOrientationSpinner.setEnabled(false);
mOrientationSpinner.setSelection(AdapterView.INVALID_POSITION);
- } else if (orientationCount == 1) {
- mOrientationSpinner.setEnabled(false);
- mOrientationSpinner.setSelection(0);
} else {
mOrientationSpinner.setEnabled(true);
final int selectedOrientationIndex = Integer.numberOfTrailingZeros(
@@ -1276,20 +1611,25 @@ public class PrintJobConfigActivity extends Activity {
}
}
}
+ mOrientationSpinner.setEnabled(true);
// Range options
PrintDocumentInfo info = mDocument.info;
if (info != null && (info.getPageCount() > 1
|| info.getPageCount() == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)) {
mRangeOptionsSpinner.setEnabled(true);
- if (mRangeOptionsSpinner.getSelectedItemPosition() > 0
- && !mRangeEditText.isEnabled()) {
- mRangeEditText.setEnabled(true);
- mRangeEditText.setVisibility(View.VISIBLE);
- mRangeEditText.requestFocus();
- InputMethodManager imm = (InputMethodManager)
- getSystemService(INPUT_METHOD_SERVICE);
- imm.showSoftInput(mRangeEditText, 0);
+ if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
+ if (!mRangeEditText.isEnabled()) {
+ mRangeEditText.setEnabled(true);
+ mRangeEditText.setVisibility(View.VISIBLE);
+ mRangeEditText.requestFocus();
+ InputMethodManager imm = (InputMethodManager)
+ getSystemService(INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mRangeEditText, 0);
+ }
+ } else {
+ mRangeEditText.setEnabled(false);
+ mRangeEditText.setVisibility(View.INVISIBLE);
}
final int pageCount = mDocument.info.getPageCount();
mRangeTitle.setText(getString(R.string.label_pages,
@@ -1307,6 +1647,7 @@ public class PrintJobConfigActivity extends Activity {
mRangeEditText.setEnabled(false);
mRangeEditText.setVisibility(View.INVISIBLE);
}
+ mRangeOptionsSpinner.setEnabled(true);
// Print/Print preview
if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
@@ -1333,99 +1674,7 @@ public class PrintJobConfigActivity extends Activity {
mCopiesEditText.selectAll();
mCopiesEditText.requestFocus();
}
- }
- }
-
- public 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.getName()));
- } else {
- Log.w(LOG_TAG, "Skipping a duplicate printer: " + addedPrinter);
- }
- }
-
- if (mDestinationSpinner.getSelectedItemPosition() == AdapterView.INVALID_POSITION
- && mDestinationSpinnerAdapter.getCount() > 0) {
- mDestinationSpinner.setSelection(0);
- }
-
- mEditor.updateUi();
- }
-
- public 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);
- }
- }
-
- if (mDestinationSpinner.getSelectedItemPosition() != AdapterView.INVALID_POSITION
- && mDestinationSpinnerAdapter.getCount() == 0) {
- mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
- }
- }
-
- @SuppressWarnings("unchecked")
- public void updatePrinters(List<PrinterInfo> pritners) {
- SpinnerItem<PrinterInfo> selectedItem =
- (SpinnerItem<PrinterInfo>) mDestinationSpinner.getSelectedItem();
- PrinterId selectedPrinterId = (selectedItem != null)
- ? selectedItem.value.getId() : null;
-
- boolean updated = false;
-
- final int printerCount = pritners.size();
- for (int i = 0; i < printerCount; i++) {
- PrinterInfo updatedPrinter = pritners.get(i);
- final int existingPrinterCount = mDestinationSpinnerAdapter.getCount();
- for (int j = 0; j < existingPrinterCount; j++) {
- PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value;
- if (updatedPrinter.getId().equals(existingPrinter.getId())) {
- existingPrinter.copyFrom(updatedPrinter);
- updated = true;
- if (selectedPrinterId != null
- && selectedPrinterId.equals(updatedPrinter.getId())) {
- // The selected printer was updated. We simulate a fake
- // selection to reuse the normal printer change handling.
- mOnItemSelectedListener.onItemSelected(mDestinationSpinner,
- mDestinationSpinner.getSelectedView(),
- mDestinationSpinner.getSelectedItemPosition(),
- mDestinationSpinner.getSelectedItemId());
- // TODO: This will reset the UI to the defaults for the
- // printer. We may need to revisit this.
-
- }
- break;
- }
- }
- }
- if (updated) {
- mDestinationSpinnerAdapter.notifyDataSetChanged();
+ mCopiesEditText.setEnabled(true);
}
}
@@ -1455,10 +1704,52 @@ public class PrintJobConfigActivity extends Activity {
}
}
- private final class DestinationAdapter extends ArrayAdapter<SpinnerItem<PrinterInfo>> {
+ private final class DestinationAdapter extends BaseAdapter
+ implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>{
+ private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
+
+ public final PrinterInfo mFakePdfPrinter;
public DestinationAdapter() {
- super( PrintJobConfigActivity.this, R.layout.spinner_dropdown_item);
+ getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
+ mFakePdfPrinter = createFakePdfPrinter();
+ }
+
+ @Override
+ public int getCount() {
+ return Math.max(Math.min(mPrinters.size(), DEST_ADAPTER_MAX_ITEM_COUNT),
+ DEST_ADAPTER_MIN_ITEM_COUNT);
+ }
+
+ @Override
+ public Object getItem(int position) {
+ if (position == DEST_ADAPTER_POSITION_SAVE_AS_PDF) {
+ return mFakePdfPrinter;
+ }
+ if (!mPrinters.isEmpty()) {
+ if (position < DEST_ADAPTER_POSITION_SAVE_AS_PDF) {
+ return mPrinters.get(position);
+ } else if (position > DEST_ADAPTER_POSITION_SAVE_AS_PDF
+ && position < getCount() - 1) {
+ return mPrinters.get(position - 1);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ if (position == DEST_ADAPTER_POSITION_SAVE_AS_PDF) {
+ return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
+ }
+ if (mPrinters.isEmpty()) {
+ if (position == DEST_ADAPTER_POSITION_SEARCHING_FOR_PRINTERS) {
+ return DEST_ADAPTER_ITEM_ID_SEARCHING_FOR_PRINTERS;
+ }
+ } else if (position == getCount() - 1) {
+ return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
+ }
+ return position;
}
@Override
@@ -1474,149 +1765,91 @@ public class PrintJobConfigActivity extends Activity {
R.layout.spinner_dropdown_item, parent, false);
}
- PrinterInfo printerInfo = getItem(position).value;
- TextView title = (TextView) convertView.findViewById(R.id.title);
- title.setText(printerInfo.getName());
-
- try {
- TextView subtitle = (TextView)
- convertView.findViewById(R.id.subtitle);
- PackageManager pm = getPackageManager();
- PackageInfo packageInfo = pm.getPackageInfo(
- printerInfo.getId().getServiceName().getPackageName(), 0);
- subtitle.setText(packageInfo.applicationInfo.loadLabel(pm));
- subtitle.setVisibility(View.VISIBLE);
- } catch (NameNotFoundException nnfe) {
- /* ignore */
- }
-
- return convertView;
- }
- }
- }
-
- private static final class PrinterDiscoverySessionObserver
- extends IPrinterDiscoverySessionObserver.Stub {
- private static final int MSG_SET_CONTROLLER = 1;
- private static final int MSG_ON_PRINTERS_ADDED = 2;
- private static final int MSG_ON_PRINTERS_REMOVED = 3;
- private static final int MSG_ON_PRINTERS_UPDATED = 4;
-
- private Handler mHandler;
- private Editor mEditor;
- private IPrinterDiscoverySessionController mController;
-
- @SuppressWarnings("unchecked")
- public PrinterDiscoverySessionObserver(Editor editor, Looper looper) {
- mEditor = editor;
- mHandler = new Handler(looper, null, true) {
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_SET_CONTROLLER: {
- mController = (IPrinterDiscoverySessionController) message.obj;
- // TODO: This should be cleaned up
- List<PrinterId> printerIds = Collections.emptyList();
- try {
- mController.open(printerIds);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Error starting printer discovery");
- }
- } break;
-
- case MSG_ON_PRINTERS_ADDED: {
- List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
- mEditor.addPrinters(printers);
- } break;
-
- case MSG_ON_PRINTERS_REMOVED: {
- List<PrinterId> printerIds = (List<PrinterId>) message.obj;
- mEditor.removePrinters(printerIds);
- } break;
+ CharSequence title = null;
+ CharSequence subtitle = null;
- case MSG_ON_PRINTERS_UPDATED: {
- List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
- mEditor.updatePrinters(printers);
- } break;
+ if (mPrinters.isEmpty()
+ && position == DEST_ADAPTER_POSITION_SEARCHING_FOR_PRINTERS) {
+ title = getString(R.string.searching_for_printers);
+ } else {
+ if (position == DEST_ADAPTER_POSITION_SAVE_AS_PDF) {
+ PrinterInfo printer = (PrinterInfo) getItem(position);
+ title = printer.getName();
+ } else if (position == getCount() - 1) {
+ title = getString(R.string.all_printers);
+ } else {
+ PrinterInfo printer = (PrinterInfo) getItem(position);
+ title = printer.getName();
+ try {
+ PackageInfo packageInfo = getPackageManager().getPackageInfo(
+ printer.getId().getServiceName().getPackageName(), 0);
+ subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
+ } catch (NameNotFoundException nnfe) {
+ /* ignore */
+ }
}
}
- };
- }
- public void open(List<PrinterId> priorityList) {
- if (mController != null) {
- try {
- mController.open(priorityList);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error closing printer discovery session", re);
- }
- }
- }
-
- public void close() {
- if (mController != null) {
- try {
- mController.close();
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error closing printer discovery session", re);
- }
- }
- }
+ TextView titleView = (TextView) convertView.findViewById(R.id.title);
+ titleView.setText(title);
- public void requestPrinterUpdate(PrinterId printerId) {
- if (mController != null) {
- try {
- mController.requestPrinterUpdate(printerId);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error requestin printer update", re);
- }
- }
- }
-
- @Override
- public void setController(IPrinterDiscoverySessionController controller) {
- synchronized (this) {
- if (mHandler != null) {
- mHandler.obtainMessage(MSG_SET_CONTROLLER, controller)
- .sendToTarget();
+ TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
+ if (!TextUtils.isEmpty(subtitle)) {
+ subtitleView.setText(subtitle);
+ subtitleView.setVisibility(View.VISIBLE);
+ } else {
+ subtitleView.setText(null);
+ subtitleView.setVisibility(View.GONE);
}
- }
- }
- @Override
- public void onPrintersAdded(List<PrinterInfo> printers) {
- synchronized (this) {
- if (mHandler != null) {
- mHandler.obtainMessage(MSG_ON_PRINTERS_ADDED, printers)
- .sendToTarget();
- }
+ return convertView;
}
- }
- @Override
- public void onPrintersRemoved(List<PrinterId> printers) {
- synchronized (this) {
- if (mHandler != null) {
- mHandler.obtainMessage(MSG_ON_PRINTERS_REMOVED, printers)
- .sendToTarget();
+ @Override
+ public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
+ if (id == LOADER_ID_PRINTERS_LOADER) {
+ return new FusedPrintersProvider(PrintJobConfigActivity.this);
}
+ return null;
}
- }
- @Override
- public void onPrintersUpdated(List<PrinterInfo> printers) {
- synchronized (this) {
- if (mHandler != null) {
- mHandler.obtainMessage(MSG_ON_PRINTERS_UPDATED, printers)
- .sendToTarget();
- }
+ @Override
+ public void onLoadFinished(Loader<List<PrinterInfo>> loader,
+ List<PrinterInfo> printers) {
+ mPrinters.clear();
+ mPrinters.addAll(printers);
+ notifyDataSetChanged();
}
- }
- public void destroy() {
- synchronized (this) {
- mHandler = null;
- mEditor = null;
+ @Override
+ public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
+ mPrinters.clear();
+ notifyDataSetInvalidated();
+ }
+
+ private PrinterInfo createFakePdfPrinter() {
+ PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
+
+ PrinterCapabilitiesInfo capabilities =
+ new PrinterCapabilitiesInfo.Builder(printerId)
+ .addMediaSize(MediaSize.createMediaSize(getPackageManager(),
+ MediaSize.ISO_A4), true)
+ .addMediaSize(MediaSize.createMediaSize(getPackageManager(),
+ MediaSize.NA_LETTER), false)
+ .addResolution(new Resolution("PDF resolution", "PDF resolution",
+ 300, 300), true)
+ .setColorModes(PrintAttributes.COLOR_MODE_COLOR
+ | PrintAttributes.COLOR_MODE_MONOCHROME,
+ PrintAttributes.COLOR_MODE_COLOR)
+ .setOrientations(PrintAttributes.ORIENTATION_PORTRAIT
+ | PrintAttributes.ORIENTATION_LANDSCAPE,
+ PrintAttributes.ORIENTATION_PORTRAIT)
+ .create();
+
+ return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf),
+ PrinterInfo.STATUS_READY)
+ .setCapabilities(capabilities)
+ .create();
}
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
deleted file mode 100644
index 1b8b81a..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
+++ /dev/null
@@ -1,979 +0,0 @@
-/*
- * 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.content.ComponentName;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.os.ParcelFileDescriptor;
-import android.print.IPrintClient;
-import android.print.IPrinterDiscoverySessionObserver;
-import android.print.PageRange;
-import android.print.PrintAttributes;
-import android.print.PrintAttributes.Margins;
-import android.print.PrintAttributes.MediaSize;
-import android.print.PrintAttributes.Resolution;
-import android.print.PrintAttributes.Tray;
-import android.print.PrintDocumentInfo;
-import android.print.PrintJobInfo;
-import android.print.PrintManager;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.util.FastXmlSerializer;
-
-import libcore.io.IoUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-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;
-
-public class PrintSpooler {
-
- private static final String LOG_TAG = "PrintSpooler";
-
- private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = true;
-
- private static final boolean DEBUG_PERSISTENCE = true;
-
- private static final boolean PERSISTNECE_MANAGER_ENABLED = true;
-
- private static final String PRINT_FILE_EXTENSION = "pdf";
-
- private static int sPrintJobIdCounter;
-
- private static final Object sLock = new Object();
-
- private static PrintSpooler sInstance;
-
- private final Object mLock = new Object();
-
- private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
-
- private final PersistenceManager mPersistanceManager;
-
- private final NotificationController mNotificationController;
-
- private final PrintSpoolerService mService;
-
- public static void destroyInstance() {
- synchronized (sLock) {
- sInstance = null;
- }
- }
-
- public static void createInstance(PrintSpoolerService service) {
- synchronized (sLock) {
- sInstance = new PrintSpooler(service);
- }
- }
-
- public static PrintSpooler peekInstance() {
- synchronized (sLock) {
- return sInstance;
- }
- }
-
- private PrintSpooler(PrintSpoolerService service) {
- mService = service;
- mPersistanceManager = new PersistenceManager(service);
- mNotificationController = new NotificationController(service);
- synchronized (mLock) {
- mPersistanceManager.readStateLocked();
- handleReadPrintJobsLocked();
- }
- }
-
- public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName,
- int state, int appId) {
- List<PrintJobInfo> foundPrintJobs = null;
- synchronized (mLock) {
- 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.getServiceName())));
- final boolean sameAppId = appId == PrintManager.APP_ID_ANY
- || printJob.getAppId() == appId;
- final boolean sameState = (state == printJob.getState())
- || (state == PrintJobInfo.STATE_ANY)
- || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
- && printJob.getState() > PrintJobInfo.STATE_CREATED);
- if (sameComponent && sameAppId && sameState) {
- if (foundPrintJobs == null) {
- foundPrintJobs = new ArrayList<PrintJobInfo>();
- }
- foundPrintJobs.add(printJob);
- }
- }
- }
- return foundPrintJobs;
- }
-
- public PrintJobInfo getPrintJobInfo(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 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);
- printJob.setState(PrintJobInfo.STATE_CREATED);
-
- addPrintJobLocked(printJob);
-
- return printJob;
- }
- }
-
- private void handleReadPrintJobsLocked() {
- final int printJobCount = mPrintJobs.size();
- for (int i = 0; i < printJobCount; i++) {
- PrintJobInfo printJob = mPrintJobs.get(i);
-
- // Update the notification.
- mNotificationController.onPrintJobStateChanged(printJob);
-
- switch (printJob.getState()) {
- case PrintJobInfo.STATE_QUEUED:
- case PrintJobInfo.STATE_STARTED: {
- // We have a print job that was queued or started in the past
- // but the device battery died or a crash occurred. In this case
- // we assume the print job failed and let the user decide whether
- // to restart the job or just
- setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
- mService.getString(R.string.no_connection_to_printer));
- } break;
- }
- }
- }
-
- public void checkAllPrintJobsHandled() {
- synchronized (mLock) {
- if (!hasActivePrintJobsLocked()) {
- notifyOnAllPrintJobsHandled();
- }
- }
- }
-
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
- mService.createPrinterDiscoverySession(observer);
- }
-
- 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;
- }
-
- public void writePrintJobData(final ParcelFileDescriptor fd, final int printJobId) {
- final PrintJobInfo printJob;
- synchronized (mLock) {
- printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- }
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- FileInputStream in = null;
- FileOutputStream out = null;
- try {
- 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 null;
- }
- 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 {
- IoUtils.closeQuietly(in);
- IoUtils.closeQuietly(out);
- IoUtils.closeQuietly(fd);
- }
- Log.i(LOG_TAG, "[END WRITE]");
- return null;
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
- }
-
- public File generateFileForPrintJob(int printJobId) {
- return new File(mService.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, CharSequence error) {
- boolean success = false;
-
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- success = true;
-
- printJob.setState(state);
- printJob.setFailureReason(error);
- mNotificationController.onPrintJobStateChanged(printJob);
-
- if (DEBUG_PRINT_JOB_LIFECYCLE) {
- Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
- }
-
- switch (state) {
- case PrintJobInfo.STATE_COMPLETED:
- case PrintJobInfo.STATE_CANCELED:
- removePrintJobLocked(printJob);
- // $fall-through$
- case PrintJobInfo.STATE_FAILED: {
- PrinterId printerId = printJob.getPrinterId();
- if (printerId != null) {
- ComponentName service = printerId.getServiceName();
- if (!hasActivePrintJobsForServiceLocked(service)) {
- mService.onAllPrintJobsForServiceHandled(service);
- }
- }
- } break;
-
- case PrintJobInfo.STATE_QUEUED: {
- mService.onPrintJobQueued(new PrintJobInfo(printJob));
- } break;
- }
-
- if (shouldPersistPrintJob(printJob)) {
- mPersistanceManager.writeStateLocked();
- }
-
- if (!hasActivePrintJobsLocked()) {
- notifyOnAllPrintJobsHandled();
- }
- }
- }
-
- return success;
- }
-
- public boolean hasActivePrintJobsLocked() {
- final int printJobCount = mPrintJobs.size();
- for (int i = 0; i < printJobCount; i++) {
- PrintJobInfo printJob = mPrintJobs.get(i);
- if (isActiveState(printJob.getState())) {
- return true;
- }
- }
- return false;
- }
-
- public boolean hasActivePrintJobsForServiceLocked(ComponentName service) {
- final int printJobCount = mPrintJobs.size();
- for (int i = 0; i < printJobCount; i++) {
- PrintJobInfo printJob = mPrintJobs.get(i);
- if (isActiveState(printJob.getState())
- && printJob.getPrinterId().getServiceName().equals(service)) {
- return true;
- }
- }
- return false;
- }
-
- private static boolean isActiveState(int printJobState) {
- return printJobState == PrintJobInfo.STATE_CREATED
- || printJobState == PrintJobInfo.STATE_QUEUED
- || printJobState == PrintJobInfo.STATE_STARTED;
- }
-
- public boolean setPrintJobTag(int printJobId, String tag) {
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- String printJobTag = printJob.getTag();
- if (printJobTag == null) {
- if (tag == null) {
- return false;
- }
- } else if (printJobTag.equals(tag)) {
- return false;
- }
- printJob.setTag(tag);
- if (shouldPersistPrintJob(printJob)) {
- mPersistanceManager.writeStateLocked();
- }
- return true;
- }
- }
- return false;
- }
-
- public void setPrintJobCopiesNoPersistence(int printJobId, int copies) {
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- printJob.setCopies(copies);
- }
- }
- }
-
- public void setPrintJobPrintDocumentInfoNoPersistence(int printJobId, PrintDocumentInfo info) {
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- printJob.setDocumentInfo(info);
- }
- }
- }
-
- public void setPrintJobAttributesNoPersistence(int printJobId, PrintAttributes attributes) {
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- printJob.setAttributes(attributes);
- }
- }
- }
-
- public void setPrintJobPrinterNoPersistence(int printJobId, PrinterInfo printer) {
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- printJob.setPrinterId(printer.getId());
- printJob.setPrinterName(printer.getName());
- }
- }
- }
-
- public void setPrintJobPagesNoPersistence(int printJobId, PageRange[] pages) {
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- printJob.setPages(pages);
- }
- }
- }
-
- private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
- return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
- }
-
- private void notifyOnAllPrintJobsHandled() {
- // This has to run on the tread that is persisting the current state
- // since this call may result in the system unbinding from the spooler
- // and as a result the spooler process may get killed before the write
- // completes.
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- mService.onAllPrintJobsHandled();
- return null;
- }
- }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
-
- 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_PRINTER_ID = "printerId";
- private static final String TAG_PAGE_RANGE = "pageRange";
- private static final String TAG_ATTRIBUTES = "attributes";
- private static final String TAG_DOCUMENT_INFO = "documentInfo";
-
- private static final String ATTR_ID = "id";
- private static final String ATTR_LABEL = "label";
- private static final String ATTR_STATE = "state";
- private static final String ATTR_APP_ID = "appId";
- private static final String ATTR_USER_ID = "userId";
- private static final String ATTR_TAG = "tag";
- private static final String ATTR_COPIES = "copies";
-
- private static final String TAG_MEDIA_SIZE = "mediaSize";
- private static final String TAG_RESOLUTION = "resolution";
- private static final String TAG_MARGINS = "margins";
- private static final String TAG_INPUT_TRAY = "inputTray";
- private static final String TAG_OUTPUT_TRAY = "outputTray";
-
- private static final String ATTR_DUPLEX_MODE = "duplexMode";
- private static final String ATTR_COLOR_MODE = "colorMode";
- private static final String ATTR_FITTING_MODE = "fittingMode";
- private static final String ATTR_ORIENTATION = "orientation";
-
- private static final String ATTR_PRINTER_NAME = "printerName";
- private static final String ATTR_SERVICE_NAME = "serviceName";
-
- private static final String ATTR_WIDTH_MILS = "widthMils";
- private static final String ATTR_HEIGHT_MILS = "heightMils";
-
- private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
- private static final String ATTR_VERTICAL_DPI = "verticalDpi";
-
- private static final String ATTR_LEFT_MILS = "leftMils";
- private static final String ATTR_TOP_MILS = "topMils";
- private static final String ATTR_RIGHT_MILS = "rightMils";
- private static final String ATTR_BOTTOM_MILS = "bottomMils";
-
- private static final String ATTR_START = "start";
- private static final String ATTR_END = "end";
-
- private static final String ATTR_NAME = "name";
- private static final String ATTR_PAGE_COUNT = "pageCount";
- private static final String ATTR_CONTENT_TYPE = "contentType";
-
- private final AtomicFile mStatePersistFile;
-
- private boolean mWriteStateScheduled;
-
- private PersistenceManager(Context context) {
- mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
- PERSIST_FILE_NAME));
- }
-
- public void writeStateLocked() {
- 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() {
- if (DEBUG_PERSISTENCE) {
- Log.i(LOG_TAG, "[PERSIST START]");
- }
- 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_CANCELED) {
- continue;
- }
-
- serializer.startTag(null, TAG_JOB);
-
- serializer.attribute(null, ATTR_ID, String.valueOf(printJob.getId()));
- serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
- serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
- serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
- serializer.attribute(null, ATTR_USER_ID, String.valueOf(printJob.getUserId()));
- String tag = printJob.getTag();
- if (tag != null) {
- serializer.attribute(null, ATTR_TAG, tag);
- }
- serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
-
- PrinterId printerId = printJob.getPrinterId();
- if (printerId != null) {
- serializer.startTag(null, TAG_PRINTER_ID);
- serializer.attribute(null, ATTR_PRINTER_NAME, printerId.getLocalId());
- serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
- .flattenToString());
- serializer.endTag(null, TAG_PRINTER_ID);
- }
-
- PageRange[] pages = printJob.getPages();
- if (pages != null) {
- for (int i = 0; i < pages.length; i++) {
- serializer.startTag(null, TAG_PAGE_RANGE);
- serializer.attribute(null, ATTR_START, String.valueOf(
- pages[i].getStart()));
- serializer.attribute(null, ATTR_END, String.valueOf(
- pages[i].getEnd()));
- serializer.endTag(null, TAG_PAGE_RANGE);
- }
- }
-
- PrintAttributes attributes = printJob.getAttributes();
- if (attributes != null) {
- serializer.startTag(null, TAG_ATTRIBUTES);
-
- final int duplexMode = attributes.getDuplexMode();
- serializer.attribute(null, ATTR_DUPLEX_MODE,
- String.valueOf(duplexMode));
-
- final int colorMode = attributes.getColorMode();
- serializer.attribute(null, ATTR_COLOR_MODE,
- String.valueOf(colorMode));
-
- final int fittingMode = attributes.getFittingMode();
- serializer.attribute(null, ATTR_FITTING_MODE,
- String.valueOf(fittingMode));
-
- final int orientation = attributes.getOrientation();
- serializer.attribute(null, ATTR_ORIENTATION,
- String.valueOf(orientation));
-
- MediaSize mediaSize = attributes.getMediaSize();
- if (mediaSize != null) {
- serializer.startTag(null, TAG_MEDIA_SIZE);
- serializer.attribute(null, ATTR_ID, mediaSize.getId());
- serializer.attribute(null, ATTR_LABEL, mediaSize.getLabel()
- .toString());
- serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf(
- mediaSize.getWidthMils()));
- serializer.attribute(null, ATTR_HEIGHT_MILS,String.valueOf(
- mediaSize.getHeightMils()));
- serializer.endTag(null, TAG_MEDIA_SIZE);
- }
-
- Resolution resolution = attributes.getResolution();
- if (resolution != null) {
- serializer.startTag(null, TAG_RESOLUTION);
- serializer.attribute(null, ATTR_ID, resolution.getId());
- serializer.attribute(null, ATTR_LABEL, resolution.getLabel()
- .toString());
- serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf(
- resolution.getHorizontalDpi()));
- serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf(
- resolution.getVerticalDpi()));
- serializer.endTag(null, TAG_RESOLUTION);
- }
-
- Margins margins = attributes.getMargins();
- if (margins != null) {
- serializer.startTag(null, TAG_MARGINS);
- serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf(
- margins.getLeftMils()));
- serializer.attribute(null, ATTR_TOP_MILS, String.valueOf(
- margins.getTopMils()));
- serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf(
- margins.getRightMils()));
- serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf(
- margins.getBottomMils()));
- serializer.endTag(null, TAG_MARGINS);
- }
-
- Tray inputTray = attributes.getInputTray();
- if (inputTray != null) {
- serializer.startTag(null, TAG_INPUT_TRAY);
- serializer.attribute(null, ATTR_ID, inputTray.getId());
- serializer.attribute(null, ATTR_LABEL, inputTray.getLabel()
- .toString());
- serializer.endTag(null, TAG_INPUT_TRAY);
- }
-
- Tray outputTray = attributes.getOutputTray();
- if (outputTray != null) {
- serializer.startTag(null, TAG_OUTPUT_TRAY);
- serializer.attribute(null, ATTR_ID, outputTray.getId());
- serializer.attribute(null, ATTR_LABEL, outputTray.getLabel()
- .toString());
- serializer.endTag(null, TAG_OUTPUT_TRAY);
- }
-
- serializer.endTag(null, TAG_ATTRIBUTES);
- }
-
- PrintDocumentInfo documentInfo = printJob.getDocumentInfo();
- if (documentInfo != null) {
- serializer.startTag(null, TAG_DOCUMENT_INFO);
- serializer.attribute(null, ATTR_NAME, documentInfo.getName());
- serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf(
- documentInfo.getContentType()));
- serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
- documentInfo.getPageCount()));
- serializer.endTag(null, TAG_DOCUMENT_INFO);
- }
-
- serializer.endTag(null, TAG_JOB);
-
- if (DEBUG_PERSISTENCE) {
- Log.i(LOG_TAG, "[PERSISTED] " + printJob);
- }
- }
-
- serializer.endTag(null, TAG_SPOOLER);
- serializer.endDocument();
- mStatePersistFile.finishWrite(out);
- if (DEBUG_PERSISTENCE) {
- Log.i(LOG_TAG, "[PERSIST END]");
- }
- } 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;
- }
-
- PrintJobInfo printJob = new PrintJobInfo();
-
- final int printJobId = Integer.parseInt(parser.getAttributeValue(null, ATTR_ID));
- printJob.setId(printJobId);
- String label = parser.getAttributeValue(null, ATTR_LABEL);
- printJob.setLabel(label);
- final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE));
- printJob.setState(state);
- final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
- printJob.setAppId(appId);
- final int userId = Integer.parseInt(parser.getAttributeValue(null, ATTR_USER_ID));
- printJob.setUserId(userId);
- String tag = parser.getAttributeValue(null, ATTR_TAG);
- printJob.setTag(tag);
- String copies = parser.getAttributeValue(null, ATTR_COPIES);
- printJob.setCopies(Integer.parseInt(copies));
-
- parser.next();
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
- String localId = parser.getAttributeValue(null, ATTR_PRINTER_NAME);
- ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
- null, ATTR_SERVICE_NAME));
- printJob.setPrinterId(new PrinterId(service, localId));
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- List<PageRange> pageRanges = null;
- while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) {
- final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START));
- final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END));
- PageRange pageRange = new PageRange(start, end);
- if (pageRanges == null) {
- pageRanges = new ArrayList<PageRange>();
- }
- pageRanges.add(pageRange);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
- parser.next();
- }
- if (pageRanges != null) {
- PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
- pageRanges.toArray(pageRangesArray);
- printJob.setPages(pageRangesArray);
- }
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
-
- PrintAttributes.Builder builder = new PrintAttributes.Builder();
-
- String duplexMode = parser.getAttributeValue(null, ATTR_DUPLEX_MODE);
- builder.setDuplexMode(Integer.parseInt(duplexMode));
-
- String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
- builder.setColorMode(Integer.parseInt(colorMode));
-
- String fittingMode = parser.getAttributeValue(null, ATTR_FITTING_MODE);
- builder.setFittingMode(Integer.parseInt(fittingMode));
-
- String orientation = parser.getAttributeValue(null, ATTR_ORIENTATION);
- builder.setOrientation(Integer.parseInt(orientation));
-
- parser.next();
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) {
- String id = parser.getAttributeValue(null, ATTR_ID);
- label = parser.getAttributeValue(null, ATTR_LABEL);
- final int widthMils = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_WIDTH_MILS));
- final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_HEIGHT_MILS));
- MediaSize mediaSize = new MediaSize(id, label, widthMils, heightMils);
- builder.setMediaSize(mediaSize);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) {
- String id = parser.getAttributeValue(null, ATTR_ID);
- label = parser.getAttributeValue(null, ATTR_LABEL);
- final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_HORIZONTAL_DPI));
- final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_VERTICAL_DPI));
- Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi);
- builder.setResolution(resolution);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
- final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_LEFT_MILS));
- final int topMils = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_TOP_MILS));
- final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_RIGHT_MILS));
- final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_BOTTOM_MILS));
- Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
- builder.setMargins(margins);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_INPUT_TRAY)) {
- String id = parser.getAttributeValue(null, ATTR_ID);
- label = parser.getAttributeValue(null, ATTR_LABEL);
- Tray tray = new Tray(id, label);
- builder.setInputTray(tray);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_INPUT_TRAY);
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_OUTPUT_TRAY)) {
- String id = parser.getAttributeValue(null, ATTR_ID);
- label = parser.getAttributeValue(null, ATTR_LABEL);
- Tray tray = new Tray(id, label);
- builder.setOutputTray(tray);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_OUTPUT_TRAY);
- parser.next();
- }
-
- printJob.setAttributes(builder.create());
-
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) {
- String name = parser.getAttributeValue(null, ATTR_NAME);
- final int pageCount = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_PAGE_COUNT));
- final int contentType = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_CONTENT_TYPE));
- PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
- .setPageCount(pageCount)
- .setContentType(contentType).create();
- printJob.setDocumentInfo(info);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
- parser.next();
- }
-
- 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
index 4fab4f8..fda64c9 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
@@ -21,9 +21,8 @@ import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentSender;
-import android.os.Handler;
+import android.os.AsyncTask;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -32,14 +31,38 @@ import android.print.IPrintDocumentAdapter;
import android.print.IPrintSpooler;
import android.print.IPrintSpoolerCallbacks;
import android.print.IPrintSpoolerClient;
-import android.print.IPrinterDiscoverySessionObserver;
+import android.print.PageRange;
import android.print.PrintAttributes;
+import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
+import android.print.PrintAttributes.Tray;
+import android.print.PrintDocumentInfo;
import android.print.PrintJobInfo;
+import android.print.PrintManager;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.FastXmlSerializer;
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+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;
/**
@@ -48,22 +71,65 @@ import java.util.List;
*/
public final class PrintSpoolerService extends Service {
+ private static final String LOG_TAG = "PrintSpoolerService";
+
+ private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = true;
+
+ private static final boolean DEBUG_PERSISTENCE = true;
+
+ private static final boolean PERSISTNECE_MANAGER_ENABLED = true;
+
private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
- private static final String LOG_TAG = "PrintSpoolerService";
+ private static final String PRINT_FILE_EXTENSION = "pdf";
+
+ private static final Object sLock = new Object();
+
+ private final Object mLock = new Object();
+
+ private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
+
+ private static PrintSpoolerService sInstance;
+
+ private static int sPrintJobIdCounter;
private Intent mStartPrintJobConfigActivityIntent;
private IPrintSpoolerClient mClient;
- private Handler mHandler;
+ private HandlerCaller mHandlerCaller;
+
+ private PersistenceManager mPersistanceManager;
+
+ private NotificationController mNotificationController;
+
+ private PrinterDiscoverySession mDiscoverySession;
+
+ public static PrintSpoolerService peekInstance() {
+ synchronized (sLock) {
+ return sInstance;
+ }
+ }
@Override
public void onCreate() {
super.onCreate();
mStartPrintJobConfigActivityIntent = new Intent(PrintSpoolerService.this,
PrintJobConfigActivity.class);
- mHandler = new MyHandler(getMainLooper());
+ mHandlerCaller = new HandlerCaller(this, getMainLooper(),
+ new HandlerCallerCallback(), false);
+
+ mPersistanceManager = new PersistenceManager();
+ mNotificationController = new NotificationController(PrintSpoolerService.this);
+
+ synchronized (mLock) {
+ mPersistanceManager.readStateLocked();
+ handleReadPrintJobsLocked();
+ }
+
+ synchronized (sLock) {
+ sInstance = this;
+ }
}
@Override
@@ -72,10 +138,10 @@ public final class PrintSpoolerService extends Service {
@Override
public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
ComponentName componentName, int state, int appId, int sequence)
- throws RemoteException {
+ throws RemoteException {
List<PrintJobInfo> printJobs = null;
try {
- printJobs = PrintSpooler.peekInstance().getPrintJobInfos(
+ printJobs = PrintSpoolerService.this.getPrintJobInfos(
componentName, state, appId);
} finally {
callback.onGetPrintJobInfosResult(printJobs, sequence);
@@ -87,7 +153,7 @@ public final class PrintSpoolerService extends Service {
int appId, int sequence) throws RemoteException {
PrintJobInfo printJob = null;
try {
- printJob = PrintSpooler.peekInstance().getPrintJobInfo(printJobId, appId);
+ printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId);
} finally {
callback.onGetPrintJobInfoResult(printJob, sequence);
}
@@ -98,11 +164,11 @@ public final class PrintSpoolerService extends Service {
public void createPrintJob(String printJobName, IPrintClient client,
IPrintDocumentAdapter printAdapter, PrintAttributes attributes,
IPrintSpoolerCallbacks callback, int appId, int sequence)
- throws RemoteException {
+ throws RemoteException {
PrintJobInfo printJob = null;
try {
- printJob = PrintSpooler.peekInstance().createPrintJob(printJobName, client,
- attributes, appId);
+ printJob = PrintSpoolerService.this.createPrintJob(
+ printJobName, client, attributes, appId);
if (printJob != null) {
Intent intent = mStartPrintJobConfigActivityIntent;
intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_DOCUMENT_ADAPTER,
@@ -113,13 +179,12 @@ public final class PrintSpoolerService extends Service {
IntentSender sender = PendingIntent.getActivity(
PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
- | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
+ | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = client;
- args.arg2 = sender;
- mHandler.obtainMessage(MyHandler.MSG_START_PRINT_JOB_CONFIG_ACTIVITY,
- args).sendToTarget();
+ Message message = mHandlerCaller.obtainMessageOO(
+ HandlerCallerCallback.MSG_START_PRINT_JOB_CONFIG_ACTIVITY,
+ client, sender);
+ mHandlerCaller.executeOrSendMessage(message);
}
} finally {
callback.onCreatePrintJobResult(printJob, sequence);
@@ -127,11 +192,11 @@ public final class PrintSpoolerService extends Service {
}
@Override
- public void setPrintJobState(int printJobId, int state, CharSequence error,
+ public void setPrintJobState(int printJobId, int state, String error,
IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
boolean success = false;
try {
- success = PrintSpooler.peekInstance().setPrintJobState(
+ success = PrintSpoolerService.this.setPrintJobState(
printJobId, state, error);
} finally {
callback.onSetPrintJobStateResult(success, sequece);
@@ -143,7 +208,7 @@ public final class PrintSpoolerService extends Service {
IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
boolean success = false;
try {
- success = PrintSpooler.peekInstance().setPrintJobTag(printJobId, tag);
+ success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag);
} finally {
callback.onSetPrintJobTagResult(success, sequece);
}
@@ -151,60 +216,191 @@ public final class PrintSpoolerService extends Service {
@Override
public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
- PrintSpooler.peekInstance().writePrintJobData(fd, printJobId);
+ PrintSpoolerService.this.writePrintJobData(fd, printJobId);
}
@Override
public void setClient(IPrintSpoolerClient client) {
- mHandler.obtainMessage(MyHandler.MSG_SET_CLIENT, client).sendToTarget();
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_SET_CLIENT, client);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
+
+ @Override
+ public void onPrintersAdded(List<PrinterInfo> printers) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_ON_PRINTERS_ADDED, printers);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
+
+ @Override
+ public void onPrintersRemoved(List<PrinterId> printerIds) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_ON_PRINTERS_REMOVED, printerIds);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
+
+ @Override
+ public void onPrintersUpdated(List<PrinterInfo> printers) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_ON_PRINTERS_UPDATED, printers);
+ mHandlerCaller.executeOrSendMessage(message);
}
};
}
- public void onPrintJobQueued(PrintJobInfo printJob) {
- mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED,
- printJob).sendToTarget();
+ public void createPrinterDiscoverySession() {
+ Message message = mHandlerCaller.obtainMessage(
+ HandlerCallerCallback.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
+ mHandlerCaller.executeOrSendMessage(message);
}
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
- mHandler.obtainMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION,
- observer).sendToTarget();
+ public void destroyPrinterDiscoverySession() {
+ Message message = mHandlerCaller.obtainMessage(
+ HandlerCallerCallback.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
+ mHandlerCaller.executeOrSendMessage(message);
}
- public void onAllPrintJobsForServiceHandled(ComponentName service) {
- mHandler.obtainMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
- service).sendToTarget();
+ public void startPrinterDiscovery(List<PrinterId> priorityList) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_START_PRINTER_DISCOVERY, priorityList);
+ mHandlerCaller.executeOrSendMessage(message);
}
- public void onAllPrintJobsHandled() {
- mHandler.sendEmptyMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED);
+ public void stopPrinterDiscovery() {
+ Message message = mHandlerCaller.obtainMessage(
+ HandlerCallerCallback.MSG_STOP_PRINTER_DISCOVERY);
+ mHandlerCaller.executeOrSendMessage(message);
}
- private final class MyHandler extends Handler {
- public static final int MSG_SET_CLIENT = 1;
- public static final int MSG_START_PRINT_JOB_CONFIG_ACTIVITY = 2;
- public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 3;
- public static final int MSG_ON_PRINT_JOB_QUEUED = 5;
- public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 6;
- public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 7;
- public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 9;
+ public void requestPrinterUpdate(PrinterId pritnerId) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_REQUEST_PRINTER_UPDATE, pritnerId);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
- public MyHandler(Looper looper) {
- super(looper, null, false);
- }
+
+ private void sendOnPrintJobQueued(PrintJobInfo printJob) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
+
+ private void sendOnAllPrintJobsForServiceHandled(ComponentName service) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED, service);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
+
+ private void sendOnAllPrintJobsHandled() {
+ Message message = mHandlerCaller.obtainMessage(
+ HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_HANDLED);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
+
+ private final class HandlerCallerCallback implements HandlerCaller.Callback {
+ public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1;
+ public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
+ public static final int MSG_START_PRINTER_DISCOVERY = 3;
+ public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
+ public static final int MSG_REQUEST_PRINTER_UPDATE = 5;
+
+ public static final int MSG_ON_PRINTERS_ADDED = 6;
+ public static final int MSG_ON_PRINTERS_REMOVED = 7;
+ public static final int MSG_ON_PRINTERS_UPDATED = 8;
+
+ public static final int MSG_SET_CLIENT = 9;
+ public static final int MSG_START_PRINT_JOB_CONFIG_ACTIVITY = 10;
+ public static final int MSG_ON_PRINT_JOB_QUEUED = 11;
+ public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 12;
+ public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 13;
+ public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 14;
@Override
- public void handleMessage(Message message) {
+ @SuppressWarnings("unchecked")
+ public void executeMessage(Message message) {
switch (message.what) {
+ case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
+ final IPrintSpoolerClient client;
+ synchronized (mLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ client.createPrinterDiscoverySession();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error creating printer discovery session.", re);
+ }
+ }
+ } break;
+
+ case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
+ final IPrintSpoolerClient client;
+ synchronized (mLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ client.destroyPrinterDiscoverySession();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error destroying printer discovery session.", re);
+ }
+ }
+ } break;
+
+ case MSG_START_PRINTER_DISCOVERY: {
+ final IPrintSpoolerClient client;
+ synchronized (mLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj;
+ try {
+ client.startPrinterDiscovery(priorityList);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error starting printer discovery.", re);
+ }
+ }
+ } break;
+
+ case MSG_STOP_PRINTER_DISCOVERY: {
+ final IPrintSpoolerClient client;
+ synchronized (mLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ client.stopPrinterDiscovery();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error stopping printer discovery.", re);
+ }
+ }
+ } break;
+
+ case MSG_REQUEST_PRINTER_UPDATE: {
+ final IPrintSpoolerClient client;
+ synchronized (mLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ PrinterId printerId = (PrinterId) message.obj;
+ try {
+ client.requestPrinterUpdate(printerId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error requesing printer update.", re);
+ }
+ }
+ } break;
+
case MSG_SET_CLIENT: {
- mClient = (IPrintSpoolerClient) message.obj;
- if (mClient != null) {
- PrintSpooler.createInstance(PrintSpoolerService.this);
- mHandler.sendEmptyMessageDelayed(
- MyHandler.MSG_CHECK_ALL_PRINTJOBS_HANDLED,
- CHECK_ALL_PRINTJOBS_HANDLED_DELAY);
- } else {
- PrintSpooler.destroyInstance();
+ synchronized (mLock) {
+ mClient = (IPrintSpoolerClient) message.obj;
+ if (mClient != null) {
+ Message msg = mHandlerCaller.obtainMessage(
+ HandlerCallerCallback.MSG_CHECK_ALL_PRINTJOBS_HANDLED);
+ mHandlerCaller.sendMessageDelayed(msg,
+ CHECK_ALL_PRINTJOBS_HANDLED_DELAY);
+ }
}
} break;
@@ -220,18 +416,6 @@ public final class PrintSpoolerService extends Service {
}
} break;
- case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
- IPrinterDiscoverySessionObserver observer =
- (IPrinterDiscoverySessionObserver) message.obj;
- if (mClient != null) {
- try {
- mClient.createPrinterDiscoverySession(observer);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error creating printer discovery session.", re);
- }
- }
- } break;
-
case MSG_ON_PRINT_JOB_QUEUED: {
PrintJobInfo printJob = (PrintJobInfo) message.obj;
if (mClient != null) {
@@ -266,12 +450,948 @@ public final class PrintSpoolerService extends Service {
} break;
case MSG_CHECK_ALL_PRINTJOBS_HANDLED: {
- PrintSpooler spooler = PrintSpooler.peekInstance();
- if (spooler != null) {
- spooler.checkAllPrintJobsHandled();
+ checkAllPrintJobsHandled();
+ } break;
+
+ case MSG_ON_PRINTERS_ADDED: {
+ final PrinterDiscoverySession session;
+ synchronized (mLock) {
+ session = mDiscoverySession;
+ }
+ if (session != null) {
+ List<PrinterInfo> printers = (ArrayList<PrinterInfo>) message.obj;
+ session.onPrintersAdded(printers);
+ }
+ } break;
+
+ case MSG_ON_PRINTERS_REMOVED: {
+ final PrinterDiscoverySession session;
+ synchronized (mLock) {
+ session = mDiscoverySession;
+ }
+ if (session != null) {
+ List<PrinterId> printerIds = (ArrayList<PrinterId>) message.obj;
+ session.onPrintersRemoved(printerIds);
+ }
+ } break;
+
+ case MSG_ON_PRINTERS_UPDATED: {
+ final PrinterDiscoverySession session;
+ synchronized (mLock) {
+ session = mDiscoverySession;
+ }
+ if (session != null) {
+ List<PrinterInfo> printers = (ArrayList<PrinterInfo>) message.obj;
+ session.onPrintersUpdated(printers);
}
} break;
}
}
}
+
+ public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName,
+ int state, int appId) {
+ List<PrintJobInfo> foundPrintJobs = null;
+ synchronized (mLock) {
+ 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.getServiceName())));
+ final boolean sameAppId = appId == PrintManager.APP_ID_ANY
+ || printJob.getAppId() == appId;
+ final boolean sameState = (state == printJob.getState())
+ || (state == PrintJobInfo.STATE_ANY)
+ || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
+ && printJob.getState() > PrintJobInfo.STATE_CREATED);
+ if (sameComponent && sameAppId && sameState) {
+ if (foundPrintJobs == null) {
+ foundPrintJobs = new ArrayList<PrintJobInfo>();
+ }
+ foundPrintJobs.add(printJob);
+ }
+ }
+ }
+ return foundPrintJobs;
+ }
+
+ public PrintJobInfo getPrintJobInfo(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 PrintJobInfo createPrintJob(String 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);
+ printJob.setState(PrintJobInfo.STATE_CREATED);
+
+ addPrintJobLocked(printJob);
+
+ return printJob;
+ }
+ }
+
+ private void handleReadPrintJobsLocked() {
+ final int printJobCount = mPrintJobs.size();
+ for (int i = 0; i < printJobCount; i++) {
+ PrintJobInfo printJob = mPrintJobs.get(i);
+
+ // Update the notification.
+ mNotificationController.onPrintJobStateChanged(printJob);
+
+ switch (printJob.getState()) {
+ case PrintJobInfo.STATE_QUEUED:
+ case PrintJobInfo.STATE_STARTED: {
+ // We have a print job that was queued or started in the
+ // past
+ // but the device battery died or a crash occurred. In this
+ // case
+ // we assume the print job failed and let the user decide
+ // whether
+ // to restart the job or just
+ setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
+ getString(R.string.no_connection_to_printer));
+ }
+ break;
+ }
+ }
+ }
+
+ public void checkAllPrintJobsHandled() {
+ synchronized (mLock) {
+ if (!hasActivePrintJobsLocked()) {
+ notifyOnAllPrintJobsHandled();
+ }
+ }
+ }
+
+ private void setPrinterDiscoverySessionClient(PrinterDiscoverySession session) {
+ synchronized (mLock) {
+ mDiscoverySession = session;
+ }
+ }
+
+ 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;
+ }
+
+ public void writePrintJobData(final ParcelFileDescriptor fd, final int printJobId) {
+ final PrintJobInfo printJob;
+ synchronized (mLock) {
+ printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ }
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ try {
+ 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 null;
+ }
+ 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 {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ IoUtils.closeQuietly(fd);
+ }
+ Log.i(LOG_TAG, "[END WRITE]");
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+
+ public File generateFileForPrintJob(int printJobId) {
+ return new File(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, String error) {
+ boolean success = false;
+
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ success = true;
+
+ printJob.setState(state);
+ printJob.setFailureReason(error);
+ mNotificationController.onPrintJobStateChanged(printJob);
+
+ if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
+ }
+
+ switch (state) {
+ case PrintJobInfo.STATE_COMPLETED:
+ case PrintJobInfo.STATE_CANCELED:
+ removePrintJobLocked(printJob);
+ // $fall-through$
+
+ case PrintJobInfo.STATE_FAILED: {
+ PrinterId printerId = printJob.getPrinterId();
+ if (printerId != null) {
+ ComponentName service = printerId.getServiceName();
+ if (!hasActivePrintJobsForServiceLocked(service)) {
+ sendOnAllPrintJobsForServiceHandled(service);
+ }
+ }
+ } break;
+
+ case PrintJobInfo.STATE_QUEUED: {
+ sendOnPrintJobQueued(new PrintJobInfo(printJob));
+ } break;
+ }
+
+ if (shouldPersistPrintJob(printJob)) {
+ mPersistanceManager.writeStateLocked();
+ }
+
+ if (!hasActivePrintJobsLocked()) {
+ notifyOnAllPrintJobsHandled();
+ }
+ }
+ }
+
+ return success;
+ }
+
+ public boolean hasActivePrintJobsLocked() {
+ final int printJobCount = mPrintJobs.size();
+ for (int i = 0; i < printJobCount; i++) {
+ PrintJobInfo printJob = mPrintJobs.get(i);
+ if (isActiveState(printJob.getState())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasActivePrintJobsForServiceLocked(ComponentName service) {
+ final int printJobCount = mPrintJobs.size();
+ for (int i = 0; i < printJobCount; i++) {
+ PrintJobInfo printJob = mPrintJobs.get(i);
+ if (isActiveState(printJob.getState())
+ && printJob.getPrinterId().getServiceName().equals(service)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isActiveState(int printJobState) {
+ return printJobState == PrintJobInfo.STATE_CREATED
+ || printJobState == PrintJobInfo.STATE_QUEUED
+ || printJobState == PrintJobInfo.STATE_STARTED;
+ }
+
+ public boolean setPrintJobTag(int printJobId, String tag) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ String printJobTag = printJob.getTag();
+ if (printJobTag == null) {
+ if (tag == null) {
+ return false;
+ }
+ } else if (printJobTag.equals(tag)) {
+ return false;
+ }
+ printJob.setTag(tag);
+ if (shouldPersistPrintJob(printJob)) {
+ mPersistanceManager.writeStateLocked();
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void setPrintJobCopiesNoPersistence(int printJobId, int copies) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setCopies(copies);
+ }
+ }
+ }
+
+ public void setPrintJobPrintDocumentInfoNoPersistence(int printJobId, PrintDocumentInfo info) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setDocumentInfo(info);
+ }
+ }
+ }
+
+ public void setPrintJobAttributesNoPersistence(int printJobId, PrintAttributes attributes) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setAttributes(attributes);
+ }
+ }
+ }
+
+ public void setPrintJobPrinterNoPersistence(int printJobId, PrinterInfo printer) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setPrinterId(printer.getId());
+ printJob.setPrinterName(printer.getName());
+ }
+ }
+ }
+
+ public void setPrintJobPagesNoPersistence(int printJobId, PageRange[] pages) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setPages(pages);
+ }
+ }
+ }
+
+ private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
+ return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
+ }
+
+ private void notifyOnAllPrintJobsHandled() {
+ // This has to run on the tread that is persisting the current state
+ // since this call may result in the system unbinding from the spooler
+ // and as a result the spooler process may get killed before the write
+ // completes.
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ sendOnAllPrintJobsHandled();
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ 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_PRINTER_ID = "printerId";
+ private static final String TAG_PAGE_RANGE = "pageRange";
+ private static final String TAG_ATTRIBUTES = "attributes";
+ private static final String TAG_DOCUMENT_INFO = "documentInfo";
+
+ private static final String ATTR_ID = "id";
+ private static final String ATTR_LABEL = "label";
+ private static final String ATTR_STATE = "state";
+ private static final String ATTR_APP_ID = "appId";
+ private static final String ATTR_USER_ID = "userId";
+ private static final String ATTR_TAG = "tag";
+ private static final String ATTR_COPIES = "copies";
+
+ private static final String TAG_MEDIA_SIZE = "mediaSize";
+ private static final String TAG_RESOLUTION = "resolution";
+ private static final String TAG_MARGINS = "margins";
+ private static final String TAG_INPUT_TRAY = "inputTray";
+ private static final String TAG_OUTPUT_TRAY = "outputTray";
+
+ private static final String ATTR_DUPLEX_MODE = "duplexMode";
+ private static final String ATTR_COLOR_MODE = "colorMode";
+ private static final String ATTR_FITTING_MODE = "fittingMode";
+ private static final String ATTR_ORIENTATION = "orientation";
+
+ private static final String ATTR_LOCAL_ID = "printerName";
+ private static final String ATTR_SERVICE_NAME = "serviceName";
+
+ private static final String ATTR_WIDTH_MILS = "widthMils";
+ private static final String ATTR_HEIGHT_MILS = "heightMils";
+
+ private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
+ private static final String ATTR_VERTICAL_DPI = "verticalDpi";
+
+ private static final String ATTR_LEFT_MILS = "leftMils";
+ private static final String ATTR_TOP_MILS = "topMils";
+ private static final String ATTR_RIGHT_MILS = "rightMils";
+ private static final String ATTR_BOTTOM_MILS = "bottomMils";
+
+ private static final String ATTR_START = "start";
+ private static final String ATTR_END = "end";
+
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_PAGE_COUNT = "pageCount";
+ private static final String ATTR_CONTENT_TYPE = "contentType";
+
+ private final AtomicFile mStatePersistFile;
+
+ private boolean mWriteStateScheduled;
+
+ private PersistenceManager() {
+ mStatePersistFile = new AtomicFile(new File(getFilesDir(),
+ PERSIST_FILE_NAME));
+ }
+
+ public void writeStateLocked() {
+ 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() {
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[PERSIST START]");
+ }
+ 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_CANCELED) {
+ continue;
+ }
+
+ serializer.startTag(null, TAG_JOB);
+
+ serializer.attribute(null, ATTR_ID, String.valueOf(printJob.getId()));
+ serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
+ serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
+ serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
+ serializer.attribute(null, ATTR_USER_ID, String.valueOf(printJob.getUserId()));
+ String tag = printJob.getTag();
+ if (tag != null) {
+ serializer.attribute(null, ATTR_TAG, tag);
+ }
+ serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
+
+ PrinterId printerId = printJob.getPrinterId();
+ if (printerId != null) {
+ serializer.startTag(null, TAG_PRINTER_ID);
+ serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
+ serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
+ .flattenToString());
+ serializer.endTag(null, TAG_PRINTER_ID);
+ }
+
+ PageRange[] pages = printJob.getPages();
+ if (pages != null) {
+ for (int i = 0; i < pages.length; i++) {
+ serializer.startTag(null, TAG_PAGE_RANGE);
+ serializer.attribute(null, ATTR_START, String.valueOf(
+ pages[i].getStart()));
+ serializer.attribute(null, ATTR_END, String.valueOf(
+ pages[i].getEnd()));
+ serializer.endTag(null, TAG_PAGE_RANGE);
+ }
+ }
+
+ PrintAttributes attributes = printJob.getAttributes();
+ if (attributes != null) {
+ serializer.startTag(null, TAG_ATTRIBUTES);
+
+ final int duplexMode = attributes.getDuplexMode();
+ serializer.attribute(null, ATTR_DUPLEX_MODE,
+ String.valueOf(duplexMode));
+
+ final int colorMode = attributes.getColorMode();
+ serializer.attribute(null, ATTR_COLOR_MODE,
+ String.valueOf(colorMode));
+
+ final int fittingMode = attributes.getFittingMode();
+ serializer.attribute(null, ATTR_FITTING_MODE,
+ String.valueOf(fittingMode));
+
+ final int orientation = attributes.getOrientation();
+ serializer.attribute(null, ATTR_ORIENTATION,
+ String.valueOf(orientation));
+
+ MediaSize mediaSize = attributes.getMediaSize();
+ if (mediaSize != null) {
+ serializer.startTag(null, TAG_MEDIA_SIZE);
+ serializer.attribute(null, ATTR_ID, mediaSize.getId());
+ serializer.attribute(null, ATTR_LABEL, mediaSize.getLabel()
+ .toString());
+ serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf(
+ mediaSize.getWidthMils()));
+ serializer.attribute(null, ATTR_HEIGHT_MILS, String.valueOf(
+ mediaSize.getHeightMils()));
+ serializer.endTag(null, TAG_MEDIA_SIZE);
+ }
+
+ Resolution resolution = attributes.getResolution();
+ if (resolution != null) {
+ serializer.startTag(null, TAG_RESOLUTION);
+ serializer.attribute(null, ATTR_ID, resolution.getId());
+ serializer.attribute(null, ATTR_LABEL, resolution.getLabel()
+ .toString());
+ serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf(
+ resolution.getHorizontalDpi()));
+ serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf(
+ resolution.getVerticalDpi()));
+ serializer.endTag(null, TAG_RESOLUTION);
+ }
+
+ Margins margins = attributes.getMargins();
+ if (margins != null) {
+ serializer.startTag(null, TAG_MARGINS);
+ serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf(
+ margins.getLeftMils()));
+ serializer.attribute(null, ATTR_TOP_MILS, String.valueOf(
+ margins.getTopMils()));
+ serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf(
+ margins.getRightMils()));
+ serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf(
+ margins.getBottomMils()));
+ serializer.endTag(null, TAG_MARGINS);
+ }
+
+ Tray inputTray = attributes.getInputTray();
+ if (inputTray != null) {
+ serializer.startTag(null, TAG_INPUT_TRAY);
+ serializer.attribute(null, ATTR_ID, inputTray.getId());
+ serializer.attribute(null, ATTR_LABEL, inputTray.getLabel()
+ .toString());
+ serializer.endTag(null, TAG_INPUT_TRAY);
+ }
+
+ Tray outputTray = attributes.getOutputTray();
+ if (outputTray != null) {
+ serializer.startTag(null, TAG_OUTPUT_TRAY);
+ serializer.attribute(null, ATTR_ID, outputTray.getId());
+ serializer.attribute(null, ATTR_LABEL, outputTray.getLabel()
+ .toString());
+ serializer.endTag(null, TAG_OUTPUT_TRAY);
+ }
+
+ serializer.endTag(null, TAG_ATTRIBUTES);
+ }
+
+ PrintDocumentInfo documentInfo = printJob.getDocumentInfo();
+ if (documentInfo != null) {
+ serializer.startTag(null, TAG_DOCUMENT_INFO);
+ serializer.attribute(null, ATTR_NAME, documentInfo.getName());
+ serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf(
+ documentInfo.getContentType()));
+ serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
+ documentInfo.getPageCount()));
+ serializer.endTag(null, TAG_DOCUMENT_INFO);
+ }
+
+ serializer.endTag(null, TAG_JOB);
+
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[PERSISTED] " + printJob);
+ }
+ }
+
+ serializer.endTag(null, TAG_SPOOLER);
+ serializer.endDocument();
+ mStatePersistFile.finishWrite(out);
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[PERSIST END]");
+ }
+ } catch (IOException e) {
+ Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
+ mStatePersistFile.failWrite(out);
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ 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 {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ 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;
+ }
+
+ PrintJobInfo printJob = new PrintJobInfo();
+
+ final int printJobId = Integer.parseInt(parser.getAttributeValue(null, ATTR_ID));
+ printJob.setId(printJobId);
+ String label = parser.getAttributeValue(null, ATTR_LABEL);
+ printJob.setLabel(label);
+ final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE));
+ printJob.setState(state);
+ final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
+ printJob.setAppId(appId);
+ final int userId = Integer.parseInt(parser.getAttributeValue(null, ATTR_USER_ID));
+ printJob.setUserId(userId);
+ String tag = parser.getAttributeValue(null, ATTR_TAG);
+ printJob.setTag(tag);
+ String copies = parser.getAttributeValue(null, ATTR_COPIES);
+ printJob.setCopies(Integer.parseInt(copies));
+
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
+ String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
+ ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
+ null, ATTR_SERVICE_NAME));
+ printJob.setPrinterId(new PrinterId(service, localId));
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ List<PageRange> pageRanges = null;
+ while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) {
+ final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START));
+ final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END));
+ PageRange pageRange = new PageRange(start, end);
+ if (pageRanges == null) {
+ pageRanges = new ArrayList<PageRange>();
+ }
+ pageRanges.add(pageRange);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
+ parser.next();
+ }
+ if (pageRanges != null) {
+ PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
+ pageRanges.toArray(pageRangesArray);
+ printJob.setPages(pageRangesArray);
+ }
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
+
+ PrintAttributes.Builder builder = new PrintAttributes.Builder();
+
+ String duplexMode = parser.getAttributeValue(null, ATTR_DUPLEX_MODE);
+ builder.setDuplexMode(Integer.parseInt(duplexMode));
+
+ String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
+ builder.setColorMode(Integer.parseInt(colorMode));
+
+ String fittingMode = parser.getAttributeValue(null, ATTR_FITTING_MODE);
+ builder.setFittingMode(Integer.parseInt(fittingMode));
+
+ String orientation = parser.getAttributeValue(null, ATTR_ORIENTATION);
+ builder.setOrientation(Integer.parseInt(orientation));
+
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) {
+ String id = parser.getAttributeValue(null, ATTR_ID);
+ label = parser.getAttributeValue(null, ATTR_LABEL);
+ final int widthMils = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_WIDTH_MILS));
+ final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_HEIGHT_MILS));
+ MediaSize mediaSize = new MediaSize(id, label, widthMils, heightMils);
+ builder.setMediaSize(mediaSize);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) {
+ String id = parser.getAttributeValue(null, ATTR_ID);
+ label = parser.getAttributeValue(null, ATTR_LABEL);
+ final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_HORIZONTAL_DPI));
+ final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_VERTICAL_DPI));
+ Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi);
+ builder.setResolution(resolution);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
+ final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_LEFT_MILS));
+ final int topMils = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_TOP_MILS));
+ final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_RIGHT_MILS));
+ final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_BOTTOM_MILS));
+ Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
+ builder.setMargins(margins);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_INPUT_TRAY)) {
+ String id = parser.getAttributeValue(null, ATTR_ID);
+ label = parser.getAttributeValue(null, ATTR_LABEL);
+ Tray tray = new Tray(id, label);
+ builder.setInputTray(tray);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_INPUT_TRAY);
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_OUTPUT_TRAY)) {
+ String id = parser.getAttributeValue(null, ATTR_ID);
+ label = parser.getAttributeValue(null, ATTR_LABEL);
+ Tray tray = new Tray(id, label);
+ builder.setOutputTray(tray);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_OUTPUT_TRAY);
+ parser.next();
+ }
+
+ printJob.setAttributes(builder.create());
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) {
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+ final int pageCount = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_PAGE_COUNT));
+ final int contentType = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_CONTENT_TYPE));
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
+ .setPageCount(pageCount)
+ .setContentType(contentType).create();
+ printJob.setDocumentInfo(info);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
+ parser.next();
+ }
+
+ 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;
+ }
+ }
+
+ public static abstract class PrinterDiscoverySession {
+
+ private PrintSpoolerService mService;
+
+ private boolean mIsStarted;
+
+ public PrinterDiscoverySession() {
+ mService = PrintSpoolerService.peekInstance();
+ mService.createPrinterDiscoverySession();
+ mService.setPrinterDiscoverySessionClient(this);
+ }
+
+ public final void startPrinterDisovery(List<PrinterId> priorityList) {
+ mIsStarted = true;
+ mService.startPrinterDiscovery(priorityList);
+ }
+
+ public final void stopPrinterDiscovery() {
+ mIsStarted = false;
+ mService.stopPrinterDiscovery();
+ }
+
+ public void requestPrinterUpdated(PrinterId printerId) {
+ mService.requestPrinterUpdate(printerId);
+ }
+
+ public final void destroy() {
+ mService.setPrinterDiscoverySessionClient(null);
+ mService.destroyPrinterDiscoverySession();
+ }
+
+ public final boolean isStarted() {
+ return mIsStarted;
+ }
+
+ public abstract void onPrintersAdded(List<PrinterInfo> printers);
+
+ public abstract void onPrintersRemoved(List<PrinterId> printerIds);
+
+ public abstract void onPrintersUpdated(List<PrinterInfo> printers);
+ }
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java
new file mode 100644
index 0000000..141dbd1
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java
@@ -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 com.android.printspooler;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.print.PrinterId;
+
+import com.android.printspooler.SelectPrinterFragment.OnPrinterSelectedListener;
+
+public class SelectPrinterActivity extends Activity implements OnPrinterSelectedListener {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.select_printer_activity);
+ }
+
+ @Override
+ public void onPrinterSelected(PrinterId printer) {
+ Intent intent = new Intent();
+ intent.putExtra(PrintJobConfigActivity.INTENT_EXTRA_PRINTER_ID, printer);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
new file mode 100644
index 0000000..9ca3a86
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
@@ -0,0 +1,401 @@
+/*
+ * 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.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.printservice.PrintServiceInfo;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is a fragment for selecting a printer.
+ */
+public final class SelectPrinterFragment extends ListFragment {
+
+ private static final int LOADER_ID_PRINTERS_LOADER = 1;
+
+ private static final String FRAGMRNT_TAG_ADD_PRINTER_DIALOG =
+ "FRAGMRNT_TAG_ADD_PRINTER_DIALOG";
+
+ private static final String FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS =
+ "FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS";
+
+ private final ArrayList<PrintServiceInfo> mAddPrinterServices =
+ new ArrayList<PrintServiceInfo>();
+
+ public static interface OnPrinterSelectedListener {
+ public void onPrinterSelected(PrinterId printerId);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ setListAdapter(new DestinationAdapter());
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.select_printer_activity, menu);
+
+ MenuItem searchItem = menu.findItem(R.id.action_search);
+ SearchView searchView = (SearchView) searchItem.getActionView();
+ searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String searchString) {
+ ((DestinationAdapter) getListAdapter()).getFilter().filter(searchString);
+ return true;
+ }
+ });
+
+ if (mAddPrinterServices.isEmpty()) {
+ menu.removeItem(R.id.action_add_printer);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ updateAddPrintersAdapter();
+ getActivity().invalidateOptionsMenu();
+ super.onResume();
+ }
+
+ @Override
+ public void onListItemClick(ListView list, View view, int position, long id) {
+ PrinterInfo printer = (PrinterInfo) list.getAdapter().getItem(position);
+ Activity activity = getActivity();
+ if (activity instanceof OnPrinterSelectedListener) {
+ ((OnPrinterSelectedListener) activity).onPrinterSelected(printer.getId());
+ } else {
+ throw new IllegalStateException("the host activity must implement"
+ + " OnPrinterSelectedListener");
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.action_add_printer) {
+ showAddPrinterSelectionDialog();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void updateAddPrintersAdapter() {
+ mAddPrinterServices.clear();
+
+ // Get all print services.
+ List<ResolveInfo> resolveInfos = getActivity().getPackageManager().queryIntentServices(
+ new Intent(android.printservice.PrintService.SERVICE_INTERFACE),
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+
+ // No print services - done.
+ if (resolveInfos.isEmpty()) {
+ return;
+ }
+
+ // Find the services with valid add printers activities.
+ final int resolveInfoCount = resolveInfos.size();
+ for (int i = 0; i < resolveInfoCount; i++) {
+ ResolveInfo resolveInfo = resolveInfos.get(i);
+
+ PrintServiceInfo printServiceInfo = PrintServiceInfo.create(
+ resolveInfo, getActivity());
+ String addPrintersActivity = printServiceInfo.getAddPrintersActivityName();
+
+ // No add printers activity declared - done.
+ if (TextUtils.isEmpty(addPrintersActivity)) {
+ continue;
+ }
+
+ ComponentName addPrintersComponentName = new ComponentName(
+ resolveInfo.serviceInfo.packageName,
+ addPrintersActivity);
+ Intent addPritnersIntent = new Intent(Intent.ACTION_MAIN)
+ .setComponent(addPrintersComponentName);
+
+ // The add printers activity is valid - add it.
+ if (!getActivity().getPackageManager().queryIntentActivities(
+ addPritnersIntent, 0).isEmpty()) {
+ mAddPrinterServices.add(printServiceInfo);
+ }
+ }
+ }
+
+ private void showAddPrinterSelectionDialog() {
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ Fragment oldFragment = getFragmentManager().findFragmentByTag(
+ FRAGMRNT_TAG_ADD_PRINTER_DIALOG);
+ if (oldFragment != null) {
+ transaction.remove(oldFragment);
+ }
+ AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment();
+ Bundle arguments = new Bundle();
+ arguments.putParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS,
+ mAddPrinterServices);
+ newFragment.setArguments(arguments);
+ transaction.add(newFragment, FRAGMRNT_TAG_ADD_PRINTER_DIALOG);
+ transaction.commit();
+ }
+
+ public static class AddPrinterAlertDialogFragment extends DialogFragment {
+
+ private static final String DEFAULT_MARKET_QUERY_STRING =
+ "market://search?q=print";
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.choose_print_service);
+
+ final List<PrintServiceInfo> printServices = (List<PrintServiceInfo>) (List<?>)
+ getArguments().getParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS);
+
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
+ android.R.layout.simple_list_item_1);
+ final int printServiceCount = printServices.size();
+ for (int i = 0; i < printServiceCount; i++) {
+ PrintServiceInfo printService = printServices.get(i);
+ adapter.add(printService.getResolveInfo().loadLabel(
+ getActivity().getPackageManager()).toString());
+ }
+
+ builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ PrintServiceInfo printService = printServices.get(which);
+ ComponentName componentName = new ComponentName(
+ printService.getResolveInfo().serviceInfo.packageName,
+ printService.getAddPrintersActivityName());
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(componentName);
+ startActivity(intent);
+ }
+ });
+
+ Uri marketUri = Uri.parse(DEFAULT_MARKET_QUERY_STRING);
+ final Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
+ if (getActivity().getPackageManager().resolveActivity(marketIntent, 0) != null) {
+ builder.setPositiveButton(R.string.search_play_store,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ startActivity(marketIntent);
+ }
+ });
+ }
+
+ return builder.create();
+ }
+ }
+
+ private final class DestinationAdapter extends BaseAdapter
+ implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>, Filterable {
+
+ private final Object mLock = new Object();
+
+ private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
+
+ private final List<PrinterInfo> mFilteredPrinters = new ArrayList<PrinterInfo>();
+
+ private CharSequence mLastSearchString;
+
+ public DestinationAdapter() {
+ getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
+ }
+
+ @Override
+ public Filter getFilter() {
+ return new Filter() {
+ @Override
+ protected FilterResults performFiltering(CharSequence constraint) {
+ synchronized (mLock) {
+ if (TextUtils.isEmpty(constraint)) {
+ return null;
+ }
+ FilterResults results = new FilterResults();
+ List<PrinterInfo> filteredPrinters = new ArrayList<PrinterInfo>();
+ String constraintLowerCase = constraint.toString().toLowerCase();
+ final int printerCount = mPrinters.size();
+ for (int i = 0; i < printerCount; i++) {
+ PrinterInfo printer = mPrinters.get(i);
+ if (printer.getName().toLowerCase().contains(constraintLowerCase)) {
+ filteredPrinters.add(printer);
+ }
+ }
+ results.values = filteredPrinters;
+ results.count = filteredPrinters.size();
+ return results;
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+ synchronized (mLock) {
+ mLastSearchString = constraint;
+ mFilteredPrinters.clear();
+ if (results == null) {
+ mFilteredPrinters.addAll(mPrinters);
+ } else {
+ List<PrinterInfo> printers = (List<PrinterInfo>) results.values;
+ mFilteredPrinters.addAll(printers);
+ }
+ }
+ notifyDataSetChanged();
+ }
+ };
+ }
+
+ @Override
+ public int getCount() {
+ synchronized (mLock) {
+ return mFilteredPrinters.size();
+ }
+ }
+
+ @Override
+ public Object getItem(int position) {
+ synchronized (mLock) {
+ return mFilteredPrinters.get(position);
+ }
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView,
+ ViewGroup parent) {
+ return getView(position, convertView, parent);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = getActivity().getLayoutInflater().inflate(
+ R.layout.spinner_dropdown_item, parent, false);
+ }
+
+ CharSequence title = null;
+ CharSequence subtitle = null;
+
+ PrinterInfo printer = (PrinterInfo) getItem(position);
+ title = printer.getName();
+ try {
+ PackageManager pm = getActivity().getPackageManager();
+ PackageInfo packageInfo = pm.getPackageInfo(printer.getId()
+ .getServiceName().getPackageName(), 0);
+ subtitle = packageInfo.applicationInfo.loadLabel(pm);
+ } catch (NameNotFoundException nnfe) {
+ /* ignore */
+ }
+
+ TextView titleView = (TextView) convertView.findViewById(R.id.title);
+ titleView.setText(title);
+
+ TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
+ if (!TextUtils.isEmpty(subtitle)) {
+ subtitleView.setText(subtitle);
+ subtitleView.setVisibility(View.VISIBLE);
+ } else {
+ subtitleView.setText(null);
+ subtitleView.setVisibility(View.GONE);
+ }
+
+ return convertView;
+ }
+
+ @Override
+ public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
+ if (id == LOADER_ID_PRINTERS_LOADER) {
+ return new FusedPrintersProvider(getActivity());
+ }
+ return null;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<PrinterInfo>> loader,
+ List<PrinterInfo> printers) {
+ synchronized (mLock) {
+ mPrinters.clear();
+ mPrinters.addAll(printers);
+ mFilteredPrinters.clear();
+ mFilteredPrinters.addAll(printers);
+ if (!TextUtils.isEmpty(mLastSearchString)) {
+ getFilter().filter(mLastSearchString);
+ }
+ }
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
+ synchronized (mLock) {
+ mPrinters.clear();
+ mFilteredPrinters.clear();
+ }
+ notifyDataSetInvalidated();
+ }
+ }
+}