/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.printservice; import android.content.pm.ParceledListSlice; import android.os.RemoteException; import android.print.PrinterCapabilitiesInfo; import android.print.PrinterId; import android.print.PrinterInfo; import android.util.ArrayMap; import android.util.Log; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * This class encapsulates the interaction between a print service and the * system during printer discovery. During printer discovery you are responsible * for adding discovered printers, removing previously added printers that * disappeared, and updating already added printers. * <p> * 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. * </p> * <p> * 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#addPrinters(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 <strong>not</strong> persisted across sessions. * </p> * <p> * The system will make a call to {@link #onValidatePrinters(List)} if you * need to update some printers. 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 request that you update a printer * if it gets selected by the user. When validating printers you do not need to * provide the printers' capabilities but may do so. * </p> * <p> * If the system is interested in being constantly updated for the state of a * printer you will receive a call to {@link #onStartPrinterStateTracking(PrinterId)} * after which you will have to do a best effort to keep the system updated for * changes in the printer state and capabilities. You also <strong>must</strong> * update the printer capabilities if you did not provide them when adding it, or * the printer will be ignored. When the system is no longer interested in getting * updates for a printer you will receive a call to {@link #onStopPrinterStateTracking( * PrinterId)}. * </p> * <p> * <strong>Note: </strong> 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. * </p> */ public abstract class PrinterDiscoverySession { private static final String LOG_TAG = "PrinterDiscoverySession"; private static int sIdCounter = 0; private final int mId; private final ArrayMap<PrinterId, PrinterInfo> mPrinters = new ArrayMap<PrinterId, PrinterInfo>(); private final List<PrinterId> mTrackedPrinters = new ArrayList<PrinterId>(); private ArrayMap<PrinterId, PrinterInfo> mLastSentPrinters; private IPrintServiceClient mObserver; private boolean mIsDestroyed; private boolean mIsDiscoveryStarted; /** * Constructor. */ public PrinterDiscoverySession() { mId = sIdCounter++; } void setObserver(IPrintServiceClient observer) { mObserver = observer; // If some printers were added in the method that // created the session, send them over. if (!mPrinters.isEmpty()) { try { mObserver.onPrintersAdded(new ParceledListSlice<PrinterInfo>(getPrinters())); } catch (RemoteException re) { Log.e(LOG_TAG, "Error sending added printers", re); } } } int getId() { 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. * <p> * <strong>Note: </strong> Calls to this method after the session is * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. * </p> * * @return The printers. * * @see #addPrinters(List) * @see #removePrinters(List) * @see #isDestroyed() */ public final List<PrinterInfo> getPrinters() { PrintService.throwIfNotCalledOnMainThread(); if (mIsDestroyed) { return Collections.emptyList(); } return new ArrayList<PrinterInfo>(mPrinters.values()); } /** * Adds discovered printers. Adding an already added printer updates it. * Removed printers can be added again. You can call this method multiple * times during the life of this session. Duplicates will be ignored. * <p> * <strong>Note: </strong> Calls to this method after the session is * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. * </p> * * @param printers The printers to add. * * @see #removePrinters(List) * @see #getPrinters() * @see #isDestroyed() */ public final void addPrinters(List<PrinterInfo> printers) { PrintService.throwIfNotCalledOnMainThread(); // If the session is destroyed - nothing do to. if (mIsDestroyed) { Log.w(LOG_TAG, "Not adding printers - session destroyed."); return; } if (mIsDiscoveryStarted) { // If during discovery, add the new printers and send them. List<PrinterInfo> addedPrinters = null; final int addedPrinterCount = printers.size(); for (int i = 0; i < addedPrinterCount; i++) { PrinterInfo addedPrinter = printers.get(i); PrinterInfo oldPrinter = mPrinters.put(addedPrinter.getId(), addedPrinter); if (oldPrinter == null || !oldPrinter.equals(addedPrinter)) { if (addedPrinters == null) { addedPrinters = new ArrayList<PrinterInfo>(); } addedPrinters.add(addedPrinter); } } // Send the added printers, if such. if (addedPrinters != null) { try { mObserver.onPrintersAdded(new ParceledListSlice<PrinterInfo>(addedPrinters)); } catch (RemoteException re) { Log.e(LOG_TAG, "Error sending added printers", re); } } } else { // Remember the last sent printers if needed. if (mLastSentPrinters == null) { mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(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); } } } } /** * 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 the lifetime of this session. * <p> * <strong>Note: </strong> Calls to this method after the session is * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. * </p> * * @param printerIds The ids of the removed printers. * * @see #addPrinters(List) * @see #getPrinters() * @see #isDestroyed() */ public final void removePrinters(List<PrinterId> printerIds) { PrintService.throwIfNotCalledOnMainThread(); // If the session is destroyed - nothing do to. if (mIsDestroyed) { Log.w(LOG_TAG, "Not removing printers - session destroyed."); return; } if (mIsDiscoveryStarted) { // If during discovery, remove existing printers and send them. List<PrinterId> removedPrinterIds = new ArrayList<PrinterId>(); 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()) { try { mObserver.onPrintersRemoved(new ParceledListSlice<PrinterId>( removedPrinterIds)); } catch (RemoteException re) { Log.e(LOG_TAG, "Error sending removed printers", re); } } } else { // Remember the last sent printers if needed. if (mLastSentPrinters == null) { mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(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 void sendOutOfDiscoveryPeriodPrinterChanges() { // Noting changed since the last discovery period - nothing to do. if (mLastSentPrinters == null || mLastSentPrinters.isEmpty()) { mLastSentPrinters = null; return; } // Determine the added printers. List<PrinterInfo> addedPrinters = null; for (PrinterInfo printer : mPrinters.values()) { PrinterInfo sentPrinter = mLastSentPrinters.get(printer.getId()); if (sentPrinter == null || !sentPrinter.equals(printer)) { if (addedPrinters == null) { addedPrinters = new ArrayList<PrinterInfo>(); } addedPrinters.add(printer); } } // Send the added printers, if such. if (addedPrinters != null) { try { mObserver.onPrintersAdded(new ParceledListSlice<PrinterInfo>(addedPrinters)); } catch (RemoteException re) { Log.e(LOG_TAG, "Error sending added printers", re); } } // Determine the removed printers. List<PrinterId> removedPrinterIds = null; for (PrinterInfo sentPrinter : mLastSentPrinters.values()) { if (!mPrinters.containsKey(sentPrinter.getId())) { if (removedPrinterIds == null) { removedPrinterIds = new ArrayList<PrinterId>(); } removedPrinterIds.add(sentPrinter.getId()); } } // Send the removed printers, if such. if (removedPrinterIds != null) { try { mObserver.onPrintersRemoved(new ParceledListSlice<PrinterId>(removedPrinterIds)); } catch (RemoteException re) { Log.e(LOG_TAG, "Error sending removed printers", re); } } mLastSentPrinters = null; } /** * 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 * #addPrinters(List)}. You will receive a call to {@link #onStopPrinterDiscovery()} * when you should stop printer discovery. * <p> * 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. You can get the printers reported during this session by * calling {@link #getPrinters()}. * </p> * <p> * <strong>Note: </strong>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. You do <strong>not need</strong> * to provide the capabilities of the printers, rather verify whether they exist * similarly to {@link #onValidatePrinters(List)}. * </p> * * @param priorityList The list of printers to validate first. Never null. * * @see #onStopPrinterDiscovery() * @see #addPrinters(List) * @see #removePrinters(List) * @see #isPrinterDiscoveryStarted() */ public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList); /** * Callback notifying you that you should stop printer discovery. * * @see #onStartPrinterDiscovery(List) * @see #isPrinterDiscoveryStarted() */ public abstract void onStopPrinterDiscovery(); /** * Callback asking you to validate that the given printers are valid, that * is they exist. You are responsible for checking whether these printers * exist and for the ones that do exist notify the system via calling * {@link #addPrinters(List)}. * <p> * <strong>Note: </strong> You are <strong>not required</strong> to provide * the printer capabilities when updating the printers that do exist. * <p> * * @param printerIds The printers to validate. * * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo) * PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo) */ public abstract void onValidatePrinters(List<PrinterId> printerIds); /** * Callback asking you to start tracking the state of a printer. Tracking * the state means that you should do a best effort to observe the state * of this printer and notify the system if that state changes via calling * {@link #addPrinters(List)}. * <p> * <strong>Note: </strong> A printer can be initially added without its * capabilities to avoid polling printers that the user will not select. * However, after this method is called you are expected to update the * printer <strong>including</strong> its capabilities. Otherwise, the * printer will be ignored. * <p> * <p> * A scenario when you may be requested to track a printer's state is if * the user selects that printer and the system has to present print * options UI based on the printer's capabilities. In this case the user * should be promptly informed if, for example, the printer becomes * unavailable. * </p> * * @param printerId The printer to start tracking. * * @see #onStopPrinterStateTracking(PrinterId) * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo) * PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo) */ public abstract void onStartPrinterStateTracking(PrinterId printerId); /** * Callback asking you to stop tracking the state of a printer. The passed * in printer id is the one for which you received a call to {@link * #onStartPrinterStateTracking(PrinterId)}. * * @param printerId The printer to stop tracking. * * @see #onStartPrinterStateTracking(PrinterId) */ public abstract void onStopPrinterStateTracking(PrinterId printerId); /** * Gets the printers that should be tracked. These are printers that are * important to the user and for which you received a call to {@link * #onStartPrinterStateTracking(PrinterId)} asking you to observer their * state and reporting it to the system via {@link #addPrinters(List)}. * You will receive a call to {@link #onStopPrinterStateTracking(PrinterId)} * if you should stop tracking a printer. * <p> * <strong>Note: </strong> Calls to this method after the session is * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. * </p> * * @return The printers. * * @see #onStartPrinterStateTracking(PrinterId) * @see #onStopPrinterStateTracking(PrinterId) * @see #isDestroyed() */ public final List<PrinterId> getTrackedPrinters() { PrintService.throwIfNotCalledOnMainThread(); if (mIsDestroyed) { return Collections.emptyList(); } return new ArrayList<PrinterId>(mTrackedPrinters); } /** * 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(); /** * Gets whether the session is destroyed. * * @return Whether the session is destroyed. * * @see #onDestroy() */ public final boolean isDestroyed() { PrintService.throwIfNotCalledOnMainThread(); return mIsDestroyed; } /** * 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; } void startPrinterDiscovery(List<PrinterId> priorityList) { if (!mIsDestroyed) { mIsDiscoveryStarted = true; sendOutOfDiscoveryPeriodPrinterChanges(); if (priorityList == null) { priorityList = Collections.emptyList(); } onStartPrinterDiscovery(priorityList); } } void stopPrinterDiscovery() { if (!mIsDestroyed) { mIsDiscoveryStarted = false; onStopPrinterDiscovery(); } } void validatePrinters(List<PrinterId> printerIds) { if (!mIsDestroyed && mObserver != null) { onValidatePrinters(printerIds); } } void startPrinterStateTracking(PrinterId printerId) { if (!mIsDestroyed && mObserver != null && !mTrackedPrinters.contains(printerId)) { mTrackedPrinters.add(printerId); onStartPrinterStateTracking(printerId); } } void stopPrinterStateTracking(PrinterId printerId) { if (!mIsDestroyed && mObserver != null && mTrackedPrinters.remove(printerId)) { onStopPrinterStateTracking(printerId); } } void destroy() { if (!mIsDestroyed) { mIsDestroyed = true; mIsDiscoveryStarted = false; mPrinters.clear(); mLastSentPrinters = null; mObserver = null; onDestroy(); } } }