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/service/notification/INotificationListener.aidl \
core/java/android/print/ILayoutResultCallback.aidl \
core/java/android/print/IPrinterDiscoverySessionController.aidl \
core/java/android/print/IPrinterDiscoverySessionObserver.aidl \
core/java/android/print/IPrintDocumentAdapter.aidl \
core/java/android/print/IPrintClient.aidl \
core/java/android/print/IPrintManager.aidl \

View File

@@ -19038,7 +19038,7 @@ package android.print {
method public android.print.PrintAttributes getAttributes();
method public int getCopies();
method public int getId();
method public java.lang.CharSequence getLabel();
method public java.lang.String getLabel();
method public android.print.PageRange[] getPages();
method public android.print.PrinterId getPrinterId();
method public int getState();
@@ -19162,7 +19162,7 @@ package android.printservice {
public final class PrintJob {
method public boolean cancel();
method public boolean complete();
method public boolean fail(java.lang.CharSequence);
method public boolean fail(java.lang.String);
method public android.printservice.PrintDocument getDocument();
method public int getId();
method public android.print.PrintJobInfo getInfo();
@@ -19191,11 +19191,15 @@ package android.printservice {
}
public abstract class PrinterDiscoverySession {
ctor public PrinterDiscoverySession(android.content.Context);
ctor public PrinterDiscoverySession();
method public final void addPrinters(java.util.List<android.print.PrinterInfo>);
method public abstract void onClose();
method public abstract void onOpen(java.util.List<android.print.PrinterId>);
method public final java.util.List<android.print.PrinterInfo> getPrinters();
method public final boolean isDestroyed();
method public final boolean isPrinterDiscoveryStarted();
method public abstract void onDestroy();
method public abstract void onRequestPrinterUpdate(android.print.PrinterId);
method public abstract void onStartPrinterDiscovery(java.util.List<android.print.PrinterId>);
method public abstract void onStopPrinterDiscovery();
method public final void removePrinters(java.util.List<android.print.PrinterId>);
method public final void updatePrinters(java.util.List<android.print.PrinterInfo>);
}

View File

@@ -18,6 +18,7 @@ package android.print;
import android.content.ComponentName;
import android.os.ParcelFileDescriptor;
import android.print.PrinterId;
import android.print.IPrintDocumentAdapter;
import android.print.IPrintClient;
import android.print.IPrintSpoolerClient;
@@ -40,10 +41,15 @@ oneway interface IPrintSpooler {
void createPrintJob(String printJobName, in IPrintClient client,
in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes,
IPrintSpoolerCallbacks callback, int appId, int sequence);
void setPrintJobState(int printJobId, int status, CharSequence error,
void setPrintJobState(int printJobId, int status, String error,
IPrintSpoolerCallbacks callback, int sequence);
void setPrintJobTag(int printJobId, String tag, IPrintSpoolerCallbacks callback,
int sequence);
void writePrintJobData(in ParcelFileDescriptor fd, int printJobId);
void setClient(IPrintSpoolerClient client);
// Printer discovery APIs
void onPrintersAdded(in List<PrinterInfo> printers);
void onPrintersRemoved(in List<PrinterId> printerIds);
void onPrintersUpdated(in List<PrinterInfo> printerIds);
}

View File

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

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

View File

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

View File

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

View File

@@ -324,7 +324,7 @@ public final class PdfDocument {
/**
* Creates a new builder with the mandatory page info attributes.
*
* @param pageSize The page size in pixels.
* @param pageSize The page size in points, <strong>not</strong> dips.
* @param pageNumber The page number.
* @param density The page density in DPI.
*/

View File

@@ -16,7 +16,7 @@
package android.printservice;
import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrinterId;
import android.print.PrintJobInfo;
import android.printservice.IPrintServiceClient;
@@ -29,5 +29,10 @@ oneway interface IPrintService {
void setClient(IPrintServiceClient client);
void requestCancelPrintJob(in PrintJobInfo printJobInfo);
void onPrintJobQueued(in PrintJobInfo printJobInfo);
void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
void createPrinterDiscoverySession();
void startPrinterDiscovery(in List<PrinterId> priorityList);
void stopPrinterDiscovery();
void requestPrinterUpdate(in PrinterId printerId);
void destroyPrinterDiscoverySession();
}

View File

@@ -29,7 +29,11 @@ import android.print.PrinterInfo;
interface IPrintServiceClient {
List<PrintJobInfo> getPrintJobInfos();
PrintJobInfo getPrintJobInfo(int printJobId);
boolean setPrintJobState(int printJobId, int state, CharSequence error);
boolean setPrintJobState(int printJobId, int state, String error);
boolean setPrintJobTag(int printJobId, String tag);
oneway void writePrintJobData(in ParcelFileDescriptor fd, int printJobId);
void onPrintersAdded(in List<PrinterInfo> printers);
void onPrintersRemoved(in List<PrinterId> printerIds);
void onPrintersUpdated(in List<PrinterInfo> printers);
}

View File

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

View File

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

View File

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

View File

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

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_gravity="center"
android:background="@color/container_background">
<include
layout="@layout/print_job_config_activity_content_editing">
</include>
</FrameLayout>

View File

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

View File

@@ -15,7 +15,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingStart="8dip"
android:paddingEnd="8dip"

View File

@@ -23,7 +23,15 @@
android:actionViewClass="android.widget.SearchView"
android:showAsAction="ifRoom"
android:alphabeticShortcut="f"
android:imeOptions="actionSearch">
android:imeOptions="actionSearch">
</item>
<item
android:id="@+id/action_add_printer"
android:title="@null"
android:icon="@drawable/ic_menu_add"
android:showAsAction="ifRoom"
android:alphabeticShortcut="a">
</item>
</menu>

View File

@@ -58,11 +58,32 @@
<!-- Title for the temporary dialog show while an app is generating a print job. [CHAR LIMIT=30] -->
<string name="generating_print_job">Generating print job</string>
<!-- Choose printer activity -->
<!-- Title for the save as PDF option in the printer list. [CHAR LIMIT=30] -->
<string name="save_as_pdf">Save as PDF</string>
<!-- Title for the open all printers UI option in the printer list. [CHAR LIMIT=30] -->
<string name="all_printers">All printers\.\.\.</string>
<!-- Title for the searching for printers option in the printer list
(only option if not printers are available). [CHAR LIMIT=40] -->
<string name="searching_for_printers">Searching for printers\.\.\.</string>
<!-- Select printer activity -->
<!-- Title for the share action bar menu item. [CHAR LIMIT=20] -->
<string name="search">Search</string>
<!-- Title for the select printer activity. [CHAR LIMIT=30] -->
<string name="all_printers_label">All printers</string>
<!-- Add printer dialog -->
<!-- Title for the alert dialog for selecting a print service. [CHAR LIMIT=50] -->
<string name="choose_print_service">Choose print service</string>
<!-- Title for the button to search the play store for print services. [CHAR LIMIT=50] -->
<string name="search_play_store">Search in play store</string>
<!-- Notifications -->
<!-- Template for the notificaiton label for a printing print job. [CHAR LIMIT=25] -->

View File

@@ -24,4 +24,12 @@
<item name="android:colorBackgroundCacheHint">@android:color/transparent</item>
</style>
<style name="SelectPrinterActivityTheme" parent="@android:style/Theme.Holo.Light">
<item name="android:actionBarStyle">@style/SelectPrinterActivityActionBarStyle</item>
</style>
<style name="SelectPrinterActivityActionBarStyle" parent="@android:style/Widget.Holo.ActionBar">
<item name="android:displayOptions">showTitle</item>
</style>
</resources>

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;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.print.PrinterId;
public class ChoosePrinterActivity extends Activity {
import com.android.printspooler.SelectPrinterFragment.OnPrinterSelectedListener;
public class SelectPrinterActivity extends Activity implements OnPrinterSelectedListener {
@Override
public void onCreate(Bundle bundle) {
setContentView(R.layout.choose_printer_activity);
protected void onCreate(Bundle savedInstanceState) {
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.RemoteException;
import android.os.UserHandle;
import android.print.IPrinterDiscoverySessionController;
import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintJobInfo;
import android.print.PrintManager;
import android.print.PrinterId;
@@ -79,6 +77,10 @@ final class RemotePrintService implements DeathRecipient {
private boolean mDestroyed;
private boolean mAllPrintJobsHandled;
private boolean mHasPrinterDiscoverySession;
public RemotePrintService(Context context, ComponentName componentName, int userId,
RemotePrintSpooler spooler) {
mContext = context;
@@ -97,6 +99,8 @@ final class RemotePrintService implements DeathRecipient {
private void handleDestroy() {
throwIfDestroyed();
ensureUnbound();
mAllPrintJobsHandled = false;
mHasPrinterDiscoverySession = false;
mDestroyed = true;
}
@@ -110,18 +114,27 @@ final class RemotePrintService implements DeathRecipient {
}
private void handleBinderDied() {
mAllPrintJobsHandled = false;
mHasPrinterDiscoverySession = false;
mPendingCommands.clear();
ensureUnbound();
}
private void handleOnAllPrintJobsHandled() {
throwIfDestroyed();
mAllPrintJobsHandled = true;
if (isBound()) {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnAllPrintJobsHandled()");
}
// If bound and all the work is completed, then unbind.
ensureUnbound();
// If the service has a printer discovery session
// created we should not disconnect from it just yet.
if (!mHasPrinterDiscoverySession) {
ensureUnbound();
}
}
}
@@ -153,6 +166,9 @@ final class RemotePrintService implements DeathRecipient {
private void handleOnPrintJobQueued(final PrintJobInfo printJob) {
throwIfDestroyed();
mAllPrintJobsHandled = false;
if (!isBound()) {
ensureBound();
mPendingCommands.add(new Runnable() {
@@ -173,20 +189,18 @@ final class RemotePrintService implements DeathRecipient {
}
}
public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
mHandler.obtainMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION,
observer).sendToTarget();
public void createPrinterDiscoverySession() {
mHandler.sendEmptyMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
}
private void handleCreatePrinterDiscoverySession(
final IPrinterDiscoverySessionObserver observer) {
private void handleCreatePrinterDiscoverySession() {
throwIfDestroyed();
if (!isBound()) {
ensureBound();
mPendingCommands.add(new Runnable() {
@Override
public void run() {
handleCreatePrinterDiscoverySession(observer);
handleCreatePrinterDiscoverySession();
}
});
} else {
@@ -194,9 +208,126 @@ final class RemotePrintService implements DeathRecipient {
Slog.i(LOG_TAG, "[user: " + mUserId + "] createPrinterDiscoverySession()");
}
try {
mPrintService.createPrinterDiscoverySession(observer);
mPrintService.createPrinterDiscoverySession();
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error 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 {
public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 1;
public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 2;
public static final int MSG_ON_PRINT_JOB_QUEUED = 3;
public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 4;
public static final int MSG_DESTROY = 6;
public static final int MSG_BINDER_DIED = 7;
public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1;
public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
public static final int MSG_START_PRINTER_DISCOVERY = 3;
public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
public static final int MSG_REQUEST_PRINTER_UPDATE = 5;
public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 6;
public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 7;
public static final int MSG_ON_PRINT_JOB_QUEUED = 8;
public static final int MSG_DESTROY = 9;
public static final int MSG_BINDER_DIED = 10;
public MyHandler(Looper looper) {
super(looper, null, false);
}
@Override
@SuppressWarnings("unchecked")
public void handleMessage(Message message) {
switch (message.what) {
case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
handleCreatePrinterDiscoverySession();
} break;
case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
handleDestroyPrinterDiscoverySession();
} break;
case MSG_START_PRINTER_DISCOVERY: {
List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj;
handleStartPrinterDiscovery(priorityList);
} break;
case MSG_STOP_PRINTER_DISCOVERY: {
handleStopPrinterDiscovery();
} break;
case MSG_REQUEST_PRINTER_UPDATE: {
PrinterId printerId = (PrinterId) message.obj;
handleRequestPrinterUpdate(printerId);
} break;
case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
handleOnAllPrintJobsHandled();
} break;
@@ -307,13 +465,6 @@ final class RemotePrintService implements DeathRecipient {
handleOnPrintJobQueued(printJob);
} break;
case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
IPrinterDiscoverySessionObserver observer =
(IPrinterDiscoverySessionObserver) message.obj;
handleCreatePrinterDiscoverySession(new SecurePrinterDiscoverySessionObserver(
mComponentName, observer));
} break;
case MSG_DESTROY: {
handleDestroy();
} break;
@@ -363,7 +514,7 @@ final class RemotePrintService implements DeathRecipient {
}
@Override
public boolean setPrintJobState(int printJobId, int state, CharSequence error) {
public boolean setPrintJobState(int printJobId, int state, String error) {
RemotePrintService service = mWeakService.get();
if (service != null) {
final long identity = Binder.clearCallingIdentity();
@@ -402,79 +553,70 @@ final class RemotePrintService implements DeathRecipient {
}
}
}
}
private static final class SecurePrinterDiscoverySessionObserver
extends IPrinterDiscoverySessionObserver.Stub {
private final ComponentName mComponentName;
private final IPrinterDiscoverySessionObserver mDecoratedObsever;
public SecurePrinterDiscoverySessionObserver(ComponentName componentName,
IPrinterDiscoverySessionObserver observer) {
mComponentName = componentName;
mDecoratedObsever = observer;
}
@Override
public void onPrintersAdded(List<PrinterInfo> printers) {
throwIfPrinterIdsForPrinterInfoTampered(printers);
try {
mDecoratedObsever.onPrintersAdded(printers);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error delegating to onPrintersAdded", re);
}
}
@Override
public void onPrintersUpdated(List<PrinterInfo> printers) {
throwIfPrinterIdsForPrinterInfoTampered(printers);
try {
mDecoratedObsever.onPrintersUpdated(printers);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error delegating to onPrintersUpdated.", re);
RemotePrintService service = mWeakService.get();
if (service != null) {
throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, printers);
final long identity = Binder.clearCallingIdentity();
try {
service.mSpooler.onPrintersAdded(printers);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
@Override
public void onPrintersRemoved(List<PrinterId> printerIds) {
throwIfPrinterIdsTampered(printerIds);
try {
mDecoratedObsever.onPrintersRemoved(printerIds);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error delegating to onPrintersRemoved", re);
RemotePrintService service = mWeakService.get();
if (service != null) {
throwIfPrinterIdsTampered(service.mComponentName, printerIds);
final long identity = Binder.clearCallingIdentity();
try {
service.mSpooler.onPrintersRemoved(printerIds);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
@Override
public void setController(IPrinterDiscoverySessionController controller) {
try {
mDecoratedObsever.setController(controller);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error setting controller", re);
public void onPrintersUpdated(List<PrinterInfo> printers) {
RemotePrintService service = mWeakService.get();
if (service != null) {
throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, printers);
final long identity = Binder.clearCallingIdentity();
try {
service.mSpooler.onPrintersUpdated(printers);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
private void throwIfPrinterIdsForPrinterInfoTampered(
private void throwIfPrinterIdsForPrinterInfoTampered(ComponentName serviceName,
List<PrinterInfo> printerInfos) {
final int printerInfoCount = printerInfos.size();
for (int i = 0; i < printerInfoCount; i++) {
PrinterId printerId = printerInfos.get(i).getId();
throwIfPrinterIdTampered(printerId);
throwIfPrinterIdTampered(serviceName, printerId);
}
}
private void throwIfPrinterIdsTampered(List<PrinterId> printerIds) {
private void throwIfPrinterIdsTampered(ComponentName serviceName,
List<PrinterId> printerIds) {
final int printerIdCount = printerIds.size();
for (int i = 0; i < printerIdCount; i++) {
PrinterId printerId = printerIds.get(i);
throwIfPrinterIdTampered(printerId);
throwIfPrinterIdTampered(serviceName, printerId);
}
}
private void throwIfPrinterIdTampered(PrinterId printerId) {
private void throwIfPrinterIdTampered(ComponentName serviceName, PrinterId printerId) {
if (printerId == null || printerId.getServiceName() == null
|| !printerId.getServiceName().equals(mComponentName)) {
|| !printerId.getServiceName().equals(serviceName)) {
throw new IllegalArgumentException("Invalid printer id: " + printerId);
}
}

View File

@@ -32,9 +32,10 @@ import android.print.IPrintDocumentAdapter;
import android.print.IPrintSpooler;
import android.print.IPrintSpoolerCallbacks;
import android.print.IPrintSpoolerClient;
import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintAttributes;
import android.print.PrintJobInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.util.Slog;
import android.util.TimedRemoteCaller;
@@ -92,7 +93,11 @@ final class RemotePrintSpooler {
public static interface PrintSpoolerCallbacks {
public void onPrintJobQueued(PrintJobInfo printJob);
public void onAllPrintJobsForServiceHandled(ComponentName printService);
public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
public void createPrinterDiscoverySession();
public void destroyPrinterDiscoverySession();
public void startPrinterDiscovery(List<PrinterId> priorityList);
public void stopPrinterDiscovery();
public void requestPrinterUpdate(PrinterId printerId);
}
public RemotePrintSpooler(Context context, int userId,
@@ -209,7 +214,7 @@ final class RemotePrintSpooler {
return null;
}
public final boolean setPrintJobState(int printJobId, int state, CharSequence error) {
public final boolean setPrintJobState(int printJobId, int state, String error) {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
@@ -300,6 +305,78 @@ final class RemotePrintSpooler {
}
}
public final void onPrintersAdded(List<PrinterInfo> printers) {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
try {
getRemoteInstanceLazy().onPrintersAdded(printers);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error adding printers.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error adding printers.", te);
} finally {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+ "] onPrintersAdded()");
}
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
}
public final void onPrintersRemoved(List<PrinterId> printerIds) {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
try {
getRemoteInstanceLazy().onPrintersRemoved(printerIds);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error removing printers.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error removing printers.", te);
} finally {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+ "] onPrintersRemoved()");
}
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
}
public final void onPrintersUpdated(List<PrinterInfo> printers) {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
try {
getRemoteInstanceLazy().onPrintersUpdated(printers);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error updating printers.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error updating printers.", te);
} finally {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+ "] onPrintersUpdted()");
}
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
}
private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException {
synchronized (mLock) {
if (mRemoteInstance != null) {
@@ -488,7 +565,7 @@ final class RemotePrintSpooler {
}
public boolean setPrintJobState(IPrintSpooler target, int printJobId,
int status, CharSequence error) throws RemoteException, TimeoutException {
int status, String error) throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
target.setPrintJobState(printJobId, status, error, mCallback, sequence);
return getResultTimed(sequence);
@@ -597,12 +674,64 @@ final class RemotePrintSpooler {
}
@Override
public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
public void createPrinterDiscoverySession() {
RemotePrintSpooler spooler = mWeakSpooler.get();
if (spooler != null) {
final long identity = Binder.clearCallingIdentity();
try {
spooler.mCallbacks.createPrinterDiscoverySession(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 {
Binder.restoreCallingIdentity(identity);
}

View File

@@ -21,8 +21,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintJobInfo;
import android.print.PrinterId;
import android.printservice.PrintServiceInfo;
import android.provider.Settings;
import android.text.TextUtils;
@@ -105,7 +105,7 @@ final class UserState implements PrintSpoolerCallbacks {
}
@Override
public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
public void createPrinterDiscoverySession() {
final List<RemotePrintService> services;
synchronized (mLock) {
throwIfDestroyedLocked();
@@ -117,7 +117,73 @@ final class UserState implements PrintSpoolerCallbacks {
final int serviceCount = services.size();
for (int i = 0; i < serviceCount; i++) {
RemotePrintService service = services.get(i);
service.createPrinterDiscoverySession(observer);
service.createPrinterDiscoverySession();
}
}
@Override
public void destroyPrinterDiscoverySession() {
final List<RemotePrintService> services;
synchronized (mLock) {
throwIfDestroyedLocked();
if (mActiveServices.isEmpty()) {
return;
}
services = new ArrayList<RemotePrintService>(mActiveServices.values());
}
final int serviceCount = services.size();
for (int i = 0; i < serviceCount; i++) {
RemotePrintService service = services.get(i);
service.destroyPrinterDiscoverySession();
}
}
@Override
public void startPrinterDiscovery(List<PrinterId> printerIds) {
final List<RemotePrintService> services;
synchronized (mLock) {
throwIfDestroyedLocked();
if (mActiveServices.isEmpty()) {
return;
}
services = new ArrayList<RemotePrintService>(mActiveServices.values());
}
final int serviceCount = services.size();
for (int i = 0; i < serviceCount; i++) {
RemotePrintService service = services.get(i);
service.startPrinterDiscovery(printerIds);
}
}
@Override
public void stopPrinterDiscovery() {
final List<RemotePrintService> services;
synchronized (mLock) {
throwIfDestroyedLocked();
if (mActiveServices.isEmpty()) {
return;
}
services = new ArrayList<RemotePrintService>(mActiveServices.values());
}
final int serviceCount = services.size();
for (int i = 0; i < serviceCount; i++) {
RemotePrintService service = services.get(i);
service.stopPrinterDiscovery();
}
}
@Override
public void requestPrinterUpdate(PrinterId printerId) {
final RemotePrintService service;
synchronized (mLock) {
throwIfDestroyedLocked();
if (mActiveServices.isEmpty()) {
return;
}
service = mActiveServices.get(printerId.getServiceName());
}
if (service != null) {
service.requestPrinterUpdate(printerId);
}
}