diff options
39 files changed, 3783 insertions, 2367 deletions
diff --git a/Android.mk b/Android.mk index 8e59ffa69e14..3c9cc2c63a60 100644 --- a/Android.mk +++ b/Android.mk @@ -162,8 +162,6 @@ LOCAL_SRC_FILES += \ 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 \ diff --git a/api/current.txt b/api/current.txt index 3fe11a6f0e3d..394acba82cbc 100644 --- a/api/current.txt +++ b/api/current.txt @@ -19038,7 +19038,7 @@ package android.print { 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(); @@ -19162,7 +19162,7 @@ package android.printservice { 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(); @@ -19191,11 +19191,15 @@ package android.printservice { } 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>); } diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl index 81781801d0db..5c8a22ae5de0 100644 --- a/core/java/android/print/IPrintSpooler.aidl +++ b/core/java/android/print/IPrintSpooler.aidl @@ -18,6 +18,7 @@ package android.print; import android.content.ComponentName; import android.os.ParcelFileDescriptor; +import android.print.PrinterId; import android.print.IPrintDocumentAdapter; import android.print.IPrintClient; import android.print.IPrintSpoolerClient; @@ -40,10 +41,15 @@ oneway interface IPrintSpooler { 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); } diff --git a/core/java/android/print/IPrintSpoolerClient.aidl b/core/java/android/print/IPrintSpoolerClient.aidl index 8db2169635af..da6012029c68 100644 --- a/core/java/android/print/IPrintSpoolerClient.aidl +++ b/core/java/android/print/IPrintSpoolerClient.aidl @@ -17,7 +17,6 @@ package android.print; import android.content.ComponentName; -import android.print.IPrinterDiscoverySessionObserver; import android.print.PrinterId; import android.print.PrintJobInfo; @@ -28,8 +27,14 @@ 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(); } diff --git a/core/java/android/print/IPrinterDiscoverySessionController.aidl b/core/java/android/print/IPrinterDiscoverySessionController.aidl deleted file mode 100644 index 13116ef1ccb6..000000000000 --- a/core/java/android/print/IPrinterDiscoverySessionController.aidl +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package 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(); -} diff --git a/core/java/android/print/IPrinterDiscoverySessionObserver.aidl b/core/java/android/print/IPrinterDiscoverySessionObserver.aidl deleted file mode 100644 index a78924c6171b..000000000000 --- a/core/java/android/print/IPrinterDiscoverySessionObserver.aidl +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package 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); -} diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java index 2fb4751af76f..602f3c1f2a44 100644 --- a/core/java/android/print/PrintJobInfo.java +++ b/core/java/android/print/PrintJobInfo.java @@ -104,7 +104,7 @@ public final class PrintJobInfo implements Parcelable { private int mId; /** The human readable print job label. */ - private CharSequence mLabel; + private String mLabel; /** The unique id of the printer. */ private PrinterId mPrinterId; @@ -128,7 +128,7 @@ public final class PrintJobInfo implements Parcelable { private int mCopies; /** Failure reason if this job failed. */ - private CharSequence mFailureReason; + private String mFailureReason; /** The pages to print */ private PageRange[] mPageRanges; @@ -163,7 +163,7 @@ public final class PrintJobInfo implements Parcelable { private PrintJobInfo(Parcel parcel) { mId = parcel.readInt(); - mLabel = parcel.readCharSequence(); + mLabel = parcel.readString(); mPrinterId = parcel.readParcelable(null); mPrinterName = parcel.readString(); mState = parcel.readInt(); @@ -171,9 +171,7 @@ public final class PrintJobInfo implements Parcelable { 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]; @@ -214,7 +212,7 @@ public final class PrintJobInfo implements Parcelable { * * @return The label. */ - public CharSequence getLabel() { + public String getLabel() { return mLabel; } @@ -225,7 +223,7 @@ public final class PrintJobInfo implements Parcelable { * * @hide */ - public void setLabel(CharSequence label) { + public void setLabel(String label) { mLabel = label; } @@ -385,7 +383,7 @@ public final class PrintJobInfo implements Parcelable { * * @hide */ - public CharSequence getFailureReason() { + public String getFailureReason() { return mFailureReason; } @@ -396,7 +394,7 @@ public final class PrintJobInfo implements Parcelable { * * @hide */ - public void setFailureReason(CharSequence failureReason) { + public void setFailureReason(String failureReason) { mFailureReason = failureReason; } @@ -470,7 +468,7 @@ public final class PrintJobInfo implements Parcelable { @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); @@ -478,12 +476,7 @@ public final class PrintJobInfo implements Parcelable { 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); diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index 636b9d4cb77b..531dcb224a3c 100644 --- a/core/java/android/print/PrintManager.java +++ b/core/java/android/print/PrintManager.java @@ -374,14 +374,14 @@ public final class PrintManager { @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); diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java index ac782a83927f..6f567a64d709 100644 --- a/core/java/android/print/PrinterInfo.java +++ b/core/java/android/print/PrinterInfo.java @@ -229,10 +229,11 @@ public final class PrinterInfo implements Parcelable { /** * 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); } /** diff --git a/core/java/android/print/pdf/PdfDocument.java b/core/java/android/print/pdf/PdfDocument.java index cfeb97543d95..dbd7dd1448c5 100644 --- a/core/java/android/print/pdf/PdfDocument.java +++ b/core/java/android/print/pdf/PdfDocument.java @@ -324,7 +324,7 @@ public final class PdfDocument { /** * 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. */ diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl index 16b7a26868ef..2cee1d84d0fb 100644 --- a/core/java/android/printservice/IPrintService.aidl +++ b/core/java/android/printservice/IPrintService.aidl @@ -16,7 +16,7 @@ package android.printservice; -import android.print.IPrinterDiscoverySessionObserver; +import android.print.PrinterId; import android.print.PrintJobInfo; import android.printservice.IPrintServiceClient; @@ -29,5 +29,10 @@ oneway interface IPrintService { 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(); } diff --git a/core/java/android/printservice/IPrintServiceClient.aidl b/core/java/android/printservice/IPrintServiceClient.aidl index f00b37c4bf0f..1e33fc07c92c 100644 --- a/core/java/android/printservice/IPrintServiceClient.aidl +++ b/core/java/android/printservice/IPrintServiceClient.aidl @@ -29,7 +29,11 @@ import android.print.PrinterInfo; 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); } diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java index 5f9a730f3f4a..d2fbef2b578e 100644 --- a/core/java/android/printservice/PrintJob.java +++ b/core/java/android/printservice/PrintJob.java @@ -24,6 +24,10 @@ import android.util.Log; * 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 { @@ -48,6 +52,7 @@ public final class PrintJob { * @return The id. */ public int getId() { + PrintService.throwIfNotCalledOnMainThread(); return mCachedInfo.getId(); } @@ -62,6 +67,7 @@ public final class PrintJob { * @return The print job info. */ public PrintJobInfo getInfo() { + PrintService.throwIfNotCalledOnMainThread(); if (isInImmutableState()) { return mCachedInfo; } @@ -83,6 +89,7 @@ public final class PrintJob { * @return The document. */ public PrintDocument getDocument() { + PrintService.throwIfNotCalledOnMainThread(); return mDocument; } @@ -96,6 +103,7 @@ public final class PrintJob { * @see #cancel() */ public boolean isQueued() { + PrintService.throwIfNotCalledOnMainThread(); return getInfo().getState() == PrintJobInfo.STATE_QUEUED; } @@ -110,6 +118,7 @@ public final class PrintJob { * @see #fail(CharSequence) */ public boolean isStarted() { + PrintService.throwIfNotCalledOnMainThread(); return getInfo().getState() == PrintJobInfo.STATE_STARTED; } @@ -122,6 +131,7 @@ public final class PrintJob { * @see #complete() */ public boolean isCompleted() { + PrintService.throwIfNotCalledOnMainThread(); return getInfo().getState() == PrintJobInfo.STATE_COMPLETED; } @@ -134,6 +144,7 @@ public final class PrintJob { * @see #fail(CharSequence) */ public boolean isFailed() { + PrintService.throwIfNotCalledOnMainThread(); return getInfo().getState() == PrintJobInfo.STATE_FAILED; } @@ -146,6 +157,7 @@ public final class PrintJob { * @see #cancel() */ public boolean isCancelled() { + PrintService.throwIfNotCalledOnMainThread(); return getInfo().getState() == PrintJobInfo.STATE_FAILED; } @@ -158,6 +170,7 @@ public final class PrintJob { * @see #isQueued() */ public boolean start() { + PrintService.throwIfNotCalledOnMainThread(); if (isQueued()) { return setState(PrintJobInfo.STATE_STARTED, null); } @@ -173,6 +186,7 @@ public final class PrintJob { * @see #isStarted() */ public boolean complete() { + PrintService.throwIfNotCalledOnMainThread(); if (isStarted()) { return setState(PrintJobInfo.STATE_COMPLETED, null); } @@ -191,7 +205,8 @@ public final class PrintJob { * @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); } @@ -210,6 +225,7 @@ public final class PrintJob { * @see #isQueued() */ public boolean cancel() { + PrintService.throwIfNotCalledOnMainThread(); if (isQueued() || isStarted()) { return setState(PrintJobInfo.STATE_CANCELED, null); } @@ -226,6 +242,7 @@ public final class PrintJob { * @return True if the tag was set, false otherwise. */ public boolean setTag(String tag) { + PrintService.throwIfNotCalledOnMainThread(); if (isInImmutableState()) { return false; } @@ -263,7 +280,7 @@ public final class PrintJob { || 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 diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java index 92bccd4e480d..8fe770c3be5f 100644 --- a/core/java/android/printservice/PrintService.java +++ b/core/java/android/printservice/PrintService.java @@ -25,7 +25,6 @@ import android.os.IBinder; 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; @@ -146,6 +145,11 @@ import java.util.List; * {@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 { @@ -175,14 +179,14 @@ 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); @@ -245,21 +249,18 @@ public abstract class PrintService extends Service { * @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) { @@ -278,23 +279,50 @@ public abstract class PrintService extends Service { * @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 @@ -312,33 +340,62 @@ public abstract class PrintService extends Service { } 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: { @@ -352,15 +409,12 @@ public abstract class PrintService extends Service { } 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: { diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java index 92dc0dd4a05f..8b959a68690a 100644 --- a/core/java/android/printservice/PrinterDiscoverySession.java +++ b/core/java/android/printservice/PrinterDiscoverySession.java @@ -16,18 +16,15 @@ package android.printservice; -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; import android.os.RemoteException; -import android.print.IPrinterDiscoverySessionController; -import android.print.IPrinterDiscoverySessionObserver; +import android.print.PrinterCapabilitiesInfo; import android.print.PrinterId; import android.print.PrinterInfo; +import android.util.ArrayMap; import android.util.Log; -import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -36,67 +33,75 @@ import java.util.List; * for adding discovered printers, removing already added printers that * disappeared, and updating already added printers. * <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()); } } @@ -105,131 +110,357 @@ public abstract class PrinterDiscoverySession { } /** + * 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 @@ -255,77 +486,72 @@ public abstract class PrinterDiscoverySession { */ public abstract void onRequestPrinterUpdate(PrinterId printerId); - void close() { - synchronized (mLock) { - mController = null; - mObserver = null; - } - } + /** + * Notifies you that the session is destroyed. After this callback is invoked + * any calls to the methods of this class will be ignored, {@link #isDestroyed()} + * will return true and you will also no longer receive callbacks. + * + * @see #isDestroyed() + */ + public abstract void onDestroy(); - private final class SessionHandler extends Handler { - public static final int MSG_OPEN = 1; - public static final int MSG_CLOSE = 2; - public static final int MSG_REQUEST_PRINTER_UPDATE = 3; + /** + * Gets whether the session is destroyed. + * + * @return Whether the session is destroyed. + * + * @see #onDestroy() + */ + public final boolean isDestroyed() { + PrintService.throwIfNotCalledOnMainThread(); + return mIsDestroyed; + } - public SessionHandler(Looper looper) { - super(looper, null, true); - } + /** + * Gets whether printer discovery is started. + * + * @return Whether printer discovery is destroyed. + * + * @see #onStartPrinterDiscovery(List) + * @see #onStopPrinterDiscovery() + */ + public final boolean isPrinterDiscoveryStarted() { + PrintService.throwIfNotCalledOnMainThread(); + return mIsDiscoveryStarted; + } - @Override - @SuppressWarnings("unchecked") - public void handleMessage(Message message) { - switch (message.what) { - case MSG_OPEN: { - List<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(); } - }; + } } diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java index b442ff52f635..d9e3ef661cce 100644 --- a/core/java/com/android/internal/os/HandlerCaller.java +++ b/core/java/com/android/internal/os/HandlerCaller.java @@ -65,7 +65,11 @@ public class HandlerCaller { mH.sendMessage(msg); } - + + public void sendMessageDelayed(Message msg, long delayMillis) { + mH.sendMessageDelayed(msg, delayMillis); + } + public boolean hasMessages(int what) { return mH.hasMessages(what); } diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml index c00639d57cdf..1f10af82e876 100644 --- a/packages/PrintSpooler/AndroidManifest.xml +++ b/packages/PrintSpooler/AndroidManifest.xml @@ -18,7 +18,7 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.printspooler" - android:sharedUserId="android.uid.printspooler" + android:sharedUserId="android.uid.system" android:versionName="1" android:versionCode="1" coreApp="true"> @@ -51,9 +51,10 @@ </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 diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.png Binary files differnew file mode 100644 index 000000000000..4b68f52ad0a4 --- /dev/null +++ b/packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.png diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png Binary files differnew file mode 100644 index 000000000000..15ffadd3606f --- /dev/null +++ b/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png Binary files differnew file mode 100644 index 000000000000..420510e935cf --- /dev/null +++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml index a0c111b3aa53..78170947915c 100644 --- a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml +++ b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml @@ -20,9 +20,4 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:background="@color/container_background"> - - <include - layout="@layout/print_job_config_activity_content_editing"> - </include> - </FrameLayout> diff --git a/packages/PrintSpooler/res/layout/choose_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml index c34a108f5024..f4e18537c7a7 100644 --- a/packages/PrintSpooler/res/layout/choose_printer_activity.xml +++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml @@ -5,7 +5,7 @@ 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, @@ -14,10 +14,16 @@ 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 diff --git a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml index 002cc1458345..d14c0642af94 100644 --- a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml +++ b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml @@ -15,7 +15,7 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" + android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingStart="8dip" android:paddingEnd="8dip" diff --git a/packages/PrintSpooler/res/menu/choose_printer_activity.xml b/packages/PrintSpooler/res/menu/select_printer_activity.xml index 3774279518ee..28fbd3536c4d 100644 --- a/packages/PrintSpooler/res/menu/choose_printer_activity.xml +++ b/packages/PrintSpooler/res/menu/select_printer_activity.xml @@ -23,7 +23,15 @@ 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> diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index 1cd611f0c920..41fc5168791f 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -58,11 +58,32 @@ <!-- Title for the temporary dialog show while an app is generating a print job. [CHAR LIMIT=30] --> <string name="generating_print_job">Generating print job</string> - <!-- 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] --> diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml index ab16c65eca99..831b0ecb2a47 100644 --- a/packages/PrintSpooler/res/values/themes.xml +++ b/packages/PrintSpooler/res/values/themes.xml @@ -24,4 +24,12 @@ <item name="android:colorBackgroundCacheHint">@android:color/transparent</item> </style> + <style name="SelectPrinterActivityTheme" parent="@android:style/Theme.Holo.Light"> + <item name="android:actionBarStyle">@style/SelectPrinterActivityActionBarStyle</item> + </style> + + <style name="SelectPrinterActivityActionBarStyle" parent="@android:style/Widget.Holo.ActionBar"> + <item name="android:displayOptions">showTitle</item> + </style> + </resources> diff --git a/packages/PrintSpooler/src/com/android/printspooler/AvailablePrinterProvider.java b/packages/PrintSpooler/src/com/android/printspooler/AvailablePrinterProvider.java deleted file mode 100644 index 658a22417418..000000000000 --- a/packages/PrintSpooler/src/com/android/printspooler/AvailablePrinterProvider.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.printspooler; - -import android.content.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(); - } - } - }; -} diff --git a/packages/PrintSpooler/src/com/android/printspooler/DataLoader.java b/packages/PrintSpooler/src/com/android/printspooler/DataLoader.java deleted file mode 100644 index 82cc2e10d5f2..000000000000 --- a/packages/PrintSpooler/src/com/android/printspooler/DataLoader.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.printspooler; - -/** - * 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(); -} diff --git a/packages/PrintSpooler/src/com/android/printspooler/DataProvider.java b/packages/PrintSpooler/src/com/android/printspooler/DataProvider.java deleted file mode 100644 index 7b10903f5570..000000000000 --- a/packages/PrintSpooler/src/com/android/printspooler/DataProvider.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.printspooler; - -import android.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); -} diff --git a/packages/PrintSpooler/src/com/android/printspooler/FavoritePrinterProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FavoritePrinterProvider.java deleted file mode 100644 index 2c539d192611..000000000000 --- a/packages/PrintSpooler/src/com/android/printspooler/FavoritePrinterProvider.java +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.printspooler; - -import android.content.ComponentName; -import android.content.Context; -import android.os.AsyncTask; -import android.os.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; - } - } -} diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java new file mode 100644 index 000000000000..6bad5b3ffb82 --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java @@ -0,0 +1,575 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.printspooler; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Loader; +import android.os.AsyncTask; +import android.os.Build; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.Log; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.util.FastXmlSerializer; +import com.android.printspooler.PrintSpoolerService.PrinterDiscoverySession; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * This class is responsible for loading printers by doing discovery + * and merging the discovered printers with the previously used ones. + */ +public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { + private static final String LOG_TAG = "FusedPrintersProvider"; + + private static final boolean DEBUG = true && Build.IS_DEBUGGABLE; + + private static final double WEIGHT_DECAY_COEFFICIENT = 0.95f; + + private static final int MAX_HISTORY_LENGTH = 50; + + private static final int MAX_HISTORICAL_PRINTER_COUNT = 4; + + private final Map<PrinterId, PrinterInfo> mPrinters = + new LinkedHashMap<PrinterId, PrinterInfo>(); + + private final PersistenceManager mPersistenceManager; + + private PrinterDiscoverySession mDiscoverySession; + + private List<PrinterInfo> mFavoritePrinters; + + public FusedPrintersProvider(Context context) { + super(context); + mPersistenceManager = new PersistenceManager(context); + } + + public void addHistoricalPrinter(PrinterInfo printer) { + mPersistenceManager.addPrinterAndWritePrinterHistory(printer); + } + + public List<PrinterInfo> getPrinters() { + return new ArrayList<PrinterInfo>(mPrinters.values()); + } + + @Override + public void deliverResult(List<PrinterInfo> printers) { + if (isStarted()) { + super.deliverResult(printers); + } + } + + @Override + protected void onStartLoading() { + if (DEBUG) { + Log.i(LOG_TAG, "onStartLoading()"); + } + // The contract is that if we already have a valid, + // result the we have to deliver it immediately. + if (!mPrinters.isEmpty()) { + deliverResult(new ArrayList<PrinterInfo>(mPrinters.values())); + } + // If the data has changed since the last load + // or is not available, start a load. + if (takeContentChanged() || mPrinters.isEmpty()) { + onForceLoad(); + } + } + + @Override + protected void onStopLoading() { + if (DEBUG) { + Log.i(LOG_TAG, "onStopLoading()"); + } + onCancelLoad(); + } + + @Override + protected void onForceLoad() { + if (DEBUG) { + Log.i(LOG_TAG, "onForceLoad()"); + } + onCancelLoad(); + loadInternal(); + } + + private void loadInternal() { + if (mDiscoverySession == null) { + mDiscoverySession = new MyPrinterDiscoverySession(); + mPersistenceManager.readPrinterHistory(); + } + if (mPersistenceManager.isReadHistoryCompleted() + && !mDiscoverySession.isStarted()) { + final int favoriteCount = Math.min(MAX_HISTORICAL_PRINTER_COUNT, + mFavoritePrinters.size()); + List<PrinterId> printerIds = new ArrayList<PrinterId>(favoriteCount); + for (int i = 0; i < favoriteCount; i++) { + printerIds.add(mFavoritePrinters.get(i).getId()); + } + mDiscoverySession.startPrinterDisovery(printerIds); + } + } + + @Override + protected boolean onCancelLoad() { + if (DEBUG) { + Log.i(LOG_TAG, "onCancelLoad()"); + } + return cancelInternal(); + } + + private boolean cancelInternal() { + if (mDiscoverySession != null && mDiscoverySession.isStarted()) { + mDiscoverySession.stopPrinterDiscovery(); + return true; + } else if (mPersistenceManager.isReadHistoryInProgress()) { + return mPersistenceManager.stopReadPrinterHistory(); + } + return false; + } + + @Override + protected void onReset() { + if (DEBUG) { + Log.i(LOG_TAG, "onReset()"); + } + onStopLoading(); + mPrinters.clear(); + if (mDiscoverySession != null) { + mDiscoverySession.destroy(); + mDiscoverySession = null; + } + } + + @Override + protected void onAbandon() { + if (DEBUG) { + Log.i(LOG_TAG, "onAbandon()"); + } + onStopLoading(); + } + + public void refreshPrinter(PrinterId printerId) { + if (isStarted() && mDiscoverySession != null && mDiscoverySession.isStarted()) { + mDiscoverySession.requestPrinterUpdated(printerId); + } + } + + private final class MyPrinterDiscoverySession extends PrinterDiscoverySession { + + @Override + public void onPrintersAdded(List<PrinterInfo> printers) { + if (DEBUG) { + Log.i(LOG_TAG, "MyPrinterDiscoverySession#onPrintersAdded()"); + } + boolean printersAdded = false; + final int addedPrinterCount = printers.size(); + for (int i = 0; i < addedPrinterCount; i++) { + PrinterInfo printer = printers.get(i); + if (!mPrinters.containsKey(printer.getId())) { + mPrinters.put(printer.getId(), printer); + printersAdded = true; + } + } + if (printersAdded) { + deliverResult(new ArrayList<PrinterInfo>(mPrinters.values())); + } + } + + @Override + public void onPrintersRemoved(List<PrinterId> printerIds) { + if (DEBUG) { + Log.i(LOG_TAG, "MyPrinterDiscoverySession#onPrintersRemoved()"); + } + boolean removedPrinters = false; + final int removedPrinterCount = printerIds.size(); + for (int i = 0; i < removedPrinterCount; i++) { + PrinterId removedPrinterId = printerIds.get(i); + if (mPrinters.remove(removedPrinterId) != null) { + removedPrinters = true; + } + } + if (removedPrinters) { + deliverResult(new ArrayList<PrinterInfo>(mPrinters.values())); + } + } + + @Override + public void onPrintersUpdated(List<PrinterInfo> printers) { + if (DEBUG) { + Log.i(LOG_TAG, "MyPrinterDiscoverySession#onPrintersUpdated()"); + } + boolean updatedPrinters = false; + final int updatedPrinterCount = printers.size(); + for (int i = 0; i < updatedPrinterCount; i++) { + PrinterInfo updatedPrinter = printers.get(i); + if (mPrinters.containsKey(updatedPrinter.getId())) { + mPrinters.put(updatedPrinter.getId(), updatedPrinter); + updatedPrinters = true; + } + } + if (updatedPrinters) { + deliverResult(new ArrayList<PrinterInfo>(mPrinters.values())); + } + } + } + + private final class PersistenceManager { + private static final String PERSIST_FILE_NAME = "printer_history.xml"; + + private static final String TAG_PRINTERS = "printers"; + + private static final String TAG_PRINTER = "printer"; + private static final String TAG_PRINTER_ID = "printerId"; + + private static final String ATTR_LOCAL_ID = "localId"; + private static final String ATTR_SERVICE_NAME = "serviceName"; + + private static final String ATTR_NAME = "name"; + private static final String ATTR_DESCRIPTION = "description"; + private static final String ATTR_STATUS = "status"; + + private final AtomicFile mStatePersistFile; + + private List<PrinterInfo> mHistoricalPrinters; + + private boolean mReadHistoryCompleted; + private boolean mReadHistoryInProgress; + + private final AsyncTask<Void, Void, List<PrinterInfo>> mReadTask = + new AsyncTask<Void, Void, List<PrinterInfo>>() { + @Override + protected List<PrinterInfo> doInBackground(Void... args) { + return doReadPrinterHistory(); + } + + @Override + protected void onPostExecute(List<PrinterInfo> printers) { + if (DEBUG) { + Log.i(LOG_TAG, "read history completed"); + } + + mHistoricalPrinters = printers; + + // Compute the favorite printers. + mFavoritePrinters = computeFavoritePrinters(printers); + + // We want the first few favorite printers on top of the list. + final int favoriteCount = Math.min(mFavoritePrinters.size(), + MAX_HISTORICAL_PRINTER_COUNT); + for (int i = 0; i < favoriteCount; i++) { + PrinterInfo favoritePrinter = mFavoritePrinters.get(i); + mPrinters.put(favoritePrinter.getId(), favoritePrinter); + } + + mReadHistoryInProgress = false; + mReadHistoryCompleted = true; + + loadInternal(); + } + + private List<PrinterInfo> doReadPrinterHistory() { + FileInputStream in = null; + try { + in = mStatePersistFile.openRead(); + } catch (FileNotFoundException fnfe) { + Log.i(LOG_TAG, "No existing printer history."); + return new ArrayList<PrinterInfo>(); + } + try { + List<PrinterInfo> printers = new ArrayList<PrinterInfo>(); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parseState(parser, printers); + return printers; + } catch (IllegalStateException ise) { + Slog.w(LOG_TAG, "Failed parsing ", ise); + } catch (NullPointerException npe) { + Slog.w(LOG_TAG, "Failed parsing ", npe); + } catch (NumberFormatException nfe) { + Slog.w(LOG_TAG, "Failed parsing ", nfe); + } catch (XmlPullParserException xppe) { + Slog.w(LOG_TAG, "Failed parsing ", xppe); + } catch (IOException ioe) { + Slog.w(LOG_TAG, "Failed parsing ", ioe); + } catch (IndexOutOfBoundsException iobe) { + Slog.w(LOG_TAG, "Failed parsing ", iobe); + } finally { + IoUtils.closeQuietly(in); + } + + return Collections.emptyList(); + } + + private void parseState(XmlPullParser parser, List<PrinterInfo> outPrinters) + throws IOException, XmlPullParserException { + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS); + parser.next(); + + while (parsePrinter(parser, outPrinters)) { + // Be nice and respond to cancellation + if (isCancelled()) { + return; + } + parser.next(); + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS); + } + + private boolean parsePrinter(XmlPullParser parser, List<PrinterInfo> outPrinters) + throws IOException, XmlPullParserException { + skipEmptyTextTags(parser); + if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) { + return false; + } + + String name = parser.getAttributeValue(null, ATTR_NAME); + String description = parser.getAttributeValue(null, ATTR_DESCRIPTION); + final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS)); + + parser.next(); + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID); + String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID); + ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue( + null, ATTR_SERVICE_NAME)); + PrinterId printerId = new PrinterId(service, localId); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID); + parser.next(); + + PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status); + builder.setDescription(description); + PrinterInfo printer = builder.create(); + + outPrinters.add(printer); + + if (DEBUG) { + Log.i(LOG_TAG, "[RESTORED] " + printer); + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_PRINTER); + + return true; + } + + private void expect(XmlPullParser parser, int type, String tag) + throws IOException, XmlPullParserException { + if (!accept(parser, type, tag)) { + throw new XmlPullParserException("Exepected event: " + type + + " and tag: " + tag + " but got event: " + parser.getEventType() + + " and tag:" + parser.getName()); + } + } + + private void skipEmptyTextTags(XmlPullParser parser) + throws IOException, XmlPullParserException { + while (accept(parser, XmlPullParser.TEXT, null) + && "\n".equals(parser.getText())) { + parser.next(); + } + } + + private boolean accept(XmlPullParser parser, int type, String tag) + throws IOException, XmlPullParserException { + if (parser.getEventType() != type) { + return false; + } + if (tag != null) { + if (!tag.equals(parser.getName())) { + return false; + } + } else if (parser.getName() != null) { + return false; + } + return true; + } + }; + + private final AsyncTask<List<PrinterInfo>, Void, Void> mWriteTask = + new AsyncTask<List<PrinterInfo>, Void, Void>() { + @Override + protected Void doInBackground(List<PrinterInfo>... printers) { + doWritePrinterHistory(printers[0]); + return null; + } + + private void doWritePrinterHistory(List<PrinterInfo> printers) { + FileOutputStream out = null; + try { + out = mStatePersistFile.startWrite(); + + XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.startDocument(null, true); + serializer.startTag(null, TAG_PRINTERS); + + final int printerCount = printers.size(); + for (int i = 0; i < printerCount; i++) { + PrinterInfo printer = printers.get(i); + + serializer.startTag(null, TAG_PRINTER); + + serializer.attribute(null, ATTR_NAME, printer.getName()); + serializer.attribute(null, ATTR_STATUS, String.valueOf( + printer.getStatus())); + String description = printer.getDescription(); + if (description != null) { + serializer.attribute(null, ATTR_DESCRIPTION, description); + } + + PrinterId printerId = printer.getId(); + serializer.startTag(null, TAG_PRINTER_ID); + serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId()); + serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName() + .flattenToString()); + serializer.endTag(null, TAG_PRINTER_ID); + + serializer.endTag(null, TAG_PRINTER); + + if (DEBUG) { + Log.i(LOG_TAG, "[PERSISTED] " + printer); + } + } + + serializer.endTag(null, TAG_PRINTERS); + serializer.endDocument(); + mStatePersistFile.finishWrite(out); + + if (DEBUG) { + Log.i(LOG_TAG, "[PERSIST END]"); + } + } catch (IOException ioe) { + Slog.w(LOG_TAG, "Failed to write printer history, restoring backup.", ioe); + mStatePersistFile.failWrite(out); + } finally { + IoUtils.closeQuietly(out); + } + } + }; + + private PersistenceManager(Context context) { + mStatePersistFile = new AtomicFile(new File(context.getFilesDir(), + PERSIST_FILE_NAME)); + } + + public boolean isReadHistoryInProgress() { + return mReadHistoryInProgress; + } + + public boolean isReadHistoryCompleted() { + return mReadHistoryCompleted; + } + + public boolean stopReadPrinterHistory() { + return mReadTask.cancel(true); + } + + public void readPrinterHistory() { + if (DEBUG) { + Log.i(LOG_TAG, "read history started"); + } + mReadHistoryInProgress = true; + mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } + + @SuppressWarnings("unchecked") + public void addPrinterAndWritePrinterHistory(PrinterInfo printer) { + if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) { + mHistoricalPrinters.remove(0); + } + mHistoricalPrinters.add(printer); + mWriteTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, mHistoricalPrinters); + } + + private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) { + Map<PrinterId, PrinterRecord> recordMap = + new ArrayMap<PrinterId, PrinterRecord>(); + + // Recompute the weights. + float currentWeight = 1.0f; + final int printerCount = printers.size(); + for (int i = printerCount - 1; i >= 0; i--) { + PrinterInfo printer = printers.get(i); + // Aggregate weight for the same printer + PrinterRecord record = recordMap.get(printer.getId()); + if (record == null) { + record = new PrinterRecord(printer); + recordMap.put(printer.getId(), record); + } + record.weight += currentWeight; + currentWeight *= WEIGHT_DECAY_COEFFICIENT; + } + + // Soft the favorite printers. + List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>( + recordMap.values()); + Collections.sort(favoriteRecords); + + // Write the favorites to the output. + final int favoriteCount = favoriteRecords.size(); + List<PrinterInfo> favoritePrinters = new ArrayList<PrinterInfo>(favoriteCount); + for (int i = 0; i < favoriteCount; i++) { + PrinterInfo printer = favoriteRecords.get(i).printer; + favoritePrinters.add(printer); + } + + return favoritePrinters; + } + + private final class PrinterRecord implements Comparable<PrinterRecord> { + public final PrinterInfo printer; + public float weight; + + public PrinterRecord(PrinterInfo printer) { + this.printer = printer; + } + + @Override + public int compareTo(PrinterRecord another) { + return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight); + } + } + } +} diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java index f8e9f43eda3f..d3dd8c973dbd 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java @@ -18,13 +18,17 @@ package com.android.printspooler; import android.app.Activity; import android.app.Dialog; +import android.app.LoaderManager; import android.content.Context; +import android.content.Intent; +import android.content.Loader; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.database.DataSetObserver; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -39,9 +43,11 @@ import android.print.IWriteResultCallback; import android.print.PageRange; import android.print.PrintAttributes; import android.print.PrintAttributes.MediaSize; +import android.print.PrintAttributes.Resolution; import android.print.PrintDocumentAdapter; import android.print.PrintDocumentInfo; import android.print.PrintJobInfo; +import android.print.PrintManager; import android.print.PrinterCapabilitiesInfo; import android.print.PrinterId; import android.print.PrinterInfo; @@ -69,6 +75,14 @@ import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; @@ -93,15 +107,32 @@ public class PrintJobConfigActivity extends Activity { public static final String EXTRA_PRINT_ATTRIBUTES = "printAttributes"; public static final String EXTRA_PRINT_JOB_ID = "printJobId"; - private static final int CONTROLLER_STATE_INITIALIZED = 1; - private static final int CONTROLLER_STATE_STARTED = 2; - private static final int CONTROLLER_STATE_LAYOUT_STARTED = 3; - private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 4; - private static final int CONTROLLER_STATE_WRITE_STARTED = 5; - private static final int CONTROLLER_STATE_WRITE_COMPLETED = 6; - private static final int CONTROLLER_STATE_FINISHED = 7; - private static final int CONTROLLER_STATE_FAILED = 8; - private static final int CONTROLLER_STATE_CANCELLED = 9; + public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID"; + + private static final int LOADER_ID_PRINTERS_LOADER = 1; + + private static final int DEST_ADAPTER_MIN_ITEM_COUNT = 2; + private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9; + + private static final int DEST_ADAPTER_POSITION_SEARCHING_FOR_PRINTERS = 0; + private static final int DEST_ADAPTER_POSITION_SAVE_AS_PDF = 1; + + private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE; + private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1; + private static final int DEST_ADAPTER_ITEM_ID_SEARCHING_FOR_PRINTERS = Integer.MAX_VALUE - 2; + + private static final int ACTIVITY_REQUEST_CREATE_FILE = 1; + private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2; + + private static final int CONTROLLER_STATE_FINISHED = 1; + private static final int CONTROLLER_STATE_FAILED = 2; + private static final int CONTROLLER_STATE_CANCELLED = 3; + private static final int CONTROLLER_STATE_INITIALIZED = 4; + private static final int CONTROLLER_STATE_STARTED = 5; + private static final int CONTROLLER_STATE_LAYOUT_STARTED = 6; + private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 7; + private static final int CONTROLLER_STATE_WRITE_STARTED = 8; + private static final int CONTROLLER_STATE_WRITE_COMPLETED = 9; private static final int EDITOR_STATE_INITIALIZED = 1; private static final int EDITOR_STATE_CONFIRMED_PRINT = 2; @@ -109,6 +140,7 @@ public class PrintJobConfigActivity extends Activity { private static final int EDITOR_STATE_CANCELLED = 4; private static final int MIN_COPIES = 1; + private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES); private static final Pattern PATTERN_DIGITS = Pattern.compile("\\d"); @@ -135,10 +167,6 @@ public class PrintJobConfigActivity extends Activity { private Document mDocument; private PrintController mController; - private AvailablePrinterProvider mAvailablePrinters; - - private FavoritePrinterProvider mFavoritePrinters; - private int mPrintJobId; private IBinder mIPrintDocumentAdapter; @@ -148,9 +176,6 @@ public class PrintJobConfigActivity extends Activity { @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); - setContentView(R.layout.print_job_config_activity_container); - - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); Bundle extras = getIntent().getExtras(); @@ -169,15 +194,17 @@ public class PrintJobConfigActivity extends Activity { 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); @@ -191,26 +218,6 @@ public class PrintJobConfigActivity extends Activity { } @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 @@ -219,10 +226,10 @@ public class PrintJobConfigActivity extends Activity { 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); @@ -333,13 +340,13 @@ public class PrintJobConfigActivity extends Activity { public void update() { if (!printAttributesChanged()) { - // If the attributes changes, then we do not do a layout but may + // If the attributes changed, then we do not do a layout but may // have to ask the app to write some pages. Hence, pretend layout // completed and nothing changed, so we handle writing as usual. handleOnLayoutFinished(mDocument.info, false, mRequestCounter.get()); } else { - PrintSpooler.peekInstance().setPrintJobAttributesNoPersistence(mPrintJobId, - mCurrPrintAttributes); + PrintSpoolerService.peekInstance().setPrintJobAttributesNoPersistence( + mPrintJobId, mCurrPrintAttributes); mMetadata.putBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW, !mEditor.isPrintConfirmed()); @@ -378,7 +385,7 @@ public class PrintJobConfigActivity extends Activity { final boolean infoChanged = !info.equals(mDocument.info); if (infoChanged) { mDocument.info = info; - PrintSpooler.peekInstance().setPrintJobPrintDocumentInfoNoPersistence( + PrintSpoolerService.peekInstance().setPrintJobPrintDocumentInfoNoPersistence( mPrintJobId, info); } @@ -465,12 +472,12 @@ public class PrintJobConfigActivity extends Activity { if (Arrays.equals(mDocument.pages, mRequestedPages)) { // We got a document with exactly the pages we wanted. Hence, // the printer has to print all pages in the data. - 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. @@ -480,7 +487,7 @@ public class PrintJobConfigActivity extends Activity { final int offset = mDocument.pages[0].getStart() - pages[0].getStart(); PageRange[] offsetPages = Arrays.copyOf(mDocument.pages, mDocument.pages.length); PageRangeUtils.offsetStart(offsetPages, offset); - 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 @@ -488,7 +495,7 @@ public class PrintJobConfigActivity extends Activity { // 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 @@ -500,7 +507,16 @@ public class PrintJobConfigActivity extends Activity { } if (mEditor.isDone()) { - PrintJobConfigActivity.this.finish(); + if (mEditor.isPrintingToPdf()) { + PrintJobInfo printJob = PrintSpoolerService.peekInstance() + .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY); + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.setType("application/pdf"); + intent.putExtra(Intent.EXTRA_TITLE, printJob.getLabel()); + startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE); + } else { + PrintJobConfigActivity.this.finish(); + } } } @@ -607,33 +623,111 @@ public class PrintJobConfigActivity extends Activity { } } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case ACTIVITY_REQUEST_CREATE_FILE: { + if (data != null) { + Uri uri = data.getData(); + writePrintJobDataAndFinish(uri); + } else { + mEditor.showUi(Editor.UI_EDITING_PRINT_JOB, + new Runnable() { + @Override + public void run() { + mEditor.initialize(); + mEditor.bindUi(); + mEditor.updateUi(); + } + }); + } + } break; + + case ACTIVITY_REQUEST_SELECT_PRINTER: { + if (resultCode == RESULT_OK) { + PrinterId printerId = (PrinterId) data.getParcelableExtra( + INTENT_EXTRA_PRINTER_ID); + // TODO: Make sure the selected printer is in the shown list. + mEditor.selectPrinter(printerId); + } + } break; + } + } + + private void writePrintJobDataAndFinish(final Uri uri) { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + InputStream in = null; + OutputStream out = null; + try { + PrintJobInfo printJob = PrintSpoolerService.peekInstance() + .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY); + if (printJob == null) { + return null; + } + File file = PrintSpoolerService.peekInstance() + .generateFileForPrintJob(mPrintJobId); + in = new FileInputStream(file); + out = getContentResolver().openOutputStream(uri); + final byte[] buffer = new byte[8192]; + while (true) { + final int readByteCount = in.read(buffer); + if (readByteCount < 0) { + break; + } + out.write(buffer, 0, readByteCount); + } + } catch (FileNotFoundException fnfe) { + Log.e(LOG_TAG, "Error writing print job data!", fnfe); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error writing print job data!", ioe); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + } + return null; + } + + @Override + public void onPostExecute(Void result) { + mEditor.cancel(); + PrintJobConfigActivity.this.finish(); + } + }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } + private final class Editor { - private final EditText mCopiesEditText; + private static final int UI_NONE = 0; + private static final int UI_EDITING_PRINT_JOB = 1; + private static final int UI_GENERATING_PRINT_JOB = 2; + + private EditText mCopiesEditText; - private final TextView mRangeTitle; - private final EditText mRangeEditText; + private TextView mRangeTitle; + private EditText mRangeEditText; - private final Spinner mDestinationSpinner; + private 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() { @@ -644,18 +738,31 @@ public class PrintJobConfigActivity extends Activity { mIgnoreNextDestinationChange = false; return; } + if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) { + mIgnoreNextDestinationChange = true; + mDestinationSpinner.setSelection(0); + Intent intent = new Intent(PrintJobConfigActivity.this, + SelectPrinterActivity.class); + startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER); + return; + } + mWaitingForPrinterCapabilities = false; mCurrPrintAttributes.clear(); 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 { @@ -668,6 +775,31 @@ public class PrintJobConfigActivity extends Activity { } } } + + // The printer changed so we want to start with a clean slate + // for the print options and let them be populated from the + // printer capabilities and use the printer defaults. + if (!mMediaSizeSpinnerAdapter.isEmpty()) { + mIgnoreNextMediaSizeChange = true; + mMediaSizeSpinnerAdapter.clear(); + } + if (!mColorModeSpinnerAdapter.isEmpty()) { + mIgnoreNextColorModeChange = true; + mColorModeSpinnerAdapter.clear(); + } + if (!mOrientationSpinnerAdapter.isEmpty()) { + mIgnoreNextOrientationChange = true; + mOrientationSpinnerAdapter.clear(); + } + if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) { + mIgnoreNextRangeOptionChange = true; + mRangeOptionsSpinner.setSelection(0); + } + if (!TextUtils.isEmpty(mCopiesEditText.getText())) { + mIgnoreNextCopiesChange = true; + mCopiesEditText.setText(MIN_COPIES_STRING); + } + updateUi(); } else if (spinner == mMediaSizeSpinner) { if (mIgnoreNextMediaSizeChange) { @@ -753,7 +885,8 @@ public class PrintJobConfigActivity extends Activity { } mCopiesEditText.setError(null); - PrintSpooler.peekInstance().setPrintJobCopiesNoPersistence(mPrintJobId, copies); + PrintSpoolerService.peekInstance().setPrintJobCopiesNoPersistence( + mPrintJobId, copies); updateUi(); if (hadErrors && !hasErrors() && printAttributesChanged()) { @@ -832,22 +965,25 @@ public class PrintJobConfigActivity extends Activity { 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 @@ -855,13 +991,9 @@ public class PrintJobConfigActivity extends Activity { 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(); @@ -872,41 +1004,23 @@ public class PrintJobConfigActivity extends Activity { 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); @@ -919,24 +1033,26 @@ public class PrintJobConfigActivity extends Activity { mRangeOptionsSpinnerAdapter.add(new SpinnerItem<Integer>( rangeOptionsValues[i], rangeOptionsLabels[i])); } - mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter); - if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) { - mIgnoreNextRangeOptionChange = true; - mRangeOptionsSpinner.setSelection(0); - } - // Print button - mPrintButton = (Button) findViewById(R.id.print_button); - mPrintButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mEditor.confirmPrint(); - 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) { @@ -966,19 +1082,104 @@ public class PrintJobConfigActivity extends Activity { } public boolean isShwoingGeneratingPrintJobUi() { - return (findViewById(R.id.content_generating) != null); + return (mCurrentUi == UI_GENERATING_PRINT_JOB); } - private void showGeneratingPrintJobUi() { - // Find everything we will shuffle around. - final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container); - final View contentEditing = contentContainer.findViewById(R.id.content_editing); - final View contentGenerating = getLayoutInflater().inflate( - R.layout.print_job_config_activity_content_generating, - contentContainer, false); + public void showUi(int ui, final Runnable postSwitchCallback) { + if (ui == UI_NONE) { + throw new IllegalStateException("cannot remove the ui"); + } + + if (mCurrentUi == ui) { + return; + } + + switch (mCurrentUi) { + case UI_NONE: { + switch (ui) { + case UI_EDITING_PRINT_JOB: { + doUiSwitch(R.layout.print_job_config_activity_content_editing); + registerPrintButtonClickListener(); + if (postSwitchCallback != null) { + postSwitchCallback.run(); + } + } break; + + 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) { @@ -988,24 +1189,38 @@ public class PrintJobConfigActivity extends Activity { mEditor.cancel(); } }); + } + + private void doUiSwitch(int showLayoutId) { + ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container); + contentContainer.removeAllViews(); + getLayoutInflater().inflate(showLayoutId, contentContainer, true); + } + + private void animateUiSwitch(int showLayoutId, final Runnable postAnimateCommand) { + // Find everything we will shuffle around. + final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container); + final View hidingView = contentContainer.getChildAt(0); + final View showingView = getLayoutInflater().inflate(showLayoutId, + null, false); // First animation - fade out the old content. - contentEditing.animate().alpha(0.0f).withLayer().withEndAction(new Runnable() { + hidingView.animate().alpha(0.0f).withLayer().withEndAction(new Runnable() { @Override public void run() { - contentEditing.setVisibility(View.INVISIBLE); + hidingView.setVisibility(View.INVISIBLE); // Prepare the new content with correct size and alpha. - contentGenerating.setMinimumWidth(contentContainer.getWidth()); - contentGenerating.setAlpha(0.0f); + showingView.setMinimumWidth(contentContainer.getWidth()); + showingView.setAlpha(0.0f); - // Compute how to much shrink the container to fit around the new content. + // Compute how to much shrink /stretch the content. final int widthSpec = MeasureSpec.makeMeasureSpec( - contentContainer.getWidth(), MeasureSpec.AT_MOST); + contentContainer.getWidth(), MeasureSpec.UNSPECIFIED); final int heightSpec = MeasureSpec.makeMeasureSpec( - contentContainer.getHeight(), MeasureSpec.AT_MOST); - contentGenerating.measure(widthSpec, heightSpec); - final float scaleY = (float) contentGenerating.getMeasuredHeight() + contentContainer.getHeight(), MeasureSpec.UNSPECIFIED); + showingView.measure(widthSpec, heightSpec); + final float scaleY = (float) showingView.getMeasuredHeight() / (float) contentContainer.getHeight(); // Second animation - resize the container. @@ -1016,10 +1231,16 @@ public class PrintJobConfigActivity extends Activity { // Swap the old and the new content. contentContainer.removeAllViews(); contentContainer.setScaleY(1.0f); - contentContainer.addView(contentGenerating); + contentContainer.addView(showingView); // Third animation - show the new content. - contentGenerating.animate().withLayer().alpha(1.0f); + showingView.animate().withLayer().alpha(1.0f).withEndAction( + new Runnable() { + @Override + public void run() { + postAnimateCommand.run(); + } + }); } }); } @@ -1028,10 +1249,6 @@ public class PrintJobConfigActivity extends Activity { public void initialize() { mEditorState = EDITOR_STATE_INITIALIZED; - if (mDestinationSpinner.getSelectedItemPosition() != AdapterView.INVALID_POSITION) { - mIgnoreNextDestinationChange = true; - mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION); - } } public boolean isCancelled() { @@ -1054,11 +1271,7 @@ public class PrintJobConfigActivity extends Activity { 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() { @@ -1104,7 +1317,79 @@ public class PrintJobConfigActivity extends Activity { return ALL_PAGES_ARRAY; } + private void bindUi() { + if (mCurrentUi != UI_EDITING_PRINT_JOB) { + return; + } + + // Content container + mContentContainer = findViewById(R.id.content_container); + + // Copies + mCopiesEditText = (EditText) findViewById(R.id.copies_edittext); + mCopiesEditText.setText(MIN_COPIES_STRING); + mCopiesEditText.addTextChangedListener(mCopiesTextWatcher); + mCopiesEditText.selectAll(); + if (!TextUtils.equals(mCopiesEditText.getText(), MIN_COPIES_STRING)) { + mIgnoreNextCopiesChange = true; + } + PrintSpoolerService.peekInstance().setPrintJobCopiesNoPersistence( + mPrintJobId, MIN_COPIES); + + // Destination. + mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner); + mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter); + mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener); + if (mDestinationSpinnerAdapter.getCount() > 0) { + mIgnoreNextDestinationChange = true; + } + + // Media size. + mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner); + mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter); + mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener); + if (mMediaSizeSpinnerAdapter.getCount() > 0) { + mIgnoreNextMediaSizeChange = true; + } + + // Color mode. + mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner); + mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter); + mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener); + if (mColorModeSpinnerAdapter.getCount() > 0) { + mIgnoreNextColorModeChange = true; + } + + // Orientation + mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner); + mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter); + mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener); + if (mOrientationSpinnerAdapter.getCount() > 0) { + mIgnoreNextOrientationChange = true; + } + + // Range + mRangeTitle = (TextView) findViewById(R.id.page_range_title); + mRangeEditText = (EditText) findViewById(R.id.page_range_edittext); + mRangeEditText.addTextChangedListener(mRangeTextWatcher); + + // Range options + mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner); + mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter); + mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener); + if (mRangeOptionsSpinnerAdapter.getCount() > 0) { + mIgnoreNextRangeOptionChange = true; + } + + // Print button + mPrintButton = (Button) findViewById(R.id.print_button); + registerPrintButtonClickListener(); + } + public void updateUi() { + if (mCurrentUi != UI_EDITING_PRINT_JOB) { + return; + } if (isPrintConfirmed() || isPreviewConfirmed() || isCancelled()) { mDestinationSpinner.setEnabled(false); mCopiesEditText.setEnabled(false); @@ -1119,14 +1404,20 @@ public class PrintJobConfigActivity extends Activity { return; } + // If a printer with capabilities is selected, then we enabled all options. + boolean allOptionsEnabled = false; final int selectedIndex = mDestinationSpinner.getSelectedItemPosition(); + if (selectedIndex >= 0) { + Object item = mDestinationSpinnerAdapter.getItem(selectedIndex); + if (item instanceof PrinterInfo) { + PrinterInfo printer = (PrinterInfo) item; + if (printer.getCapabilities() != null) { + allOptionsEnabled = true; + } + } + } - if (selectedIndex < 0 || ((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; @@ -1183,9 +1474,6 @@ public class PrintJobConfigActivity extends Activity { PrinterCapabilitiesInfo capabilities = printer.getCapabilities(); printer.getCapabilities().getDefaults(defaultAttributes); - // Destination - mDestinationSpinner.setEnabled(true); - // Copies mCopiesEditText.setEnabled(true); @@ -1223,6 +1511,7 @@ public class PrintJobConfigActivity extends Activity { } } } + mMediaSizeSpinner.setEnabled(true); // Color mode. final int colorModes = capabilities.getColorModes(); @@ -1271,6 +1560,7 @@ public class PrintJobConfigActivity extends Activity { } } } + mColorModeSpinner.setEnabled(true); // Orientation. final int orientations = capabilities.getOrientations(); @@ -1321,20 +1611,25 @@ public class PrintJobConfigActivity extends Activity { } } } + mOrientationSpinner.setEnabled(true); // Range options PrintDocumentInfo info = mDocument.info; if (info != null && (info.getPageCount() > 1 || info.getPageCount() == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)) { mRangeOptionsSpinner.setEnabled(true); - if (mRangeOptionsSpinner.getSelectedItemPosition() > 0 - && !mRangeEditText.isEnabled()) { - mRangeEditText.setEnabled(true); - mRangeEditText.setVisibility(View.VISIBLE); - mRangeEditText.requestFocus(); - InputMethodManager imm = (InputMethodManager) - getSystemService(INPUT_METHOD_SERVICE); - imm.showSoftInput(mRangeEditText, 0); + if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) { + if (!mRangeEditText.isEnabled()) { + mRangeEditText.setEnabled(true); + mRangeEditText.setVisibility(View.VISIBLE); + mRangeEditText.requestFocus(); + InputMethodManager imm = (InputMethodManager) + getSystemService(INPUT_METHOD_SERVICE); + imm.showSoftInput(mRangeEditText, 0); + } + } else { + mRangeEditText.setEnabled(false); + mRangeEditText.setVisibility(View.INVISIBLE); } final int pageCount = mDocument.info.getPageCount(); mRangeTitle.setText(getString(R.string.label_pages, @@ -1352,6 +1647,7 @@ public class PrintJobConfigActivity extends Activity { mRangeEditText.setEnabled(false); mRangeEditText.setVisibility(View.INVISIBLE); } + mRangeOptionsSpinner.setEnabled(true); // Print/Print preview if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1 @@ -1378,6 +1674,7 @@ public class PrintJobConfigActivity extends Activity { mCopiesEditText.selectAll(); mCopiesEditText.requestFocus(); } + mCopiesEditText.setEnabled(true); } } @@ -1407,38 +1704,51 @@ public class PrintJobConfigActivity extends Activity { } } - 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; } @@ -1455,24 +1765,92 @@ public class PrintJobConfigActivity extends Activity { 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(); + } } } diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java deleted file mode 100644 index c2cf65ee7ffa..000000000000 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java +++ /dev/null @@ -1,969 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.printspooler; - -import android.content.ComponentName; -import android.content.Context; -import android.os.AsyncTask; -import android.os.ParcelFileDescriptor; -import android.print.IPrintClient; -import android.print.IPrinterDiscoverySessionObserver; -import android.print.PageRange; -import android.print.PrintAttributes; -import android.print.PrintAttributes.Margins; -import android.print.PrintAttributes.MediaSize; -import android.print.PrintAttributes.Resolution; -import android.print.PrintAttributes.Tray; -import android.print.PrintDocumentInfo; -import android.print.PrintJobInfo; -import android.print.PrintManager; -import android.print.PrinterId; -import android.print.PrinterInfo; -import android.util.AtomicFile; -import android.util.Log; -import android.util.Slog; -import android.util.Xml; - -import com.android.internal.util.FastXmlSerializer; - -import libcore.io.IoUtils; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class PrintSpooler { - - private static final String LOG_TAG = "PrintSpooler"; - - private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = true; - - private static final boolean DEBUG_PERSISTENCE = true; - - private static final boolean PERSISTNECE_MANAGER_ENABLED = true; - - private static final String PRINT_FILE_EXTENSION = "pdf"; - - private static int sPrintJobIdCounter; - - private static final Object sLock = new Object(); - - private static PrintSpooler sInstance; - - private final Object mLock = new Object(); - - private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>(); - - private final PersistenceManager mPersistanceManager; - - private final NotificationController mNotificationController; - - private final PrintSpoolerService mService; - - public static void destroyInstance() { - synchronized (sLock) { - sInstance = null; - } - } - - public static void createInstance(PrintSpoolerService service) { - synchronized (sLock) { - sInstance = new PrintSpooler(service); - } - } - - public static PrintSpooler peekInstance() { - synchronized (sLock) { - return sInstance; - } - } - - private PrintSpooler(PrintSpoolerService service) { - mService = service; - mPersistanceManager = new PersistenceManager(service); - mNotificationController = new NotificationController(service); - synchronized (mLock) { - mPersistanceManager.readStateLocked(); - handleReadPrintJobsLocked(); - } - } - - public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, - int state, int appId) { - List<PrintJobInfo> foundPrintJobs = null; - synchronized (mLock) { - final int printJobCount = mPrintJobs.size(); - for (int i = 0; i < printJobCount; i++) { - PrintJobInfo printJob = mPrintJobs.get(i); - PrinterId printerId = printJob.getPrinterId(); - final boolean sameComponent = (componentName == null - || (printerId != null - && componentName.equals(printerId.getServiceName()))); - final boolean sameAppId = appId == PrintManager.APP_ID_ANY - || printJob.getAppId() == appId; - final boolean sameState = (state == printJob.getState()) - || (state == PrintJobInfo.STATE_ANY) - || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS - && printJob.getState() > PrintJobInfo.STATE_CREATED); - if (sameComponent && sameAppId && sameState) { - if (foundPrintJobs == null) { - foundPrintJobs = new ArrayList<PrintJobInfo>(); - } - foundPrintJobs.add(printJob); - } - } - } - return foundPrintJobs; - } - - public PrintJobInfo getPrintJobInfo(int printJobId, int appId) { - synchronized (mLock) { - final int printJobCount = mPrintJobs.size(); - for (int i = 0; i < printJobCount; i++) { - PrintJobInfo printJob = mPrintJobs.get(i); - if (printJob.getId() == printJobId - && (appId == PrintManager.APP_ID_ANY - || appId == printJob.getAppId())) { - return printJob; - } - } - return null; - } - } - - public PrintJobInfo createPrintJob(CharSequence label, IPrintClient client, - PrintAttributes attributes, int appId) { - synchronized (mLock) { - final int printJobId = generatePrintJobIdLocked(); - PrintJobInfo printJob = new PrintJobInfo(); - printJob.setId(printJobId); - printJob.setAppId(appId); - printJob.setLabel(label); - printJob.setAttributes(attributes); - printJob.setState(PrintJobInfo.STATE_CREATED); - - addPrintJobLocked(printJob); - - return printJob; - } - } - - private void handleReadPrintJobsLocked() { - final int printJobCount = mPrintJobs.size(); - for (int i = 0; i < printJobCount; i++) { - PrintJobInfo printJob = mPrintJobs.get(i); - - // Update the notification. - mNotificationController.onPrintJobStateChanged(printJob); - - switch (printJob.getState()) { - case PrintJobInfo.STATE_QUEUED: - case PrintJobInfo.STATE_STARTED: { - // We have a print job that was queued or started in the past - // but the device battery died or a crash occurred. In this case - // we assume the print job failed and let the user decide whether - // to restart the job or just - setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED, - mService.getString(R.string.no_connection_to_printer)); - } break; - } - } - } - - public void checkAllPrintJobsHandled() { - synchronized (mLock) { - if (!hasActivePrintJobsLocked()) { - notifyOnAllPrintJobsHandled(); - } - } - } - - public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) { - mService.createPrinterDiscoverySession(observer); - } - - private int generatePrintJobIdLocked() { - int printJobId = sPrintJobIdCounter++; - while (isDuplicatePrintJobId(printJobId)) { - printJobId = sPrintJobIdCounter++; - } - return printJobId; - } - - private boolean isDuplicatePrintJobId(int printJobId) { - final int printJobCount = mPrintJobs.size(); - for (int j = 0; j < printJobCount; j++) { - PrintJobInfo printJob = mPrintJobs.get(j); - if (printJob.getId() == printJobId) { - return true; - } - } - return false; - } - - public void writePrintJobData(final ParcelFileDescriptor fd, final int printJobId) { - final PrintJobInfo printJob; - synchronized (mLock) { - printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); - } - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - FileInputStream in = null; - FileOutputStream out = null; - try { - if (printJob != null) { - File file = generateFileForPrintJob(printJobId); - in = new FileInputStream(file); - out = new FileOutputStream(fd.getFileDescriptor()); - } - final byte[] buffer = new byte[8192]; - while (true) { - final int readByteCount = in.read(buffer); - if (readByteCount < 0) { - return null; - } - out.write(buffer, 0, readByteCount); - } - } catch (FileNotFoundException fnfe) { - Log.e(LOG_TAG, "Error writing print job data!", fnfe); - } catch (IOException ioe) { - Log.e(LOG_TAG, "Error writing print job data!", ioe); - } finally { - IoUtils.closeQuietly(in); - IoUtils.closeQuietly(out); - IoUtils.closeQuietly(fd); - } - Log.i(LOG_TAG, "[END WRITE]"); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); - } - - public File generateFileForPrintJob(int printJobId) { - return new File(mService.getFilesDir(), "print_job_" - + printJobId + "." + PRINT_FILE_EXTENSION); - } - - private void addPrintJobLocked(PrintJobInfo printJob) { - mPrintJobs.add(printJob); - if (DEBUG_PRINT_JOB_LIFECYCLE) { - Slog.i(LOG_TAG, "[ADD] " + printJob); - } - } - - private void removePrintJobLocked(PrintJobInfo printJob) { - if (mPrintJobs.remove(printJob)) { - generateFileForPrintJob(printJob.getId()).delete(); - if (DEBUG_PRINT_JOB_LIFECYCLE) { - Slog.i(LOG_TAG, "[REMOVE] " + printJob); - } - } - } - - public boolean setPrintJobState(int printJobId, int state, CharSequence error) { - boolean success = false; - - synchronized (mLock) { - PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); - if (printJob != null) { - success = true; - - printJob.setState(state); - printJob.setFailureReason(error); - mNotificationController.onPrintJobStateChanged(printJob); - - if (DEBUG_PRINT_JOB_LIFECYCLE) { - Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob); - } - - switch (state) { - case PrintJobInfo.STATE_COMPLETED: - case PrintJobInfo.STATE_CANCELED: - removePrintJobLocked(printJob); - // $fall-through$ - case PrintJobInfo.STATE_FAILED: { - PrinterId printerId = printJob.getPrinterId(); - if (printerId != null) { - ComponentName service = printerId.getServiceName(); - if (!hasActivePrintJobsForServiceLocked(service)) { - mService.onAllPrintJobsForServiceHandled(service); - } - } - } break; - - case PrintJobInfo.STATE_QUEUED: { - mService.onPrintJobQueued(new PrintJobInfo(printJob)); - } break; - } - - if (shouldPersistPrintJob(printJob)) { - mPersistanceManager.writeStateLocked(); - } - - if (!hasActivePrintJobsLocked()) { - notifyOnAllPrintJobsHandled(); - } - } - } - - return success; - } - - public boolean hasActivePrintJobsLocked() { - final int printJobCount = mPrintJobs.size(); - for (int i = 0; i < printJobCount; i++) { - PrintJobInfo printJob = mPrintJobs.get(i); - if (isActiveState(printJob.getState())) { - return true; - } - } - return false; - } - - public boolean hasActivePrintJobsForServiceLocked(ComponentName service) { - final int printJobCount = mPrintJobs.size(); - for (int i = 0; i < printJobCount; i++) { - PrintJobInfo printJob = mPrintJobs.get(i); - if (isActiveState(printJob.getState()) - && printJob.getPrinterId().getServiceName().equals(service)) { - return true; - } - } - return false; - } - - private static boolean isActiveState(int printJobState) { - return printJobState == PrintJobInfo.STATE_CREATED - || printJobState == PrintJobInfo.STATE_QUEUED - || printJobState == PrintJobInfo.STATE_STARTED; - } - - public boolean setPrintJobTag(int printJobId, String tag) { - synchronized (mLock) { - PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); - if (printJob != null) { - String printJobTag = printJob.getTag(); - if (printJobTag == null) { - if (tag == null) { - return false; - } - } else if (printJobTag.equals(tag)) { - return false; - } - printJob.setTag(tag); - if (shouldPersistPrintJob(printJob)) { - mPersistanceManager.writeStateLocked(); - } - return true; - } - } - return false; - } - - public void setPrintJobCopiesNoPersistence(int printJobId, int copies) { - synchronized (mLock) { - PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); - if (printJob != null) { - printJob.setCopies(copies); - } - } - } - - public void setPrintJobPrintDocumentInfoNoPersistence(int printJobId, PrintDocumentInfo info) { - synchronized (mLock) { - PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); - if (printJob != null) { - printJob.setDocumentInfo(info); - } - } - } - - public void setPrintJobAttributesNoPersistence(int printJobId, PrintAttributes attributes) { - synchronized (mLock) { - PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); - if (printJob != null) { - printJob.setAttributes(attributes); - } - } - } - - public void setPrintJobPrinterNoPersistence(int printJobId, PrinterInfo printer) { - synchronized (mLock) { - PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); - if (printJob != null) { - printJob.setPrinterId(printer.getId()); - printJob.setPrinterName(printer.getName()); - } - } - } - - public void setPrintJobPagesNoPersistence(int printJobId, PageRange[] pages) { - synchronized (mLock) { - PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); - if (printJob != null) { - printJob.setPages(pages); - } - } - } - - private boolean shouldPersistPrintJob(PrintJobInfo printJob) { - return printJob.getState() >= PrintJobInfo.STATE_QUEUED; - } - - private void notifyOnAllPrintJobsHandled() { - // This has to run on the tread that is persisting the current state - // since this call may result in the system unbinding from the spooler - // and as a result the spooler process may get killed before the write - // completes. - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - mService.onAllPrintJobsHandled(); - return null; - } - }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); - } - - private final class PersistenceManager { - private static final String PERSIST_FILE_NAME = "print_spooler_state.xml"; - - private static final String TAG_SPOOLER = "spooler"; - private static final String TAG_JOB = "job"; - - private static final String TAG_PRINTER_ID = "printerId"; - private static final String TAG_PAGE_RANGE = "pageRange"; - private static final String TAG_ATTRIBUTES = "attributes"; - private static final String TAG_DOCUMENT_INFO = "documentInfo"; - - private static final String ATTR_ID = "id"; - private static final String ATTR_LABEL = "label"; - private static final String ATTR_STATE = "state"; - private static final String ATTR_APP_ID = "appId"; - private static final String ATTR_USER_ID = "userId"; - private static final String ATTR_TAG = "tag"; - private static final String ATTR_COPIES = "copies"; - - private static final String TAG_MEDIA_SIZE = "mediaSize"; - private static final String TAG_RESOLUTION = "resolution"; - private static final String TAG_MARGINS = "margins"; - private static final String TAG_INPUT_TRAY = "inputTray"; - private static final String TAG_OUTPUT_TRAY = "outputTray"; - - private static final String ATTR_DUPLEX_MODE = "duplexMode"; - private static final String ATTR_COLOR_MODE = "colorMode"; - private static final String ATTR_FITTING_MODE = "fittingMode"; - private static final String ATTR_ORIENTATION = "orientation"; - - private static final String ATTR_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; - } - } -} diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java index 4fab4f886d5c..fda64c9e6a0b 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java @@ -21,9 +21,8 @@ import android.app.Service; import android.content.ComponentName; import android.content.Intent; import android.content.IntentSender; -import android.os.Handler; +import android.os.AsyncTask; import android.os.IBinder; -import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -32,14 +31,38 @@ import android.print.IPrintDocumentAdapter; import android.print.IPrintSpooler; import android.print.IPrintSpoolerCallbacks; import android.print.IPrintSpoolerClient; -import android.print.IPrinterDiscoverySessionObserver; +import android.print.PageRange; import android.print.PrintAttributes; +import android.print.PrintAttributes.Margins; +import android.print.PrintAttributes.MediaSize; +import android.print.PrintAttributes.Resolution; +import android.print.PrintAttributes.Tray; +import android.print.PrintDocumentInfo; import android.print.PrintJobInfo; +import android.print.PrintManager; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.util.AtomicFile; import android.util.Log; import android.util.Slog; +import android.util.Xml; +import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; +import com.android.internal.util.FastXmlSerializer; +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; import java.util.List; /** @@ -48,22 +71,65 @@ import java.util.List; */ public final class PrintSpoolerService extends Service { + private static final String LOG_TAG = "PrintSpoolerService"; + + private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = true; + + private static final boolean DEBUG_PERSISTENCE = true; + + private static final boolean PERSISTNECE_MANAGER_ENABLED = true; + private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000; - private static final String LOG_TAG = "PrintSpoolerService"; + private static final String PRINT_FILE_EXTENSION = "pdf"; + + private static final Object sLock = new Object(); + + private final Object mLock = new Object(); + + private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>(); + + private static PrintSpoolerService sInstance; + + private static int sPrintJobIdCounter; private Intent mStartPrintJobConfigActivityIntent; private IPrintSpoolerClient mClient; - private Handler mHandler; + private HandlerCaller mHandlerCaller; + + private PersistenceManager mPersistanceManager; + + private NotificationController mNotificationController; + + private PrinterDiscoverySession mDiscoverySession; + + public static PrintSpoolerService peekInstance() { + synchronized (sLock) { + return sInstance; + } + } @Override public void onCreate() { super.onCreate(); mStartPrintJobConfigActivityIntent = new Intent(PrintSpoolerService.this, PrintJobConfigActivity.class); - mHandler = new MyHandler(getMainLooper()); + mHandlerCaller = new HandlerCaller(this, getMainLooper(), + new HandlerCallerCallback(), false); + + mPersistanceManager = new PersistenceManager(); + mNotificationController = new NotificationController(PrintSpoolerService.this); + + synchronized (mLock) { + mPersistanceManager.readStateLocked(); + handleReadPrintJobsLocked(); + } + + synchronized (sLock) { + sInstance = this; + } } @Override @@ -72,10 +138,10 @@ public final class PrintSpoolerService extends Service { @Override public void getPrintJobInfos(IPrintSpoolerCallbacks callback, ComponentName componentName, int state, int appId, int sequence) - throws RemoteException { + throws RemoteException { List<PrintJobInfo> printJobs = null; try { - printJobs = PrintSpooler.peekInstance().getPrintJobInfos( + printJobs = PrintSpoolerService.this.getPrintJobInfos( componentName, state, appId); } finally { callback.onGetPrintJobInfosResult(printJobs, sequence); @@ -87,7 +153,7 @@ public final class PrintSpoolerService extends Service { int appId, int sequence) throws RemoteException { PrintJobInfo printJob = null; try { - printJob = PrintSpooler.peekInstance().getPrintJobInfo(printJobId, appId); + printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId); } finally { callback.onGetPrintJobInfoResult(printJob, sequence); } @@ -98,11 +164,11 @@ public final class PrintSpoolerService extends Service { public void createPrintJob(String printJobName, IPrintClient client, IPrintDocumentAdapter printAdapter, PrintAttributes attributes, IPrintSpoolerCallbacks callback, int appId, int sequence) - throws RemoteException { + throws RemoteException { PrintJobInfo printJob = null; try { - printJob = PrintSpooler.peekInstance().createPrintJob(printJobName, client, - attributes, appId); + printJob = PrintSpoolerService.this.createPrintJob( + printJobName, client, attributes, appId); if (printJob != null) { Intent intent = mStartPrintJobConfigActivityIntent; intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_DOCUMENT_ADAPTER, @@ -113,13 +179,12 @@ public final class PrintSpoolerService extends Service { IntentSender sender = PendingIntent.getActivity( PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender(); + | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender(); - SomeArgs args = SomeArgs.obtain(); - args.arg1 = client; - args.arg2 = sender; - mHandler.obtainMessage(MyHandler.MSG_START_PRINT_JOB_CONFIG_ACTIVITY, - args).sendToTarget(); + Message message = mHandlerCaller.obtainMessageOO( + HandlerCallerCallback.MSG_START_PRINT_JOB_CONFIG_ACTIVITY, + client, sender); + mHandlerCaller.executeOrSendMessage(message); } } finally { callback.onCreatePrintJobResult(printJob, sequence); @@ -127,11 +192,11 @@ public final class PrintSpoolerService extends Service { } @Override - public void setPrintJobState(int printJobId, int state, CharSequence error, + public void setPrintJobState(int printJobId, int state, String error, IPrintSpoolerCallbacks callback, int sequece) throws RemoteException { boolean success = false; try { - success = PrintSpooler.peekInstance().setPrintJobState( + success = PrintSpoolerService.this.setPrintJobState( printJobId, state, error); } finally { callback.onSetPrintJobStateResult(success, sequece); @@ -143,7 +208,7 @@ public final class PrintSpoolerService extends Service { IPrintSpoolerCallbacks callback, int sequece) throws RemoteException { boolean success = false; try { - success = PrintSpooler.peekInstance().setPrintJobTag(printJobId, tag); + success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag); } finally { callback.onSetPrintJobTagResult(success, sequece); } @@ -151,60 +216,191 @@ public final class PrintSpoolerService extends Service { @Override public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) { - PrintSpooler.peekInstance().writePrintJobData(fd, printJobId); + PrintSpoolerService.this.writePrintJobData(fd, printJobId); } @Override public void setClient(IPrintSpoolerClient client) { - mHandler.obtainMessage(MyHandler.MSG_SET_CLIENT, client).sendToTarget(); + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_SET_CLIENT, client); + mHandlerCaller.executeOrSendMessage(message); + } + + @Override + public void onPrintersAdded(List<PrinterInfo> printers) { + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_ON_PRINTERS_ADDED, printers); + mHandlerCaller.executeOrSendMessage(message); + } + + @Override + public void onPrintersRemoved(List<PrinterId> printerIds) { + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_ON_PRINTERS_REMOVED, printerIds); + mHandlerCaller.executeOrSendMessage(message); + } + + @Override + public void onPrintersUpdated(List<PrinterInfo> printers) { + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_ON_PRINTERS_UPDATED, printers); + mHandlerCaller.executeOrSendMessage(message); } }; } - public void onPrintJobQueued(PrintJobInfo printJob) { - mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED, - printJob).sendToTarget(); + public void createPrinterDiscoverySession() { + Message message = mHandlerCaller.obtainMessage( + HandlerCallerCallback.MSG_CREATE_PRINTER_DISCOVERY_SESSION); + mHandlerCaller.executeOrSendMessage(message); } - public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) { - mHandler.obtainMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION, - observer).sendToTarget(); + public void destroyPrinterDiscoverySession() { + Message message = mHandlerCaller.obtainMessage( + HandlerCallerCallback.MSG_DESTROY_PRINTER_DISCOVERY_SESSION); + mHandlerCaller.executeOrSendMessage(message); } - public void onAllPrintJobsForServiceHandled(ComponentName service) { - mHandler.obtainMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED, - service).sendToTarget(); + public void startPrinterDiscovery(List<PrinterId> priorityList) { + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_START_PRINTER_DISCOVERY, priorityList); + mHandlerCaller.executeOrSendMessage(message); } - public void onAllPrintJobsHandled() { - mHandler.sendEmptyMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED); + public void stopPrinterDiscovery() { + Message message = mHandlerCaller.obtainMessage( + HandlerCallerCallback.MSG_STOP_PRINTER_DISCOVERY); + mHandlerCaller.executeOrSendMessage(message); } - private final class MyHandler extends Handler { - public static final int MSG_SET_CLIENT = 1; - public static final int MSG_START_PRINT_JOB_CONFIG_ACTIVITY = 2; - public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 3; - public static final int MSG_ON_PRINT_JOB_QUEUED = 5; - public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 6; - public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 7; - public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 9; + public void requestPrinterUpdate(PrinterId pritnerId) { + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_REQUEST_PRINTER_UPDATE, pritnerId); + mHandlerCaller.executeOrSendMessage(message); + } - public MyHandler(Looper looper) { - super(looper, null, false); - } + + private void sendOnPrintJobQueued(PrintJobInfo printJob) { + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob); + mHandlerCaller.executeOrSendMessage(message); + } + + private void sendOnAllPrintJobsForServiceHandled(ComponentName service) { + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED, service); + mHandlerCaller.executeOrSendMessage(message); + } + + private void sendOnAllPrintJobsHandled() { + Message message = mHandlerCaller.obtainMessage( + HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_HANDLED); + mHandlerCaller.executeOrSendMessage(message); + } + + private final class HandlerCallerCallback implements HandlerCaller.Callback { + public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1; + public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2; + public static final int MSG_START_PRINTER_DISCOVERY = 3; + public static final int MSG_STOP_PRINTER_DISCOVERY = 4; + public static final int MSG_REQUEST_PRINTER_UPDATE = 5; + + public static final int MSG_ON_PRINTERS_ADDED = 6; + public static final int MSG_ON_PRINTERS_REMOVED = 7; + public static final int MSG_ON_PRINTERS_UPDATED = 8; + + public static final int MSG_SET_CLIENT = 9; + public static final int MSG_START_PRINT_JOB_CONFIG_ACTIVITY = 10; + public static final int MSG_ON_PRINT_JOB_QUEUED = 11; + public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 12; + public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 13; + public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 14; @Override - public void handleMessage(Message message) { + @SuppressWarnings("unchecked") + public void executeMessage(Message message) { switch (message.what) { + case MSG_CREATE_PRINTER_DISCOVERY_SESSION: { + final IPrintSpoolerClient client; + synchronized (mLock) { + client = mClient; + } + if (client != null) { + try { + client.createPrinterDiscoverySession(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error creating printer discovery session.", re); + } + } + } break; + + case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: { + final IPrintSpoolerClient client; + synchronized (mLock) { + client = mClient; + } + if (client != null) { + try { + client.destroyPrinterDiscoverySession(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error destroying printer discovery session.", re); + } + } + } break; + + case MSG_START_PRINTER_DISCOVERY: { + final IPrintSpoolerClient client; + synchronized (mLock) { + client = mClient; + } + if (client != null) { + List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj; + try { + client.startPrinterDiscovery(priorityList); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error starting printer discovery.", re); + } + } + } break; + + case MSG_STOP_PRINTER_DISCOVERY: { + final IPrintSpoolerClient client; + synchronized (mLock) { + client = mClient; + } + if (client != null) { + try { + client.stopPrinterDiscovery(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error stopping printer discovery.", re); + } + } + } break; + + case MSG_REQUEST_PRINTER_UPDATE: { + final IPrintSpoolerClient client; + synchronized (mLock) { + client = mClient; + } + if (client != null) { + PrinterId printerId = (PrinterId) message.obj; + try { + client.requestPrinterUpdate(printerId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error requesing printer update.", re); + } + } + } break; + case MSG_SET_CLIENT: { - mClient = (IPrintSpoolerClient) message.obj; - if (mClient != null) { - PrintSpooler.createInstance(PrintSpoolerService.this); - mHandler.sendEmptyMessageDelayed( - MyHandler.MSG_CHECK_ALL_PRINTJOBS_HANDLED, - CHECK_ALL_PRINTJOBS_HANDLED_DELAY); - } else { - PrintSpooler.destroyInstance(); + synchronized (mLock) { + mClient = (IPrintSpoolerClient) message.obj; + if (mClient != null) { + Message msg = mHandlerCaller.obtainMessage( + HandlerCallerCallback.MSG_CHECK_ALL_PRINTJOBS_HANDLED); + mHandlerCaller.sendMessageDelayed(msg, + CHECK_ALL_PRINTJOBS_HANDLED_DELAY); + } } } break; @@ -220,18 +416,6 @@ public final class PrintSpoolerService extends Service { } } break; - case MSG_CREATE_PRINTER_DISCOVERY_SESSION: { - IPrinterDiscoverySessionObserver observer = - (IPrinterDiscoverySessionObserver) message.obj; - if (mClient != null) { - try { - mClient.createPrinterDiscoverySession(observer); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error creating printer discovery session.", re); - } - } - } break; - case MSG_ON_PRINT_JOB_QUEUED: { PrintJobInfo printJob = (PrintJobInfo) message.obj; if (mClient != null) { @@ -266,12 +450,948 @@ public final class PrintSpoolerService extends Service { } break; case MSG_CHECK_ALL_PRINTJOBS_HANDLED: { - PrintSpooler spooler = PrintSpooler.peekInstance(); - if (spooler != null) { - spooler.checkAllPrintJobsHandled(); + checkAllPrintJobsHandled(); + } break; + + case MSG_ON_PRINTERS_ADDED: { + final PrinterDiscoverySession session; + synchronized (mLock) { + session = mDiscoverySession; + } + if (session != null) { + List<PrinterInfo> printers = (ArrayList<PrinterInfo>) message.obj; + session.onPrintersAdded(printers); + } + } break; + + case MSG_ON_PRINTERS_REMOVED: { + final PrinterDiscoverySession session; + synchronized (mLock) { + session = mDiscoverySession; + } + if (session != null) { + List<PrinterId> printerIds = (ArrayList<PrinterId>) message.obj; + session.onPrintersRemoved(printerIds); + } + } break; + + case MSG_ON_PRINTERS_UPDATED: { + final PrinterDiscoverySession session; + synchronized (mLock) { + session = mDiscoverySession; + } + if (session != null) { + List<PrinterInfo> printers = (ArrayList<PrinterInfo>) message.obj; + session.onPrintersUpdated(printers); } } break; } } } + + public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, + int state, int appId) { + List<PrintJobInfo> foundPrintJobs = null; + synchronized (mLock) { + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + PrinterId printerId = printJob.getPrinterId(); + final boolean sameComponent = (componentName == null + || (printerId != null + && componentName.equals(printerId.getServiceName()))); + final boolean sameAppId = appId == PrintManager.APP_ID_ANY + || printJob.getAppId() == appId; + final boolean sameState = (state == printJob.getState()) + || (state == PrintJobInfo.STATE_ANY) + || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS + && printJob.getState() > PrintJobInfo.STATE_CREATED); + if (sameComponent && sameAppId && sameState) { + if (foundPrintJobs == null) { + foundPrintJobs = new ArrayList<PrintJobInfo>(); + } + foundPrintJobs.add(printJob); + } + } + } + return foundPrintJobs; + } + + public PrintJobInfo getPrintJobInfo(int printJobId, int appId) { + synchronized (mLock) { + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + if (printJob.getId() == printJobId + && (appId == PrintManager.APP_ID_ANY + || appId == printJob.getAppId())) { + return printJob; + } + } + return null; + } + } + + public PrintJobInfo createPrintJob(String label, IPrintClient client, + PrintAttributes attributes, int appId) { + synchronized (mLock) { + final int printJobId = generatePrintJobIdLocked(); + PrintJobInfo printJob = new PrintJobInfo(); + printJob.setId(printJobId); + printJob.setAppId(appId); + printJob.setLabel(label); + printJob.setAttributes(attributes); + printJob.setState(PrintJobInfo.STATE_CREATED); + + addPrintJobLocked(printJob); + + return printJob; + } + } + + private void handleReadPrintJobsLocked() { + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + + // Update the notification. + mNotificationController.onPrintJobStateChanged(printJob); + + switch (printJob.getState()) { + case PrintJobInfo.STATE_QUEUED: + case PrintJobInfo.STATE_STARTED: { + // We have a print job that was queued or started in the + // past + // but the device battery died or a crash occurred. In this + // case + // we assume the print job failed and let the user decide + // whether + // to restart the job or just + setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED, + getString(R.string.no_connection_to_printer)); + } + break; + } + } + } + + public void checkAllPrintJobsHandled() { + synchronized (mLock) { + if (!hasActivePrintJobsLocked()) { + notifyOnAllPrintJobsHandled(); + } + } + } + + private void setPrinterDiscoverySessionClient(PrinterDiscoverySession session) { + synchronized (mLock) { + mDiscoverySession = session; + } + } + + private int generatePrintJobIdLocked() { + int printJobId = sPrintJobIdCounter++; + while (isDuplicatePrintJobId(printJobId)) { + printJobId = sPrintJobIdCounter++; + } + return printJobId; + } + + private boolean isDuplicatePrintJobId(int printJobId) { + final int printJobCount = mPrintJobs.size(); + for (int j = 0; j < printJobCount; j++) { + PrintJobInfo printJob = mPrintJobs.get(j); + if (printJob.getId() == printJobId) { + return true; + } + } + return false; + } + + public void writePrintJobData(final ParcelFileDescriptor fd, final int printJobId) { + final PrintJobInfo printJob; + synchronized (mLock) { + printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + } + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + FileInputStream in = null; + FileOutputStream out = null; + try { + if (printJob != null) { + File file = generateFileForPrintJob(printJobId); + in = new FileInputStream(file); + out = new FileOutputStream(fd.getFileDescriptor()); + } + final byte[] buffer = new byte[8192]; + while (true) { + final int readByteCount = in.read(buffer); + if (readByteCount < 0) { + return null; + } + out.write(buffer, 0, readByteCount); + } + } catch (FileNotFoundException fnfe) { + Log.e(LOG_TAG, "Error writing print job data!", fnfe); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error writing print job data!", ioe); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + IoUtils.closeQuietly(fd); + } + Log.i(LOG_TAG, "[END WRITE]"); + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + } + + public File generateFileForPrintJob(int printJobId) { + return new File(getFilesDir(), "print_job_" + + printJobId + "." + PRINT_FILE_EXTENSION); + } + + private void addPrintJobLocked(PrintJobInfo printJob) { + mPrintJobs.add(printJob); + if (DEBUG_PRINT_JOB_LIFECYCLE) { + Slog.i(LOG_TAG, "[ADD] " + printJob); + } + } + + private void removePrintJobLocked(PrintJobInfo printJob) { + if (mPrintJobs.remove(printJob)) { + generateFileForPrintJob(printJob.getId()).delete(); + if (DEBUG_PRINT_JOB_LIFECYCLE) { + Slog.i(LOG_TAG, "[REMOVE] " + printJob); + } + } + } + + public boolean setPrintJobState(int printJobId, int state, String error) { + boolean success = false; + + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + success = true; + + printJob.setState(state); + printJob.setFailureReason(error); + mNotificationController.onPrintJobStateChanged(printJob); + + if (DEBUG_PRINT_JOB_LIFECYCLE) { + Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob); + } + + switch (state) { + case PrintJobInfo.STATE_COMPLETED: + case PrintJobInfo.STATE_CANCELED: + removePrintJobLocked(printJob); + // $fall-through$ + + case PrintJobInfo.STATE_FAILED: { + PrinterId printerId = printJob.getPrinterId(); + if (printerId != null) { + ComponentName service = printerId.getServiceName(); + if (!hasActivePrintJobsForServiceLocked(service)) { + sendOnAllPrintJobsForServiceHandled(service); + } + } + } break; + + case PrintJobInfo.STATE_QUEUED: { + sendOnPrintJobQueued(new PrintJobInfo(printJob)); + } break; + } + + if (shouldPersistPrintJob(printJob)) { + mPersistanceManager.writeStateLocked(); + } + + if (!hasActivePrintJobsLocked()) { + notifyOnAllPrintJobsHandled(); + } + } + } + + return success; + } + + public boolean hasActivePrintJobsLocked() { + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + if (isActiveState(printJob.getState())) { + return true; + } + } + return false; + } + + public boolean hasActivePrintJobsForServiceLocked(ComponentName service) { + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + if (isActiveState(printJob.getState()) + && printJob.getPrinterId().getServiceName().equals(service)) { + return true; + } + } + return false; + } + + private boolean isActiveState(int printJobState) { + return printJobState == PrintJobInfo.STATE_CREATED + || printJobState == PrintJobInfo.STATE_QUEUED + || printJobState == PrintJobInfo.STATE_STARTED; + } + + public boolean setPrintJobTag(int printJobId, String tag) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + String printJobTag = printJob.getTag(); + if (printJobTag == null) { + if (tag == null) { + return false; + } + } else if (printJobTag.equals(tag)) { + return false; + } + printJob.setTag(tag); + if (shouldPersistPrintJob(printJob)) { + mPersistanceManager.writeStateLocked(); + } + return true; + } + } + return false; + } + + public void setPrintJobCopiesNoPersistence(int printJobId, int copies) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setCopies(copies); + } + } + } + + public void setPrintJobPrintDocumentInfoNoPersistence(int printJobId, PrintDocumentInfo info) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setDocumentInfo(info); + } + } + } + + public void setPrintJobAttributesNoPersistence(int printJobId, PrintAttributes attributes) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setAttributes(attributes); + } + } + } + + public void setPrintJobPrinterNoPersistence(int printJobId, PrinterInfo printer) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setPrinterId(printer.getId()); + printJob.setPrinterName(printer.getName()); + } + } + } + + public void setPrintJobPagesNoPersistence(int printJobId, PageRange[] pages) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setPages(pages); + } + } + } + + private boolean shouldPersistPrintJob(PrintJobInfo printJob) { + return printJob.getState() >= PrintJobInfo.STATE_QUEUED; + } + + private void notifyOnAllPrintJobsHandled() { + // This has to run on the tread that is persisting the current state + // since this call may result in the system unbinding from the spooler + // and as a result the spooler process may get killed before the write + // completes. + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + sendOnAllPrintJobsHandled(); + return null; + } + }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } + + private final class PersistenceManager { + private static final String PERSIST_FILE_NAME = "print_spooler_state.xml"; + + private static final String TAG_SPOOLER = "spooler"; + private static final String TAG_JOB = "job"; + + private static final String TAG_PRINTER_ID = "printerId"; + private static final String TAG_PAGE_RANGE = "pageRange"; + private static final String TAG_ATTRIBUTES = "attributes"; + private static final String TAG_DOCUMENT_INFO = "documentInfo"; + + private static final String ATTR_ID = "id"; + private static final String ATTR_LABEL = "label"; + private static final String ATTR_STATE = "state"; + private static final String ATTR_APP_ID = "appId"; + private static final String ATTR_USER_ID = "userId"; + private static final String ATTR_TAG = "tag"; + private static final String ATTR_COPIES = "copies"; + + private static final String TAG_MEDIA_SIZE = "mediaSize"; + private static final String TAG_RESOLUTION = "resolution"; + private static final String TAG_MARGINS = "margins"; + private static final String TAG_INPUT_TRAY = "inputTray"; + private static final String TAG_OUTPUT_TRAY = "outputTray"; + + private static final String ATTR_DUPLEX_MODE = "duplexMode"; + private static final String ATTR_COLOR_MODE = "colorMode"; + private static final String ATTR_FITTING_MODE = "fittingMode"; + private static final String ATTR_ORIENTATION = "orientation"; + + private static final String ATTR_LOCAL_ID = "printerName"; + private static final String ATTR_SERVICE_NAME = "serviceName"; + + private static final String ATTR_WIDTH_MILS = "widthMils"; + private static final String ATTR_HEIGHT_MILS = "heightMils"; + + private static final String ATTR_HORIZONTAL_DPI = "horizontalDip"; + private static final String ATTR_VERTICAL_DPI = "verticalDpi"; + + private static final String ATTR_LEFT_MILS = "leftMils"; + private static final String ATTR_TOP_MILS = "topMils"; + private static final String ATTR_RIGHT_MILS = "rightMils"; + private static final String ATTR_BOTTOM_MILS = "bottomMils"; + + private static final String ATTR_START = "start"; + private static final String ATTR_END = "end"; + + private static final String ATTR_NAME = "name"; + private static final String ATTR_PAGE_COUNT = "pageCount"; + private static final String ATTR_CONTENT_TYPE = "contentType"; + + private final AtomicFile mStatePersistFile; + + private boolean mWriteStateScheduled; + + private PersistenceManager() { + mStatePersistFile = new AtomicFile(new File(getFilesDir(), + PERSIST_FILE_NAME)); + } + + public void writeStateLocked() { + if (!PERSISTNECE_MANAGER_ENABLED) { + return; + } + if (mWriteStateScheduled) { + return; + } + mWriteStateScheduled = true; + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + synchronized (mLock) { + mWriteStateScheduled = false; + doWriteStateLocked(); + } + return null; + } + }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } + + private void doWriteStateLocked() { + if (DEBUG_PERSISTENCE) { + Log.i(LOG_TAG, "[PERSIST START]"); + } + FileOutputStream out = null; + try { + out = mStatePersistFile.startWrite(); + + XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.startDocument(null, true); + serializer.startTag(null, TAG_SPOOLER); + + List<PrintJobInfo> printJobs = mPrintJobs; + + final int printJobCount = printJobs.size(); + for (int j = 0; j < printJobCount; j++) { + PrintJobInfo printJob = printJobs.get(j); + + final int state = printJob.getState(); + if (state < PrintJobInfo.STATE_QUEUED + || state > PrintJobInfo.STATE_CANCELED) { + continue; + } + + serializer.startTag(null, TAG_JOB); + + serializer.attribute(null, ATTR_ID, String.valueOf(printJob.getId())); + serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString()); + serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState())); + serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId())); + serializer.attribute(null, ATTR_USER_ID, String.valueOf(printJob.getUserId())); + String tag = printJob.getTag(); + if (tag != null) { + serializer.attribute(null, ATTR_TAG, tag); + } + serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies())); + + PrinterId printerId = printJob.getPrinterId(); + if (printerId != null) { + serializer.startTag(null, TAG_PRINTER_ID); + serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId()); + serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName() + .flattenToString()); + serializer.endTag(null, TAG_PRINTER_ID); + } + + PageRange[] pages = printJob.getPages(); + if (pages != null) { + for (int i = 0; i < pages.length; i++) { + serializer.startTag(null, TAG_PAGE_RANGE); + serializer.attribute(null, ATTR_START, String.valueOf( + pages[i].getStart())); + serializer.attribute(null, ATTR_END, String.valueOf( + pages[i].getEnd())); + serializer.endTag(null, TAG_PAGE_RANGE); + } + } + + PrintAttributes attributes = printJob.getAttributes(); + if (attributes != null) { + serializer.startTag(null, TAG_ATTRIBUTES); + + final int duplexMode = attributes.getDuplexMode(); + serializer.attribute(null, ATTR_DUPLEX_MODE, + String.valueOf(duplexMode)); + + final int colorMode = attributes.getColorMode(); + serializer.attribute(null, ATTR_COLOR_MODE, + String.valueOf(colorMode)); + + final int fittingMode = attributes.getFittingMode(); + serializer.attribute(null, ATTR_FITTING_MODE, + String.valueOf(fittingMode)); + + final int orientation = attributes.getOrientation(); + serializer.attribute(null, ATTR_ORIENTATION, + String.valueOf(orientation)); + + MediaSize mediaSize = attributes.getMediaSize(); + if (mediaSize != null) { + serializer.startTag(null, TAG_MEDIA_SIZE); + serializer.attribute(null, ATTR_ID, mediaSize.getId()); + serializer.attribute(null, ATTR_LABEL, mediaSize.getLabel() + .toString()); + serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf( + mediaSize.getWidthMils())); + serializer.attribute(null, ATTR_HEIGHT_MILS, String.valueOf( + mediaSize.getHeightMils())); + serializer.endTag(null, TAG_MEDIA_SIZE); + } + + Resolution resolution = attributes.getResolution(); + if (resolution != null) { + serializer.startTag(null, TAG_RESOLUTION); + serializer.attribute(null, ATTR_ID, resolution.getId()); + serializer.attribute(null, ATTR_LABEL, resolution.getLabel() + .toString()); + serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf( + resolution.getHorizontalDpi())); + serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf( + resolution.getVerticalDpi())); + serializer.endTag(null, TAG_RESOLUTION); + } + + Margins margins = attributes.getMargins(); + if (margins != null) { + serializer.startTag(null, TAG_MARGINS); + serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf( + margins.getLeftMils())); + serializer.attribute(null, ATTR_TOP_MILS, String.valueOf( + margins.getTopMils())); + serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf( + margins.getRightMils())); + serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf( + margins.getBottomMils())); + serializer.endTag(null, TAG_MARGINS); + } + + Tray inputTray = attributes.getInputTray(); + if (inputTray != null) { + serializer.startTag(null, TAG_INPUT_TRAY); + serializer.attribute(null, ATTR_ID, inputTray.getId()); + serializer.attribute(null, ATTR_LABEL, inputTray.getLabel() + .toString()); + serializer.endTag(null, TAG_INPUT_TRAY); + } + + Tray outputTray = attributes.getOutputTray(); + if (outputTray != null) { + serializer.startTag(null, TAG_OUTPUT_TRAY); + serializer.attribute(null, ATTR_ID, outputTray.getId()); + serializer.attribute(null, ATTR_LABEL, outputTray.getLabel() + .toString()); + serializer.endTag(null, TAG_OUTPUT_TRAY); + } + + serializer.endTag(null, TAG_ATTRIBUTES); + } + + PrintDocumentInfo documentInfo = printJob.getDocumentInfo(); + if (documentInfo != null) { + serializer.startTag(null, TAG_DOCUMENT_INFO); + serializer.attribute(null, ATTR_NAME, documentInfo.getName()); + serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf( + documentInfo.getContentType())); + serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf( + documentInfo.getPageCount())); + serializer.endTag(null, TAG_DOCUMENT_INFO); + } + + serializer.endTag(null, TAG_JOB); + + if (DEBUG_PERSISTENCE) { + Log.i(LOG_TAG, "[PERSISTED] " + printJob); + } + } + + serializer.endTag(null, TAG_SPOOLER); + serializer.endDocument(); + mStatePersistFile.finishWrite(out); + if (DEBUG_PERSISTENCE) { + Log.i(LOG_TAG, "[PERSIST END]"); + } + } catch (IOException e) { + Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e); + mStatePersistFile.failWrite(out); + } finally { + IoUtils.closeQuietly(out); + } + } + + public void readStateLocked() { + if (!PERSISTNECE_MANAGER_ENABLED) { + return; + } + FileInputStream in = null; + try { + in = mStatePersistFile.openRead(); + } catch (FileNotFoundException e) { + Log.i(LOG_TAG, "No existing print spooler state."); + return; + } + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parseState(parser); + } catch (IllegalStateException ise) { + Slog.w(LOG_TAG, "Failed parsing ", ise); + } catch (NullPointerException npe) { + Slog.w(LOG_TAG, "Failed parsing ", npe); + } catch (NumberFormatException nfe) { + Slog.w(LOG_TAG, "Failed parsing ", nfe); + } catch (XmlPullParserException xppe) { + Slog.w(LOG_TAG, "Failed parsing ", xppe); + } catch (IOException ioe) { + Slog.w(LOG_TAG, "Failed parsing ", ioe); + } catch (IndexOutOfBoundsException iobe) { + Slog.w(LOG_TAG, "Failed parsing ", iobe); + } finally { + IoUtils.closeQuietly(in); + } + } + + private void parseState(XmlPullParser parser) + throws IOException, XmlPullParserException { + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER); + parser.next(); + + while (parsePrintJob(parser)) { + parser.next(); + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER); + } + + private boolean parsePrintJob(XmlPullParser parser) + throws IOException, XmlPullParserException { + skipEmptyTextTags(parser); + if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) { + return false; + } + + PrintJobInfo printJob = new PrintJobInfo(); + + final int printJobId = Integer.parseInt(parser.getAttributeValue(null, ATTR_ID)); + printJob.setId(printJobId); + String label = parser.getAttributeValue(null, ATTR_LABEL); + printJob.setLabel(label); + final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE)); + printJob.setState(state); + final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID)); + printJob.setAppId(appId); + final int userId = Integer.parseInt(parser.getAttributeValue(null, ATTR_USER_ID)); + printJob.setUserId(userId); + String tag = parser.getAttributeValue(null, ATTR_TAG); + printJob.setTag(tag); + String copies = parser.getAttributeValue(null, ATTR_COPIES); + printJob.setCopies(Integer.parseInt(copies)); + + parser.next(); + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) { + String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID); + ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue( + null, ATTR_SERVICE_NAME)); + printJob.setPrinterId(new PrinterId(service, localId)); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID); + parser.next(); + } + + skipEmptyTextTags(parser); + List<PageRange> pageRanges = null; + while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) { + final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START)); + final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END)); + PageRange pageRange = new PageRange(start, end); + if (pageRanges == null) { + pageRanges = new ArrayList<PageRange>(); + } + pageRanges.add(pageRange); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE); + parser.next(); + } + if (pageRanges != null) { + PageRange[] pageRangesArray = new PageRange[pageRanges.size()]; + pageRanges.toArray(pageRangesArray); + printJob.setPages(pageRangesArray); + } + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) { + + PrintAttributes.Builder builder = new PrintAttributes.Builder(); + + String duplexMode = parser.getAttributeValue(null, ATTR_DUPLEX_MODE); + builder.setDuplexMode(Integer.parseInt(duplexMode)); + + String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE); + builder.setColorMode(Integer.parseInt(colorMode)); + + String fittingMode = parser.getAttributeValue(null, ATTR_FITTING_MODE); + builder.setFittingMode(Integer.parseInt(fittingMode)); + + String orientation = parser.getAttributeValue(null, ATTR_ORIENTATION); + builder.setOrientation(Integer.parseInt(orientation)); + + parser.next(); + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) { + String id = parser.getAttributeValue(null, ATTR_ID); + label = parser.getAttributeValue(null, ATTR_LABEL); + final int widthMils = Integer.parseInt(parser.getAttributeValue(null, + ATTR_WIDTH_MILS)); + final int heightMils = Integer.parseInt(parser.getAttributeValue(null, + ATTR_HEIGHT_MILS)); + MediaSize mediaSize = new MediaSize(id, label, widthMils, heightMils); + builder.setMediaSize(mediaSize); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE); + parser.next(); + } + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) { + String id = parser.getAttributeValue(null, ATTR_ID); + label = parser.getAttributeValue(null, ATTR_LABEL); + final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null, + ATTR_HORIZONTAL_DPI)); + final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null, + ATTR_VERTICAL_DPI)); + Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi); + builder.setResolution(resolution); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION); + parser.next(); + } + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) { + final int leftMils = Integer.parseInt(parser.getAttributeValue(null, + ATTR_LEFT_MILS)); + final int topMils = Integer.parseInt(parser.getAttributeValue(null, + ATTR_TOP_MILS)); + final int rightMils = Integer.parseInt(parser.getAttributeValue(null, + ATTR_RIGHT_MILS)); + final int bottomMils = Integer.parseInt(parser.getAttributeValue(null, + ATTR_BOTTOM_MILS)); + Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils); + builder.setMargins(margins); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_MARGINS); + parser.next(); + } + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_INPUT_TRAY)) { + String id = parser.getAttributeValue(null, ATTR_ID); + label = parser.getAttributeValue(null, ATTR_LABEL); + Tray tray = new Tray(id, label); + builder.setInputTray(tray); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_INPUT_TRAY); + parser.next(); + } + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_OUTPUT_TRAY)) { + String id = parser.getAttributeValue(null, ATTR_ID); + label = parser.getAttributeValue(null, ATTR_LABEL); + Tray tray = new Tray(id, label); + builder.setOutputTray(tray); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_OUTPUT_TRAY); + parser.next(); + } + + printJob.setAttributes(builder.create()); + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES); + parser.next(); + } + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) { + String name = parser.getAttributeValue(null, ATTR_NAME); + final int pageCount = Integer.parseInt(parser.getAttributeValue(null, + ATTR_PAGE_COUNT)); + final int contentType = Integer.parseInt(parser.getAttributeValue(null, + ATTR_CONTENT_TYPE)); + PrintDocumentInfo info = new PrintDocumentInfo.Builder(name) + .setPageCount(pageCount) + .setContentType(contentType).create(); + printJob.setDocumentInfo(info); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO); + parser.next(); + } + + mPrintJobs.add(printJob); + + if (DEBUG_PERSISTENCE) { + Log.i(LOG_TAG, "[RESTORED] " + printJob); + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_JOB); + + return true; + } + + private void expect(XmlPullParser parser, int type, String tag) + throws IOException, XmlPullParserException { + if (!accept(parser, type, tag)) { + throw new XmlPullParserException("Exepected event: " + type + + " and tag: " + tag + " but got event: " + parser.getEventType() + + " and tag:" + parser.getName()); + } + } + + private void skipEmptyTextTags(XmlPullParser parser) + throws IOException, XmlPullParserException { + while (accept(parser, XmlPullParser.TEXT, null) + && "\n".equals(parser.getText())) { + parser.next(); + } + } + + private boolean accept(XmlPullParser parser, int type, String tag) + throws IOException, XmlPullParserException { + if (parser.getEventType() != type) { + return false; + } + if (tag != null) { + if (!tag.equals(parser.getName())) { + return false; + } + } else if (parser.getName() != null) { + return false; + } + return true; + } + } + + public static abstract class PrinterDiscoverySession { + + private PrintSpoolerService mService; + + private boolean mIsStarted; + + public PrinterDiscoverySession() { + mService = PrintSpoolerService.peekInstance(); + mService.createPrinterDiscoverySession(); + mService.setPrinterDiscoverySessionClient(this); + } + + public final void startPrinterDisovery(List<PrinterId> priorityList) { + mIsStarted = true; + mService.startPrinterDiscovery(priorityList); + } + + public final void stopPrinterDiscovery() { + mIsStarted = false; + mService.stopPrinterDiscovery(); + } + + public void requestPrinterUpdated(PrinterId printerId) { + mService.requestPrinterUpdate(printerId); + } + + public final void destroy() { + mService.setPrinterDiscoverySessionClient(null); + mService.destroyPrinterDiscoverySession(); + } + + public final boolean isStarted() { + return mIsStarted; + } + + public abstract void onPrintersAdded(List<PrinterInfo> printers); + + public abstract void onPrintersRemoved(List<PrinterId> printerIds); + + public abstract void onPrintersUpdated(List<PrinterInfo> printers); + } } diff --git a/packages/PrintSpooler/src/com/android/printspooler/ChoosePrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java index 8b0dd66a9739..141dbd1e27bb 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ChoosePrinterActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java @@ -17,12 +17,25 @@ 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(); } } diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java new file mode 100644 index 000000000000..9ca3a869626a --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.printspooler; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.app.ListFragment; +import android.app.LoaderManager; +import android.content.ComponentName; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.Loader; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Bundle; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.printservice.PrintServiceInfo; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ListView; +import android.widget.SearchView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +/** + * This is a fragment for selecting a printer. + */ +public final class SelectPrinterFragment extends ListFragment { + + private static final int LOADER_ID_PRINTERS_LOADER = 1; + + private static final String FRAGMRNT_TAG_ADD_PRINTER_DIALOG = + "FRAGMRNT_TAG_ADD_PRINTER_DIALOG"; + + private static final String FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS = + "FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS"; + + private final ArrayList<PrintServiceInfo> mAddPrinterServices = + new ArrayList<PrintServiceInfo>(); + + public static interface OnPrinterSelectedListener { + public void onPrinterSelected(PrinterId printerId); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setListAdapter(new DestinationAdapter()); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.select_printer_activity, menu); + + MenuItem searchItem = menu.findItem(R.id.action_search); + SearchView searchView = (SearchView) searchItem.getActionView(); + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + return true; + } + + @Override + public boolean onQueryTextChange(String searchString) { + ((DestinationAdapter) getListAdapter()).getFilter().filter(searchString); + return true; + } + }); + + if (mAddPrinterServices.isEmpty()) { + menu.removeItem(R.id.action_add_printer); + } + } + + @Override + public void onResume() { + updateAddPrintersAdapter(); + getActivity().invalidateOptionsMenu(); + super.onResume(); + } + + @Override + public void onListItemClick(ListView list, View view, int position, long id) { + PrinterInfo printer = (PrinterInfo) list.getAdapter().getItem(position); + Activity activity = getActivity(); + if (activity instanceof OnPrinterSelectedListener) { + ((OnPrinterSelectedListener) activity).onPrinterSelected(printer.getId()); + } else { + throw new IllegalStateException("the host activity must implement" + + " OnPrinterSelectedListener"); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.action_add_printer) { + showAddPrinterSelectionDialog(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void updateAddPrintersAdapter() { + mAddPrinterServices.clear(); + + // Get all print services. + List<ResolveInfo> resolveInfos = getActivity().getPackageManager().queryIntentServices( + new Intent(android.printservice.PrintService.SERVICE_INTERFACE), + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); + + // No print services - done. + if (resolveInfos.isEmpty()) { + return; + } + + // Find the services with valid add printers activities. + final int resolveInfoCount = resolveInfos.size(); + for (int i = 0; i < resolveInfoCount; i++) { + ResolveInfo resolveInfo = resolveInfos.get(i); + + PrintServiceInfo printServiceInfo = PrintServiceInfo.create( + resolveInfo, getActivity()); + String addPrintersActivity = printServiceInfo.getAddPrintersActivityName(); + + // No add printers activity declared - done. + if (TextUtils.isEmpty(addPrintersActivity)) { + continue; + } + + ComponentName addPrintersComponentName = new ComponentName( + resolveInfo.serviceInfo.packageName, + addPrintersActivity); + Intent addPritnersIntent = new Intent(Intent.ACTION_MAIN) + .setComponent(addPrintersComponentName); + + // The add printers activity is valid - add it. + if (!getActivity().getPackageManager().queryIntentActivities( + addPritnersIntent, 0).isEmpty()) { + mAddPrinterServices.add(printServiceInfo); + } + } + } + + private void showAddPrinterSelectionDialog() { + FragmentTransaction transaction = getFragmentManager().beginTransaction(); + Fragment oldFragment = getFragmentManager().findFragmentByTag( + FRAGMRNT_TAG_ADD_PRINTER_DIALOG); + if (oldFragment != null) { + transaction.remove(oldFragment); + } + AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment(); + Bundle arguments = new Bundle(); + arguments.putParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS, + mAddPrinterServices); + newFragment.setArguments(arguments); + transaction.add(newFragment, FRAGMRNT_TAG_ADD_PRINTER_DIALOG); + transaction.commit(); + } + + public static class AddPrinterAlertDialogFragment extends DialogFragment { + + private static final String DEFAULT_MARKET_QUERY_STRING = + "market://search?q=print"; + + @Override + @SuppressWarnings("unchecked") + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.choose_print_service); + + final List<PrintServiceInfo> printServices = (List<PrintServiceInfo>) (List<?>) + getArguments().getParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS); + + ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), + android.R.layout.simple_list_item_1); + final int printServiceCount = printServices.size(); + for (int i = 0; i < printServiceCount; i++) { + PrintServiceInfo printService = printServices.get(i); + adapter.add(printService.getResolveInfo().loadLabel( + getActivity().getPackageManager()).toString()); + } + + builder.setAdapter(adapter, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + PrintServiceInfo printService = printServices.get(which); + ComponentName componentName = new ComponentName( + printService.getResolveInfo().serviceInfo.packageName, + printService.getAddPrintersActivityName()); + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(componentName); + startActivity(intent); + } + }); + + Uri marketUri = Uri.parse(DEFAULT_MARKET_QUERY_STRING); + final Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri); + if (getActivity().getPackageManager().resolveActivity(marketIntent, 0) != null) { + builder.setPositiveButton(R.string.search_play_store, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + startActivity(marketIntent); + } + }); + } + + return builder.create(); + } + } + + private final class DestinationAdapter extends BaseAdapter + implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>, Filterable { + + private final Object mLock = new Object(); + + private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>(); + + private final List<PrinterInfo> mFilteredPrinters = new ArrayList<PrinterInfo>(); + + private CharSequence mLastSearchString; + + public DestinationAdapter() { + getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this); + } + + @Override + public Filter getFilter() { + return new Filter() { + @Override + protected FilterResults performFiltering(CharSequence constraint) { + synchronized (mLock) { + if (TextUtils.isEmpty(constraint)) { + return null; + } + FilterResults results = new FilterResults(); + List<PrinterInfo> filteredPrinters = new ArrayList<PrinterInfo>(); + String constraintLowerCase = constraint.toString().toLowerCase(); + final int printerCount = mPrinters.size(); + for (int i = 0; i < printerCount; i++) { + PrinterInfo printer = mPrinters.get(i); + if (printer.getName().toLowerCase().contains(constraintLowerCase)) { + filteredPrinters.add(printer); + } + } + results.values = filteredPrinters; + results.count = filteredPrinters.size(); + return results; + } + } + + @Override + @SuppressWarnings("unchecked") + protected void publishResults(CharSequence constraint, FilterResults results) { + synchronized (mLock) { + mLastSearchString = constraint; + mFilteredPrinters.clear(); + if (results == null) { + mFilteredPrinters.addAll(mPrinters); + } else { + List<PrinterInfo> printers = (List<PrinterInfo>) results.values; + mFilteredPrinters.addAll(printers); + } + } + notifyDataSetChanged(); + } + }; + } + + @Override + public int getCount() { + synchronized (mLock) { + return mFilteredPrinters.size(); + } + } + + @Override + public Object getItem(int position) { + synchronized (mLock) { + return mFilteredPrinters.get(position); + } + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getDropDownView(int position, View convertView, + ViewGroup parent) { + return getView(position, convertView, parent); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = getActivity().getLayoutInflater().inflate( + R.layout.spinner_dropdown_item, parent, false); + } + + CharSequence title = null; + CharSequence subtitle = null; + + PrinterInfo printer = (PrinterInfo) getItem(position); + title = printer.getName(); + try { + PackageManager pm = getActivity().getPackageManager(); + PackageInfo packageInfo = pm.getPackageInfo(printer.getId() + .getServiceName().getPackageName(), 0); + subtitle = packageInfo.applicationInfo.loadLabel(pm); + } catch (NameNotFoundException nnfe) { + /* ignore */ + } + + TextView titleView = (TextView) convertView.findViewById(R.id.title); + titleView.setText(title); + + TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle); + if (!TextUtils.isEmpty(subtitle)) { + subtitleView.setText(subtitle); + subtitleView.setVisibility(View.VISIBLE); + } else { + subtitleView.setText(null); + subtitleView.setVisibility(View.GONE); + } + + return convertView; + } + + @Override + public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) { + if (id == LOADER_ID_PRINTERS_LOADER) { + return new FusedPrintersProvider(getActivity()); + } + return null; + } + + @Override + public void onLoadFinished(Loader<List<PrinterInfo>> loader, + List<PrinterInfo> printers) { + synchronized (mLock) { + mPrinters.clear(); + mPrinters.addAll(printers); + mFilteredPrinters.clear(); + mFilteredPrinters.addAll(printers); + if (!TextUtils.isEmpty(mLastSearchString)) { + getFilter().filter(mLastSearchString); + } + } + notifyDataSetChanged(); + } + + @Override + public void onLoaderReset(Loader<List<PrinterInfo>> loader) { + synchronized (mLock) { + mPrinters.clear(); + mFilteredPrinters.clear(); + } + notifyDataSetInvalidated(); + } + } +} diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java index 491dddcaa9b0..5c6846014672 100644 --- a/services/java/com/android/server/print/RemotePrintService.java +++ b/services/java/com/android/server/print/RemotePrintService.java @@ -30,8 +30,6 @@ import android.os.Message; 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; @@ -79,6 +77,10 @@ final class RemotePrintService implements DeathRecipient { private boolean mDestroyed; + private boolean mAllPrintJobsHandled; + + private boolean mHasPrinterDiscoverySession; + public RemotePrintService(Context context, ComponentName componentName, int userId, RemotePrintSpooler spooler) { mContext = context; @@ -97,6 +99,8 @@ final class RemotePrintService implements DeathRecipient { private void handleDestroy() { throwIfDestroyed(); ensureUnbound(); + mAllPrintJobsHandled = false; + mHasPrinterDiscoverySession = false; mDestroyed = true; } @@ -110,18 +114,27 @@ final class RemotePrintService implements DeathRecipient { } 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(); + } } } @@ -153,6 +166,9 @@ final class RemotePrintService implements DeathRecipient { private void handleOnPrintJobQueued(final PrintJobInfo printJob) { throwIfDestroyed(); + + mAllPrintJobsHandled = false; + if (!isBound()) { ensureBound(); mPendingCommands.add(new Runnable() { @@ -173,20 +189,18 @@ final class RemotePrintService implements DeathRecipient { } } - 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 { @@ -194,9 +208,126 @@ final class RemotePrintService implements DeathRecipient { 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); } } } @@ -279,20 +410,47 @@ final class RemotePrintService implements DeathRecipient { } 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; @@ -307,13 +465,6 @@ final class RemotePrintService implements DeathRecipient { 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; @@ -363,7 +514,7 @@ final class RemotePrintService implements DeathRecipient { } @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(); @@ -402,79 +553,70 @@ final class RemotePrintService implements DeathRecipient { } } } - } - - 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); } } diff --git a/services/java/com/android/server/print/RemotePrintSpooler.java b/services/java/com/android/server/print/RemotePrintSpooler.java index c932e9b418f8..d261288763f8 100644 --- a/services/java/com/android/server/print/RemotePrintSpooler.java +++ b/services/java/com/android/server/print/RemotePrintSpooler.java @@ -32,9 +32,10 @@ import android.print.IPrintDocumentAdapter; 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; @@ -92,7 +93,11 @@ final class RemotePrintSpooler { 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, @@ -209,7 +214,7 @@ final class RemotePrintSpooler { 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(); @@ -300,6 +305,78 @@ final class RemotePrintSpooler { } } + 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) { @@ -488,7 +565,7 @@ final class RemotePrintSpooler { } 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); @@ -597,12 +674,64 @@ final class RemotePrintSpooler { } @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); } diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java index ffcc9c3057bf..9d7cfdd97fbd 100644 --- a/services/java/com/android/server/print/UserState.java +++ b/services/java/com/android/server/print/UserState.java @@ -21,8 +21,8 @@ import android.content.Context; 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; @@ -105,7 +105,7 @@ final class UserState implements PrintSpoolerCallbacks { } @Override - public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) { + public void createPrinterDiscoverySession() { final List<RemotePrintService> services; synchronized (mLock) { throwIfDestroyedLocked(); @@ -117,7 +117,73 @@ final class UserState implements PrintSpoolerCallbacks { 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); } } |