Merge "Implemented advanced printer selection and API refactoring." into klp-dev

This commit is contained in:
Svetoslav
2013-08-20 20:42:05 +00:00
committed by Android (Google) Code Review
39 changed files with 3794 additions and 2378 deletions

View File

@@ -162,8 +162,6 @@ LOCAL_SRC_FILES += \
core/java/android/os/IVibratorService.aidl \ core/java/android/os/IVibratorService.aidl \
core/java/android/service/notification/INotificationListener.aidl \ core/java/android/service/notification/INotificationListener.aidl \
core/java/android/print/ILayoutResultCallback.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/IPrintDocumentAdapter.aidl \
core/java/android/print/IPrintClient.aidl \ core/java/android/print/IPrintClient.aidl \
core/java/android/print/IPrintManager.aidl \ core/java/android/print/IPrintManager.aidl \

View File

@@ -19038,7 +19038,7 @@ package android.print {
method public android.print.PrintAttributes getAttributes(); method public android.print.PrintAttributes getAttributes();
method public int getCopies(); method public int getCopies();
method public int getId(); 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.PageRange[] getPages();
method public android.print.PrinterId getPrinterId(); method public android.print.PrinterId getPrinterId();
method public int getState(); method public int getState();
@@ -19162,7 +19162,7 @@ package android.printservice {
public final class PrintJob { public final class PrintJob {
method public boolean cancel(); method public boolean cancel();
method public boolean complete(); 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 android.printservice.PrintDocument getDocument();
method public int getId(); method public int getId();
method public android.print.PrintJobInfo getInfo(); method public android.print.PrintJobInfo getInfo();
@@ -19191,11 +19191,15 @@ package android.printservice {
} }
public abstract class PrinterDiscoverySession { 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 final void addPrinters(java.util.List<android.print.PrinterInfo>);
method public abstract void onClose(); method public final java.util.List<android.print.PrinterInfo> getPrinters();
method public abstract void onOpen(java.util.List<android.print.PrinterId>); 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 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 removePrinters(java.util.List<android.print.PrinterId>);
method public final void updatePrinters(java.util.List<android.print.PrinterInfo>); method public final void updatePrinters(java.util.List<android.print.PrinterInfo>);
} }

View File

@@ -18,6 +18,7 @@ package android.print;
import android.content.ComponentName; import android.content.ComponentName;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.print.PrinterId;
import android.print.IPrintDocumentAdapter; import android.print.IPrintDocumentAdapter;
import android.print.IPrintClient; import android.print.IPrintClient;
import android.print.IPrintSpoolerClient; import android.print.IPrintSpoolerClient;
@@ -40,10 +41,15 @@ oneway interface IPrintSpooler {
void createPrintJob(String printJobName, in IPrintClient client, void createPrintJob(String printJobName, in IPrintClient client,
in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes, in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes,
IPrintSpoolerCallbacks callback, int appId, int sequence); 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); IPrintSpoolerCallbacks callback, int sequence);
void setPrintJobTag(int printJobId, String tag, IPrintSpoolerCallbacks callback, void setPrintJobTag(int printJobId, String tag, IPrintSpoolerCallbacks callback,
int sequence); int sequence);
void writePrintJobData(in ParcelFileDescriptor fd, int printJobId); void writePrintJobData(in ParcelFileDescriptor fd, int printJobId);
void setClient(IPrintSpoolerClient client); 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);
} }

View File

@@ -17,7 +17,6 @@
package android.print; package android.print;
import android.content.ComponentName; import android.content.ComponentName;
import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrinterId; import android.print.PrinterId;
import android.print.PrintJobInfo; import android.print.PrintJobInfo;
@@ -28,8 +27,14 @@ import android.print.PrintJobInfo;
* @hide * @hide
*/ */
oneway interface IPrintSpoolerClient { oneway interface IPrintSpoolerClient {
void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
void onPrintJobQueued(in PrintJobInfo printJob); void onPrintJobQueued(in PrintJobInfo printJob);
void onAllPrintJobsForServiceHandled(in ComponentName printService); void onAllPrintJobsForServiceHandled(in ComponentName printService);
void onAllPrintJobsHandled(); void onAllPrintJobsHandled();
// Printer discovery APIs
void createPrinterDiscoverySession();
void startPrinterDiscovery(in List<PrinterId> priorityList);
void stopPrinterDiscovery();
void requestPrinterUpdate(in PrinterId printerId);
void destroyPrinterDiscoverySession();
} }

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -104,7 +104,7 @@ public final class PrintJobInfo implements Parcelable {
private int mId; private int mId;
/** The human readable print job label. */ /** The human readable print job label. */
private CharSequence mLabel; private String mLabel;
/** The unique id of the printer. */ /** The unique id of the printer. */
private PrinterId mPrinterId; private PrinterId mPrinterId;
@@ -128,7 +128,7 @@ public final class PrintJobInfo implements Parcelable {
private int mCopies; private int mCopies;
/** Failure reason if this job failed. */ /** Failure reason if this job failed. */
private CharSequence mFailureReason; private String mFailureReason;
/** The pages to print */ /** The pages to print */
private PageRange[] mPageRanges; private PageRange[] mPageRanges;
@@ -163,7 +163,7 @@ public final class PrintJobInfo implements Parcelable {
private PrintJobInfo(Parcel parcel) { private PrintJobInfo(Parcel parcel) {
mId = parcel.readInt(); mId = parcel.readInt();
mLabel = parcel.readCharSequence(); mLabel = parcel.readString();
mPrinterId = parcel.readParcelable(null); mPrinterId = parcel.readParcelable(null);
mPrinterName = parcel.readString(); mPrinterName = parcel.readString();
mState = parcel.readInt(); mState = parcel.readInt();
@@ -171,9 +171,7 @@ public final class PrintJobInfo implements Parcelable {
mUserId = parcel.readInt(); mUserId = parcel.readInt();
mTag = parcel.readString(); mTag = parcel.readString();
mCopies = parcel.readInt(); mCopies = parcel.readInt();
if (parcel.readInt() == 1) { mFailureReason = parcel.readString();
mFailureReason = parcel.readCharSequence();
}
if (parcel.readInt() == 1) { if (parcel.readInt() == 1) {
Parcelable[] parcelables = parcel.readParcelableArray(null); Parcelable[] parcelables = parcel.readParcelableArray(null);
mPageRanges = new PageRange[parcelables.length]; mPageRanges = new PageRange[parcelables.length];
@@ -214,7 +212,7 @@ public final class PrintJobInfo implements Parcelable {
* *
* @return The label. * @return The label.
*/ */
public CharSequence getLabel() { public String getLabel() {
return mLabel; return mLabel;
} }
@@ -225,7 +223,7 @@ public final class PrintJobInfo implements Parcelable {
* *
* @hide * @hide
*/ */
public void setLabel(CharSequence label) { public void setLabel(String label) {
mLabel = label; mLabel = label;
} }
@@ -385,7 +383,7 @@ public final class PrintJobInfo implements Parcelable {
* *
* @hide * @hide
*/ */
public CharSequence getFailureReason() { public String getFailureReason() {
return mFailureReason; return mFailureReason;
} }
@@ -396,7 +394,7 @@ public final class PrintJobInfo implements Parcelable {
* *
* @hide * @hide
*/ */
public void setFailureReason(CharSequence failureReason) { public void setFailureReason(String failureReason) {
mFailureReason = failureReason; mFailureReason = failureReason;
} }
@@ -470,7 +468,7 @@ public final class PrintJobInfo implements Parcelable {
@Override @Override
public void writeToParcel(Parcel parcel, int flags) { public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mId); parcel.writeInt(mId);
parcel.writeCharSequence(mLabel); parcel.writeString(mLabel);
parcel.writeParcelable(mPrinterId, flags); parcel.writeParcelable(mPrinterId, flags);
parcel.writeString(mPrinterName); parcel.writeString(mPrinterName);
parcel.writeInt(mState); parcel.writeInt(mState);
@@ -478,12 +476,7 @@ public final class PrintJobInfo implements Parcelable {
parcel.writeInt(mUserId); parcel.writeInt(mUserId);
parcel.writeString(mTag); parcel.writeString(mTag);
parcel.writeInt(mCopies); parcel.writeInt(mCopies);
if (mFailureReason != null) { parcel.writeString(mFailureReason);
parcel.writeInt(1);
parcel.writeCharSequence(mFailureReason);
} else {
parcel.writeInt(0);
}
if (mPageRanges != null) { if (mPageRanges != null) {
parcel.writeInt(1); parcel.writeInt(1);
parcel.writeParcelableArray(mPageRanges, flags); parcel.writeParcelableArray(mPageRanges, flags);

View File

@@ -374,14 +374,14 @@ public final class PrintManager {
@Override @Override
public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
if (info == null) {
throw new NullPointerException("document info cannot be null");
}
final ILayoutResultCallback callback; final ILayoutResultCallback callback;
synchronized (mLock) { synchronized (mLock) {
callback = mCallback; callback = mCallback;
clearLocked(); clearLocked();
} }
if (info == null) {
throw new IllegalArgumentException("info cannot be null");
}
if (callback != null) { if (callback != null) {
try { try {
callback.onLayoutFinished(info, changed, mSequence); callback.onLayoutFinished(info, changed, mSequence);

View File

@@ -229,10 +229,11 @@ public final class PrinterInfo implements Parcelable {
/** /**
* Constructor. * Constructor.
* *
* @param prototype Prototype from which to start building. * @param other Other info from which to start building.
*/ */
public Builder(PrinterInfo prototype) { public Builder(PrinterInfo other) {
mPrototype = prototype; mPrototype = new PrinterInfo();
mPrototype.copyFrom(other);
} }
/** /**

View File

@@ -324,7 +324,7 @@ public final class PdfDocument {
/** /**
* Creates a new builder with the mandatory page info attributes. * 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 pageNumber The page number.
* @param density The page density in DPI. * @param density The page density in DPI.
*/ */

View File

@@ -16,7 +16,7 @@
package android.printservice; package android.printservice;
import android.print.IPrinterDiscoverySessionObserver; import android.print.PrinterId;
import android.print.PrintJobInfo; import android.print.PrintJobInfo;
import android.printservice.IPrintServiceClient; import android.printservice.IPrintServiceClient;
@@ -29,5 +29,10 @@ oneway interface IPrintService {
void setClient(IPrintServiceClient client); void setClient(IPrintServiceClient client);
void requestCancelPrintJob(in PrintJobInfo printJobInfo); void requestCancelPrintJob(in PrintJobInfo printJobInfo);
void onPrintJobQueued(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();
} }

View File

@@ -29,7 +29,11 @@ import android.print.PrinterInfo;
interface IPrintServiceClient { interface IPrintServiceClient {
List<PrintJobInfo> getPrintJobInfos(); List<PrintJobInfo> getPrintJobInfos();
PrintJobInfo getPrintJobInfo(int printJobId); 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); boolean setPrintJobTag(int printJobId, String tag);
oneway void writePrintJobData(in ParcelFileDescriptor fd, int printJobId); 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);
} }

View File

@@ -24,6 +24,10 @@ import android.util.Log;
* This class represents a print job from the perspective of a print * This class represents a print job from the perspective of a print
* service. It provides APIs for observing the print job state and * service. It provides APIs for observing the print job state and
* performing operations on the print job. * 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 { public final class PrintJob {
@@ -48,6 +52,7 @@ public final class PrintJob {
* @return The id. * @return The id.
*/ */
public int getId() { public int getId() {
PrintService.throwIfNotCalledOnMainThread();
return mCachedInfo.getId(); return mCachedInfo.getId();
} }
@@ -62,6 +67,7 @@ public final class PrintJob {
* @return The print job info. * @return The print job info.
*/ */
public PrintJobInfo getInfo() { public PrintJobInfo getInfo() {
PrintService.throwIfNotCalledOnMainThread();
if (isInImmutableState()) { if (isInImmutableState()) {
return mCachedInfo; return mCachedInfo;
} }
@@ -83,6 +89,7 @@ public final class PrintJob {
* @return The document. * @return The document.
*/ */
public PrintDocument getDocument() { public PrintDocument getDocument() {
PrintService.throwIfNotCalledOnMainThread();
return mDocument; return mDocument;
} }
@@ -96,6 +103,7 @@ public final class PrintJob {
* @see #cancel() * @see #cancel()
*/ */
public boolean isQueued() { public boolean isQueued() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_QUEUED; return getInfo().getState() == PrintJobInfo.STATE_QUEUED;
} }
@@ -110,6 +118,7 @@ public final class PrintJob {
* @see #fail(CharSequence) * @see #fail(CharSequence)
*/ */
public boolean isStarted() { public boolean isStarted() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_STARTED; return getInfo().getState() == PrintJobInfo.STATE_STARTED;
} }
@@ -122,6 +131,7 @@ public final class PrintJob {
* @see #complete() * @see #complete()
*/ */
public boolean isCompleted() { public boolean isCompleted() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_COMPLETED; return getInfo().getState() == PrintJobInfo.STATE_COMPLETED;
} }
@@ -134,6 +144,7 @@ public final class PrintJob {
* @see #fail(CharSequence) * @see #fail(CharSequence)
*/ */
public boolean isFailed() { public boolean isFailed() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_FAILED; return getInfo().getState() == PrintJobInfo.STATE_FAILED;
} }
@@ -146,6 +157,7 @@ public final class PrintJob {
* @see #cancel() * @see #cancel()
*/ */
public boolean isCancelled() { public boolean isCancelled() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_FAILED; return getInfo().getState() == PrintJobInfo.STATE_FAILED;
} }
@@ -158,6 +170,7 @@ public final class PrintJob {
* @see #isQueued() * @see #isQueued()
*/ */
public boolean start() { public boolean start() {
PrintService.throwIfNotCalledOnMainThread();
if (isQueued()) { if (isQueued()) {
return setState(PrintJobInfo.STATE_STARTED, null); return setState(PrintJobInfo.STATE_STARTED, null);
} }
@@ -173,6 +186,7 @@ public final class PrintJob {
* @see #isStarted() * @see #isStarted()
*/ */
public boolean complete() { public boolean complete() {
PrintService.throwIfNotCalledOnMainThread();
if (isStarted()) { if (isStarted()) {
return setState(PrintJobInfo.STATE_COMPLETED, null); return setState(PrintJobInfo.STATE_COMPLETED, null);
} }
@@ -191,7 +205,8 @@ public final class PrintJob {
* @see #isQueued() * @see #isQueued()
* @see #isStarted() * @see #isStarted()
*/ */
public boolean fail(CharSequence error) { public boolean fail(String error) {
PrintService.throwIfNotCalledOnMainThread();
if (isQueued() || isStarted()) { if (isQueued() || isStarted()) {
return setState(PrintJobInfo.STATE_FAILED, error); return setState(PrintJobInfo.STATE_FAILED, error);
} }
@@ -210,6 +225,7 @@ public final class PrintJob {
* @see #isQueued() * @see #isQueued()
*/ */
public boolean cancel() { public boolean cancel() {
PrintService.throwIfNotCalledOnMainThread();
if (isQueued() || isStarted()) { if (isQueued() || isStarted()) {
return setState(PrintJobInfo.STATE_CANCELED, null); return setState(PrintJobInfo.STATE_CANCELED, null);
} }
@@ -226,6 +242,7 @@ public final class PrintJob {
* @return True if the tag was set, false otherwise. * @return True if the tag was set, false otherwise.
*/ */
public boolean setTag(String tag) { public boolean setTag(String tag) {
PrintService.throwIfNotCalledOnMainThread();
if (isInImmutableState()) { if (isInImmutableState()) {
return false; return false;
} }
@@ -263,7 +280,7 @@ public final class PrintJob {
|| state == PrintJobInfo.STATE_CANCELED; || state == PrintJobInfo.STATE_CANCELED;
} }
private boolean setState(int state, CharSequence error) { private boolean setState(int state, String error) {
try { try {
if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state, error)) { if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state, error)) {
// Best effort - update the state of the cached info since // Best effort - update the state of the cached info since

View File

@@ -25,7 +25,6 @@ import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.RemoteException; import android.os.RemoteException;
import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintJobInfo; import android.print.PrintJobInfo;
import android.print.PrinterId; import android.print.PrinterId;
import android.util.Log; import android.util.Log;
@@ -146,6 +145,11 @@ import java.util.List;
* {@link #SERVICE_META_DATA} and <code>&lt;{@link android.R.styleable#PrintService * {@link #SERVICE_META_DATA} and <code>&lt;{@link android.R.styleable#PrintService
* print-service}&gt;</code>. * print-service}&gt;</code>.
* </p> * </p>
* <p>
* <strong>Note: </strong> All callbacks in this class are executed on the main
* application thread. You should also invoke any method of this class on the main
* application thread.
* </p>
*/ */
public abstract class PrintService extends Service { public abstract class PrintService extends Service {
@@ -175,14 +179,14 @@ public abstract class PrintService extends Service {
*/ */
public static final String SERVICE_META_DATA = "android.printservice"; public static final String SERVICE_META_DATA = "android.printservice";
private final Object mLock = new Object();
private Handler mHandler; private Handler mHandler;
private IPrintServiceClient mClient; private IPrintServiceClient mClient;
private int mLastSessionId = -1; private int mLastSessionId = -1;
private PrinterDiscoverySession mDiscoverySession;
@Override @Override
protected final void attachBaseContext(Context base) { protected final void attachBaseContext(Context base) {
super.attachBaseContext(base); super.attachBaseContext(base);
@@ -245,21 +249,18 @@ public abstract class PrintService extends Service {
* @see PrintJob#isStarted() PrintJob.isStarted() * @see PrintJob#isStarted() PrintJob.isStarted()
*/ */
public final List<PrintJob> getActivePrintJobs() { public final List<PrintJob> getActivePrintJobs() {
final IPrintServiceClient client; throwIfNotCalledOnMainThread();
synchronized (mLock) { if (mClient == null) {
client = mClient;
}
if (client == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
try { try {
List<PrintJob> printJobs = null; List<PrintJob> printJobs = null;
List<PrintJobInfo> printJobInfos = client.getPrintJobInfos(); List<PrintJobInfo> printJobInfos = mClient.getPrintJobInfos();
if (printJobInfos != null) { if (printJobInfos != null) {
final int printJobInfoCount = printJobInfos.size(); final int printJobInfoCount = printJobInfos.size();
printJobs = new ArrayList<PrintJob>(printJobInfoCount); printJobs = new ArrayList<PrintJob>(printJobInfoCount);
for (int i = 0; i < printJobInfoCount; i++) { 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) { if (printJobs != null) {
@@ -278,23 +279,50 @@ public abstract class PrintService extends Service {
* @return Global printer id. * @return Global printer id.
*/ */
public final PrinterId generatePrinterId(String localId) { public final PrinterId generatePrinterId(String localId) {
throwIfNotCalledOnMainThread();
return new PrinterId(new ComponentName(getPackageName(), return new PrinterId(new ComponentName(getPackageName(),
getClass().getName()), localId); getClass().getName()), localId);
} }
static void throwIfNotCalledOnMainThread() {
if (!Looper.getMainLooper().isCurrentThread()) {
throw new IllegalAccessError("must be called from the main thread");
}
}
@Override @Override
public final IBinder onBind(Intent intent) { public final IBinder onBind(Intent intent) {
return new IPrintService.Stub() { return new IPrintService.Stub() {
@Override @Override
public void setClient(IPrintServiceClient client) { public void createPrinterDiscoverySession() {
mHandler.obtainMessage(ServiceHandler.MSG_SET_CLEINT, client) mHandler.sendEmptyMessage(ServiceHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
.sendToTarget();
} }
@Override @Override
public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) { public void destroyPrinterDiscoverySession() {
mHandler.obtainMessage(ServiceHandler.MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION, mHandler.sendEmptyMessage(ServiceHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
observer).sendToTarget(); }
public void startPrinterDiscovery(List<PrinterId> priorityList) {
mHandler.obtainMessage(ServiceHandler.MSG_START_PRINTER_DISCOVERY,
priorityList).sendToTarget();
}
@Override
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 @Override
@@ -312,33 +340,62 @@ public abstract class PrintService extends Service {
} }
private final class ServiceHandler extends Handler { private final class ServiceHandler extends Handler {
public static final int MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION = 1; public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1;
public static final int MSG_ON_PRINTJOB_QUEUED = 2; public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 3; public static final int MSG_START_PRINTER_DISCOVERY = 3;
public static final int MSG_SET_CLEINT = 4; 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) { public ServiceHandler(Looper looper) {
super(looper, null, true); super(looper, null, true);
} }
@Override @Override
@SuppressWarnings("unchecked")
public void handleMessage(Message message) { public void handleMessage(Message message) {
final int action = message.what; final int action = message.what;
switch (action) { switch (action) {
case MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION: { case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
IPrinterDiscoverySessionObserver observer =
(IPrinterDiscoverySessionObserver) message.obj;
PrinterDiscoverySession session = onCreatePrinterDiscoverySession(); PrinterDiscoverySession session = onCreatePrinterDiscoverySession();
if (session == null) { if (session == null) {
throw new NullPointerException("session cannot be null"); throw new NullPointerException("session cannot be null");
} }
synchronized (mLock) { if (session.getId() == mLastSessionId) {
if (session.getId() == mLastSessionId) { throw new IllegalStateException("cannot reuse session instances");
throw new IllegalStateException("cannot reuse sessions"); }
} mDiscoverySession = session;
mLastSessionId = session.getId(); 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; } break;
case MSG_ON_REQUEST_CANCEL_PRINTJOB: { case MSG_ON_REQUEST_CANCEL_PRINTJOB: {
@@ -352,15 +409,12 @@ public abstract class PrintService extends Service {
} break; } break;
case MSG_SET_CLEINT: { case MSG_SET_CLEINT: {
IPrintServiceClient client = (IPrintServiceClient) message.obj; mClient = (IPrintServiceClient) message.obj;
synchronized (mLock) { if (mClient != null) {
mClient = client;
}
if (client != null) {
onConnected(); onConnected();
} else { } else {
onDisconnected(); onDisconnected();
} }
} break; } break;
default: { default: {

View File

@@ -16,18 +16,15 @@
package android.printservice; 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.os.RemoteException;
import android.print.IPrinterDiscoverySessionController; import android.print.PrinterCapabilitiesInfo;
import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrinterId; import android.print.PrinterId;
import android.print.PrinterInfo; import android.print.PrinterInfo;
import android.util.ArrayMap;
import android.util.Log; import android.util.Log;
import java.lang.ref.WeakReference; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
@@ -36,67 +33,75 @@ import java.util.List;
* for adding discovered printers, removing already added printers that * for adding discovered printers, removing already added printers that
* disappeared, and updating already added printers. * disappeared, and updating already added printers.
* <p> * <p>
* The opening of the session is announced by a call to {@link * During the lifetime of this session you may be asked to start and stop
* PrinterDiscoverySession#onOpen(List)} at which point you should start printer * performing printer discovery multiple times. You will receive a call to {@link
* discovery. The closing of the session is announced by a call to {@link * PrinterDiscoverySession#onStartPrinterDiscovery(List)} to start printer
* PrinterDiscoverySession#onClose()} at which point you should stop printer * discovery and a call to {@link PrinterDiscoverySession#onStopPrinterDiscovery()}
* discovery. Discovered printers are added by invoking {@link * to stop printer discovery. When the system is no longer interested in printers
* PrinterDiscoverySession#addPrinters(List)}. Added printers that disappeared * discovered by this session you will receive a call to {@link #onDestroy()} at
* are removed by invoking {@link PrinterDiscoverySession#removePrinters(List)}. * which point the system will no longer call into the session and all the session
* Added printers whose properties or capabilities changed are updated through * methods will do nothing.
* a call to {@link PrinterDiscoverySession#updatePrinters(List)}. * </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>
* <p> * <p>
* The system will make a call to * The system will make a call to
* {@link PrinterDiscoverySession#onRequestPrinterUpdate(PrinterId)} if you * {@link PrinterDiscoverySession#onRequestPrinterUpdate(PrinterId)} if you
* need to update a given printer. It is possible that you add a printer without * 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 * specifying its capabilities. This enables you to avoid querying all discovered
* discovered printers for their capabilities, rather querying the capabilities * printers for their capabilities, rather querying the capabilities of a printer
* of a printer only if necessary. For example, the system will require that you * only if necessary. For example, the system will request that you update a printer
* update a printer if it gets selected by the user. If you did not report the * if it gets selected by the user. If you did not report the printer capabilities
* printer capabilities when adding it, you must do so after the system requests * when adding it, you must do so after the system requests a printer update.
* a printer update. Otherwise, the printer will be ignored. * Otherwise, the printer will be ignored.
* </p> * </p>
* <p> * <p>
* During printer discovery all printers that are known to your print service * <strong>Note: </strong> All callbacks in this class are executed on the main
* have to be added. The system does not retain any printers from previous * application thread. You also have to invoke any method of this class on the main
* sessions. * application thread.
* </p> * </p>
*/ */
public abstract class PrinterDiscoverySession { public abstract class PrinterDiscoverySession {
private static final String LOG_TAG = "PrinterDiscoverySession"; private static final String LOG_TAG = "PrinterDiscoverySession";
private static final int MAX_ITEMS_PER_CALLBACK = 100;
private static int sIdCounter = 0; private static int sIdCounter = 0;
private final Object mLock = new Object();
private final Handler mHandler;
private final int mId; private final int mId;
private IPrinterDiscoverySessionController mController; private final ArrayMap<PrinterId, PrinterInfo> mPrinters =
new ArrayMap<PrinterId, PrinterInfo>();
private IPrinterDiscoverySessionObserver mObserver; private ArrayMap<PrinterId, PrinterInfo> mLastSentPrinters;
private IPrintServiceClient mObserver;
private boolean mIsDestroyed;
private boolean mIsDiscoveryStarted;
/** /**
* Constructor. * Constructor.
*
* @param context A context instance.
*/ */
public PrinterDiscoverySession(Context context) { public PrinterDiscoverySession() {
mId = sIdCounter++; mId = sIdCounter++;
mHandler = new SessionHandler(context.getMainLooper());
mController = new PrinterDiscoverySessionController(this);
} }
void setObserver(IPrinterDiscoverySessionObserver observer) { void setObserver(IPrintServiceClient observer) {
synchronized (mLock) { mObserver = observer;
mObserver = observer; // If some printers were added in the method that
try { // created the session, send them over.
mObserver.setController(mController); if (!mPrinters.isEmpty()) {
} catch (RemoteException re) { sendAddedPrinters(mObserver, getPrinters());
Log.e(LOG_TAG, "Error setting session controller", re);
}
} }
} }
@@ -104,132 +109,358 @@ public abstract class PrinterDiscoverySession {
return mId; return mId;
} }
/**
* Gets the printers reported in this session. For example, if you add two
* printers and remove one of them, the returned list will contain only
* the printer that was added but not removed.
* <p>
* <strong>Note: </strong> Calls to this method after the session is
* destroyed, 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. * Adds discovered printers. Adding an already added printer has no effect.
* Removed printers can be added again. You can call this method multiple * 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> * <p>
* <strong>Note: </strong> Calls to this method before the session is opened, * <strong>Note: </strong> Calls to this method after the session is
* i.e. before the {@link #onOpen(List)} call, and after the session is closed, * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
* i.e. after the call to {@link #onClose()}, will be ignored.
* </p> * </p>
* *
* @param printers The printers to add. * @param printers The printers to add.
* *
* @see #removePrinters(List) * @see #removePrinters(List)
* @see #updatePrinters(List) * @see #updatePrinters(List)
* @see #getPrinters()
* @see #isDestroyed()
*/ */
public final void addPrinters(List<PrinterInfo> printers) { public final void addPrinters(List<PrinterInfo> printers) {
final IPrinterDiscoverySessionObserver observer; PrintService.throwIfNotCalledOnMainThread();
synchronized (mLock) {
observer = mObserver; // If the session is destroyed - nothing do to.
if (mIsDestroyed) {
Log.w(LOG_TAG, "Not adding printers - session destroyed.");
return;
} }
if (observer != null) {
try { if (mIsDiscoveryStarted) {
observer.onPrintersAdded(printers); // If during discovery, add the new printers and send them.
} catch (RemoteException re) { List<PrinterInfo> addedPrinters = new ArrayList<PrinterInfo>();
Log.e(LOG_TAG, "Error adding printers", re); 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 { } 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 * Removes added printers. Removing an already removed or never added
* printer has no effect. Removed printers can be added again. You * printer has no effect. Removed printers can be added again. You can
* can call this method multiple times during printer discovery. * call this method multiple times during the lifetime of this session.
* <p> * <p>
* <strong>Note: </strong> Calls to this method before the session is opened, * <strong>Note: </strong> Calls to this method after the session is
* i.e. before the {@link #onOpen(List)} call, and after the session is closed, * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
* i.e. after the call to {@link #onClose()}, will be ignored.
* </p> * </p>
* *
* @param printerIds The ids of the removed printers. * @param printerIds The ids of the removed printers.
* *
* @see #addPrinters(List) * @see #addPrinters(List)
* @see #updatePrinters(List) * @see #updatePrinters(List)
* @see #getPrinters()
* @see #isDestroyed()
*/ */
public final void removePrinters(List<PrinterId> printerIds) { public final void removePrinters(List<PrinterId> printerIds) {
final IPrinterDiscoverySessionObserver observer; PrintService.throwIfNotCalledOnMainThread();
synchronized (mLock) {
observer = mObserver; // If the session is destroyed - nothing do to.
if (mIsDestroyed) {
Log.w(LOG_TAG, "Not removing printers - session destroyed.");
return;
} }
if (observer != null) {
try { if (mIsDiscoveryStarted) {
observer.onPrintersRemoved(printerIds); // If during discovery, remove existing printers and send them.
} catch (RemoteException re) { List<PrinterId> removedPrinterIds = new ArrayList<PrinterId>();
Log.e(LOG_TAG, "Error removing printers", re); 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 { } 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 * Updates added printers. Updating a printer that was not added or that
* was removed has no effect. You can call this method multiple times * was removed has no effect. You can call this method multiple times
* during printer discovery. * during the lifetime of this session.
* <p> * <p>
* <strong>Note: </strong> Calls to this method before the session is opened, * <strong>Note: </strong> Calls to this method after the session is
* i.e. before the {@link #onOpen(List)} call, and after the session is closed, * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
* i.e. after the call to {@link #onClose()}, will be ignored.
* </p> * </p>
* *
* @param printers The printers to update. * @param printers The printers to update.
* *
* @see #addPrinters(List) * @see #addPrinters(List)
* @see #removePrinters(List) * @see #removePrinters(List)
* @see #getPrinters()
* @see #isDestroyed()
*/ */
public final void updatePrinters(List<PrinterInfo> printers) { public final void updatePrinters(List<PrinterInfo> printers) {
final IPrinterDiscoverySessionObserver observer; PrintService.throwIfNotCalledOnMainThread();
synchronized (mLock) {
observer = mObserver; // If the session is destroyed - nothing do to.
if (mIsDestroyed) {
Log.w(LOG_TAG, "Not updating printers - session destroyed.");
return;
} }
if (observer != null) {
try { if (mIsDiscoveryStarted) {
observer.onPrintersUpdated(printers); // If during discovery, update existing printers and send them.
} catch (RemoteException re) { List<PrinterInfo> updatedPrinters = new ArrayList<PrinterInfo>();
Log.e(LOG_TAG, "Error updating printers", re); 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 { } 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 * Callback asking you to start printer discovery. Discovered printers should be
* printer discovery. Discovered printers should be added via calling * added via calling {@link #addPrinters(List)}. Added printers that disappeared
* {@link #addPrinters(List)}. Added printers that disappeared should be * should be removed via calling {@link #removePrinters(List)}. Added printers
* removed via calling {@link #removePrinters(List)}. Added printers whose * whose properties or capabilities changed should be updated via calling {@link
* properties or capabilities changes should be updated via calling {@link * #updatePrinters(List)}. You will receive a call to call to {@link
* #updatePrinters(List)}. When the session is closed you will receive a * #onStopPrinterDiscovery()} when you should stop printer discovery.
* call to {@link #onClose()}.
* <p> * <p>
* During printer discovery all printers that are known to your print * 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 from * service have to be added. The system does not retain any printers across sessions.
* previous 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>
* <p> * <p>
* <strong>Note: </strong>You are also given a list of printers whose * <strong>Note: </strong>You are also given a list of printers whose availability
* availability has to be checked first. For example, these printers could * has to be checked first. For example, these printers could be the user's favorite
* be the user's favorite ones, therefore they have to be verified first. * ones, therefore they have to be verified first.
* </p> * </p>
* *
* @see #onClose() * @param priorityList The list of printers to validate first. Never null.
*
* @see #onStopPrinterDiscovery()
* @see #addPrinters(List) * @see #addPrinters(List)
* @see #removePrinters(List) * @see #removePrinters(List)
* @see #updatePrinters(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 * Callback notifying you that you should stop printer discovery.
* printer discovery. After the session is closed any call to the methods *
* of this instance will be ignored. Once the session is closed * @see #onStartPrinterDiscovery(List)
* it will never be opened again. * @see #isPrinterDiscoveryStarted()
*/ */
public abstract void onClose(); public abstract void onStopPrinterDiscovery();
/** /**
* Requests that you update a printer. You are responsible for updating * 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); public abstract void onRequestPrinterUpdate(PrinterId printerId);
void close() { /**
synchronized (mLock) { * Notifies you that the session is destroyed. After this callback is invoked
mController = null; * any calls to the methods of this class will be ignored, {@link #isDestroyed()}
* will return true and you will also no longer receive callbacks.
*
* @see #isDestroyed()
*/
public abstract void onDestroy();
/**
* Gets whether the session is destroyed.
*
* @return Whether the session is destroyed.
*
* @see #onDestroy()
*/
public final boolean isDestroyed() {
PrintService.throwIfNotCalledOnMainThread();
return mIsDestroyed;
}
/**
* Gets whether printer discovery is started.
*
* @return Whether printer discovery is destroyed.
*
* @see #onStartPrinterDiscovery(List)
* @see #onStopPrinterDiscovery()
*/
public final boolean isPrinterDiscoveryStarted() {
PrintService.throwIfNotCalledOnMainThread();
return mIsDiscoveryStarted;
}
void startPrinterDiscovery(List<PrinterId> priorityList) {
if (!mIsDestroyed) {
mIsDiscoveryStarted = true;
sendOutOfDiscoveryPeriodPrinterChanges();
if (priorityList == null) {
priorityList = Collections.emptyList();
}
onStartPrinterDiscovery(priorityList);
}
}
void stopPrinterDiscovery() {
if (!mIsDestroyed) {
mIsDiscoveryStarted = false;
onStopPrinterDiscovery();
}
}
void requestPrinterUpdate(PrinterId printerId) {
if (!mIsDestroyed) {
onRequestPrinterUpdate(printerId);
}
}
void destroy() {
if (!mIsDestroyed) {
mIsDestroyed = true;
mIsDiscoveryStarted = false;
mPrinters.clear();
mLastSentPrinters = null;
mObserver = null; mObserver = null;
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;
public SessionHandler(Looper looper) {
super(looper, null, true);
}
@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;
}
}
}
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();
}
}
@Override
public void close() {
PrinterDiscoverySession session = mWeakSession.get();
if (session != null) {
session.mHandler.sendEmptyMessage(SessionHandler.MSG_CLOSE);
}
}
@Override
public void requestPrinterUpdate(PrinterId printerId) {
PrinterDiscoverySession session = mWeakSession.get();
if (session != null) {
session.mHandler.obtainMessage(
SessionHandler.MSG_REQUEST_PRINTER_UPDATE,
printerId).sendToTarget();
}
}
};
} }

View File

@@ -65,7 +65,11 @@ public class HandlerCaller {
mH.sendMessage(msg); mH.sendMessage(msg);
} }
public void sendMessageDelayed(Message msg, long delayMillis) {
mH.sendMessageDelayed(msg, delayMillis);
}
public boolean hasMessages(int what) { public boolean hasMessages(int what) {
return mH.hasMessages(what); return mH.hasMessages(what);
} }

View File

@@ -18,7 +18,7 @@
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.printspooler" package="com.android.printspooler"
android:sharedUserId="android.uid.printspooler" android:sharedUserId="android.uid.system"
android:versionName="1" android:versionName="1"
android:versionCode="1" android:versionCode="1"
coreApp="true"> coreApp="true">
@@ -51,9 +51,10 @@
</activity> </activity>
<activity <activity
android:name=".ChoosePrinterActivity" android:name=".SelectPrinterActivity"
android:exported="false" android:label="@string/all_printers_label"
android:theme="@android:style/Theme.Holo.Light"> android:theme="@style/SelectPrinterActivityTheme"
android:exported="false">
</activity> </activity>
<receiver <receiver

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

View File

@@ -20,9 +20,4 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:background="@color/container_background"> android:background="@color/container_background">
<include
layout="@layout/print_job_config_activity_content_editing">
</include>
</FrameLayout> </FrameLayout>

View File

@@ -5,7 +5,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at 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 Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,10 +14,16 @@
limitations under the License. limitations under the License.
--> -->
<ListView xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_view" android:orientation="horizontal"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="wrap_content">
android:orientation="vertical">
</ListView>
<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>

View File

@@ -15,7 +15,7 @@
--> -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <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:layout_height="wrap_content"
android:paddingStart="8dip" android:paddingStart="8dip"
android:paddingEnd="8dip" android:paddingEnd="8dip"

View File

@@ -23,7 +23,15 @@
android:actionViewClass="android.widget.SearchView" android:actionViewClass="android.widget.SearchView"
android:showAsAction="ifRoom" android:showAsAction="ifRoom"
android:alphabeticShortcut="f" 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> </item>
</menu> </menu>

View File

@@ -58,11 +58,32 @@
<!-- Title for the temporary dialog show while an app is generating a print job. [CHAR LIMIT=30] --> <!-- 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> <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] --> <!-- Title for the share action bar menu item. [CHAR LIMIT=20] -->
<string name="search">Search</string> <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 --> <!-- Notifications -->
<!-- Template for the notificaiton label for a printing print job. [CHAR LIMIT=25] --> <!-- Template for the notificaiton label for a printing print job. [CHAR LIMIT=25] -->

View File

@@ -24,4 +24,12 @@
<item name="android:colorBackgroundCacheHint">@android:color/transparent</item> <item name="android:colorBackgroundCacheHint">@android:color/transparent</item>
</style> </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> </resources>

View File

@@ -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();
}
}
};
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -17,12 +17,25 @@
package com.android.printspooler; package com.android.printspooler;
import android.app.Activity; import android.app.Activity;
import android.content.Intent;
import android.os.Bundle; 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 @Override
public void onCreate(Bundle bundle) { protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.choose_printer_activity); super.onCreate(savedInstanceState);
setContentView(R.layout.select_printer_activity);
}
@Override
public void onPrinterSelected(PrinterId printer) {
Intent intent = new Intent();
intent.putExtra(PrintJobConfigActivity.INTENT_EXTRA_PRINTER_ID, printer);
setResult(RESULT_OK, intent);
finish();
} }
} }

View File

@@ -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();
}
}
}

View File

@@ -30,8 +30,6 @@ import android.os.Message;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.UserHandle; import android.os.UserHandle;
import android.print.IPrinterDiscoverySessionController;
import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintJobInfo; import android.print.PrintJobInfo;
import android.print.PrintManager; import android.print.PrintManager;
import android.print.PrinterId; import android.print.PrinterId;
@@ -79,6 +77,10 @@ final class RemotePrintService implements DeathRecipient {
private boolean mDestroyed; private boolean mDestroyed;
private boolean mAllPrintJobsHandled;
private boolean mHasPrinterDiscoverySession;
public RemotePrintService(Context context, ComponentName componentName, int userId, public RemotePrintService(Context context, ComponentName componentName, int userId,
RemotePrintSpooler spooler) { RemotePrintSpooler spooler) {
mContext = context; mContext = context;
@@ -97,6 +99,8 @@ final class RemotePrintService implements DeathRecipient {
private void handleDestroy() { private void handleDestroy() {
throwIfDestroyed(); throwIfDestroyed();
ensureUnbound(); ensureUnbound();
mAllPrintJobsHandled = false;
mHasPrinterDiscoverySession = false;
mDestroyed = true; mDestroyed = true;
} }
@@ -110,18 +114,27 @@ final class RemotePrintService implements DeathRecipient {
} }
private void handleBinderDied() { private void handleBinderDied() {
mAllPrintJobsHandled = false;
mHasPrinterDiscoverySession = false;
mPendingCommands.clear(); mPendingCommands.clear();
ensureUnbound(); ensureUnbound();
} }
private void handleOnAllPrintJobsHandled() { private void handleOnAllPrintJobsHandled() {
throwIfDestroyed(); throwIfDestroyed();
mAllPrintJobsHandled = true;
if (isBound()) { if (isBound()) {
if (DEBUG) { if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnAllPrintJobsHandled()"); 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) { private void handleOnPrintJobQueued(final PrintJobInfo printJob) {
throwIfDestroyed(); throwIfDestroyed();
mAllPrintJobsHandled = false;
if (!isBound()) { if (!isBound()) {
ensureBound(); ensureBound();
mPendingCommands.add(new Runnable() { mPendingCommands.add(new Runnable() {
@@ -173,20 +189,18 @@ final class RemotePrintService implements DeathRecipient {
} }
} }
public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) { public void createPrinterDiscoverySession() {
mHandler.obtainMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION, mHandler.sendEmptyMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
observer).sendToTarget();
} }
private void handleCreatePrinterDiscoverySession( private void handleCreatePrinterDiscoverySession() {
final IPrinterDiscoverySessionObserver observer) {
throwIfDestroyed(); throwIfDestroyed();
if (!isBound()) { if (!isBound()) {
ensureBound(); ensureBound();
mPendingCommands.add(new Runnable() { mPendingCommands.add(new Runnable() {
@Override @Override
public void run() { public void run() {
handleCreatePrinterDiscoverySession(observer); handleCreatePrinterDiscoverySession();
} }
}); });
} else { } else {
@@ -194,9 +208,126 @@ final class RemotePrintService implements DeathRecipient {
Slog.i(LOG_TAG, "[user: " + mUserId + "] createPrinterDiscoverySession()"); Slog.i(LOG_TAG, "[user: " + mUserId + "] createPrinterDiscoverySession()");
} }
try { try {
mPrintService.createPrinterDiscoverySession(observer); mPrintService.createPrinterDiscoverySession();
} catch (RemoteException re) { } catch (RemoteException re) {
Slog.e(LOG_TAG, "Error announcing start printer dicovery.", 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 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 { private final class MyHandler extends Handler {
public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 1; public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1;
public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 2; public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
public static final int MSG_ON_PRINT_JOB_QUEUED = 3; public static final int MSG_START_PRINTER_DISCOVERY = 3;
public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 4; public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
public static final int MSG_DESTROY = 6; public static final int MSG_REQUEST_PRINTER_UPDATE = 5;
public static final int MSG_BINDER_DIED = 7; 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) { public MyHandler(Looper looper) {
super(looper, null, false); super(looper, null, false);
} }
@Override @Override
@SuppressWarnings("unchecked")
public void handleMessage(Message message) { public void handleMessage(Message message) {
switch (message.what) { 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: { case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
handleOnAllPrintJobsHandled(); handleOnAllPrintJobsHandled();
} break; } break;
@@ -307,13 +465,6 @@ final class RemotePrintService implements DeathRecipient {
handleOnPrintJobQueued(printJob); handleOnPrintJobQueued(printJob);
} break; } break;
case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
IPrinterDiscoverySessionObserver observer =
(IPrinterDiscoverySessionObserver) message.obj;
handleCreatePrinterDiscoverySession(new SecurePrinterDiscoverySessionObserver(
mComponentName, observer));
} break;
case MSG_DESTROY: { case MSG_DESTROY: {
handleDestroy(); handleDestroy();
} break; } break;
@@ -363,7 +514,7 @@ final class RemotePrintService implements DeathRecipient {
} }
@Override @Override
public boolean setPrintJobState(int printJobId, int state, CharSequence error) { public boolean setPrintJobState(int printJobId, int state, String error) {
RemotePrintService service = mWeakService.get(); RemotePrintService service = mWeakService.get();
if (service != null) { if (service != null) {
final long identity = Binder.clearCallingIdentity(); 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 @Override
public void onPrintersAdded(List<PrinterInfo> printers) { public void onPrintersAdded(List<PrinterInfo> printers) {
throwIfPrinterIdsForPrinterInfoTampered(printers); RemotePrintService service = mWeakService.get();
try { if (service != null) {
mDecoratedObsever.onPrintersAdded(printers); throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, printers);
} catch (RemoteException re) { final long identity = Binder.clearCallingIdentity();
Slog.e(LOG_TAG, "Error delegating to onPrintersAdded", re); try {
} service.mSpooler.onPrintersAdded(printers);
} } finally {
Binder.restoreCallingIdentity(identity);
@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);
} }
} }
@Override @Override
public void onPrintersRemoved(List<PrinterId> printerIds) { public void onPrintersRemoved(List<PrinterId> printerIds) {
throwIfPrinterIdsTampered(printerIds); RemotePrintService service = mWeakService.get();
try { if (service != null) {
mDecoratedObsever.onPrintersRemoved(printerIds); throwIfPrinterIdsTampered(service.mComponentName, printerIds);
} catch (RemoteException re) { final long identity = Binder.clearCallingIdentity();
Slog.e(LOG_TAG, "Error delegating to onPrintersRemoved", re); try {
service.mSpooler.onPrintersRemoved(printerIds);
} finally {
Binder.restoreCallingIdentity(identity);
}
} }
} }
@Override @Override
public void setController(IPrinterDiscoverySessionController controller) { public void onPrintersUpdated(List<PrinterInfo> printers) {
try { RemotePrintService service = mWeakService.get();
mDecoratedObsever.setController(controller); if (service != null) {
} catch (RemoteException re) { throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, printers);
Slog.e(LOG_TAG, "Error setting controller", re); 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) { List<PrinterInfo> printerInfos) {
final int printerInfoCount = printerInfos.size(); final int printerInfoCount = printerInfos.size();
for (int i = 0; i < printerInfoCount; i++) { for (int i = 0; i < printerInfoCount; i++) {
PrinterId printerId = printerInfos.get(i).getId(); 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(); final int printerIdCount = printerIds.size();
for (int i = 0; i < printerIdCount; i++) { for (int i = 0; i < printerIdCount; i++) {
PrinterId printerId = printerIds.get(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 if (printerId == null || printerId.getServiceName() == null
|| !printerId.getServiceName().equals(mComponentName)) { || !printerId.getServiceName().equals(serviceName)) {
throw new IllegalArgumentException("Invalid printer id: " + printerId); throw new IllegalArgumentException("Invalid printer id: " + printerId);
} }
} }

View File

@@ -32,9 +32,10 @@ import android.print.IPrintDocumentAdapter;
import android.print.IPrintSpooler; import android.print.IPrintSpooler;
import android.print.IPrintSpoolerCallbacks; import android.print.IPrintSpoolerCallbacks;
import android.print.IPrintSpoolerClient; import android.print.IPrintSpoolerClient;
import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintAttributes; import android.print.PrintAttributes;
import android.print.PrintJobInfo; import android.print.PrintJobInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.util.Slog; import android.util.Slog;
import android.util.TimedRemoteCaller; import android.util.TimedRemoteCaller;
@@ -92,7 +93,11 @@ final class RemotePrintSpooler {
public static interface PrintSpoolerCallbacks { public static interface PrintSpoolerCallbacks {
public void onPrintJobQueued(PrintJobInfo printJob); public void onPrintJobQueued(PrintJobInfo printJob);
public void onAllPrintJobsForServiceHandled(ComponentName printService); 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, public RemotePrintSpooler(Context context, int userId,
@@ -209,7 +214,7 @@ final class RemotePrintSpooler {
return null; return null;
} }
public final boolean setPrintJobState(int printJobId, int state, CharSequence error) { public final boolean setPrintJobState(int printJobId, int state, String error) {
throwIfCalledOnMainThread(); throwIfCalledOnMainThread();
synchronized (mLock) { synchronized (mLock) {
throwIfDestroyedLocked(); 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 { private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException {
synchronized (mLock) { synchronized (mLock) {
if (mRemoteInstance != null) { if (mRemoteInstance != null) {
@@ -488,7 +565,7 @@ final class RemotePrintSpooler {
} }
public boolean setPrintJobState(IPrintSpooler target, int printJobId, 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(); final int sequence = onBeforeRemoteCall();
target.setPrintJobState(printJobId, status, error, mCallback, sequence); target.setPrintJobState(printJobId, status, error, mCallback, sequence);
return getResultTimed(sequence); return getResultTimed(sequence);
@@ -597,12 +674,64 @@ final class RemotePrintSpooler {
} }
@Override @Override
public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) { public void createPrinterDiscoverySession() {
RemotePrintSpooler spooler = mWeakSpooler.get(); RemotePrintSpooler spooler = mWeakSpooler.get();
if (spooler != null) { if (spooler != null) {
final long identity = Binder.clearCallingIdentity(); final long identity = Binder.clearCallingIdentity();
try { try {
spooler.mCallbacks.createPrinterDiscoverySession(observer); 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.requestPrinterUpdate(printerId);
} finally { } finally {
Binder.restoreCallingIdentity(identity); Binder.restoreCallingIdentity(identity);
} }

View File

@@ -21,8 +21,8 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintJobInfo; import android.print.PrintJobInfo;
import android.print.PrinterId;
import android.printservice.PrintServiceInfo; import android.printservice.PrintServiceInfo;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils; import android.text.TextUtils;
@@ -105,7 +105,7 @@ final class UserState implements PrintSpoolerCallbacks {
} }
@Override @Override
public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) { public void createPrinterDiscoverySession() {
final List<RemotePrintService> services; final List<RemotePrintService> services;
synchronized (mLock) { synchronized (mLock) {
throwIfDestroyedLocked(); throwIfDestroyedLocked();
@@ -117,7 +117,73 @@ final class UserState implements PrintSpoolerCallbacks {
final int serviceCount = services.size(); final int serviceCount = services.size();
for (int i = 0; i < serviceCount; i++) { for (int i = 0; i < serviceCount; i++) {
RemotePrintService service = services.get(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);
} }
} }