diff options
Diffstat (limited to 'packages/PrintSpooler')
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 Binary files differnew file mode 100644 index 0000000..4b68f52 --- /dev/null +++ b/packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.png diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png Binary files differnew file mode 100644 index 0000000..15ffadd --- /dev/null +++ b/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png Binary files differnew file mode 100644 index 0000000..420510e --- /dev/null +++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png 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(); + } + } +} |