core/java/android/os/IVibratorService.aidl \
core/java/android/service/notification/INotificationListener.aidl \
core/java/android/print/ILayoutResultCallback.aidl \
- core/java/android/print/IPrinterDiscoverySessionController.aidl \
- core/java/android/print/IPrinterDiscoverySessionObserver.aidl \
core/java/android/print/IPrintDocumentAdapter.aidl \
core/java/android/print/IPrintClient.aidl \
core/java/android/print/IPrintManager.aidl \
method public android.print.PrintAttributes getAttributes();
method public int getCopies();
method public int getId();
- method public java.lang.CharSequence getLabel();
+ method public java.lang.String getLabel();
method public android.print.PageRange[] getPages();
method public android.print.PrinterId getPrinterId();
method public int getState();
public final class PrintJob {
method public boolean cancel();
method public boolean complete();
- method public boolean fail(java.lang.CharSequence);
+ method public boolean fail(java.lang.String);
method public android.printservice.PrintDocument getDocument();
method public int getId();
method public android.print.PrintJobInfo getInfo();
}
public abstract class PrinterDiscoverySession {
- ctor public PrinterDiscoverySession(android.content.Context);
+ ctor public PrinterDiscoverySession();
method public final void addPrinters(java.util.List<android.print.PrinterInfo>);
- method public abstract void onClose();
- method public abstract void onOpen(java.util.List<android.print.PrinterId>);
+ method public final java.util.List<android.print.PrinterInfo> getPrinters();
+ method public final boolean isDestroyed();
+ method public final boolean isPrinterDiscoveryStarted();
+ method public abstract void onDestroy();
method public abstract void onRequestPrinterUpdate(android.print.PrinterId);
+ method public abstract void onStartPrinterDiscovery(java.util.List<android.print.PrinterId>);
+ method public abstract void onStopPrinterDiscovery();
method public final void removePrinters(java.util.List<android.print.PrinterId>);
method public final void updatePrinters(java.util.List<android.print.PrinterInfo>);
}
import android.content.ComponentName;
import android.os.ParcelFileDescriptor;
+import android.print.PrinterId;
import android.print.IPrintDocumentAdapter;
import android.print.IPrintClient;
import android.print.IPrintSpoolerClient;
void createPrintJob(String printJobName, in IPrintClient client,
in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes,
IPrintSpoolerCallbacks callback, int appId, int sequence);
- void setPrintJobState(int printJobId, int status, CharSequence error,
+ void setPrintJobState(int printJobId, int status, String error,
IPrintSpoolerCallbacks callback, int sequence);
void setPrintJobTag(int printJobId, String tag, IPrintSpoolerCallbacks callback,
int sequence);
void writePrintJobData(in ParcelFileDescriptor fd, int printJobId);
void setClient(IPrintSpoolerClient client);
+
+ // Printer discovery APIs
+ void onPrintersAdded(in List<PrinterInfo> printers);
+ void onPrintersRemoved(in List<PrinterId> printerIds);
+ void onPrintersUpdated(in List<PrinterInfo> printerIds);
}
package android.print;
import android.content.ComponentName;
-import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrinterId;
import android.print.PrintJobInfo;
* @hide
*/
oneway interface IPrintSpoolerClient {
- void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
void onPrintJobQueued(in PrintJobInfo printJob);
void onAllPrintJobsForServiceHandled(in ComponentName printService);
void onAllPrintJobsHandled();
+
+ // Printer discovery APIs
+ void createPrinterDiscoverySession();
+ void startPrinterDiscovery(in List<PrinterId> priorityList);
+ void stopPrinterDiscovery();
+ void requestPrinterUpdate(in PrinterId printerId);
+ void destroyPrinterDiscoverySession();
}
+++ /dev/null
-/*
- * 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.print;
-
-import android.print.PrinterId;
-
-/**
-* Interface for the controlling part of a printer discovery session.
- *
- * @hide
- */
-oneway interface IPrinterDiscoverySessionController {
- void open(in List<PrinterId> priorityList);
- void requestPrinterUpdate(in PrinterId printerId);
- void close();
-}
+++ /dev/null
-/*
- * 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.print;
-
-import android.print.IPrinterDiscoverySessionController;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-
-/**
- * Interface for the observing part of a printer discovery session.
- *
- * @hide
- */
-oneway interface IPrinterDiscoverySessionObserver {
- void setController(IPrinterDiscoverySessionController controller);
- void onPrintersAdded(in List<PrinterInfo> printers);
- void onPrintersRemoved(in List<PrinterId> printerIds);
- void onPrintersUpdated(in List<PrinterInfo> printerIds);
-}
private int mId;
/** The human readable print job label. */
- private CharSequence mLabel;
+ private String mLabel;
/** The unique id of the printer. */
private PrinterId mPrinterId;
private int mCopies;
/** Failure reason if this job failed. */
- private CharSequence mFailureReason;
+ private String mFailureReason;
/** The pages to print */
private PageRange[] mPageRanges;
private PrintJobInfo(Parcel parcel) {
mId = parcel.readInt();
- mLabel = parcel.readCharSequence();
+ mLabel = parcel.readString();
mPrinterId = parcel.readParcelable(null);
mPrinterName = parcel.readString();
mState = parcel.readInt();
mUserId = parcel.readInt();
mTag = parcel.readString();
mCopies = parcel.readInt();
- if (parcel.readInt() == 1) {
- mFailureReason = parcel.readCharSequence();
- }
+ mFailureReason = parcel.readString();
if (parcel.readInt() == 1) {
Parcelable[] parcelables = parcel.readParcelableArray(null);
mPageRanges = new PageRange[parcelables.length];
*
* @return The label.
*/
- public CharSequence getLabel() {
+ public String getLabel() {
return mLabel;
}
*
* @hide
*/
- public void setLabel(CharSequence label) {
+ public void setLabel(String label) {
mLabel = label;
}
*
* @hide
*/
- public CharSequence getFailureReason() {
+ public String getFailureReason() {
return mFailureReason;
}
*
* @hide
*/
- public void setFailureReason(CharSequence failureReason) {
+ public void setFailureReason(String failureReason) {
mFailureReason = failureReason;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mId);
- parcel.writeCharSequence(mLabel);
+ parcel.writeString(mLabel);
parcel.writeParcelable(mPrinterId, flags);
parcel.writeString(mPrinterName);
parcel.writeInt(mState);
parcel.writeInt(mUserId);
parcel.writeString(mTag);
parcel.writeInt(mCopies);
- if (mFailureReason != null) {
- parcel.writeInt(1);
- parcel.writeCharSequence(mFailureReason);
- } else {
- parcel.writeInt(0);
- }
+ parcel.writeString(mFailureReason);
if (mPageRanges != null) {
parcel.writeInt(1);
parcel.writeParcelableArray(mPageRanges, flags);
@Override
public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
+ if (info == null) {
+ throw new NullPointerException("document info cannot be null");
+ }
final ILayoutResultCallback callback;
synchronized (mLock) {
callback = mCallback;
clearLocked();
}
- if (info == null) {
- throw new IllegalArgumentException("info cannot be null");
- }
if (callback != null) {
try {
callback.onLayoutFinished(info, changed, mSequence);
/**
* Constructor.
*
- * @param prototype Prototype from which to start building.
+ * @param other Other info from which to start building.
*/
- public Builder(PrinterInfo prototype) {
- mPrototype = prototype;
+ public Builder(PrinterInfo other) {
+ mPrototype = new PrinterInfo();
+ mPrototype.copyFrom(other);
}
/**
/**
* Creates a new builder with the mandatory page info attributes.
*
- * @param pageSize The page size in pixels.
+ * @param pageSize The page size in points, <strong>not</strong> dips.
* @param pageNumber The page number.
* @param density The page density in DPI.
*/
package android.printservice;
-import android.print.IPrinterDiscoverySessionObserver;
+import android.print.PrinterId;
import android.print.PrintJobInfo;
import android.printservice.IPrintServiceClient;
void setClient(IPrintServiceClient client);
void requestCancelPrintJob(in PrintJobInfo printJobInfo);
void onPrintJobQueued(in PrintJobInfo printJobInfo);
- void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
+
+ void createPrinterDiscoverySession();
+ void startPrinterDiscovery(in List<PrinterId> priorityList);
+ void stopPrinterDiscovery();
+ void requestPrinterUpdate(in PrinterId printerId);
+ void destroyPrinterDiscoverySession();
}
interface IPrintServiceClient {
List<PrintJobInfo> getPrintJobInfos();
PrintJobInfo getPrintJobInfo(int printJobId);
- boolean setPrintJobState(int printJobId, int state, CharSequence error);
+ boolean setPrintJobState(int printJobId, int state, String error);
boolean setPrintJobTag(int printJobId, String tag);
oneway void writePrintJobData(in ParcelFileDescriptor fd, int printJobId);
+
+ void onPrintersAdded(in List<PrinterInfo> printers);
+ void onPrintersRemoved(in List<PrinterId> printerIds);
+ void onPrintersUpdated(in List<PrinterInfo> printers);
}
* This class represents a print job from the perspective of a print
* service. It provides APIs for observing the print job state and
* performing operations on the print job.
+ * <p>
+ * <strong>Note: </strong> All methods of this class must be executed on the main
+ * application thread.
+ * </p>
*/
public final class PrintJob {
* @return The id.
*/
public int getId() {
+ PrintService.throwIfNotCalledOnMainThread();
return mCachedInfo.getId();
}
* @return The print job info.
*/
public PrintJobInfo getInfo() {
+ PrintService.throwIfNotCalledOnMainThread();
if (isInImmutableState()) {
return mCachedInfo;
}
* @return The document.
*/
public PrintDocument getDocument() {
+ PrintService.throwIfNotCalledOnMainThread();
return mDocument;
}
* @see #cancel()
*/
public boolean isQueued() {
+ PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_QUEUED;
}
* @see #fail(CharSequence)
*/
public boolean isStarted() {
+ PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_STARTED;
}
* @see #complete()
*/
public boolean isCompleted() {
+ PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_COMPLETED;
}
* @see #fail(CharSequence)
*/
public boolean isFailed() {
+ PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_FAILED;
}
* @see #cancel()
*/
public boolean isCancelled() {
+ PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_FAILED;
}
* @see #isQueued()
*/
public boolean start() {
+ PrintService.throwIfNotCalledOnMainThread();
if (isQueued()) {
return setState(PrintJobInfo.STATE_STARTED, null);
}
* @see #isStarted()
*/
public boolean complete() {
+ PrintService.throwIfNotCalledOnMainThread();
if (isStarted()) {
return setState(PrintJobInfo.STATE_COMPLETED, null);
}
* @see #isQueued()
* @see #isStarted()
*/
- public boolean fail(CharSequence error) {
+ public boolean fail(String error) {
+ PrintService.throwIfNotCalledOnMainThread();
if (isQueued() || isStarted()) {
return setState(PrintJobInfo.STATE_FAILED, error);
}
* @see #isQueued()
*/
public boolean cancel() {
+ PrintService.throwIfNotCalledOnMainThread();
if (isQueued() || isStarted()) {
return setState(PrintJobInfo.STATE_CANCELED, null);
}
* @return True if the tag was set, false otherwise.
*/
public boolean setTag(String tag) {
+ PrintService.throwIfNotCalledOnMainThread();
if (isInImmutableState()) {
return false;
}
|| state == PrintJobInfo.STATE_CANCELED;
}
- private boolean setState(int state, CharSequence error) {
+ private boolean setState(int state, String error) {
try {
if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state, error)) {
// Best effort - update the state of the cached info since
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintJobInfo;
import android.print.PrinterId;
import android.util.Log;
* {@link #SERVICE_META_DATA} and <code><{@link android.R.styleable#PrintService
* print-service}></code>.
* </p>
+ * <p>
+ * <strong>Note: </strong> All callbacks in this class are executed on the main
+ * application thread. You should also invoke any method of this class on the main
+ * application thread.
+ * </p>
*/
public abstract class PrintService extends Service {
*/
public static final String SERVICE_META_DATA = "android.printservice";
- private final Object mLock = new Object();
-
private Handler mHandler;
private IPrintServiceClient mClient;
private int mLastSessionId = -1;
+ private PrinterDiscoverySession mDiscoverySession;
+
@Override
protected final void attachBaseContext(Context base) {
super.attachBaseContext(base);
* @see PrintJob#isStarted() PrintJob.isStarted()
*/
public final List<PrintJob> getActivePrintJobs() {
- final IPrintServiceClient client;
- synchronized (mLock) {
- client = mClient;
- }
- if (client == null) {
+ throwIfNotCalledOnMainThread();
+ if (mClient == null) {
return Collections.emptyList();
}
try {
List<PrintJob> printJobs = null;
- List<PrintJobInfo> printJobInfos = client.getPrintJobInfos();
+ List<PrintJobInfo> printJobInfos = mClient.getPrintJobInfos();
if (printJobInfos != null) {
final int printJobInfoCount = printJobInfos.size();
printJobs = new ArrayList<PrintJob>(printJobInfoCount);
for (int i = 0; i < printJobInfoCount; i++) {
- printJobs.add(new PrintJob(printJobInfos.get(i), client));
+ printJobs.add(new PrintJob(printJobInfos.get(i), mClient));
}
}
if (printJobs != null) {
* @return Global printer id.
*/
public final PrinterId generatePrinterId(String localId) {
+ throwIfNotCalledOnMainThread();
return new PrinterId(new ComponentName(getPackageName(),
getClass().getName()), localId);
}
+ static void throwIfNotCalledOnMainThread() {
+ if (!Looper.getMainLooper().isCurrentThread()) {
+ throw new IllegalAccessError("must be called from the main thread");
+ }
+ }
+
@Override
public final IBinder onBind(Intent intent) {
return new IPrintService.Stub() {
@Override
- public void setClient(IPrintServiceClient client) {
- mHandler.obtainMessage(ServiceHandler.MSG_SET_CLEINT, client)
- .sendToTarget();
+ public void createPrinterDiscoverySession() {
+ mHandler.sendEmptyMessage(ServiceHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
+ }
+
+ @Override
+ public void destroyPrinterDiscoverySession() {
+ mHandler.sendEmptyMessage(ServiceHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
+ }
+
+ public void startPrinterDiscovery(List<PrinterId> priorityList) {
+ mHandler.obtainMessage(ServiceHandler.MSG_START_PRINTER_DISCOVERY,
+ priorityList).sendToTarget();
}
@Override
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
- mHandler.obtainMessage(ServiceHandler.MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION,
- observer).sendToTarget();
+ public void stopPrinterDiscovery() {
+ mHandler.sendEmptyMessage(ServiceHandler.MSG_STOP_PRINTER_DISCOVERY);
+ }
+
+ @Override
+ public void requestPrinterUpdate(PrinterId printerId) {
+ mHandler.obtainMessage(ServiceHandler.MSG_REQUEST_PRINTER_UPDATE,
+ printerId).sendToTarget();
+ }
+
+ @Override
+ public void setClient(IPrintServiceClient client) {
+ mHandler.obtainMessage(ServiceHandler.MSG_SET_CLEINT, client)
+ .sendToTarget();
}
@Override
}
private final class ServiceHandler extends Handler {
- public static final int MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION = 1;
- public static final int MSG_ON_PRINTJOB_QUEUED = 2;
- public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 3;
- public static final int MSG_SET_CLEINT = 4;
+ 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_PRINTJOB_QUEUED = 6;
+ public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 7;
+ public static final int MSG_SET_CLEINT = 8;
public ServiceHandler(Looper looper) {
super(looper, null, true);
}
@Override
+ @SuppressWarnings("unchecked")
public void handleMessage(Message message) {
final int action = message.what;
switch (action) {
- case MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION: {
- IPrinterDiscoverySessionObserver observer =
- (IPrinterDiscoverySessionObserver) message.obj;
+ case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
PrinterDiscoverySession session = onCreatePrinterDiscoverySession();
if (session == null) {
throw new NullPointerException("session cannot be null");
}
- synchronized (mLock) {
- if (session.getId() == mLastSessionId) {
- throw new IllegalStateException("cannot reuse sessions");
- }
- mLastSessionId = session.getId();
+ if (session.getId() == mLastSessionId) {
+ throw new IllegalStateException("cannot reuse session instances");
+ }
+ mDiscoverySession = session;
+ mLastSessionId = session.getId();
+ session.setObserver(mClient);
+ } break;
+
+ case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
+ if (mDiscoverySession != null) {
+ mDiscoverySession.destroy();
+ mDiscoverySession = null;
+ }
+ } break;
+
+ case MSG_START_PRINTER_DISCOVERY: {
+ if (mDiscoverySession != null) {
+ List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj;
+ mDiscoverySession.startPrinterDiscovery(priorityList);
+ }
+ } break;
+
+ case MSG_STOP_PRINTER_DISCOVERY: {
+ if (mDiscoverySession != null) {
+ mDiscoverySession.stopPrinterDiscovery();
+ }
+ } break;
+
+ case MSG_REQUEST_PRINTER_UPDATE: {
+ if (mDiscoverySession != null) {
+ PrinterId printerId = (PrinterId) message.obj;
+ mDiscoverySession.requestPrinterUpdate(printerId);
}
- session.setObserver(observer);
} break;
case MSG_ON_REQUEST_CANCEL_PRINTJOB: {
} break;
case MSG_SET_CLEINT: {
- IPrintServiceClient client = (IPrintServiceClient) message.obj;
- synchronized (mLock) {
- mClient = client;
- }
- if (client != null) {
+ mClient = (IPrintServiceClient) message.obj;
+ if (mClient != null) {
onConnected();
} else {
onDisconnected();
- }
+ }
} break;
default: {
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;
/**
* for adding discovered printers, removing already added printers that
* disappeared, and updating already added printers.
* <p>
- * 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.
+ * </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#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 <strong>not</strong> persisted across sessions.
* </p>
* <p>
* 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.
* </p>
* <p>
- * 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.
+ * <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 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<PrinterId, PrinterInfo> mPrinters =
+ new ArrayMap<PrinterId, PrinterInfo>();
- private final int mId;
+ private ArrayMap<PrinterId, PrinterInfo> 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());
}
}
}
/**
+ * 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, i.e. after the {@link #onDestroy()} callback, will be ignored.
+ * </p>
+ *
+ * @return The printers.
+ *
+ * @see #addPrinters(List)
+ * @see #removePrinters(List)
+ * @see #updatePrinters(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 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.
* <p>
- * <strong>Note: </strong> 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.
+ * <strong>Note: </strong> Calls to this method after the session is
+ * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
* </p>
*
* @param printers The printers to add.
*
* @see #removePrinters(List)
* @see #updatePrinters(List)
+ * @see #getPrinters()
+ * @see #isDestroyed()
*/
public final void addPrinters(List<PrinterInfo> 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<PrinterInfo> addedPrinters = new ArrayList<PrinterInfo>();
+ 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<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);
+ }
+ }
+ }
+ }
+
+ private static void sendAddedPrinters(IPrintServiceClient observer,
+ List<PrinterInfo> 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<PrinterInfo> 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.
* <p>
- * <strong>Note: </strong> 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.
+ * <strong>Note: </strong> Calls to this method after the session is
+ * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
* </p>
*
* @param printerIds The ids of the removed printers.
*
* @see #addPrinters(List)
* @see #updatePrinters(List)
+ * @see #getPrinters()
+ * @see #isDestroyed()
*/
public final void removePrinters(List<PrinterId> 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<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()) {
+ 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<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 static void sendRemovedPrinters(IPrintServiceClient observer,
+ List<PrinterId> 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<PrinterId> 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.
* <p>
- * <strong>Note: </strong> 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.
+ * <strong>Note: </strong> Calls to this method after the session is
+ * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
* </p>
*
* @param printers The printers to update.
*
* @see #addPrinters(List)
* @see #removePrinters(List)
+ * @see #getPrinters()
+ * @see #isDestroyed()
*/
public final void updatePrinters(List<PrinterInfo> 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<PrinterInfo> updatedPrinters = new ArrayList<PrinterInfo>();
+ 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<PrinterId, PrinterInfo>(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<PrinterInfo> 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<PrinterInfo> 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<PrinterInfo> addedPrinters = null;
+ List<PrinterInfo> updatedPrinters = null;
+ List<PrinterId> 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<PrinterInfo>();
+ }
+ updatedPrinters.add(printer);
+ }
+ } else {
+ if (addedPrinters == null) {
+ addedPrinters = new ArrayList<PrinterInfo>();
+ }
+ 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<PrinterId>();
+ }
+ 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.
* <p>
- * 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.
* </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.
+ * <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.
* </p>
*
- * @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<PrinterId> priorityList);
+ public abstract void onStartPrinterDiscovery(List<PrinterId> 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
*/
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<PrinterId> priorityList = (List<PrinterId>) 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<PrinterId> 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<PrinterDiscoverySession> mWeakSession;
-
- public PrinterDiscoverySessionController(PrinterDiscoverySession session) {
- mWeakSession = new WeakReference<PrinterDiscoverySession>(session);
- }
-
- @Override
- public void open(List<PrinterId> 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();
}
- };
+ }
}
mH.sendMessage(msg);
}
-
+
+ public void sendMessageDelayed(Message msg, long delayMillis) {
+ mH.sendMessageDelayed(msg, delayMillis);
+ }
+
public boolean hasMessages(int what) {
return mH.hasMessages(what);
}
-->
<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">
</activity>
<activity
- android:name=".ChoosePrinterActivity"
- android:exported="false"
- android:theme="@android:style/Theme.Holo.Light">
+ android:name=".SelectPrinterActivity"
+ android:label="@string/all_printers_label"
+ android:theme="@style/SelectPrinterActivityTheme"
+ android:exported="false">
</activity>
<receiver
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>
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
+ 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,
limitations under the License.
-->
-<ListView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/list_view"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical">
-</ListView>
+ 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
-->
<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"
android:actionViewClass="android.widget.SearchView"
android:showAsAction="ifRoom"
android:alphabeticShortcut="f"
- android:imeOptions="actionSearch">
+ 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>
<!-- 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>
- <!-- Choose printer activity -->
+ <!-- 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] -->
<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>
+++ /dev/null
-/*
- * 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.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.PrinterId;
-import android.print.PrinterInfo;
-import android.util.ArraySet;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-/**
- * This class is responsible to provide the available printers.
- * It starts and stops printer discovery and manages the returned
- * printers.
- */
-public class AvailablePrinterProvider extends DataProvider<PrinterInfo>
- implements DataLoader {
- private static final String LOG_TAG = "AvailablePrinterProvider";
-
- private final Set<PrinterId> mPrinteIdsSet = new ArraySet<PrinterId>();
-
- private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
-
- private final List<PrinterId> mPriorityList;
-
- private PrinterDiscoverySession mDiscoverySession;
-
- public AvailablePrinterProvider(Context context, List<PrinterId> priorityList) {
- mDiscoverySession = new PrinterDiscoverySession(context.getMainLooper());
- mPriorityList = priorityList;
- }
-
- @Override
- public void startLoadData() {
- mDiscoverySession.open();
- }
-
- @Override
- public void stopLoadData() {
- mDiscoverySession.close();
- }
-
- @Override
- public int getItemCount() {
- return mPrinters.size();
- }
-
- @Override
- public int getItemIndex(PrinterInfo printer) {
- return mPrinters.indexOf(printer);
- }
-
- @Override
- public PrinterInfo getItemAt(int index) {
- return mPrinters.get(index);
- }
-
- public void refreshItem(int index) {
- PrinterInfo printer = getItemAt(index);
- mDiscoverySession.requestPrinterUpdate(printer.getId());
- }
-
- private void addPrinters(List<PrinterInfo> printers) {
- boolean addedPrinters = false;
-
- final int addedPrinterCount = printers.size();
- for (int i = 0; i < addedPrinterCount; i++) {
- PrinterInfo addedPrinter = printers.get(i);
- if (mPrinteIdsSet.add(addedPrinter.getId())) {
- mPrinters.add(addedPrinter);
- addedPrinters = true;
- }
- }
-
- if (addedPrinters) {
- notifyChanged();
- }
- }
-
- private void updatePrinters(List<PrinterInfo> printers) {
- boolean updatedPrinters = false;
-
- final int updatedPrinterCount = printers.size();
- for (int i = 0; i < updatedPrinterCount; i++) {
- PrinterInfo updatedPrinter = printers.get(i);
- if (mPrinteIdsSet.contains(updatedPrinter.getId())) {
- final int oldPrinterCount = mPrinters.size();
- for (int j = 0; j < oldPrinterCount; j++) {
- PrinterInfo oldPrinter = mPrinters.get(j);
- if (updatedPrinter.getId().equals(oldPrinter.getId())) {
- mPrinters.set(j, updatedPrinter);
- updatedPrinters = true;
- break;
- }
- }
- }
- }
-
- if (updatedPrinters) {
- notifyChanged();
- }
- }
-
- private void removePrinters(List<PrinterId> printers) {
- boolean removedPrinters = false;
-
- final int removedPrinterCount = printers.size();
- for (int i = 0; i < removedPrinterCount; i++) {
- PrinterId removedPrinter = printers.get(i);
- if (mPrinteIdsSet.contains(removedPrinter)) {
- mPrinteIdsSet.remove(removedPrinter);
- Iterator<PrinterInfo> iterator = mPrinters.iterator();
- while (iterator.hasNext()) {
- PrinterInfo oldPrinter = iterator.next();
- if (removedPrinter.equals(oldPrinter.getId())) {
- iterator.remove();
- break;
- }
- }
- }
- }
-
- if (removedPrinters) {
- notifyChanged();
- }
- }
-
- private final class PrinterDiscoverySession {
-
- private final Handler mHandler;
-
- private final IPrinterDiscoverySessionObserver mObserver;
-
- private IPrinterDiscoverySessionController mController;
-
- public PrinterDiscoverySession(Looper looper) {
- mHandler = new SessionHandler(looper);
- mObserver = new PrinterDiscoverySessionObserver(this);
- }
-
- public void open() {
- PrintSpooler.peekInstance().createPrinterDiscoverySession(
- mObserver);
- }
-
- public void close() {
- if (mController != null) {
- try {
- mController.close();
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error closing printer discovery session", re);
- } finally {
- mController = null;
- }
- }
- }
-
- public void requestPrinterUpdate(PrinterId printerId) {
- if (mController != null) {
- try {
- mController.requestPrinterUpdate(printerId);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error requesting printer udpdate", re);
- }
- }
- }
-
- private final class SessionHandler extends Handler {
- public static final int MSG_SET_CONTROLLER = 1;
- public static final int MSG_ON_PRINTERS_ADDED = 2;
- public static final int MSG_ON_PRINTERS_REMOVED = 3;
- public static final int MSG_ON_PRINTERS_UPDATED = 4;
-
- public SessionHandler(Looper looper) {
- super(looper, null, false);
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_SET_CONTROLLER: {
- mController = (IPrinterDiscoverySessionController) message.obj;
- try {
- mController.open(mPriorityList);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Error starting printer discovery");
- }
- } break;
-
- case MSG_ON_PRINTERS_ADDED: {
- List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
- addPrinters(printers);
- } break;
-
- case MSG_ON_PRINTERS_REMOVED: {
- List<PrinterId> printers = (List<PrinterId>) message.obj;
- removePrinters(printers);
- } break;
-
- case MSG_ON_PRINTERS_UPDATED: {
- List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
- updatePrinters(printers);
- } break;
- };
- }
- }
- }
-
- private static final class PrinterDiscoverySessionObserver
- extends IPrinterDiscoverySessionObserver.Stub {
-
- private final WeakReference<PrinterDiscoverySession> mWeakSession;
-
- public PrinterDiscoverySessionObserver(PrinterDiscoverySession session) {
- mWeakSession = new WeakReference<PrinterDiscoverySession>(session);
- }
-
- @Override
- public void setController(IPrinterDiscoverySessionController controller) {
- PrinterDiscoverySession sesison = mWeakSession.get();
- if (sesison != null) {
- sesison.mHandler.obtainMessage(
- PrinterDiscoverySession.SessionHandler.MSG_SET_CONTROLLER,
- controller).sendToTarget();
- }
- }
-
- @Override
- public void onPrintersAdded(List<PrinterInfo> printers) {
- PrinterDiscoverySession sesison = mWeakSession.get();
- if (sesison != null) {
- sesison.mHandler.obtainMessage(
- PrinterDiscoverySession.SessionHandler.MSG_ON_PRINTERS_ADDED,
- printers).sendToTarget();
- }
- }
-
- @Override
- public void onPrintersRemoved(List<PrinterId> printers) {
- PrinterDiscoverySession session = mWeakSession.get();
- if (session != null) {
- session.mHandler.obtainMessage(
- PrinterDiscoverySession.SessionHandler.MSG_ON_PRINTERS_REMOVED,
- printers).sendToTarget();
- }
- }
-
- @Override
- public void onPrintersUpdated(List<PrinterInfo> printers) {
- PrinterDiscoverySession session = mWeakSession.get();
- if (session != null) {
- session.mHandler.obtainMessage(
- PrinterDiscoverySession.SessionHandler.MSG_ON_PRINTERS_UPDATED,
- printers).sendToTarget();
- }
- }
- };
-}
+++ /dev/null
-/*
- * 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;
-
-/**
- * This is the contract for a class that know how to load data.
- */
-public interface DataLoader {
-
- /**
- * Requests to start loading data.
- */
- public void startLoadData();
-
- /**
- * Requests to stop loading data.
- */
- public void stopLoadData();
-}
+++ /dev/null
-/*
- * 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.database.DataSetObservable;
-
-/**
- * This is the simple contract for data providers.
- *
- * @param <T> The type of the providers data.
- */
-public abstract class DataProvider<T> extends DataSetObservable {
-
- /**
- * Gets the number of items.
- *
- * @return The item count.
- */
- public abstract int getItemCount();
-
- /**
- * Gets the index of an item.
- *
- * @param item The item.
- * @return The item index.
- */
- public abstract int getItemIndex(T item);
-
- /**
- * Gets an item at a given position.
- *
- * @param index The position.
- * @return The item.
- */
- public abstract T getItemAt(int index);
-}
+++ /dev/null
-/*
- * 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.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 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.List;
-import java.util.Map;
-
-/**
- * This class provides the favorite printers based on past usage.
- */
-final class FavoritePrinterProvider extends DataProvider<PrinterInfo> implements DataLoader {
-
- private static final String LOG_TAG = "FavoritePrinterProvider";
-
- private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
-
- private static final int MAX_HISTORY_LENGTH = 50;
-
- private static final double WEIGHT_DECAY_COEFFICIENT = 0.95f;
-
- private final List<PrinterRecord> mHistoricalPrinters = new ArrayList<PrinterRecord>();
-
- private final List<PrinterRecord> mFavoritePrinters = new ArrayList<PrinterRecord>();
-
- private final PersistenceManager mPersistenceManager;
-
- public FavoritePrinterProvider(Context context) {
- mPersistenceManager = new PersistenceManager(context);
- }
-
- public void addPrinter(PrinterInfo printer) {
- addPrinterInternal(printer);
- computeFavoritePrinters();
- mPersistenceManager.writeState();
- }
-
- @Override
- public int getItemCount() {
- return mFavoritePrinters.size();
- }
-
- @Override
- public PrinterInfo getItemAt(int index) {
- return mFavoritePrinters.get(index).printer;
- }
-
- @Override
- public int getItemIndex(PrinterInfo printer) {
- return mFavoritePrinters.indexOf(printer);
- }
-
- @Override
- public void startLoadData() {
- mPersistenceManager.readStateLocked();
- computeFavoritePrinters();
- }
-
- @Override
- public void stopLoadData() {
- /* do nothing */
- }
-
- private void addPrinterInternal(PrinterInfo printer) {
- if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
- mHistoricalPrinters.remove(0);
- }
- mHistoricalPrinters.add(new PrinterRecord(printer));
- }
-
- private void computeFavoritePrinters() {
- Map<PrinterId, PrinterRecord> recordMap =
- new ArrayMap<PrinterId, PrinterRecord>();
-
- // Recompute the weights.
- float currentWeight = 1.0f;
- final int printerCount = mHistoricalPrinters.size();
- for (int i = printerCount - 1; i >= 0; i--) {
- PrinterRecord record = mHistoricalPrinters.get(i);
- record.weight = currentWeight;
- // Aggregate weight for the same printer
- PrinterRecord oldRecord = recordMap.put(record.printer.getId(), record);
- if (oldRecord != null) {
- record.weight += oldRecord.weight;
- }
- currentWeight *= WEIGHT_DECAY_COEFFICIENT;
- }
-
- // Copy the unique printer records with computed weights.
- mFavoritePrinters.addAll(recordMap.values());
-
- // Soft the favorite printers.
- Collections.sort(mFavoritePrinters);
- }
-
- 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);
- }
- }
-
- 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 PersistenceManager(Context context) {
- mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
- PERSIST_FILE_NAME));
- }
-
- @SuppressWarnings("unchecked")
- public void writeState() {
-
- new AsyncTask<List<PrinterRecord>, Void, Void>() {
- @Override
- protected Void doInBackground(List<PrinterRecord>... printers) {
- doWriteState(printers[0]);
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- notifyChanged();
- }
-
- }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
- new ArrayList<PrinterRecord>(mHistoricalPrinters));
- }
-
- private void doWriteState(List<PrinterRecord> 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 = printerCount - 1; i >= 0; i--) {
- PrinterInfo printer = printers.get(i).printer;
-
- 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);
- }
- }
-
- public void readStateLocked() {
- FileInputStream in = null;
- try {
- in = mStatePersistFile.openRead();
- } catch (FileNotFoundException e) {
- Log.i(LOG_TAG, "No existing printer history.");
- 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);
- }
- notifyChanged();
- }
-
- private void parseState(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS);
- parser.next();
-
- while (parsePrinter(parser)) {
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);
-
- // We were reading the new records first and appended them first,
- // hence the historical list is in a reversed order, so fix that.
- Collections.reverse(mHistoricalPrinters);
- }
-
- private boolean parsePrinter(XmlPullParser parser)
- 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();
-
- addPrinterInternal(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;
- }
- }
-}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+ }
+}
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;
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;
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;
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;
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");
private Document mDocument;
private PrintController mController;
- private AvailablePrinterProvider mAvailablePrinters;
-
- private FavoritePrinterProvider mFavoritePrinters;
-
private int mPrintJobId;
private IBinder mIPrintDocumentAdapter;
@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();
mCurrPrintAttributes.copyFrom(attributes);
}
- // TODO: Use history
- mAvailablePrinters = new AvailablePrinterProvider(this, null);
- mFavoritePrinters = new FavoritePrinterProvider(this);
+ 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),
- PrintSpooler.peekInstance().generateFileForPrintJob(mPrintJobId)));
+ PrintSpoolerService.peekInstance().generateFileForPrintJob(mPrintJobId)));
try {
mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
}
@Override
- protected void onResume() {
- super.onResume();
- // TODO: Polish this
- if (!mEditor.isPrintConfirmed()) {
- mAvailablePrinters.startLoadData();
- mFavoritePrinters.startLoadData();
- }
- }
-
- @Override
- protected void onPause() {
- // TODO: Polish this
- if (!mEditor.isPrintConfirmed()) {
- mAvailablePrinters.stopLoadData();
- mFavoritePrinters.stopLoadData();
- }
- super.onPause();
- }
-
- @Override
protected void onDestroy() {
// We can safely do the work in here since at this point
// the system is bound to our (spooler) process which
mController.finish();
}
if (mEditor.isPrintConfirmed() && mController.isFinished()) {
- PrintSpooler.peekInstance().setPrintJobState(mPrintJobId,
+ PrintSpoolerService.peekInstance().setPrintJobState(mPrintJobId,
PrintJobInfo.STATE_QUEUED, null);
} else {
- PrintSpooler.peekInstance().setPrintJobState(mPrintJobId,
+ PrintSpoolerService.peekInstance().setPrintJobState(mPrintJobId,
PrintJobInfo.STATE_CANCELED, null);
}
mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
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 {
- PrintSpooler.peekInstance().setPrintJobAttributesNoPersistence(mPrintJobId,
- mCurrPrintAttributes);
+ PrintSpoolerService.peekInstance().setPrintJobAttributesNoPersistence(
+ mPrintJobId, mCurrPrintAttributes);
mMetadata.putBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW,
!mEditor.isPrintConfirmed());
final boolean infoChanged = !info.equals(mDocument.info);
if (infoChanged) {
mDocument.info = info;
- PrintSpooler.peekInstance().setPrintJobPrintDocumentInfoNoPersistence(
+ PrintSpoolerService.peekInstance().setPrintJobPrintDocumentInfoNoPersistence(
mPrintJobId, info);
}
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.
- PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+ 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.
- PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+ PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
mRequestedPages);
} else if (PageRangeUtils.contains(mDocument.pages, mRequestedPages)) {
// We requested specific pages and got more but not all pages.
final int offset = mDocument.pages[0].getStart() - pages[0].getStart();
PageRange[] offsetPages = Arrays.copyOf(mDocument.pages, mDocument.pages.length);
PageRangeUtils.offsetStart(offsetPages, offset);
- PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+ PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
offsetPages);
} else if (Arrays.equals(mRequestedPages, ALL_PAGES_ARRAY)
&& mDocument.pages.length == 1 && mDocument.pages[0].getStart() == 0
// 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.
- PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+ PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
mDocument.pages);
} else {
// We did not get the pages we requested, then the application
}
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();
+ }
}
}
}
}
+ @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 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() {
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();
PrinterInfo printer = (PrinterInfo) mDestinationSpinnerAdapter
.getItem(position);
if (printer != null) {
- PrintSpooler.peekInstance().setPrintJobPrinterNoPersistence(
+ PrintSpoolerService.peekInstance().setPrintJobPrinterNoPersistence(
mPrintJobId, printer);
PrinterCapabilitiesInfo capabilities = printer.getCapabilities();
if (capabilities == null) {
List<PrinterId> printerIds = new ArrayList<PrinterId>();
printerIds.add(printer.getId());
- final int index = mAvailablePrinters.getItemIndex(printer);
- mAvailablePrinters.refreshItem(index);
+ 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 {
}
}
}
+
+ // 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) {
}
mCopiesEditText.setError(null);
- PrintSpooler.peekInstance().setPrintJobCopiesNoPersistence(mPrintJobId, copies);
+ PrintSpoolerService.peekInstance().setPrintJobCopiesNoPersistence(
+ mPrintJobId, copies);
updateUi();
if (hadErrors && !hasErrors() && printAttributesChanged()) {
private boolean mWaitingForPrinterCapabilities;
- public Editor() {
- // Content container
- mContentContainer = findViewById(R.id.content_container);
-
- // Copies
- mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
- mCopiesEditText.setText(String.valueOf(MIN_COPIES));
- PrintSpooler.peekInstance().setPrintJobCopiesNoPersistence(mPrintJobId, MIN_COPIES);
- mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
- mCopiesEditText.selectAll();
+ private int mCurrentUi = UI_NONE;
+ public Editor() {
// Destination.
- mDestinationSpinnerAdapter = new DestinationAdapter(mAvailablePrinters);
+ mDestinationSpinnerAdapter = new DestinationAdapter();
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
if (mWaitingForPrinterCapabilities) {
mWaitingForPrinterCapabilities = false;
PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
- if (printer != null) {
- if (printer.getCapabilities() != null) {
- final int selectedPosition =
- mDestinationSpinner.getSelectedItemPosition();
- mOnItemSelectedListener.onItemSelected(mDestinationSpinner, null,
- selectedPosition, selectedPosition);
- }
+ if (printer != null && printer.getCapabilities() != null) {
+ mOnItemSelectedListener.onItemSelected(mDestinationSpinner, null,
+ selectedPosition, selectedPosition);
}
}
updateUi();
updateUi();
}
});
- mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
- mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
- mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// 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);
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();
- 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) {
}
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;
+
+ 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();
+ }
+ }
+ });
+ }
- // Wire the cancel action.
- Button cancelButton = (Button) contentGenerating.findViewById(R.id.cancel_button);
+ private void registerCancelButtonClickListener() {
+ Button cancelButton = (Button) findViewById(R.id.cancel_button);
cancelButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
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.
// 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();
+ }
+ });
}
});
}
public void initialize() {
mEditorState = EDITOR_STATE_INITIALIZED;
- if (mDestinationSpinner.getSelectedItemPosition() != AdapterView.INVALID_POSITION) {
- mIgnoreNextDestinationChange = true;
- mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
- }
}
public boolean isCancelled() {
public void confirmPrint() {
mEditorState = EDITOR_STATE_CONFIRMED_PRINT;
- PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
- if (printer != null) {
- mFavoritePrinters.addPrinter(printer);
- }
- updateUi();
+ showUi(UI_GENERATING_PRINT_JOB, null);
}
public boolean isPreviewConfirmed() {
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);
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 || ((PrinterInfo) mDestinationSpinnerAdapter.getItem(
- selectedIndex)).getCapabilities() == null) {
-
- // Destination
- mDestinationSpinner.setEnabled(false);
-
+ if (!allOptionsEnabled) {
String minCopiesString = String.valueOf(MIN_COPIES);
if (!TextUtils.equals(mCopiesEditText.getText(), minCopiesString)) {
mIgnoreNextCopiesChange = true;
PrinterCapabilitiesInfo capabilities = printer.getCapabilities();
printer.getCapabilities().getDefaults(defaultAttributes);
- // Destination
- mDestinationSpinner.setEnabled(true);
-
// Copies
mCopiesEditText.setEnabled(true);
}
}
}
+ mMediaSizeSpinner.setEnabled(true);
// Color mode.
final int colorModes = capabilities.getColorModes();
}
}
}
+ mColorModeSpinner.setEnabled(true);
// Orientation.
final int orientations = capabilities.getOrientations();
}
}
}
+ 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,
mRangeEditText.setEnabled(false);
mRangeEditText.setVisibility(View.INVISIBLE);
}
+ mRangeOptionsSpinner.setEnabled(true);
// Print/Print preview
if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
mCopiesEditText.selectAll();
mCopiesEditText.requestFocus();
}
+ mCopiesEditText.setEnabled(true);
}
}
}
}
- private final class DestinationAdapter extends BaseAdapter {
- private final AvailablePrinterProvider mProvider;
-
- private final DataSetObserver mObserver = new DataSetObserver() {
- @Override
- public void onChanged() {
- notifyDataSetChanged();
- }
+ private final class DestinationAdapter extends BaseAdapter
+ implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>{
+ private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
- @Override
- public void onInvalidated() {
- notifyDataSetInvalidated();
- }
- };
+ public final PrinterInfo mFakePdfPrinter;
- public DestinationAdapter(AvailablePrinterProvider provider) {
- mProvider = provider;
- mProvider.registerObserver(mObserver);
+ public DestinationAdapter() {
+ getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
+ mFakePdfPrinter = createFakePdfPrinter();
}
@Override
public int getCount() {
- return mProvider.getItemCount();
+ return Math.max(Math.min(mPrinters.size(), DEST_ADAPTER_MAX_ITEM_COUNT),
+ DEST_ADAPTER_MIN_ITEM_COUNT);
}
@Override
public Object getItem(int position) {
- return mProvider.getItemAt(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;
}
R.layout.spinner_dropdown_item, parent, false);
}
- PrinterInfo printerInfo = mProvider.getItemAt(position);
- TextView title = (TextView) convertView.findViewById(R.id.title);
- title.setText(printerInfo.getName());
+ CharSequence title = null;
+ CharSequence subtitle = null;
- 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 */
+ 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 */
+ }
+ }
+ }
+
+ 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(PrintJobConfigActivity.this);
+ }
+ return null;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<PrinterInfo>> loader,
+ List<PrinterInfo> printers) {
+ mPrinters.clear();
+ mPrinters.addAll(printers);
+ notifyDataSetChanged();
+ }
+
+ @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();
+ }
}
}
+++ /dev/null
-/*
- * 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_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(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_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;
- }
- }
-}
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;
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;
/**
*/
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
@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);
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);
}
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,
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);
}
@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);
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);
}
@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;
}
} 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) {
} 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);
+ }
}
package com.android.printspooler;
import android.app.Activity;
+import android.content.Intent;
import android.os.Bundle;
+import android.print.PrinterId;
-public class ChoosePrinterActivity extends Activity {
+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 onCreate(Bundle bundle) {
- setContentView(R.layout.choose_printer_activity);
+ public void onPrinterSelected(PrinterId printer) {
+ Intent intent = new Intent();
+ intent.putExtra(PrintJobConfigActivity.INTENT_EXTRA_PRINTER_ID, printer);
+ setResult(RESULT_OK, intent);
+ finish();
}
}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.print.IPrinterDiscoverySessionController;
-import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintJobInfo;
import android.print.PrintManager;
import android.print.PrinterId;
private boolean mDestroyed;
+ private boolean mAllPrintJobsHandled;
+
+ private boolean mHasPrinterDiscoverySession;
+
public RemotePrintService(Context context, ComponentName componentName, int userId,
RemotePrintSpooler spooler) {
mContext = context;
private void handleDestroy() {
throwIfDestroyed();
ensureUnbound();
+ mAllPrintJobsHandled = false;
+ mHasPrinterDiscoverySession = false;
mDestroyed = true;
}
}
private void handleBinderDied() {
+ mAllPrintJobsHandled = false;
+ mHasPrinterDiscoverySession = false;
mPendingCommands.clear();
ensureUnbound();
}
private void handleOnAllPrintJobsHandled() {
throwIfDestroyed();
+
+ mAllPrintJobsHandled = true;
+
if (isBound()) {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnAllPrintJobsHandled()");
}
- // If bound and all the work is completed, then unbind.
- ensureUnbound();
+
+ // If the service has a printer discovery session
+ // created we should not disconnect from it just yet.
+ if (!mHasPrinterDiscoverySession) {
+ ensureUnbound();
+ }
}
}
private void handleOnPrintJobQueued(final PrintJobInfo printJob) {
throwIfDestroyed();
+
+ mAllPrintJobsHandled = false;
+
if (!isBound()) {
ensureBound();
mPendingCommands.add(new Runnable() {
}
}
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
- mHandler.obtainMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION,
- observer).sendToTarget();
+ public void createPrinterDiscoverySession() {
+ mHandler.sendEmptyMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
}
- private void handleCreatePrinterDiscoverySession(
- final IPrinterDiscoverySessionObserver observer) {
+ private void handleCreatePrinterDiscoverySession() {
throwIfDestroyed();
if (!isBound()) {
ensureBound();
mPendingCommands.add(new Runnable() {
@Override
public void run() {
- handleCreatePrinterDiscoverySession(observer);
+ handleCreatePrinterDiscoverySession();
}
});
} else {
Slog.i(LOG_TAG, "[user: " + mUserId + "] createPrinterDiscoverySession()");
}
try {
- mPrintService.createPrinterDiscoverySession(observer);
+ mPrintService.createPrinterDiscoverySession();
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error creating printer dicovery session.", re);
+ }
+
+ mHasPrinterDiscoverySession = true;
+ }
+ }
+
+ public void destroyPrinterDiscoverySession() {
+ mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
+ }
+
+ private void handleDestroyPrinterDiscoverySession() {
+ throwIfDestroyed();
+ if (!isBound()) {
+ ensureBound();
+ mPendingCommands.add(new Runnable() {
+ @Override
+ public void run() {
+ handleDestroyPrinterDiscoverySession();
+ }
+ });
+ } else {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserId + "] destroyPrinterDiscoverySession()");
+ }
+
+ mHasPrinterDiscoverySession = false;
+
+ try {
+ mPrintService.destroyPrinterDiscoverySession();
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error destroying printer dicovery session.", re);
+ }
+
+ // If the service has no print jobs and no active discovery
+ // session anymore we should disconnect from it.
+ if (mAllPrintJobsHandled) {
+ ensureUnbound();
+ }
+ }
+ }
+
+ public void startPrinterDiscovery(List<PrinterId> priorityList) {
+ mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_DISCOVERY,
+ priorityList).sendToTarget();
+ }
+
+ private void handleStartPrinterDiscovery(final List<PrinterId> priorityList) {
+ throwIfDestroyed();
+ if (!isBound()) {
+ ensureBound();
+ mPendingCommands.add(new Runnable() {
+ @Override
+ public void run() {
+ handleStartPrinterDiscovery(priorityList);
+ }
+ });
+ } else {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserId + "] startPrinterDiscovery()");
+ }
+ try {
+ mPrintService.startPrinterDiscovery(priorityList);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error starting printer dicovery.", re);
+ }
+ }
+ }
+
+ public void stopPrinterDiscovery() {
+ mHandler.sendEmptyMessage(MyHandler.MSG_STOP_PRINTER_DISCOVERY);
+ }
+
+ private void handleStopPrinterDiscovery() {
+ throwIfDestroyed();
+ if (!isBound()) {
+ ensureBound();
+ mPendingCommands.add(new Runnable() {
+ @Override
+ public void run() {
+ handleStopPrinterDiscovery();
+ }
+ });
+ } else {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterDiscovery()");
+ }
+ try {
+ mPrintService.stopPrinterDiscovery();
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error announcing start printer dicovery.", re);
+ Slog.e(LOG_TAG, "Error stopping printer dicovery.", re);
+ }
+ }
+ }
+
+ public void requestPrinterUpdate(PrinterId printerId) {
+ mHandler.obtainMessage(MyHandler.MSG_REQUEST_PRINTER_UPDATE,
+ printerId).sendToTarget();
+ }
+
+ private void handleRequestPrinterUpdate(final PrinterId printerId) {
+ throwIfDestroyed();
+ if (!isBound()) {
+ ensureBound();
+ mPendingCommands.add(new Runnable() {
+ @Override
+ public void run() {
+ handleRequestPrinterUpdate(printerId);
+ }
+ });
+ } else {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserId + "] requestPrinterUpdate()");
+ }
+ try {
+ mPrintService.requestPrinterUpdate(printerId);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error requesting a printer update.", re);
}
}
}
}
private final class MyHandler extends Handler {
- public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 1;
- public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 2;
- public static final int MSG_ON_PRINT_JOB_QUEUED = 3;
- public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 4;
- public static final int MSG_DESTROY = 6;
- public static final int MSG_BINDER_DIED = 7;
+ 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_ALL_PRINT_JOBS_HANDLED = 6;
+ public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 7;
+ public static final int MSG_ON_PRINT_JOB_QUEUED = 8;
+ public static final int MSG_DESTROY = 9;
+ public static final int MSG_BINDER_DIED = 10;
public MyHandler(Looper looper) {
super(looper, null, false);
}
@Override
+ @SuppressWarnings("unchecked")
public void handleMessage(Message message) {
switch (message.what) {
+ case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
+ handleCreatePrinterDiscoverySession();
+ } break;
+
+ case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
+ handleDestroyPrinterDiscoverySession();
+ } break;
+
+ case MSG_START_PRINTER_DISCOVERY: {
+ List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj;
+ handleStartPrinterDiscovery(priorityList);
+ } break;
+
+ case MSG_STOP_PRINTER_DISCOVERY: {
+ handleStopPrinterDiscovery();
+ } break;
+
+ case MSG_REQUEST_PRINTER_UPDATE: {
+ PrinterId printerId = (PrinterId) message.obj;
+ handleRequestPrinterUpdate(printerId);
+ } break;
+
case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
handleOnAllPrintJobsHandled();
} break;
handleOnPrintJobQueued(printJob);
} break;
- case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
- IPrinterDiscoverySessionObserver observer =
- (IPrinterDiscoverySessionObserver) message.obj;
- handleCreatePrinterDiscoverySession(new SecurePrinterDiscoverySessionObserver(
- mComponentName, observer));
- } break;
-
case MSG_DESTROY: {
handleDestroy();
} break;
}
@Override
- public boolean setPrintJobState(int printJobId, int state, CharSequence error) {
+ public boolean setPrintJobState(int printJobId, int state, String error) {
RemotePrintService service = mWeakService.get();
if (service != null) {
final long identity = Binder.clearCallingIdentity();
}
}
}
- }
-
- private static final class SecurePrinterDiscoverySessionObserver
- extends IPrinterDiscoverySessionObserver.Stub {
- private final ComponentName mComponentName;
-
- private final IPrinterDiscoverySessionObserver mDecoratedObsever;
-
- public SecurePrinterDiscoverySessionObserver(ComponentName componentName,
- IPrinterDiscoverySessionObserver observer) {
- mComponentName = componentName;
- mDecoratedObsever = observer;
- }
@Override
public void onPrintersAdded(List<PrinterInfo> printers) {
- throwIfPrinterIdsForPrinterInfoTampered(printers);
- try {
- mDecoratedObsever.onPrintersAdded(printers);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error delegating to onPrintersAdded", re);
- }
- }
-
- @Override
- public void onPrintersUpdated(List<PrinterInfo> printers) {
- throwIfPrinterIdsForPrinterInfoTampered(printers);
- try {
- mDecoratedObsever.onPrintersUpdated(printers);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error delegating to onPrintersUpdated.", re);
+ RemotePrintService service = mWeakService.get();
+ if (service != null) {
+ throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, printers);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ service.mSpooler.onPrintersAdded(printers);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
@Override
public void onPrintersRemoved(List<PrinterId> printerIds) {
- throwIfPrinterIdsTampered(printerIds);
- try {
- mDecoratedObsever.onPrintersRemoved(printerIds);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error delegating to onPrintersRemoved", re);
+ RemotePrintService service = mWeakService.get();
+ if (service != null) {
+ throwIfPrinterIdsTampered(service.mComponentName, printerIds);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ service.mSpooler.onPrintersRemoved(printerIds);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
@Override
- public void setController(IPrinterDiscoverySessionController controller) {
- try {
- mDecoratedObsever.setController(controller);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error setting controller", re);
+ public void onPrintersUpdated(List<PrinterInfo> printers) {
+ RemotePrintService service = mWeakService.get();
+ if (service != null) {
+ throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, printers);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ service.mSpooler.onPrintersUpdated(printers);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
- private void throwIfPrinterIdsForPrinterInfoTampered(
+ private void throwIfPrinterIdsForPrinterInfoTampered(ComponentName serviceName,
List<PrinterInfo> printerInfos) {
final int printerInfoCount = printerInfos.size();
for (int i = 0; i < printerInfoCount; i++) {
PrinterId printerId = printerInfos.get(i).getId();
- throwIfPrinterIdTampered(printerId);
+ throwIfPrinterIdTampered(serviceName, printerId);
}
}
- private void throwIfPrinterIdsTampered(List<PrinterId> printerIds) {
+ private void throwIfPrinterIdsTampered(ComponentName serviceName,
+ List<PrinterId> printerIds) {
final int printerIdCount = printerIds.size();
for (int i = 0; i < printerIdCount; i++) {
PrinterId printerId = printerIds.get(i);
- throwIfPrinterIdTampered(printerId);
+ throwIfPrinterIdTampered(serviceName, printerId);
}
}
- private void throwIfPrinterIdTampered(PrinterId printerId) {
+ private void throwIfPrinterIdTampered(ComponentName serviceName, PrinterId printerId) {
if (printerId == null || printerId.getServiceName() == null
- || !printerId.getServiceName().equals(mComponentName)) {
+ || !printerId.getServiceName().equals(serviceName)) {
throw new IllegalArgumentException("Invalid printer id: " + printerId);
}
}
import android.print.IPrintSpooler;
import android.print.IPrintSpoolerCallbacks;
import android.print.IPrintSpoolerClient;
-import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintAttributes;
import android.print.PrintJobInfo;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
import android.util.Slog;
import android.util.TimedRemoteCaller;
public static interface PrintSpoolerCallbacks {
public void onPrintJobQueued(PrintJobInfo printJob);
public void onAllPrintJobsForServiceHandled(ComponentName printService);
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
+ public void createPrinterDiscoverySession();
+ public void destroyPrinterDiscoverySession();
+ public void startPrinterDiscovery(List<PrinterId> priorityList);
+ public void stopPrinterDiscovery();
+ public void requestPrinterUpdate(PrinterId printerId);
}
public RemotePrintSpooler(Context context, int userId,
return null;
}
- public final boolean setPrintJobState(int printJobId, int state, CharSequence error) {
+ public final boolean setPrintJobState(int printJobId, int state, String error) {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
}
}
+ public final void onPrintersAdded(List<PrinterInfo> printers) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ getRemoteInstanceLazy().onPrintersAdded(printers);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error adding printers.", re);
+ } catch (TimeoutException te) {
+ Slog.e(LOG_TAG, "Error adding printers.", te);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+ + "] onPrintersAdded()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ public final void onPrintersRemoved(List<PrinterId> printerIds) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ getRemoteInstanceLazy().onPrintersRemoved(printerIds);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error removing printers.", re);
+ } catch (TimeoutException te) {
+ Slog.e(LOG_TAG, "Error removing printers.", te);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+ + "] onPrintersRemoved()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ public final void onPrintersUpdated(List<PrinterInfo> printers) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ getRemoteInstanceLazy().onPrintersUpdated(printers);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error updating printers.", re);
+ } catch (TimeoutException te) {
+ Slog.e(LOG_TAG, "Error updating printers.", te);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+ + "] onPrintersUpdted()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException {
synchronized (mLock) {
if (mRemoteInstance != null) {
}
public boolean setPrintJobState(IPrintSpooler target, int printJobId,
- int status, CharSequence error) throws RemoteException, TimeoutException {
+ int status, String error) throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
target.setPrintJobState(printJobId, status, error, mCallback, sequence);
return getResultTimed(sequence);
}
@Override
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
+ public void createPrinterDiscoverySession() {
+ RemotePrintSpooler spooler = mWeakSpooler.get();
+ if (spooler != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ spooler.mCallbacks.createPrinterDiscoverySession();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void destroyPrinterDiscoverySession() {
+ RemotePrintSpooler spooler = mWeakSpooler.get();
+ if (spooler != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ spooler.mCallbacks.destroyPrinterDiscoverySession();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void startPrinterDiscovery(List<PrinterId> priorityList) {
+ RemotePrintSpooler spooler = mWeakSpooler.get();
+ if (spooler != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ spooler.mCallbacks.startPrinterDiscovery(priorityList);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void stopPrinterDiscovery() {
+ RemotePrintSpooler spooler = mWeakSpooler.get();
+ if (spooler != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ spooler.mCallbacks.stopPrinterDiscovery();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void requestPrinterUpdate(PrinterId printerId) {
RemotePrintSpooler spooler = mWeakSpooler.get();
if (spooler != null) {
final long identity = Binder.clearCallingIdentity();
try {
- spooler.mCallbacks.createPrinterDiscoverySession(observer);
+ spooler.mCallbacks.requestPrinterUpdate(printerId);
} finally {
Binder.restoreCallingIdentity(identity);
}
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintJobInfo;
+import android.print.PrinterId;
import android.printservice.PrintServiceInfo;
import android.provider.Settings;
import android.text.TextUtils;
}
@Override
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
+ public void createPrinterDiscoverySession() {
final List<RemotePrintService> services;
synchronized (mLock) {
throwIfDestroyedLocked();
final int serviceCount = services.size();
for (int i = 0; i < serviceCount; i++) {
RemotePrintService service = services.get(i);
- service.createPrinterDiscoverySession(observer);
+ service.createPrinterDiscoverySession();
+ }
+ }
+
+ @Override
+ public void destroyPrinterDiscoverySession() {
+ final List<RemotePrintService> services;
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ if (mActiveServices.isEmpty()) {
+ return;
+ }
+ services = new ArrayList<RemotePrintService>(mActiveServices.values());
+ }
+ final int serviceCount = services.size();
+ for (int i = 0; i < serviceCount; i++) {
+ RemotePrintService service = services.get(i);
+ service.destroyPrinterDiscoverySession();
+ }
+ }
+
+ @Override
+ public void startPrinterDiscovery(List<PrinterId> printerIds) {
+ final List<RemotePrintService> services;
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ if (mActiveServices.isEmpty()) {
+ return;
+ }
+ services = new ArrayList<RemotePrintService>(mActiveServices.values());
+ }
+ final int serviceCount = services.size();
+ for (int i = 0; i < serviceCount; i++) {
+ RemotePrintService service = services.get(i);
+ service.startPrinterDiscovery(printerIds);
+ }
+ }
+
+ @Override
+ public void stopPrinterDiscovery() {
+ final List<RemotePrintService> services;
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ if (mActiveServices.isEmpty()) {
+ return;
+ }
+ services = new ArrayList<RemotePrintService>(mActiveServices.values());
+ }
+ final int serviceCount = services.size();
+ for (int i = 0; i < serviceCount; i++) {
+ RemotePrintService service = services.get(i);
+ service.stopPrinterDiscovery();
+ }
+ }
+
+ @Override
+ public void requestPrinterUpdate(PrinterId printerId) {
+ final RemotePrintService service;
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ if (mActiveServices.isEmpty()) {
+ return;
+ }
+ service = mActiveServices.get(printerId.getServiceName());
+ }
+ if (service != null) {
+ service.requestPrinterUpdate(printerId);
}
}