From 269403b032f965ff3847eb982c2f697229dc5a92 Mon Sep 17 00:00:00 2001 From: Svetoslav Date: Wed, 14 Aug 2013 17:31:04 -0700 Subject: Implemented advanced printer selection and API refactoring. 1. Added past printer history tracking and merging favorite printers with discovered printers. 2. Added save as PDF support. 3. Added all printers activity with search capability and optional add printers chooser (if any print service provides add printers activity) 4. Refactored the printer discovery session APIs. Now one session can have multiple window discovery windows and the session stores the printers found during past discovery periods. 5. Merged the print spooler and the print spooler service - much simpler and easier to maintain. Change-Id: I4830b0eb6367e1c748b768a5ea9ea11baf36cfad --- .../printservice/PrinterDiscoverySession.java | 554 +++++++++++++++------ 1 file changed, 390 insertions(+), 164 deletions(-) (limited to 'core/java/android/printservice/PrinterDiscoverySession.java') diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java index 92dc0dd4a05f..8b959a68690a 100644 --- a/core/java/android/printservice/PrinterDiscoverySession.java +++ b/core/java/android/printservice/PrinterDiscoverySession.java @@ -16,18 +16,15 @@ package android.printservice; -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; import android.os.RemoteException; -import android.print.IPrinterDiscoverySessionController; -import android.print.IPrinterDiscoverySessionObserver; +import android.print.PrinterCapabilitiesInfo; import android.print.PrinterId; import android.print.PrinterInfo; +import android.util.ArrayMap; import android.util.Log; -import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -36,67 +33,75 @@ import java.util.List; * for adding discovered printers, removing already added printers that * disappeared, and updating already added printers. *

- * The opening of the session is announced by a call to {@link - * PrinterDiscoverySession#onOpen(List)} at which point you should start printer - * discovery. The closing of the session is announced by a call to {@link - * PrinterDiscoverySession#onClose()} at which point you should stop printer - * discovery. Discovered printers are added by invoking {@link - * PrinterDiscoverySession#addPrinters(List)}. Added printers that disappeared - * are removed by invoking {@link PrinterDiscoverySession#removePrinters(List)}. - * Added printers whose properties or capabilities changed are updated through - * a call to {@link PrinterDiscoverySession#updatePrinters(List)}. + * During the lifetime of this session you may be asked to start and stop + * performing printer discovery multiple times. You will receive a call to {@link + * PrinterDiscoverySession#onStartPrinterDiscovery(List)} to start printer + * discovery and a call to {@link PrinterDiscoverySession#onStopPrinterDiscovery()} + * to stop printer discovery. When the system is no longer interested in printers + * discovered by this session you will receive a call to {@link #onDestroy()} at + * which point the system will no longer call into the session and all the session + * methods will do nothing. + *

+ *

+ * Discovered printers are added by invoking {@link + * PrinterDiscoverySession#addPrinters(List)}. Added printers that disappeared are + * removed by invoking {@link PrinterDiscoverySession#removePrinters(List)}. Added + * printers whose properties or capabilities changed are updated through a call to + * {@link PrinterDiscoverySession#updatePrinters(List)}. The printers added in this + * session can be acquired via {@link #getPrinters()} where the returned printers + * will be an up-to-date snapshot of the printers that you reported during the + * session. Printers are not persisted across sessions. *

*

* The system will make a call to * {@link PrinterDiscoverySession#onRequestPrinterUpdate(PrinterId)} if you * need to update a given printer. It is possible that you add a printer without - * specifying its capabilities. This enables you to avoid querying all - * discovered printers for their capabilities, rather querying the capabilities - * of a printer only if necessary. For example, the system will require that you - * update a printer if it gets selected by the user. If you did not report the - * printer capabilities when adding it, you must do so after the system requests - * a printer update. Otherwise, the printer will be ignored. + * specifying its capabilities. This enables you to avoid querying all discovered + * printers for their capabilities, rather querying the capabilities of a printer + * only if necessary. For example, the system will request that you update a printer + * if it gets selected by the user. If you did not report the printer capabilities + * when adding it, you must do so after the system requests a printer update. + * Otherwise, the printer will be ignored. *

*

- * During printer discovery all printers that are known to your print service - * have to be added. The system does not retain any printers from previous - * sessions. + * Note: All callbacks in this class are executed on the main + * application thread. You also have to invoke any method of this class on the main + * application thread. *

*/ public abstract class PrinterDiscoverySession { private static final String LOG_TAG = "PrinterDiscoverySession"; + private static final int MAX_ITEMS_PER_CALLBACK = 100; + private static int sIdCounter = 0; - private final Object mLock = new Object(); + private final int mId; - private final Handler mHandler; + private final ArrayMap mPrinters = + new ArrayMap(); - private final int mId; + private ArrayMap mLastSentPrinters; + + private IPrintServiceClient mObserver; - private IPrinterDiscoverySessionController mController; + private boolean mIsDestroyed; - private IPrinterDiscoverySessionObserver mObserver; + private boolean mIsDiscoveryStarted; /** * Constructor. - * - * @param context A context instance. */ - public PrinterDiscoverySession(Context context) { + public PrinterDiscoverySession() { mId = sIdCounter++; - mHandler = new SessionHandler(context.getMainLooper()); - mController = new PrinterDiscoverySessionController(this); } - void setObserver(IPrinterDiscoverySessionObserver observer) { - synchronized (mLock) { - mObserver = observer; - try { - mObserver.setController(mController); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error setting session controller", re); - } + void setObserver(IPrintServiceClient observer) { + mObserver = observer; + // If some printers were added in the method that + // created the session, send them over. + if (!mPrinters.isEmpty()) { + sendAddedPrinters(mObserver, getPrinters()); } } @@ -104,132 +109,358 @@ public abstract class PrinterDiscoverySession { return mId; } + /** + * Gets the printers reported in this session. For example, if you add two + * printers and remove one of them, the returned list will contain only + * the printer that was added but not removed. + *

+ * Note: Calls to this method after the session is + * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored. + *

+ * + * @return The printers. + * + * @see #addPrinters(List) + * @see #removePrinters(List) + * @see #updatePrinters(List) + * @see #isDestroyed() + */ + public final List getPrinters() { + PrintService.throwIfNotCalledOnMainThread(); + if (mIsDestroyed) { + return Collections.emptyList(); + } + return new ArrayList(mPrinters.values()); + } + /** * Adds discovered printers. Adding an already added printer has no effect. * Removed printers can be added again. You can call this method multiple - * times during printer discovery. + * times during the life of this session. Duplicates will be ignored. *

- * Note: Calls to this method before the session is opened, - * i.e. before the {@link #onOpen(List)} call, and after the session is closed, - * i.e. after the call to {@link #onClose()}, will be ignored. + * Note: Calls to this method after the session is + * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored. *

* * @param printers The printers to add. * * @see #removePrinters(List) * @see #updatePrinters(List) + * @see #getPrinters() + * @see #isDestroyed() */ public final void addPrinters(List printers) { - final IPrinterDiscoverySessionObserver observer; - synchronized (mLock) { - observer = mObserver; + PrintService.throwIfNotCalledOnMainThread(); + + // If the session is destroyed - nothing do to. + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not adding printers - session destroyed."); + return; } - if (observer != null) { - try { - observer.onPrintersAdded(printers); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error adding printers", re); + + if (mIsDiscoveryStarted) { + // If during discovery, add the new printers and send them. + List addedPrinters = new ArrayList(); + final int addedPrinterCount = printers.size(); + for (int i = 0; i < addedPrinterCount; i++) { + PrinterInfo addedPrinter = printers.get(i); + if (mPrinters.get(addedPrinter.getId()) == null) { + mPrinters.put(addedPrinter.getId(), addedPrinter); + addedPrinters.add(addedPrinter); + } + } + + // Send the added printers, if such. + if (!addedPrinters.isEmpty()) { + sendAddedPrinters(mObserver, addedPrinters); } } else { - Log.w(LOG_TAG, "Printer discovery session not open not adding printers."); + // Remember the last sent printers if needed. + if (mLastSentPrinters == null) { + mLastSentPrinters = new ArrayMap(mPrinters); + } + + // Update the printers. + final int addedPrinterCount = printers.size(); + for (int i = 0; i < addedPrinterCount; i++) { + PrinterInfo addedPrinter = printers.get(i); + if (mPrinters.get(addedPrinter.getId()) == null) { + mPrinters.put(addedPrinter.getId(), addedPrinter); + } + } + } + } + + private static void sendAddedPrinters(IPrintServiceClient observer, + List printers) { + try { + final int printerCount = printers.size(); + if (printerCount <= MAX_ITEMS_PER_CALLBACK) { + observer.onPrintersAdded(printers); + } else { + // Send the added printers in chunks avoiding the binder transaction limit. + final int transactionCount = (printerCount / MAX_ITEMS_PER_CALLBACK) + 1; + for (int i = 0; i < transactionCount; i++) { + final int start = i * MAX_ITEMS_PER_CALLBACK; + final int end = Math.min(start + MAX_ITEMS_PER_CALLBACK, printerCount); + List subPrinters = printers.subList(start, end); + observer.onPrintersAdded(subPrinters); + } + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error sending added printers", re); } } /** * Removes added printers. Removing an already removed or never added - * printer has no effect. Removed printers can be added again. You - * can call this method multiple times during printer discovery. + * printer has no effect. Removed printers can be added again. You can + * call this method multiple times during the lifetime of this session. *

- * Note: Calls to this method before the session is opened, - * i.e. before the {@link #onOpen(List)} call, and after the session is closed, - * i.e. after the call to {@link #onClose()}, will be ignored. + * Note: Calls to this method after the session is + * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored. *

* * @param printerIds The ids of the removed printers. * * @see #addPrinters(List) * @see #updatePrinters(List) + * @see #getPrinters() + * @see #isDestroyed() */ public final void removePrinters(List printerIds) { - final IPrinterDiscoverySessionObserver observer; - synchronized (mLock) { - observer = mObserver; + PrintService.throwIfNotCalledOnMainThread(); + + // If the session is destroyed - nothing do to. + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not removing printers - session destroyed."); + return; } - if (observer != null) { - try { - observer.onPrintersRemoved(printerIds); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error removing printers", re); + + if (mIsDiscoveryStarted) { + // If during discovery, remove existing printers and send them. + List removedPrinterIds = new ArrayList(); + final int removedPrinterIdCount = printerIds.size(); + for (int i = 0; i < removedPrinterIdCount; i++) { + PrinterId removedPrinterId = printerIds.get(i); + if (mPrinters.remove(removedPrinterId) != null) { + removedPrinterIds.add(removedPrinterId); + } + } + + // Send the removed printers, if such. + if (!removedPrinterIds.isEmpty()) { + sendRemovedPrinters(mObserver, removedPrinterIds); } } else { - Log.w(LOG_TAG, "Printer discovery session not open not removing printers."); + // Remember the last sent printers if needed. + if (mLastSentPrinters == null) { + mLastSentPrinters = new ArrayMap(mPrinters); + } + + // Update the printers. + final int removedPrinterIdCount = printerIds.size(); + for (int i = 0; i < removedPrinterIdCount; i++) { + PrinterId removedPrinterId = printerIds.get(i); + mPrinters.remove(removedPrinterId); + } + } + } + + private static void sendRemovedPrinters(IPrintServiceClient observer, + List printerIds) { + try { + final int printerIdCount = printerIds.size(); + if (printerIdCount <= MAX_ITEMS_PER_CALLBACK) { + observer.onPrintersRemoved(printerIds); + } else { + final int transactionCount = (printerIdCount / MAX_ITEMS_PER_CALLBACK) + 1; + for (int i = 0; i < transactionCount; i++) { + final int start = i * MAX_ITEMS_PER_CALLBACK; + final int end = Math.min(start + MAX_ITEMS_PER_CALLBACK, printerIdCount); + List subPrinterIds = printerIds.subList(start, end); + observer.onPrintersRemoved(subPrinterIds); + } + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error sending removed printers", re); } } /** * Updates added printers. Updating a printer that was not added or that * was removed has no effect. You can call this method multiple times - * during printer discovery. + * during the lifetime of this session. *

- * Note: Calls to this method before the session is opened, - * i.e. before the {@link #onOpen(List)} call, and after the session is closed, - * i.e. after the call to {@link #onClose()}, will be ignored. + * Note: Calls to this method after the session is + * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored. *

* * @param printers The printers to update. * * @see #addPrinters(List) * @see #removePrinters(List) + * @see #getPrinters() + * @see #isDestroyed() */ public final void updatePrinters(List printers) { - final IPrinterDiscoverySessionObserver observer; - synchronized (mLock) { - observer = mObserver; + PrintService.throwIfNotCalledOnMainThread(); + + // If the session is destroyed - nothing do to. + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not updating printers - session destroyed."); + return; } - if (observer != null) { - try { - observer.onPrintersUpdated(printers); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error updating printers", re); + + if (mIsDiscoveryStarted) { + // If during discovery, update existing printers and send them. + List updatedPrinters = new ArrayList(); + final int updatedPrinterCount = printers.size(); + for (int i = 0; i < updatedPrinterCount; i++) { + PrinterInfo updatedPrinter = printers.get(i); + PrinterInfo oldPrinter = mPrinters.get(updatedPrinter.getId()); + if (oldPrinter != null && !oldPrinter.equals(updatedPrinter)) { + mPrinters.put(updatedPrinter.getId(), updatedPrinter); + updatedPrinters.add(updatedPrinter); + } + } + + // Send the updated printers, if such. + if (!updatedPrinters.isEmpty()) { + sendUpdatedPrinters(mObserver, updatedPrinters); } } else { - Log.w(LOG_TAG, "Printer discovery session not open not updating printers."); + // Remember the last sent printers if needed. + if (mLastSentPrinters == null) { + mLastSentPrinters = new ArrayMap(mPrinters); + } + + // Update the printers. + final int updatedPrinterCount = printers.size(); + for (int i = 0; i < updatedPrinterCount; i++) { + PrinterInfo updatedPrinter = printers.get(i); + PrinterInfo oldPrinter = mPrinters.get(updatedPrinter.getId()); + if (oldPrinter != null && !oldPrinter.equals(updatedPrinter)) { + mPrinters.put(updatedPrinter.getId(), updatedPrinter); + } + } + } + } + + private static void sendUpdatedPrinters(IPrintServiceClient observer, + List printers) { + try { + final int printerCount = printers.size(); + if (printerCount <= MAX_ITEMS_PER_CALLBACK) { + observer.onPrintersUpdated(printers); + } else { + final int transactionCount = (printerCount / MAX_ITEMS_PER_CALLBACK) + 1; + for (int i = 0; i < transactionCount; i++) { + final int start = i * MAX_ITEMS_PER_CALLBACK; + final int end = Math.min(start + MAX_ITEMS_PER_CALLBACK, printerCount); + List subPrinters = printers.subList(start, end); + observer.onPrintersUpdated(subPrinters); + } + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error sending updated printers", re); + } + } + + private void sendOutOfDiscoveryPeriodPrinterChanges() { + // Noting changed since the last discovery period - nothing to do. + if (mLastSentPrinters == null || mLastSentPrinters.isEmpty()) { + mLastSentPrinters = null; + return; + } + + List addedPrinters = null; + List updatedPrinters = null; + List removedPrinterIds = null; + + // Determine the added and updated printers. + for (PrinterInfo printer : mPrinters.values()) { + PrinterInfo sentPrinter = mLastSentPrinters.get(printer.getId()); + if (sentPrinter != null) { + if (!sentPrinter.equals(printer)) { + if (updatedPrinters == null) { + updatedPrinters = new ArrayList(); + } + updatedPrinters.add(printer); + } + } else { + if (addedPrinters == null) { + addedPrinters = new ArrayList(); + } + addedPrinters.add(printer); + } } + + // Send the added printers, if such. + if (addedPrinters != null) { + sendAddedPrinters(mObserver, addedPrinters); + } + + // Send the updated printers, if such. + if (updatedPrinters != null) { + sendUpdatedPrinters(mObserver, updatedPrinters); + } + + // Determine the removed printers. + for (PrinterInfo sentPrinter : mLastSentPrinters.values()) { + if (!mPrinters.containsKey(sentPrinter.getId())) { + if (removedPrinterIds == null) { + removedPrinterIds = new ArrayList(); + } + removedPrinterIds.add(sentPrinter.getId()); + } + } + + // Send the removed printers, if such. + if (removedPrinterIds != null) { + sendRemovedPrinters(mObserver, removedPrinterIds); + } + + mLastSentPrinters = null; } /** - * Callback notifying you that the session is open and you should start - * printer discovery. Discovered printers should be added via calling - * {@link #addPrinters(List)}. Added printers that disappeared should be - * removed via calling {@link #removePrinters(List)}. Added printers whose - * properties or capabilities changes should be updated via calling {@link - * #updatePrinters(List)}. When the session is closed you will receive a - * call to {@link #onClose()}. + * Callback asking you to start printer discovery. Discovered printers should be + * added via calling {@link #addPrinters(List)}. Added printers that disappeared + * should be removed via calling {@link #removePrinters(List)}. Added printers + * whose properties or capabilities changed should be updated via calling {@link + * #updatePrinters(List)}. You will receive a call to call to {@link + * #onStopPrinterDiscovery()} when you should stop printer discovery. *

- * During printer discovery all printers that are known to your print - * service have to be added. The system does not retain any printers from - * previous sessions. + * During the lifetime of this session all printers that are known to your print + * service have to be added. The system does not retain any printers across sessions. + * However, if you were asked to start and then stop performing printer discovery + * in this session, then a subsequent discovering should not re-discover already + * discovered printers. *

*

- * Note: You are also given a list of printers whose - * availability has to be checked first. For example, these printers could - * be the user's favorite ones, therefore they have to be verified first. + * Note: You are also given a list of printers whose availability + * has to be checked first. For example, these printers could be the user's favorite + * ones, therefore they have to be verified first. *

* - * @see #onClose() + * @param priorityList The list of printers to validate first. Never null. + * + * @see #onStopPrinterDiscovery() * @see #addPrinters(List) * @see #removePrinters(List) * @see #updatePrinters(List) + * @see #isPrinterDiscoveryStarted() */ - public abstract void onOpen(List priorityList); + public abstract void onStartPrinterDiscovery(List priorityList); /** - * Callback notifying you that the session is closed and you should stop - * printer discovery. After the session is closed any call to the methods - * of this instance will be ignored. Once the session is closed - * it will never be opened again. + * Callback notifying you that you should stop printer discovery. + * + * @see #onStartPrinterDiscovery(List) + * @see #isPrinterDiscoveryStarted() */ - public abstract void onClose(); + public abstract void onStopPrinterDiscovery(); /** * Requests that you update a printer. You are responsible for updating @@ -255,77 +486,72 @@ public abstract class PrinterDiscoverySession { */ public abstract void onRequestPrinterUpdate(PrinterId printerId); - void close() { - synchronized (mLock) { - mController = null; - mObserver = null; - } - } + /** + * Notifies you that the session is destroyed. After this callback is invoked + * any calls to the methods of this class will be ignored, {@link #isDestroyed()} + * will return true and you will also no longer receive callbacks. + * + * @see #isDestroyed() + */ + public abstract void onDestroy(); - private final class SessionHandler extends Handler { - public static final int MSG_OPEN = 1; - public static final int MSG_CLOSE = 2; - public static final int MSG_REQUEST_PRINTER_UPDATE = 3; + /** + * Gets whether the session is destroyed. + * + * @return Whether the session is destroyed. + * + * @see #onDestroy() + */ + public final boolean isDestroyed() { + PrintService.throwIfNotCalledOnMainThread(); + return mIsDestroyed; + } - public SessionHandler(Looper looper) { - super(looper, null, true); - } + /** + * Gets whether printer discovery is started. + * + * @return Whether printer discovery is destroyed. + * + * @see #onStartPrinterDiscovery(List) + * @see #onStopPrinterDiscovery() + */ + public final boolean isPrinterDiscoveryStarted() { + PrintService.throwIfNotCalledOnMainThread(); + return mIsDiscoveryStarted; + } - @Override - @SuppressWarnings("unchecked") - public void handleMessage(Message message) { - switch (message.what) { - case MSG_OPEN: { - List priorityList = (List) message.obj; - onOpen(priorityList); - } break; - - case MSG_CLOSE: { - onClose(); - close(); - } break; - - case MSG_REQUEST_PRINTER_UPDATE: { - PrinterId printerId = (PrinterId) message.obj; - onRequestPrinterUpdate(printerId); - } break; + void startPrinterDiscovery(List priorityList) { + if (!mIsDestroyed) { + mIsDiscoveryStarted = true; + sendOutOfDiscoveryPeriodPrinterChanges(); + if (priorityList == null) { + priorityList = Collections.emptyList(); } + onStartPrinterDiscovery(priorityList); } } - private static final class PrinterDiscoverySessionController extends - IPrinterDiscoverySessionController.Stub { - private final WeakReference mWeakSession; - - public PrinterDiscoverySessionController(PrinterDiscoverySession session) { - mWeakSession = new WeakReference(session); - } - - @Override - public void open(List priorityList) { - PrinterDiscoverySession session = mWeakSession.get(); - if (session != null) { - session.mHandler.obtainMessage(SessionHandler.MSG_OPEN, - priorityList).sendToTarget(); - } + void stopPrinterDiscovery() { + if (!mIsDestroyed) { + mIsDiscoveryStarted = false; + onStopPrinterDiscovery(); } + } - @Override - public void close() { - PrinterDiscoverySession session = mWeakSession.get(); - if (session != null) { - session.mHandler.sendEmptyMessage(SessionHandler.MSG_CLOSE); - } + void requestPrinterUpdate(PrinterId printerId) { + if (!mIsDestroyed) { + onRequestPrinterUpdate(printerId); } + } - @Override - public void requestPrinterUpdate(PrinterId printerId) { - PrinterDiscoverySession session = mWeakSession.get(); - if (session != null) { - session.mHandler.obtainMessage( - SessionHandler.MSG_REQUEST_PRINTER_UPDATE, - printerId).sendToTarget(); - } + void destroy() { + if (!mIsDestroyed) { + mIsDestroyed = true; + mIsDiscoveryStarted = false; + mPrinters.clear(); + mLastSentPrinters = null; + mObserver = null; + onDestroy(); } - }; + } } -- cgit v1.2.3