Print - platform APIs

Related changes:
    Skia (inlcude PDF APIs): https://googleplex-android-review.googlesource.com/#/c/305814/
    Canvas to PDF: https://googleplex-android-review.googlesource.com/#/c/319367/
    Settings (initial version): https://googleplex-android-review.googlesource.com/#/c/306077/
    Build: https://googleplex-android-review.googlesource.com/#/c/292437/
    Sample print services: https://googleplex-android-review.googlesource.com/#/c/281785/

Change-Id: I104d12efd12577f05c7b9b2a5e5e49125c0f09da
This commit is contained in:
Svetoslav Ganov
2013-06-11 15:20:06 -07:00
parent 142dd91583
commit 4b9a4d1687
56 changed files with 9489 additions and 3 deletions

View File

@@ -157,6 +157,15 @@ LOCAL_SRC_FILES += \
core/java/android/os/IUserManager.aidl \
core/java/android/os/IVibratorService.aidl \
core/java/android/service/notification/INotificationListener.aidl \
core/java/android/print/IPrinterDiscoveryObserver.aidl \
core/java/android/print/IPrintAdapter.aidl \
core/java/android/print/IPrintClient.aidl \
core/java/android/print/IPrintProgressListener.aidl \
core/java/android/print/IPrintManager.aidl \
core/java/android/print/IPrintSpoolerService.aidl \
core/java/android/print/IPrintSpoolerServiceCallbacks.aidl \
core/java/android/printservice/IPrintService.aidl \
core/java/android/printservice/IPrintServiceClient.aidl \
core/java/android/service/dreams/IDreamManager.aidl \
core/java/android/service/dreams/IDreamService.aidl \
core/java/android/service/wallpaper/IWallpaperConnection.aidl \

View File

@@ -23,6 +23,7 @@ package android {
field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
@@ -254,6 +255,7 @@ package android {
field public static final int activityCloseExitAnimation = 16842939; // 0x10100bb
field public static final int activityOpenEnterAnimation = 16842936; // 0x10100b8
field public static final int activityOpenExitAnimation = 16842937; // 0x10100b9
field public static final int addPrintersActivity = 16843747; // 0x10103e3
field public static final int addStatesFromChildren = 16842992; // 0x10100f0
field public static final int adjustViewBounds = 16843038; // 0x101011e
field public static final int alertDialogIcon = 16843605; // 0x1010355
@@ -1142,6 +1144,7 @@ package android {
field public static final int valueTo = 16843487; // 0x10102df
field public static final int valueType = 16843488; // 0x10102e0
field public static final int variablePadding = 16843157; // 0x1010195
field public static final int vendor = 16843748; // 0x10103e4
field public static final int versionCode = 16843291; // 0x101021b
field public static final int versionName = 16843292; // 0x101021c
field public static final int verticalCorrection = 16843322; // 0x101023a
@@ -5766,6 +5769,7 @@ package android.content {
field public static final java.lang.String NOTIFICATION_SERVICE = "notification";
field public static final java.lang.String NSD_SERVICE = "servicediscovery";
field public static final java.lang.String POWER_SERVICE = "power";
field public static final java.lang.String PRINT_SERVICE = "print";
field public static final java.lang.String SEARCH_SERVICE = "search";
field public static final java.lang.String SENSOR_SERVICE = "sensor";
field public static final java.lang.String STORAGE_SERVICE = "storage";
@@ -18385,6 +18389,237 @@ package android.preference {
}
package android.print {
public final class PageRange implements android.os.Parcelable {
method public int describeContents();
method public int getEnd();
method public int getStart();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.print.PageRange ALL_PAGES;
field public static final android.os.Parcelable.Creator CREATOR;
}
public abstract class PrintAdapter {
ctor public PrintAdapter();
method public abstract android.print.PrintAdapterInfo getInfo();
method public void onFinish();
method public abstract void onPrint(java.util.List<android.print.PageRange>, java.io.FileDescriptor, android.os.CancellationSignal, android.print.PrintAdapter.PrintProgressCallback);
method public boolean onPrintAttributesChanged(android.print.PrintAttributes);
method public void onStart();
}
public static abstract class PrintAdapter.PrintProgressCallback {
method public void onPrintFailed(java.lang.CharSequence);
method public void onPrintFinished(java.util.List<android.print.PageRange>);
}
public final class PrintAdapterInfo implements android.os.Parcelable {
method public int describeContents();
method public int getFlags();
method public int getPageCount();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int PAGE_COUNT_UNKNOWN = -1; // 0xffffffff
}
public static final class PrintAdapterInfo.Builder {
ctor public PrintAdapterInfo.Builder();
method public android.print.PrintAdapterInfo create();
method public android.print.PrintAdapterInfo.Builder setFlags(int);
method public android.print.PrintAdapterInfo.Builder setPageCount(int);
}
public final class PrintAttributes implements android.os.Parcelable {
method public void clear();
method public int describeContents();
method public int getColorMode();
method public int getCopies();
method public int getDuplexMode();
method public int getFittingMode();
method public android.print.PrintAttributes.Tray getInputTray();
method public android.print.PrintAttributes.Margins getMargins();
method public android.print.PrintAttributes.MediaSize getMediaSize();
method public int getOrientation();
method public android.print.PrintAttributes.Tray getOutputTray();
method public android.print.PrintAttributes.Resolution getResolution();
method public void writeToParcel(android.os.Parcel, int);
field public static final int COLOR_MODE_COLOR = 2; // 0x2
field public static final int COLOR_MODE_MONOCHROME = 1; // 0x1
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int DUPLEX_MODE_LONG_EDGE = 2; // 0x2
field public static final int DUPLEX_MODE_NONE = 1; // 0x1
field public static final int DUPLEX_MODE_SHORT_EDGE = 4; // 0x4
field public static final int FITTING_MODE_FIT_TO_PAGE = 2; // 0x2
field public static final int FITTING_MODE_NONE = 1; // 0x1
field public static final int ORIENTATION_LANDSCAPE = 2; // 0x2
field public static final int ORIENTATION_PORTRAIT = 1; // 0x1
}
public static final class PrintAttributes.Builder {
ctor public PrintAttributes.Builder();
method public android.print.PrintAttributes create();
method public android.print.PrintAttributes.Builder setColorMode(int);
method public android.print.PrintAttributes.Builder setCopyCount(int);
method public android.print.PrintAttributes.Builder setDuplexMode(int);
method public android.print.PrintAttributes.Builder setFittingMode(int);
method public android.print.PrintAttributes.Builder setInputTray(android.print.PrintAttributes.Tray);
method public android.print.PrintAttributes.Builder setMargins(android.print.PrintAttributes.Margins);
method public android.print.PrintAttributes.Builder setMediaSize(android.print.PrintAttributes.MediaSize);
method public android.print.PrintAttributes.Builder setOrientation(int);
method public android.print.PrintAttributes.Builder setOutputTray(android.print.PrintAttributes.Tray);
method public android.print.PrintAttributes.Builder setResolution(android.print.PrintAttributes.Resolution);
}
public static final class PrintAttributes.Margins {
ctor public PrintAttributes.Margins(int, int, int, int);
method public int getBottomMils();
method public int getLeftMils();
method public int getRightMils();
method public int getTopMils();
}
public static final class PrintAttributes.MediaSize {
ctor public PrintAttributes.MediaSize(java.lang.String, java.lang.String, int, int, int);
method public int getHeightMils();
method public java.lang.String getId();
method public java.lang.CharSequence getLabel(android.content.pm.PackageManager);
method public int getWidthMils();
field public static final android.print.PrintAttributes.MediaSize ISO_A0;
field public static final android.print.PrintAttributes.MediaSize ISO_A1;
field public static final android.print.PrintAttributes.MediaSize ISO_A10;
field public static final android.print.PrintAttributes.MediaSize ISO_A2;
field public static final android.print.PrintAttributes.MediaSize ISO_A3;
field public static final android.print.PrintAttributes.MediaSize ISO_A4;
field public static final android.print.PrintAttributes.MediaSize ISO_A5;
field public static final android.print.PrintAttributes.MediaSize ISO_A6;
field public static final android.print.PrintAttributes.MediaSize ISO_A7;
field public static final android.print.PrintAttributes.MediaSize ISO_A8;
field public static final android.print.PrintAttributes.MediaSize ISO_A9;
field public static final android.print.PrintAttributes.MediaSize ISO_B0;
field public static final android.print.PrintAttributes.MediaSize ISO_B1;
field public static final android.print.PrintAttributes.MediaSize ISO_B10;
field public static final android.print.PrintAttributes.MediaSize ISO_B2;
field public static final android.print.PrintAttributes.MediaSize ISO_B3;
field public static final android.print.PrintAttributes.MediaSize ISO_B4;
field public static final android.print.PrintAttributes.MediaSize ISO_B5;
field public static final android.print.PrintAttributes.MediaSize ISO_B6;
field public static final android.print.PrintAttributes.MediaSize ISO_B7;
field public static final android.print.PrintAttributes.MediaSize ISO_B8;
field public static final android.print.PrintAttributes.MediaSize ISO_B9;
field public static final android.print.PrintAttributes.MediaSize ISO_C0;
field public static final android.print.PrintAttributes.MediaSize ISO_C1;
field public static final android.print.PrintAttributes.MediaSize ISO_C10;
field public static final android.print.PrintAttributes.MediaSize ISO_C2;
field public static final android.print.PrintAttributes.MediaSize ISO_C3;
field public static final android.print.PrintAttributes.MediaSize ISO_C4;
field public static final android.print.PrintAttributes.MediaSize ISO_C5;
field public static final android.print.PrintAttributes.MediaSize ISO_C6;
field public static final android.print.PrintAttributes.MediaSize ISO_C7;
field public static final android.print.PrintAttributes.MediaSize ISO_C8;
field public static final android.print.PrintAttributes.MediaSize ISO_C9;
field public static final android.print.PrintAttributes.MediaSize NA_GOVT_LETTER;
field public static final android.print.PrintAttributes.MediaSize NA_JUNIOR_LEGAL;
field public static final android.print.PrintAttributes.MediaSize NA_LEDGER;
field public static final android.print.PrintAttributes.MediaSize NA_LEGAL;
field public static final android.print.PrintAttributes.MediaSize NA_LETTER;
field public static final android.print.PrintAttributes.MediaSize NA_TBLOID;
}
public static final class PrintAttributes.Resolution {
ctor public PrintAttributes.Resolution(java.lang.String, java.lang.String, int, int, int);
method public int getHorizontalDpi();
method public java.lang.String getId();
method public java.lang.CharSequence getLabel(android.content.pm.PackageManager);
method public int getVerticalDpi();
}
public static final class PrintAttributes.Tray {
ctor public PrintAttributes.Tray(java.lang.String, java.lang.String, int);
method public java.lang.String getId();
method public java.lang.CharSequence getLabel(android.content.pm.PackageManager);
}
public final class PrintJob {
method public void cancel();
method public int getId();
method public android.print.PrintJobInfo getInfo();
}
public final class PrintJobInfo implements android.os.Parcelable {
method public int describeContents();
method public android.print.PrintAttributes getAttributes();
method public int getId();
method public java.lang.CharSequence getLabel();
method public android.print.PageRange[] getPageRanges();
method public android.print.PrinterId getPrinterId();
method public int getState();
method public java.lang.String getTag();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int PRINT_JOB_ID_UNDEFINED = -1; // 0xffffffff
field public static final int STATE_CANCELED = 6; // 0x6
field public static final int STATE_COMPLETED = 4; // 0x4
field public static final int STATE_CREATED = 1; // 0x1
field public static final int STATE_FAILED = 5; // 0x5
field public static final int STATE_QUEUED = 2; // 0x2
field public static final int STATE_STARTED = 3; // 0x3
}
public final class PrintManager {
method public java.util.List<android.print.PrintJob> getPrintJobs();
method public android.print.PrintJob print(java.lang.String, java.io.File, android.print.PrintAttributes);
method public android.print.PrintJob print(java.lang.String, android.print.PrintAdapter, android.print.PrintAttributes);
}
public static abstract interface PrintManager.PrintJobStateListener {
method public abstract void onStateChanged(int);
}
public final class PrinterId implements android.os.Parcelable {
method public int describeContents();
method public java.lang.String getLocalId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
}
public final class PrinterInfo implements android.os.Parcelable {
method public int describeContents();
method public int getColorModes();
method public void getDefaults(android.print.PrintAttributes);
method public int getDuplexModes();
method public int getFittingModes();
method public android.print.PrinterId getId();
method public java.util.List<android.print.PrintAttributes.Tray> getInputTrays();
method public java.lang.CharSequence getLabel();
method public java.util.List<android.print.PrintAttributes.MediaSize> getMediaSizes();
method public android.print.PrintAttributes.Margins getMinMargins();
method public int getOrientations();
method public java.util.List<android.print.PrintAttributes.Tray> getOutputTrays();
method public java.util.List<android.print.PrintAttributes.Resolution> getResolutions();
method public int getStatus();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int STATUS_READY = 1; // 0x1
}
public static final class PrinterInfo.Builder {
ctor public PrinterInfo.Builder(android.print.PrinterId, java.lang.CharSequence);
method public android.print.PrinterInfo.Builder addInputTray(android.print.PrintAttributes.Tray, boolean);
method public android.print.PrinterInfo.Builder addMediaSize(android.print.PrintAttributes.MediaSize, boolean);
method public android.print.PrinterInfo.Builder addOutputTray(android.print.PrintAttributes.Tray, boolean);
method public android.print.PrinterInfo.Builder addResolution(android.print.PrintAttributes.Resolution, boolean);
method public android.print.PrinterInfo create();
method public android.print.PrinterInfo.Builder setColorModes(int, int);
method public android.print.PrinterInfo.Builder setDuplexModes(int, int);
method public android.print.PrinterInfo.Builder setFittingModes(int, int);
method public android.print.PrinterInfo.Builder setMinMargins(android.print.PrintAttributes.Margins, android.print.PrintAttributes.Margins);
method public android.print.PrinterInfo.Builder setOrientations(int, int);
method public android.print.PrinterInfo.Builder setStatus(int);
}
}
package android.print.pdf {
public final class PdfDocument {
@@ -18418,6 +18653,40 @@ package android.print.pdf {
}
package android.printservice {
public final class PrintJob {
method public boolean cancel();
method public boolean complete();
method public boolean fail(java.lang.CharSequence);
method public final java.io.FileDescriptor getData();
method public int getId();
method public android.print.PrintJobInfo getInfo();
method public boolean isQueued();
method public boolean isStarted();
method public boolean setTag(java.lang.String);
method public boolean start();
}
public abstract class PrintService extends android.app.Service {
ctor public PrintService();
method public final void addDiscoveredPrinters(java.util.List<android.print.PrinterInfo>);
method public final android.print.PrinterId generatePrinterId(java.lang.String);
method public final java.util.List<android.printservice.PrintJob> getPrintJobs();
method public final android.os.IBinder onBind(android.content.Intent);
method protected void onConnected();
method protected void onDisconnected();
method protected abstract void onPrintJobQueued(android.printservice.PrintJob);
method protected void onRequestCancelPrintJob(android.printservice.PrintJob);
method protected abstract void onStartPrinterDiscovery();
method protected abstract void onStopPrinterDiscovery();
method public final void removeDiscoveredPrinters(java.util.List<android.print.PrinterId>);
field public static final java.lang.String SERVICE_INTERFACE = "android.printservice.PrintService";
field public static final java.lang.String SERVICE_META_DATA = "android.printservice";
}
}
package android.provider {
public final class AlarmClock {

View File

@@ -91,6 +91,8 @@ import android.os.UserHandle;
import android.os.SystemVibrator;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.print.IPrintManager;
import android.print.PrintManager;
import android.telephony.TelephonyManager;
import android.content.ClipboardManager;
import android.util.AndroidRuntimeException;
@@ -548,6 +550,15 @@ class ContextImpl extends Context {
registerService(CAMERA_SERVICE, new StaticServiceFetcher() {
public Object createStaticService() {
return new CameraManager();
}
});
registerService(PRINT_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE);
IPrintManager service = IPrintManager.Stub.asInterface(iBinder);
return new PrintManager(ctx.getOuterContext(), service, UserHandle.myUserId(),
UserHandle.getAppId(Process.myUid()));
}});
}

View File

@@ -45,7 +45,6 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* Interface to global information about an application environment. This is
@@ -2293,6 +2292,15 @@ public abstract class Context {
*/
public static final String CAMERA_SERVICE = "camera";
/**
* {@link android.print.PrintManager} for printing and managing
* printers and print taks.
*
* @see #getSystemService
* @see android.print.PrintManager
*/
public static final String PRINT_SERVICE = "print";
/**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.

View File

@@ -19,7 +19,6 @@ package android.net;
import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Environment.UserEnvironment;
import android.os.StrictMode;
import android.util.Log;
import java.io.File;

View File

@@ -17,7 +17,6 @@
package android.os;
import android.os.ICancellationSignal;
import android.os.ICancellationSignal.Stub;
/**
* Provides the ability to cancel an operation in progress.

View File

@@ -0,0 +1,35 @@
/*
* 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.os.ParcelFileDescriptor;
import android.print.IPrintProgressListener;
import android.print.PageRange;
import android.print.PrintAttributes;
/**
* Interface for communication with the print adapter object.
*
* @hide
*/
oneway interface IPrintAdapter {
void start();
void printAttributesChanged(in PrintAttributes attributes);
void print(in List<PageRange> pages, in ParcelFileDescriptor fd,
IPrintProgressListener progressListener);
void finish();
}

View File

@@ -0,0 +1,30 @@
/*
* 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.content.IntentSender;
/**
* Interface for communication with a printing app.
*
* @see android.print.IPrintClientCallback
*
* @hide
*/
oneway interface IPrintClient {
void startPrintJobConfigActivity(in IntentSender intent);
}

View File

@@ -0,0 +1,41 @@
/*
* 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.os.ICancellationSignal;
import android.print.IPrintAdapter;
import android.print.IPrintClient;
import android.print.IPrinterDiscoveryObserver;
import android.print.PrinterId;
import android.print.PrintJobInfo;
import android.print.PrintAttributes;
/**
* Interface for communication with the core print manager service.
*
* @hide
*/
interface IPrintManager {
List<PrintJobInfo> getPrintJobs(int appId, int userId);
PrintJobInfo getPrintJob(int printJobId, int appId, int userId);
PrintJobInfo print(String printJobName, in IPrintClient client, in IPrintAdapter printAdapter,
in PrintAttributes attributes, int appId, int userId);
void cancelPrintJob(int printJobId, int appId, int userId);
void onPrintJobQueued(in PrinterId printerId, in PrintJobInfo printJob);
void startDiscoverPrinters(IPrinterDiscoveryObserver observer);
void stopDiscoverPrinters();
}

View File

@@ -0,0 +1,33 @@
/*
* 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.os.ICancellationSignal;
import android.print.PageRange;
import android.print.PrintAdapterInfo;
/**
* Callbacks for observing the print progress (writing of printed content)
* of a PrintAdapter.
*
* @hide
*/
oneway interface IPrintProgressListener {
void onWriteStarted(in PrintAdapterInfo info, ICancellationSignal cancellationSignal);
void onWriteFinished(in List<PageRange> pages);
void onWriteFailed(CharSequence error);
}

View File

@@ -0,0 +1,49 @@
/*
* 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.content.ComponentName;
import android.os.ParcelFileDescriptor;
import android.print.IPrintAdapter;
import android.print.IPrintClient;
import android.print.IPrintSpoolerServiceCallbacks;
import android.print.PrinterInfo;
import android.print.PrintAttributes;
/**
* Interface for communication with the print spooler service.
*
* @see android.print.IPrintSpoolerServiceCallbacks
*
* @hide
*/
oneway interface IPrintSpoolerService {
void getPrintJobs(IPrintSpoolerServiceCallbacks callback, in ComponentName componentName,
int state, int appId, int sequence);
void getPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback,
int appId, int sequence);
void createPrintJob(String printJobName, in IPrintClient client, in IPrintAdapter printAdapter,
in PrintAttributes attributes, IPrintSpoolerServiceCallbacks callback, int appId,
int sequence);
void cancelPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback,
int appId, int sequence);
void setPrintJobState(int printJobId, int status, IPrintSpoolerServiceCallbacks callback,
int sequence);
void setPrintJobTag(int printJobId, String tag, IPrintSpoolerServiceCallbacks callback,
int sequence);
void writePrintJobData(in ParcelFileDescriptor fd, int printJobId);
}

View File

@@ -0,0 +1,36 @@
/*
* 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.PrintJobInfo;
import java.util.List;
/**
* Callbacks for communication with the print spooler service.
*
* @see android.print.IPrintSpoolerService
*
* @hide
*/
oneway interface IPrintSpoolerServiceCallbacks {
void onGetPrintJobsResult(in List<PrintJobInfo> printJob, int sequence);
void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence);
void onCreatePrintJobResult(in PrintJobInfo printJob, int sequence);
void onCancelPrintJobResult(boolean canceled, int sequence);
void onSetPrintJobStateResult(boolean success, int sequence);
void onSetPrintJobTagResult(boolean success, int sequence);
}

View File

@@ -0,0 +1,30 @@
/*
* 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;
import android.print.PrinterInfo;
/**
* Interface for observing printer discovery.
*
* @hide
*/
oneway interface IPrinterDiscoveryObserver {
void addDiscoveredPrinters(in List<PrinterInfo> printers);
void removeDiscoveredPrinters(in List<PrinterId> printers);
}

View File

@@ -0,0 +1,19 @@
/**
* 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;
parcelable PageRange;

View File

@@ -0,0 +1,119 @@
/*
* 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.os.Parcel;
import android.os.Parcelable;
/**
* Represents a range of pages. The start and end page indices of
* the range are zero based and are inclusive.
*/
public final class PageRange implements Parcelable {
/**
* Constant for specifying all pages.
*/
public static final PageRange ALL_PAGES = new PageRange(0, Integer.MAX_VALUE);
private final int mStart;
private final int mEnd;
/**
* Creates a new instance.
*
* @param start The start page index (zero based and inclusive).
* @param end The end page index (zero based and inclusive).
*
* @throws IllegalArgumentException If start is less than zero.
* @throws IllegalArgumentException If end is less than zero.
* @throws IllegalArgumentException If start greater than end.
*/
PageRange(int start, int end) {
if (start < 0) {
throw new IllegalArgumentException("start cannot be less than zero.");
}
if (end < 0) {
throw new IllegalArgumentException("end cannot be less than zero.");
}
if (start > end) {
throw new IllegalArgumentException("start must be lesser than end.");
}
mStart = start;
mEnd = end;
}
private PageRange (Parcel parcel) {
this(parcel.readInt(), parcel.readInt());
}
/**
* Gets the start page index (zero based and inclusive).
*
* @return The start page index.
*/
public int getStart() {
return mStart;
}
/**
* Gets the end page index (zero based and inclusive).
*
* @return The end page index.
*/
public int getEnd() {
return mEnd;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mStart);
parcel.writeInt(mEnd);
}
@Override
public String toString() {
if (this == ALL_PAGES) {
return "PageRange[<all pages>]";
}
StringBuilder builder = new StringBuilder();
builder.append("PageRange[")
.append(mStart)
.append(" - ")
.append(mEnd)
.append("]");
return builder.toString();
}
public static final Parcelable.Creator<PageRange> CREATOR =
new Creator<PageRange>() {
@Override
public PageRange createFromParcel(Parcel parcel) {
return new PageRange(parcel);
}
@Override
public PageRange[] newArray(int size) {
return new PageRange[size];
}
};
}

View File

@@ -0,0 +1,162 @@
/*
* 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 java.io.FileDescriptor;
import java.io.IOException;
import java.util.List;
import android.os.CancellationSignal;
/**
* Base class that provides data to be printed.
*
* <h3>Lifecycle</h3>
* <p>
* <ul>
* <li>
* You will receive a call on {@link #onStart()} when printing starts.
* This callback can be used to allocate resources.
* </li>
* <li>
* Next you will get one or more calls to the pair
* {@link #onPrintAttributesChanged(PrintAttributes)} and {@link #onPrint(List,
* FileDescriptor, CancellationSignal, PrintProgressCallback)}. The first callback
* informs you that the print attributes (page size, density, etc) changed giving
* you an opportunity to re-layout the content. The second method asks you to write
* a PDF file with the content for specific pages.
* </li>
* <li>
* Finally, you will receive a call on {@link #onFinish()} right after printing.
* You can use this callback to release resources.
* </li>
* <li>
* You can receive calls to {@link #getInfo()} at any point which should return
* a {@link PrintAdapterInfo} describing your {@link PrintAdapter}.
* </li>
* </ul>
* </p>
* <p>
*/
public abstract class PrintAdapter {
/**
* Called when printing started. You can use this callback to
* allocate resources.
* <p>
* <strong>Note:</strong> Invoked on the main thread.
* </p>
*/
public void onStart() {
/* do nothing - stub */
}
/**
* Called when the print job attributes (page size, density, etc)
* changed giving you a chance to re-layout the content such that
* it matches the new constraints.
* <p>
* <strong>Note:</strong> Invoked on the main thread.
* </p>
*
* @param attributes The print job attributes.
* @return Whether the content changed based on the provided attributes.
*/
public boolean onPrintAttributesChanged(PrintAttributes attributes) {
return false;
}
/**
* Called when specific pages of the content have to be printed in the from of
* a PDF file to the given file descriptor. You should <strong>not</strong>
* close the file descriptor instead you have to invoke {@link PrintProgressCallback
* #onWriteFinished()} or {@link PrintProgressCallback#onPrintFailed(CharSequence)}.
* <p>
* <strong>Note:</strong> If the printed content is large, it is a good
* practice to schedule writing it on a dedicated thread and register a
* callback in the provided {@link CancellationSignal} upon which to stop
* writing data. The cancellation callback will not be made on the main
* thread.
* </p>
* <p>
* <strong>Note:</strong> Invoked on the main thread.
* </p>
* <p>
*
* @param pages The pages whose content to write.
* @param destination The destination file descriptor to which to start writing.
* @param cancellationSignal Signal for observing cancel write requests.
* @param progressListener Callback to inform the system with the write progress.
*
* @see CancellationSignal
*/
public abstract void onPrint(List<PageRange> pages, FileDescriptor destination,
CancellationSignal cancellationSignal, PrintProgressCallback progressListener);
/**
* Called when printing finished. You can use this callback to release
* resources.
* <p>
* <strong>Note:</strong> Invoked on the main thread.
* </p>
*/
public void onFinish() {
/* do nothing - stub */
}
/**
* Gets a {@link PrinterInfo} object that contains metadata about the
* printed content.
* <p>
* <strong>Note:</strong> Invoked on the main thread.
* </p>
*
* @return The info object for this {@link PrintAdapter}.
*
* @see PrintAdapterInfo
*/
public abstract PrintAdapterInfo getInfo();
/**
* Base class for implementing a listener for the printing progress
* of a {@link PrintAdapter}.
*/
public static abstract class PrintProgressCallback {
PrintProgressCallback() {
/* do nothing - hide constructor */
}
/**
* Notifies that all the data was printed.
*
* @param pages The pages that were printed.
*/
public void onPrintFinished(List<PageRange> pages) {
/* do nothing - stub */
}
/**
* Notifies that an error occurred while printing the data.
*
* @param error Error message. May be null if error is unknown.
*/
public void onPrintFailed(CharSequence error) {
/* do nothing - stub */
}
}
}

View File

@@ -0,0 +1,19 @@
/**
* 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;
parcelable PrintAdapterInfo;

View File

@@ -0,0 +1,136 @@
/*
* 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.os.Parcel;
import android.os.Parcelable;
/**
* This class encapsulates information about a {@link PrintAdapter} object.
*/
public final class PrintAdapterInfo implements Parcelable {
/**
* Constant for unknown page count.
*/
public static final int PAGE_COUNT_UNKNOWN = -1;
private int mPageCount;
private int mFlags;
/**
* Creates a new instance.
*/
private PrintAdapterInfo() {
/* do nothing */
}
/**
* Creates a new instance.
*
* @param parcel Data from which to initialize.
*/
private PrintAdapterInfo(Parcel parcel) {
mPageCount = parcel.readInt();
mFlags = parcel.readInt();
}
/**
* Gets the total number of pages.
*
* @return The number of pages.
*/
public int getPageCount() {
return mPageCount;
}
/**
* @return The flags of this printable info.
*
* @see #FLAG_NOTIFY_FOR_ATTRIBUTES_CHANGE
*/
public int getFlags() {
return mFlags;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mPageCount);
parcel.writeInt(mFlags);
}
/**
* Builder for creating an {@link PrintAdapterInfo}.
*/
public static final class Builder {
private final PrintAdapterInfo mPrintableInfo = new PrintAdapterInfo();
/**
* Sets the total number of pages.
*
* @param pageCount The number of pages. Must be
* greater than zero.
*/
public Builder setPageCount(int pageCount) {
if (pageCount < 0) {
throw new IllegalArgumentException("pageCount"
+ " must be greater than or euqal to zero!");
}
mPrintableInfo.mPageCount = pageCount;
return this;
}
/**
* Sets the flags of this printable info.
*
* @param flags The flags.
*
* @see #FLAG_NOTIFY_FOR_ATTRIBUTES_CHANGE
*/
public Builder setFlags(int flags) {
mPrintableInfo.mFlags = flags;
return this;
}
/**
* Creates a new {@link PrintAdapterInfo} instance.
*
* @return The new instance.
*/
public PrintAdapterInfo create() {
return mPrintableInfo;
}
}
public static final Parcelable.Creator<PrintAdapterInfo> CREATOR =
new Creator<PrintAdapterInfo>() {
@Override
public PrintAdapterInfo createFromParcel(Parcel parcel) {
return new PrintAdapterInfo(parcel);
}
@Override
public PrintAdapterInfo[] newArray(int size) {
return new PrintAdapterInfo[size];
}
};
}

View File

@@ -0,0 +1,19 @@
/**
* 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;
parcelable PrintAttributes;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,125 @@
/*
* 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.os.AsyncTask;
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.util.Log;
import libcore.io.IoUtils;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* Adapter for printing files.
*/
class PrintFileAdapter extends PrintAdapter {
private static final String LOG_TAG = "PrintFileAdapter";
private final File mFile;
private WriteFileAsyncTask mWriteFileAsyncTask;
public PrintFileAdapter(File file) {
if (file == null) {
throw new IllegalArgumentException("File cannot be null!");
}
mFile = file;
}
@Override
public void onPrint(List<PageRange> pages, FileDescriptor destination,
CancellationSignal cancellationSignal, PrintProgressCallback progressListener) {
mWriteFileAsyncTask = new WriteFileAsyncTask(mFile, destination, cancellationSignal,
progressListener);
mWriteFileAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
(Void[]) null);
}
@Override
public PrintAdapterInfo getInfo() {
// TODO: When we have PDF render library we should query the page count.
return new PrintAdapterInfo.Builder().create();
}
private static final class WriteFileAsyncTask extends AsyncTask<Void, Void, Void> {
private final File mSource;
private final FileDescriptor mDestination;
private final PrintProgressCallback mProgressListener;
private final CancellationSignal mCancellationSignal;
public WriteFileAsyncTask(File source, FileDescriptor destination,
CancellationSignal cancellationSignal, PrintProgressCallback progressListener) {
mSource = source;
mDestination = destination;
mProgressListener = progressListener;
mCancellationSignal = cancellationSignal;
mCancellationSignal.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel() {
cancel(true);
}
});
}
@Override
protected Void doInBackground(Void... params) {
InputStream in = null;
OutputStream out = new FileOutputStream(mDestination);
final byte[] buffer = new byte[8192];
try {
in = new FileInputStream(mSource);
while (true) {
final int readByteCount = in.read(buffer);
if (readByteCount < 0) {
break;
}
out.write(buffer, 0, readByteCount);
}
} catch (IOException ioe) {
Log.e(LOG_TAG, "Error writing data!", ioe);
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
if (!isCancelled()) {
List<PageRange> pages = new ArrayList<PageRange>();
pages.add(PageRange.ALL_PAGES);
mProgressListener.onPrintFinished(pages);
} else {
mProgressListener.onPrintFailed("Cancelled");
}
}
return null;
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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;
/**
* This class represents a print job from the perspective of
* an application.
*/
public final class PrintJob {
private final int mId;
private final PrintManager mPrintManager;
private PrintJobInfo mCachedInfo;
PrintJob(PrintJobInfo info, PrintManager printManager) {
mCachedInfo = info;
mPrintManager = printManager;
mId = info.getId();
}
/**
* Gets the unique print job id.
*
* @return The id.
*/
public int getId() {
return mId;
}
/**
* Gets the {@link PrintJobInfo} that describes this job.
* <p>
* <strong>Node:</strong>The returned info object is a snapshot of the
* current print job state. Every call to this method returns a fresh
* info object that reflects the current print job state.
* </p>
*
* @return The print job info.
*/
public PrintJobInfo getInfo() {
PrintJobInfo info = mPrintManager.getPrintJob(mId);
if (info != null) {
mCachedInfo = info;
}
return mCachedInfo;
}
/**
* Cancels this print job.
*/
public void cancel() {
mPrintManager.cancelPrintJob(mId);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PrintJob other = (PrintJob) obj;
return mId == other.mId;
}
@Override
public int hashCode() {
return mId;
}
}

View File

@@ -0,0 +1,19 @@
/**
* 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;
parcelable PrintJobInfo;

View File

@@ -0,0 +1,414 @@
/*
* 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.os.Parcel;
import android.os.Parcelable;
/**
* This class represents the description of a print job.
*/
public final class PrintJobInfo implements Parcelable {
/** Undefined print job id. */
public static final int PRINT_JOB_ID_UNDEFINED = -1;
/**
* Constant for matching any print job state.
*
* @hide
*/
public static final int STATE_ANY = -1;
/**
* Print job state: The print job is being created but not yet
* ready to be printed.
* <p>
* Next valid states: {@link #STATE_QUEUED}
* </p>
*/
public static final int STATE_CREATED = 1;
/**
* Print job status: The print jobs is created, it is ready
* to be printed and should be processed.
* <p>
* Next valid states: {@link #STATE_STARTED}, {@link #STATE_FAILED},
* {@link #STATE_CANCELED}
* </p>
*/
public static final int STATE_QUEUED = 2;
/**
* Print job status: The print job is being printed.
* <p>
* Next valid states: {@link #STATE_COMPLETED}, {@link #STATE_FAILED},
* {@link #STATE_CANCELED}
* </p>
*/
public static final int STATE_STARTED = 3;
/**
* Print job status: The print job was successfully printed.
* This is a terminal state.
* <p>
* Next valid states: None
* </p>
*/
public static final int STATE_COMPLETED = 4;
/**
* Print job status: The print job was printing but printing failed.
* This is a terminal state.
* <p>
* Next valid states: None
* </p>
*/
public static final int STATE_FAILED = 5;
/**
* Print job status: The print job was canceled.
* This is a terminal state.
* <p>
* Next valid states: None
* </p>
*/
public static final int STATE_CANCELED = 6;
/** The unique print job id. */
private int mId;
/** The human readable print job label. */
private CharSequence mLabel;
/** The unique id of the printer. */
private PrinterId mPrinterId;
/** The status of the print job. */
private int mState;
/** The id of the app that created the job. */
private int mAppId;
/** The id of the user that created the job. */
private int mUserId;
/** Optional tag assigned by a print service.*/
private String mTag;
/** The pages to print */
private PageRange[] mPageRanges;
/** The print job attributes size. */
private PrintAttributes mAttributes;
/**
* Gets the unique print job id.
*
* @return The id.
*/
public int getId() {
return mId;
}
/**
* Sets the unique print job id.
*
* @param The job id.
*
* @hide
*/
public void setId(int id) {
this.mId = id;
}
/**
* Gets the human readable job label.
*
* @return The label.
*/
public CharSequence getLabel() {
return mLabel;
}
/**
* Sets the human readable job label.
*
* @param label The label.
*
* @hide
*/
public void setLabel(CharSequence label) {
mLabel = label;
}
/**
* Gets the unique target printer id.
*
* @return The target printer id.
*/
public PrinterId getPrinterId() {
return mPrinterId;
}
/**
* Sets the unique target pritner id.
*
* @param printerId The target printer id.
*
* @hide
*/
public void setPrinterId(PrinterId printerId) {
mPrinterId = printerId;
}
/**
* Gets the current job state.
*
* @return The job state.
*/
public int getState() {
return mState;
}
/**
* Sets the current job state.
*
* @param state The job state.
*
* @hide
*/
public void setState(int state) {
mState = state;
}
/**
* Sets the owning application id.
*
* @return The owning app id.
*
* @hide
*/
public int getAppId() {
return mAppId;
}
/**
* Sets the owning application id.
*
* @param appId The owning app id.
*
* @hide
*/
public void setAppId(int appId) {
mAppId = appId;
}
/**
* Gets the owning user id.
*
* @return The user id.
*
* @hide
*/
public int getUserId() {
return mUserId;
}
/**
* Sets the owning user id.
*
* @param userId The user id.
*
* @hide
*/
public void setUserId(int userId) {
mUserId = userId;
}
/**
* Gets the optional tag assigned by a print service.
*
* @return The tag.
*/
public String getTag() {
return mTag;
}
/**
* Sets the optional tag assigned by a print service.
*
* @param tag The tag.
*
* @hide
*/
public void setTag(String tag) {
mTag = tag;
}
/**
* Gets the included page ranges.
*
* @return The included page ranges or <code>null</code> if not set.
*/
public PageRange[] getPageRanges() {
return mPageRanges;
}
/**
* Sets the included page ranges.
*
* @return The included page ranges.
*
* @hide
*/
public void setPageRanges(PageRange[] pageRanges) {
mPageRanges = pageRanges;
}
/**
* Gets the print job attributes.
*
* @return The attributes.
*/
public PrintAttributes getAttributes() {
return mAttributes;
}
/**
* Sets the print job attributes.
*
* @param attributes The attributes.
*
* @hide
*/
public void setAttributes(PrintAttributes attributes) {
mAttributes = attributes;
}
/** @hide*/
public PrintJobInfo() {
/* do nothing */
}
/** @hide */
public PrintJobInfo(PrintJobInfo other) {
mId = other.mId;
mLabel = other.mLabel;
mPrinterId = other.mPrinterId;
mState = other.mState;
mAppId = other.mAppId;
mUserId = other.mUserId;
mAttributes = other.mAttributes;
}
private PrintJobInfo(Parcel parcel) {
mId = parcel.readInt();
mLabel = parcel.readCharSequence();
mPrinterId = parcel.readParcelable(null);
mState = parcel.readInt();
mAppId = parcel.readInt();
mUserId = parcel.readInt();
if (parcel.readInt() == 1) {
mPageRanges = (PageRange[]) parcel.readParcelableArray(null);
}
if (parcel.readInt() == 1) {
mAttributes = PrintAttributes.CREATOR.createFromParcel(parcel);
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mId);
parcel.writeCharSequence(mLabel);
parcel.writeParcelable(mPrinterId, flags);
parcel.writeInt(mState);
parcel.writeInt(mAppId);
parcel.writeInt(mUserId);
if (mPageRanges != null) {
parcel.writeInt(1);
parcel.writeParcelableArray(mPageRanges, flags);
} else {
parcel.writeInt(0);
}
if (mAttributes != null) {
parcel.writeInt(1);
mAttributes.writeToParcel(parcel, flags);
} else {
parcel.writeInt(0);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("PrintJobInfo{");
builder.append("label: ").append(mLabel);
builder.append(", id: ").append(mId);
builder.append(", status: ").append(stateToString(mState));
builder.append(", printer: " + mPrinterId);
builder.append(", attributes: " + (mAttributes != null ? mAttributes.toString() : null));
builder.append("}");
return builder.toString();
}
/** @hide */
public static String stateToString(int state) {
switch (state) {
case STATE_CREATED: {
return "STATUS_CREATED";
}
case STATE_QUEUED: {
return "STATE_QUEUED";
}
case STATE_STARTED: {
return "STATE_STARTED";
}
case STATE_FAILED: {
return "STATUS_FAILED";
}
case STATE_COMPLETED: {
return "STATUS_COMPLETED";
}
case STATE_CANCELED: {
return "STATUS_CANCELED";
}
default: {
return "STATUS_UNKNOWN";
}
}
}
public static final Parcelable.Creator<PrintJobInfo> CREATOR =
new Creator<PrintJobInfo>() {
@Override
public PrintJobInfo createFromParcel(Parcel parcel) {
return new PrintJobInfo(parcel);
}
@Override
public PrintJobInfo[] newArray(int size) {
return new PrintJobInfo[size];
}
};
}

View File

@@ -0,0 +1,399 @@
/*
* 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.content.Context;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.PrintAdapter.PrintProgressCallback;
import android.util.Log;
import com.android.internal.os.SomeArgs;
import libcore.io.IoUtils;
import java.io.File;
import java.io.FileDescriptor;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* System level service for accessing the printing capabilities of the platform.
* <p>
* To obtain a handle to the print manager do the following:
* </p>
* <pre>
* PrintManager printManager =
* (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
* </pre>
*/
public final class PrintManager {
private static final String LOG_TAG = "PrintManager";
/** @hide */
public static final int APP_ID_ANY = -2;
private final Context mContext;
private final IPrintManager mService;
private final int mUserId;
private final int mAppId;
private final PrintClient mPrintClient;
private final Handler mHandler;
/**
* Listener for the state of a print job.
*/
public static interface PrintJobStateListener {
public void onStateChanged(int state);
}
/**
* Creates a new instance.
*
* @param context The current context in which to operate.
* @param service The backing system service.
*
* @hide
*/
public PrintManager(Context context, IPrintManager service, int userId, int appId) {
mContext = context;
mService = service;
mUserId = userId;
mAppId = appId;
mPrintClient = new PrintClient(this);
mHandler = new Handler(context.getMainLooper(), null, false) {
@Override
public void handleMessage(Message message) {
SomeArgs args = (SomeArgs) message.obj;
Context context = (Context) args.arg1;
IntentSender intent = (IntentSender) args.arg2;
args.recycle();
try {
context.startIntentSender(intent, null, 0, 0, 0);
} catch (SendIntentException sie) {
Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
}
}
};
}
/**
* Creates an instance that can access all print jobs.
*
* @param userId The user id for which to get all print jobs.
* @return An instance of the caller has the permission to access
* all print jobs, null otherwise.
*
* @hide
*/
public PrintManager getGlobalPrintManagerForUser(int userId) {
return new PrintManager(mContext, mService, userId, APP_ID_ANY);
}
PrintJobInfo getPrintJob(int printJobId) {
try {
return mService.getPrintJob(printJobId, mAppId, mUserId);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error getting print job:" + printJobId, re);
}
return null;
}
/**
* Gets the print jobs for this application.
*
* @return The print job list.
*
* @see PrintJob
*/
public List<PrintJob> getPrintJobs() {
try {
List<PrintJobInfo> printJobInfos = mService.getPrintJobs(mAppId, mUserId);
if (printJobInfos == null) {
return Collections.emptyList();
}
final int printJobCount = printJobInfos.size();
List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount);
for (int i = 0; i < printJobCount; i++) {
printJobs.add(new PrintJob(printJobInfos.get(i), this));
}
return printJobs;
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error getting print jobs!", re);
}
return Collections.emptyList();
}
ICancellationSignal cancelPrintJob(int printJobId) {
try {
mService.cancelPrintJob(printJobId, mAppId, mUserId);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error cancleing a print job:" + printJobId, re);
}
return null;
}
/**
* Creates a print job for printing a file with default print attributes.
*
* @param printJobName A name for the new print job.
* @param pdfFile The PDF file to print.
* @param attributes The default print job attributes.
* @return The created print job.
*/
public PrintJob print(String printJobName, File pdfFile, PrintAttributes attributes) {
PrintFileAdapter printable = new PrintFileAdapter(pdfFile);
return print(printJobName, printable, attributes);
}
/**
* Creates a print job for printing a {@link PrintAdapter} with default print
* attributes.
*
* @param printJobName A name for the new print job.
* @param printAdapter The printable adapter to print.
* @param attributes The default print job attributes.
* @return The created print job.
*/
public PrintJob print(String printJobName, PrintAdapter printAdapter,
PrintAttributes attributes) {
PrintAdapterDelegate delegate = new PrintAdapterDelegate(printAdapter,
mContext.getMainLooper());
try {
PrintJobInfo printJob = mService.print(printJobName, mPrintClient, delegate,
attributes, mAppId, mUserId);
if (printJob != null) {
return new PrintJob(printJob, this);
}
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error creating a print job", re);
}
return null;
}
private static final class PrintClient extends IPrintClient.Stub {
private final WeakReference<PrintManager> mWeakPrintManager;
public PrintClient(PrintManager manager) {
mWeakPrintManager = new WeakReference<PrintManager>(manager);
}
@Override
public void startPrintJobConfigActivity(IntentSender intent) {
PrintManager manager = mWeakPrintManager.get();
if (manager != null) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = manager.mContext;
args.arg2 = intent;
manager.mHandler.obtainMessage(0, args).sendToTarget();
}
}
}
private static final class PrintAdapterDelegate extends IPrintAdapter.Stub {
private final Object mLock = new Object();
private PrintAdapter mPrintAdapter;
private Handler mHandler;
public PrintAdapterDelegate(PrintAdapter printAdapter, Looper looper) {
mPrintAdapter = printAdapter;
mHandler = new MyHandler(looper);
}
@Override
public void start() {
synchronized (mLock) {
if (isFinishedLocked()) {
return;
}
mHandler.obtainMessage(MyHandler.MESSAGE_START,
mPrintAdapter).sendToTarget();
}
}
@Override
public void printAttributesChanged(PrintAttributes attributes) {
synchronized (mLock) {
if (isFinishedLocked()) {
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = mPrintAdapter;
args.arg2 = attributes;
mHandler.obtainMessage(MyHandler.MESSAGE_PRINT_ATTRIBUTES_CHANGED,
args).sendToTarget();
}
}
@Override
public void print(List<PageRange> pages, ParcelFileDescriptor fd,
IPrintProgressListener progressListener) {
synchronized (mLock) {
if (isFinishedLocked()) {
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = mPrintAdapter;
args.arg2 = pages;
args.arg3 = fd.getFileDescriptor();
args.arg4 = progressListener;
mHandler.obtainMessage(MyHandler.MESSAGE_PRINT, args).sendToTarget();
}
}
@Override
public void finish() {
synchronized (mLock) {
if (isFinishedLocked()) {
return;
}
mHandler.obtainMessage(MyHandler.MESSAGE_FINIS,
mPrintAdapter).sendToTarget();
}
}
private boolean isFinishedLocked() {
return mPrintAdapter == null;
}
private void finishLocked() {
mPrintAdapter = null;
mHandler = null;
}
private final class MyHandler extends Handler {
public static final int MESSAGE_START = 1;
public static final int MESSAGE_PRINT_ATTRIBUTES_CHANGED = 2;
public static final int MESSAGE_PRINT = 3;
public static final int MESSAGE_FINIS = 4;
public MyHandler(Looper looper) {
super(looper, null, true);
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MESSAGE_START: {
PrintAdapter adapter = (PrintAdapter) message.obj;
adapter.onStart();
} break;
case MESSAGE_PRINT_ATTRIBUTES_CHANGED: {
SomeArgs args = (SomeArgs) message.obj;
PrintAdapter adapter = (PrintAdapter) args.arg1;
PrintAttributes attributes = (PrintAttributes) args.arg2;
args.recycle();
adapter.onPrintAttributesChanged(attributes);
} break;
case MESSAGE_PRINT: {
SomeArgs args = (SomeArgs) message.obj;
PrintAdapter adapter = (PrintAdapter) args.arg1;
@SuppressWarnings("unchecked")
List<PageRange> pages = (List<PageRange>) args.arg2;
final FileDescriptor fd = (FileDescriptor) args.arg3;
IPrintProgressListener listener = (IPrintProgressListener) args.arg4;
args.recycle();
try {
ICancellationSignal remoteSignal = CancellationSignal.createTransport();
listener.onWriteStarted(adapter.getInfo(), remoteSignal);
CancellationSignal localSignal = CancellationSignal.fromTransport(
remoteSignal);
adapter.onPrint(pages, fd, localSignal,
new PrintProgressListenerWrapper(listener) {
@Override
public void onPrintFinished(List<PageRange> pages) {
IoUtils.closeQuietly(fd);
super.onPrintFinished(pages);
}
@Override
public void onPrintFailed(CharSequence error) {
IoUtils.closeQuietly(fd);
super.onPrintFailed(error);
}
});
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error printing", re);
IoUtils.closeQuietly(fd);
}
} break;
case MESSAGE_FINIS: {
PrintAdapter adapter = (PrintAdapter) message.obj;
adapter.onFinish();
synchronized (mLock) {
finishLocked();
}
} break;
default: {
throw new IllegalArgumentException("Unknown message: "
+ message.what);
}
}
}
}
}
private static abstract class PrintProgressListenerWrapper extends PrintProgressCallback {
private final IPrintProgressListener mWrappedListener;
public PrintProgressListenerWrapper(IPrintProgressListener listener) {
mWrappedListener = listener;
}
@Override
public void onPrintFinished(List<PageRange> pages) {
try {
mWrappedListener.onWriteFinished(pages);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling onWriteFinished", re);
}
}
@Override
public void onPrintFailed(CharSequence error) {
try {
mWrappedListener.onWriteFailed(error);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling onWriteFailed", re);
}
}
}
}

View File

@@ -0,0 +1,19 @@
/**
* 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;
parcelable PrinterId;

View File

@@ -0,0 +1,160 @@
/*
* 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.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
/**
* This class represents the unique id of a printer.
*/
public final class PrinterId implements Parcelable {
private final ComponentName mServiceComponentName;
private final String mLocalId;
/**
* Creates a new instance.
*
* @param serviceComponentName The managing print service.
* @param localId The unique id within the managing service.
*
* @hide
*/
public PrinterId(ComponentName serviceComponentName, String localId) {
mServiceComponentName = serviceComponentName;
mLocalId = localId;
}
private PrinterId(Parcel parcel) {
mServiceComponentName = parcel.readParcelable(null);
mLocalId = parcel.readString();
}
/**
* The id of the print service this printer is managed by.
*
* @return The print service component name.
*
* @hide
*/
public ComponentName getServiceComponentName() {
return mServiceComponentName;
}
/**
* Gets the local id of this printer in the context
* of the print service that manages it.
*
* @return The local id.
*/
public String getLocalId() {
return mLocalId;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mServiceComponentName, flags);
parcel.writeString(mLocalId);
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null) {
return false;
}
if (getClass() != object.getClass()) {
return false;
}
PrinterId other = (PrinterId) object;
if (mServiceComponentName == null) {
if (other.mServiceComponentName != null) {
return false;
}
} else if (!mServiceComponentName.equals(other.mServiceComponentName)) {
return false;
}
if (mLocalId != other.mLocalId) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int hashCode = 1;
hashCode = prime * hashCode + ((mServiceComponentName != null)
? mServiceComponentName.hashCode() : 1);
hashCode = prime * hashCode + mLocalId.hashCode();
return hashCode;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("PrinterId{");
builder.append(mServiceComponentName.flattenToString());
builder.append(":");
builder.append(mLocalId);
builder.append('}');
return builder.toString();
}
/**
* @hide
*/
public String flattenToString() {
return mServiceComponentName.flattenToString() + ":" + mLocalId;
}
/**
* @hide
*/
public static PrinterId unflattenFromString(String string) {
String[] split = string.split(":");
if (split.length != 2) {
throw new IllegalArgumentException("Not well-formed printer id:" + string);
}
ComponentName component = ComponentName.unflattenFromString(split[0]);
String localId = String.valueOf(split[1]);
return new PrinterId(component, localId);
}
public static final Parcelable.Creator<PrinterId> CREATOR =
new Creator<PrinterId>() {
@Override
public PrinterId createFromParcel(Parcel parcel) {
return new PrinterId(parcel);
}
@Override
public PrinterId[] newArray(int size) {
return new PrinterId[size];
}
};
}

View File

@@ -0,0 +1,19 @@
/**
* 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;
parcelable PrinterInfo;

View File

@@ -0,0 +1,798 @@
/*
* 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.os.Parcel;
import android.os.Parcelable;
import android.print.PrintAttributes.Margins;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Resolution;
import android.print.PrintAttributes.Tray;
import android.text.TextUtils;
import android.util.SparseIntArray;
import java.util.ArrayList;
import java.util.List;
/**
* This class represents the description of a printer. A description
* contains the printer id, human readable name, status, and available
* options for various printer capabilities, such as media size, etc.
*/
public final class PrinterInfo implements Parcelable {
/**
* Undefined default value.
*
* @hide
*/
public static final int DEFAULT_UNDEFINED = -1;
private static final int MIN_COPIES = 1;
private static final int PROPERTY_MEDIA_SIZE = 0;
private static final int PROPERTY_RESOLUTION = 1;
private static final int PROPERTY_INPUT_TRAY = 2;
private static final int PROPERTY_OUTPUT_TRAY = 3;
private static final int PROPERTY_DUPLEX_MODE = 4;
private static final int PROPERTY_COLOR_MODE = 5;
private static final int PROPERTY_FITTING_MODE = 6;
private static final int PROPERTY_ORIENTATION = 7;
/** Printer status: the printer is ready to print. */
public static final int STATUS_READY = 1;
// TODO: Add printer status constants.
private PrinterId mId;
private CharSequence mLabel;
private int mStatus;
private Margins mMinMargins;
private final List<MediaSize> mMediaSizes = new ArrayList<MediaSize>(); // required
private final List<Resolution> mResolutions = new ArrayList<Resolution>(); // required
private List<Tray> mInputTrays;
private List<Tray> mOutputTrays;
private int mDuplexModes;
private int mColorModes;
private int mFittingModes;
private int mOrientations;
private final SparseIntArray mDefaults = new SparseIntArray();
private Margins mDefaultMargins;
private PrinterInfo() {
mDefaults.put(PROPERTY_MEDIA_SIZE, DEFAULT_UNDEFINED);
mDefaults.put(PROPERTY_RESOLUTION, DEFAULT_UNDEFINED);
mDefaults.put(PROPERTY_INPUT_TRAY, DEFAULT_UNDEFINED);
mDefaults.put(PROPERTY_OUTPUT_TRAY, DEFAULT_UNDEFINED);
mDefaults.put(PROPERTY_DUPLEX_MODE, DEFAULT_UNDEFINED);
mDefaults.put(PROPERTY_COLOR_MODE, DEFAULT_UNDEFINED);
mDefaults.put(PROPERTY_FITTING_MODE, DEFAULT_UNDEFINED);
mDefaults.put(PROPERTY_ORIENTATION, DEFAULT_UNDEFINED);
}
/**
* Get the globally unique printer id.
*
* @return The printer id.
*/
public PrinterId getId() {
return mId;
}
/**
* Gets the human readable printer label.
*
* @return The human readable label.
*/
public CharSequence getLabel() {
return mLabel;
}
/**
* Gets the status of the printer.
*
* @return The status.
*/
public int getStatus() {
return mStatus;
}
/**
* Gets the supported media sizes.
*
* @return The supported media sizes.
*/
public List<MediaSize> getMediaSizes() {
return mMediaSizes;
}
/**
* Gets the supported resolutions.
*
* @return The supported resolutions.
*/
public List<Resolution> getResolutions() {
return mResolutions;
}
/**
* Gets the minimal supported margins.
*
* @return The minimal margins.
*/
public Margins getMinMargins() {
return mMinMargins;
}
/**
* Gets the available input trays.
*
* @return The input trays.
*/
public List<Tray> getInputTrays() {
return mInputTrays;
}
/**
* Gets the available output trays.
*
* @return The output trays.
*/
public List<Tray> getOutputTrays() {
return mOutputTrays;
}
/**
* Gets the supported duplex modes.
*
* @return The duplex modes.
*
* @see PrintAttributes#DUPLEX_MODE_NONE
* @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
* @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
*/
public int getDuplexModes() {
return mDuplexModes;
}
/**
* Gets the supported color modes.
*
* @return The color modes.
*
* @see PrintAttributes#COLOR_MODE_COLOR
* @see PrintAttributes#COLOR_MODE_MONOCHROME
*/
public int getColorModes() {
return mColorModes;
}
/**
* Gets the supported fitting modes.
*
* @return The fitting modes.
*
* @see PrintAttributes#FITTING_MODE_NONE
* @see PrintAttributes#FITTING_MODE_FIT_TO_PAGE
*/
public int getFittingModes() {
return mFittingModes;
}
/**
* Gets the supported orientations.
*
* @return The orientations.
*
* @see PrintAttributes#ORIENTATION_PORTRAIT
* @see PrintAttributes#ORIENTATION_LANDSCAPE
*/
public int getOrientations() {
return mOrientations;
}
/**
* Gets the default print attributes.
*
* @param outAttributes The attributes to populated.
*/
public void getDefaults(PrintAttributes outAttributes) {
outAttributes.clear();
// TODO: Do we want a printer to specify default copies?
outAttributes.setCopies(MIN_COPIES);
outAttributes.setMargins(mDefaultMargins);
final int mediaSizeIndex = mDefaults.get(PROPERTY_MEDIA_SIZE);
if (mediaSizeIndex >= 0) {
outAttributes.setMediaSize(mMediaSizes.get(mediaSizeIndex));
}
final int resolutionIndex = mDefaults.get(PROPERTY_RESOLUTION);
if (resolutionIndex >= 0) {
outAttributes.setResolution(mResolutions.get(resolutionIndex));
}
final int inputTrayIndex = mDefaults.get(PROPERTY_INPUT_TRAY);
if (inputTrayIndex >= 0) {
outAttributes.setInputTray(mInputTrays.get(inputTrayIndex));
}
final int outputTrayIndex = mDefaults.get(PROPERTY_OUTPUT_TRAY);
if (outputTrayIndex >= 0) {
outAttributes.setOutputTray(mOutputTrays.get(outputTrayIndex));
}
final int duplexMode = mDefaults.get(PROPERTY_DUPLEX_MODE);
if (duplexMode > 0) {
outAttributes.setDuplexMode(duplexMode);
}
final int colorMode = mDefaults.get(PROPERTY_COLOR_MODE);
if (colorMode > 0) {
outAttributes.setColorMode(mColorModes & colorMode);
}
final int fittingMode = mDefaults.get(PROPERTY_FITTING_MODE);
if (fittingMode > 0) {
outAttributes.setFittingMode(fittingMode);
}
final int orientation = mDefaults.get(PROPERTY_ORIENTATION);
if (orientation > 0) {
outAttributes.setOrientation(orientation);
}
}
private PrinterInfo(Parcel parcel) {
mId = parcel.readParcelable(null);
mLabel = parcel.readCharSequence();
mStatus = parcel.readInt();
mMinMargins = readMargins(parcel);
readMediaSizes(parcel);
readResolutions(parcel);
mInputTrays = readInputTrays(parcel);
mOutputTrays = readOutputTrays(parcel);
mColorModes = parcel.readInt();
mDuplexModes = parcel.readInt();
mFittingModes = parcel.readInt();
mOrientations = parcel.readInt();
readDefaults(parcel);
mDefaultMargins = readMargins(parcel);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mId, flags);
parcel.writeCharSequence(mLabel);
parcel.writeInt(mStatus);
writeMargins(mMinMargins, parcel);
writeMediaSizes(parcel);
writeResolutions(parcel);
writeInputTrays(parcel);
writeOutputTrays(parcel);
parcel.writeInt(mColorModes);
parcel.writeInt(mDuplexModes);
parcel.writeInt(mFittingModes);
parcel.writeInt(mOrientations);
writeDefaults(parcel);
writeMargins(mDefaultMargins, parcel);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("PrinterInfo{");
builder.append(mId).append(", \"");
builder.append(mLabel);
builder.append("\"}");
return builder.toString();
}
private void writeMediaSizes(Parcel parcel) {
if (mMediaSizes == null) {
parcel.writeInt(0);
return;
}
final int mediaSizeCount = mMediaSizes.size();
parcel.writeInt(mediaSizeCount);
for (int i = 0; i < mediaSizeCount; i++) {
mMediaSizes.get(i).writeToParcel(parcel);
}
}
private void readMediaSizes(Parcel parcel) {
final int mediaSizeCount = parcel.readInt();
for (int i = 0; i < mediaSizeCount; i++) {
mMediaSizes.add(MediaSize.createFromParcel(parcel));
}
}
private void writeResolutions(Parcel parcel) {
final int resolutionCount = mResolutions.size();
parcel.writeInt(resolutionCount);
for (int i = 0; i < resolutionCount; i++) {
mResolutions.get(i).writeToParcel(parcel);
}
}
private void readResolutions(Parcel parcel) {
final int resolutionCount = parcel.readInt();
for (int i = 0; i < resolutionCount; i++) {
mResolutions.add(Resolution.createFromParcel(parcel));
}
}
private void writeMargins(Margins margins, Parcel parcel) {
if (margins == null) {
parcel.writeInt(0);
} else {
parcel.writeInt(1);
margins.writeToParcel(parcel);
}
}
private Margins readMargins(Parcel parcel) {
return (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
}
private void writeInputTrays(Parcel parcel) {
if (mInputTrays == null) {
parcel.writeInt(0);
return;
}
final int inputTrayCount = mInputTrays.size();
parcel.writeInt(inputTrayCount);
for (int i = 0; i < inputTrayCount; i++) {
mInputTrays.get(i).writeToParcel(parcel);
}
}
private List<Tray> readInputTrays(Parcel parcel) {
final int inputTrayCount = parcel.readInt();
if (inputTrayCount <= 0) {
return null;
}
List<Tray> inputTrays = new ArrayList<Tray>(inputTrayCount);
for (int i = 0; i < inputTrayCount; i++) {
inputTrays.add(Tray.createFromParcel(parcel));
}
return inputTrays;
}
private void writeOutputTrays(Parcel parcel) {
if (mOutputTrays == null) {
parcel.writeInt(0);
return;
}
final int outputTrayCount = mOutputTrays.size();
parcel.writeInt(outputTrayCount);
for (int i = 0; i < outputTrayCount; i++) {
mOutputTrays.get(i).writeToParcel(parcel);
}
}
private List<Tray> readOutputTrays(Parcel parcel) {
final int outputTrayCount = parcel.readInt();
if (outputTrayCount <= 0) {
return null;
}
List<Tray> outputTrays = new ArrayList<Tray>(outputTrayCount);
for (int i = 0; i < outputTrayCount; i++) {
outputTrays.add(Tray.createFromParcel(parcel));
}
return outputTrays;
}
private void readDefaults(Parcel parcel) {
final int defaultCount = parcel.readInt();
for (int i = 0; i < defaultCount; i++) {
mDefaults.append(mDefaults.size(), parcel.readInt());
}
}
private void writeDefaults(Parcel parcel) {
final int defaultCount = mDefaults.size();
parcel.writeInt(defaultCount);
for (int i = 0; i < defaultCount; i++) {
parcel.writeInt(mDefaults.valueAt(i));
}
}
/**
* Builder for creating of a {@link PrinterInfo}. This class is responsible
* to enforce that all required attributes have at least one default value.
* In other words, this class creates only well-formed {@link PrinterInfo}s.
* <p>
* Look at the individual methods for a reference whether a property is
* required or if it is optional.
* </p>
*/
public static final class Builder {
private final PrinterInfo mPrinterInfo;
/**
* Creates a new instance.
*
* @param printerId The printer id.
* @param label The human readable printer label.
*
* @throws IllegalArgumentException IF the printer id is null.
* @throws IllegalArgumentException IF the label is empty.
*/
public Builder(PrinterId printerId, CharSequence label) {
if (printerId == null) {
throw new IllegalArgumentException("printerId cannot be null.");
}
if (TextUtils.isEmpty(label)) {
throw new IllegalArgumentException("label cannot be empty.");
}
mPrinterInfo = new PrinterInfo();
mPrinterInfo.mLabel = label;
mPrinterInfo.mId = printerId;
}
/**
* Sets the printer status.
* <p>
* <strong>Required:</strong> Yes
* </p>
*
* @param status The status.
* @return This builder.
*/
public Builder setStatus(int status) {
mPrinterInfo.mStatus = status;
return this;
}
/**
* Adds a supported media size.
* <p>
* <strong>Required:</strong> Yes
* </p>
*
* @param mediaSize A media size.
* @param isDefault Whether this is the default.
* @return This builder.
* @throws IllegalArgumentException If set as default and there
* is already a default.
*
* @see PrintAttributes.MediaSize
*/
public Builder addMediaSize(MediaSize mediaSize, boolean isDefault) {
final int insertionIndex = mPrinterInfo.mMediaSizes.size();
mPrinterInfo.mMediaSizes.add(mediaSize);
if (isDefault) {
throwIfDefaultAlreadySpecified(PROPERTY_MEDIA_SIZE);
mPrinterInfo.mDefaults.put(PROPERTY_MEDIA_SIZE, insertionIndex);
}
return this;
}
/**
* Adds a supported resolution.
* <p>
* <strong>Required:</strong> Yes
* </p>
*
* @param resolution A resolution.
* @param isDefault Whether this is the default.
* @return This builder.
*
* @throws IllegalArgumentException If set as default and there
* is already a default.
*
* @see PrintAttributes.Resolution
*/
public Builder addResolution(Resolution resolution, boolean isDefault) {
final int insertionIndex = mPrinterInfo.mResolutions.size();
mPrinterInfo.mResolutions.add(resolution);
if (isDefault) {
throwIfDefaultAlreadySpecified(PROPERTY_RESOLUTION);
mPrinterInfo.mDefaults.put(PROPERTY_RESOLUTION, insertionIndex);
}
return this;
}
/**
* Sets the minimal margins.
* <p>
* <strong>Required:</strong> No
* </p>
*
* @param margins The margins.
* @param defaultMargins The default margins.
* @return This builder.
*
* @see PrintAttributes.Margins
*/
public Builder setMinMargins(Margins margins, Margins defaultMargins) {
if (margins.getLeftMils() > defaultMargins.getLeftMils()
|| margins.getTopMils() > defaultMargins.getTopMils()
|| margins.getRightMils() < defaultMargins.getRightMils()
|| margins.getBottomMils() < defaultMargins.getBottomMils()) {
throw new IllegalArgumentException("Default margins"
+ " cannot be outside of the min margins.");
}
mPrinterInfo.mMinMargins = margins;
mPrinterInfo.mDefaultMargins = defaultMargins;
return this;
}
/**
* Adds an input tray.
* <p>
* <strong>Required:</strong> No
* </p>
*
* @param inputTray A tray.
* @param isDefault Whether this is the default.
* @return This builder.
*
* @throws IllegalArgumentException If set as default and there
* is already a default.
*
* @see PrintAttributes.Tray
*/
public Builder addInputTray(Tray inputTray, boolean isDefault) {
if (mPrinterInfo.mInputTrays == null) {
mPrinterInfo.mInputTrays = new ArrayList<Tray>();
}
final int insertionIndex = mPrinterInfo.mInputTrays.size();
mPrinterInfo.mInputTrays.add(inputTray);
if (isDefault) {
throwIfDefaultAlreadySpecified(PROPERTY_INPUT_TRAY);
mPrinterInfo.mDefaults.put(PROPERTY_INPUT_TRAY, insertionIndex);
}
return this;
}
/**
* Adds an output tray.
* <p>
* <strong>Required:</strong> No
* </p>
*
* @param outputTray A tray.
* @param isDefault Whether this is the default.
* @return This builder.
*
* @throws IllegalArgumentException If set as default and there
* is already a default.
*
* @see PrintAttributes.Tray
*/
public Builder addOutputTray(Tray outputTray, boolean isDefault) {
if (mPrinterInfo.mOutputTrays == null) {
mPrinterInfo.mOutputTrays = new ArrayList<Tray>();
}
final int insertionIndex = mPrinterInfo.mOutputTrays.size();
mPrinterInfo.mOutputTrays.add(outputTray);
if (isDefault) {
throwIfDefaultAlreadySpecified(PROPERTY_OUTPUT_TRAY);
mPrinterInfo.mDefaults.put(PROPERTY_OUTPUT_TRAY, insertionIndex);
}
return this;
}
/**
* Sets the color modes.
* <p>
* <strong>Required:</strong> Yes
* </p>
*
* @param colorModes The color mode bit mask.
* @param defaultColorMode The default color mode.
* @return This builder.
*
* @throws IllegalArgumentException If color modes contains an invalid
* mode bit or if the default color mode is invalid.
*
* @see PrintAttributes#COLOR_MODE_COLOR
* @see PrintAttributes#COLOR_MODE_MONOCHROME
*/
public Builder setColorModes(int colorModes, int defaultColorMode) {
int currentModes = colorModes;
while (currentModes > 0) {
final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
currentModes &= ~currentMode;
PrintAttributes.enforceValidColorMode(currentMode);
}
if ((colorModes & defaultColorMode) == 0) {
throw new IllegalArgumentException("Default color mode not in color modes.");
}
PrintAttributes.enforceValidColorMode(colorModes);
mPrinterInfo.mColorModes = colorModes;
mPrinterInfo.mDefaults.put(PROPERTY_COLOR_MODE, defaultColorMode);
return this;
}
/**
* Set the duplex modes.
* <p>
* <strong>Required:</strong> No
* </p>
*
* @param duplexModes The duplex mode bit mask.
* @param defaultDuplexMode The default duplex mode.
* @return This builder.
*
* @throws IllegalArgumentException If duplex modes contains an invalid
* mode bit or if the default duplex mode is invalid.
*
* @see PrintAttributes#DUPLEX_MODE_NONE
* @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
* @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
*/
public Builder setDuplexModes(int duplexModes, int defaultDuplexMode) {
int currentModes = duplexModes;
while (currentModes > 0) {
final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
currentModes &= ~currentMode;
PrintAttributes.enforceValidDuplexMode(currentMode);
}
if ((duplexModes & defaultDuplexMode) == 0) {
throw new IllegalArgumentException("Default duplex mode not in duplex modes.");
}
PrintAttributes.enforceValidDuplexMode(defaultDuplexMode);
mPrinterInfo.mDuplexModes = duplexModes;
mPrinterInfo.mDefaults.put(PROPERTY_DUPLEX_MODE, defaultDuplexMode);
return this;
}
/**
* Sets the fitting modes.
* <p>
* <strong>Required:</strong> No
* </p>
*
* @param fittingModes The fitting mode bit mask.
* @param defaultFittingMode The default fitting mode.
* @return This builder.
*
* @throws IllegalArgumentException If fitting modes contains an invalid
* mode bit or if the default fitting mode is invalid.
*
* @see PrintAttributes#FITTING_MODE_NONE
* @see PrintAttributes#FITTING_MODE_FIT_TO_PAGE
*/
public Builder setFittingModes(int fittingModes, int defaultFittingMode) {
int currentModes = fittingModes;
while (currentModes > 0) {
final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
currentModes &= ~currentMode;
PrintAttributes.enfoceValidFittingMode(currentMode);
}
if ((fittingModes & defaultFittingMode) == 0) {
throw new IllegalArgumentException("Default fitting mode not in fiting modes.");
}
PrintAttributes.enfoceValidFittingMode(defaultFittingMode);
mPrinterInfo.mFittingModes = fittingModes;
mPrinterInfo.mDefaults.put(PROPERTY_FITTING_MODE, defaultFittingMode);
return this;
}
/**
* Sets the orientations.
* <p>
* <strong>Required:</strong> Yes
* </p>
*
* @param orientations The orientation bit mask.
* @param defaultOrientation The default orientation.
* @return This builder.
*
* @throws IllegalArgumentException If orientations contains an invalid
* mode bit or if the default orientation is invalid.
*
* @see PrintAttributes#ORIENTATION_PORTRAIT
* @see PrintAttributes#ORIENTATION_LANDSCAPE
*/
public Builder setOrientations(int orientations, int defaultOrientation) {
int currentOrientaions = orientations;
while (currentOrientaions > 0) {
final int currentOrnt = (1 << Integer.numberOfTrailingZeros(currentOrientaions));
currentOrientaions &= ~currentOrnt;
PrintAttributes.enforceValidOrientation(currentOrnt);
}
if ((orientations & defaultOrientation) == 0) {
throw new IllegalArgumentException("Default orientation not in orientations.");
}
PrintAttributes.enforceValidOrientation(defaultOrientation);
mPrinterInfo.mOrientations = orientations;
mPrinterInfo.mDefaults.put(PROPERTY_ORIENTATION, defaultOrientation);
return this;
}
/**
* Crates a new {@link PrinterInfo} enforcing that all required properties
* have need specified. See individual methods in this class for reference
* about required attributes.
*
* @return A new {@link PrinterInfo}.
*
* @throws IllegalStateException If a required attribute was not specified.
*/
public PrinterInfo create() {
if (mPrinterInfo.mMediaSizes == null || mPrinterInfo.mMediaSizes.isEmpty()) {
throw new IllegalStateException("No media size specified.");
}
if (mPrinterInfo.mDefaults.valueAt(PROPERTY_MEDIA_SIZE) == DEFAULT_UNDEFINED) {
throw new IllegalStateException("No default media size specified.");
}
if (mPrinterInfo.mResolutions == null || mPrinterInfo.mResolutions.isEmpty()) {
throw new IllegalStateException("No resolution specified.");
}
if (mPrinterInfo.mDefaults.valueAt(PROPERTY_RESOLUTION) == DEFAULT_UNDEFINED) {
throw new IllegalStateException("No default resolution specified.");
}
if (mPrinterInfo.mColorModes == 0) {
throw new IllegalStateException("No color mode specified.");
}
if (mPrinterInfo.mDefaults.valueAt(PROPERTY_COLOR_MODE) == DEFAULT_UNDEFINED) {
throw new IllegalStateException("No default color mode specified.");
}
if (mPrinterInfo.mOrientations == 0) {
throw new IllegalStateException("No oprientation specified.");
}
if (mPrinterInfo.mDefaults.valueAt(PROPERTY_ORIENTATION) == DEFAULT_UNDEFINED) {
throw new IllegalStateException("No default orientation specified.");
}
if (mPrinterInfo.mMinMargins == null) {
mPrinterInfo.mMinMargins = new Margins(0, 0, 0, 0);
}
if (mPrinterInfo.mDefaultMargins == null) {
mPrinterInfo.mDefaultMargins = mPrinterInfo.mMinMargins;
}
return mPrinterInfo;
}
private void throwIfDefaultAlreadySpecified(int propertyIndex) {
if (mPrinterInfo.mDefaults.get(propertyIndex) != DEFAULT_UNDEFINED) {
throw new IllegalArgumentException("Default already specified.");
}
}
}
public static final Parcelable.Creator<PrinterInfo> CREATOR =
new Parcelable.Creator<PrinterInfo>() {
@Override
public PrinterInfo createFromParcel(Parcel parcel) {
return new PrinterInfo(parcel);
}
@Override
public PrinterInfo[] newArray(int size) {
return new PrinterInfo[size];
}
};
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.printservice;
import android.os.ICancellationSignal;
import android.print.PrintJobInfo;
import android.print.PrinterId;
import android.printservice.IPrintServiceClient;
/**
* Top-level interface to a print service component.
*
* @hide
*/
oneway interface IPrintService {
void setClient(IPrintServiceClient client);
void requestCancelPrintJob(in PrintJobInfo printJob);
void onPrintJobQueued(in PrintJobInfo printJob);
void startPrinterDiscovery();
void stopPrinterDiscovery();
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.printservice;
import android.os.ParcelFileDescriptor;
import android.print.PrintJobInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
/**
* The top-level interface from a print service to the system.
*
* @hide
*/
interface IPrintServiceClient {
List<PrintJobInfo> getPrintJobs();
PrintJobInfo getPrintJob(int printJobId);
boolean setPrintJobState(int printJobId, int status);
boolean setPrintJobTag(int printJobId, String tag);
oneway void writePrintJobData(in ParcelFileDescriptor fd, int printJobId);
oneway void addDiscoveredPrinters(in List<PrinterInfo> printers);
oneway void removeDiscoveredPrinters(in List<PrinterId> printers);
}

View File

@@ -0,0 +1,254 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.printservice;
import java.io.FileDescriptor;
import java.io.IOException;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.PrintJobInfo;
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.
*/
public final class PrintJob {
private static final String LOG_TAG = "PrintJob";
private final int mId;
private final IPrintServiceClient mPrintServiceClient;
private PrintJobInfo mCachedInfo;
PrintJob(PrintJobInfo info, IPrintServiceClient client) {
if (client == null) {
throw new IllegalStateException("Print serivice not connected!");
}
mCachedInfo = info;
mId = info.getId();
mPrintServiceClient = client;
}
/**
* Gets the unique print job id.
*
* @return The id.
*/
public int getId() {
return mId;
}
/**
* Gets the {@link PrintJobInfo} that describes this job.
* <p>
* <strong>Node:</strong>The returned info object is a snapshot of the
* current print job state. Every call to this method returns a fresh
* info object that reflects the current print jobs state.
* </p>
*
* @return The print job info.
*/
public PrintJobInfo getInfo() {
PrintJobInfo info = null;
try {
info = mPrintServiceClient.getPrintJob(mId);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Couldn't get info for job: " + mId, re);
}
if (info != null) {
mCachedInfo = info;
}
return mCachedInfo;
}
/**
* Gets whether this print job is queued. Such a print job is
* ready to be printed and can be started.
*
* @return Whether the print job is queued.
*
* @see #start()
*/
public boolean isQueued() {
return getInfo().getState() == PrintJobInfo.STATE_QUEUED;
}
/**
* Gets whether this print job is started. Such a print job is
* being printed and can be completed or canceled or failed.
*
* @return Whether the print job is started.
*
* @see #complete()
* @see #cancel()
* @see #fail()
*/
public boolean isStarted() {
return getInfo().getState() == PrintJobInfo.STATE_STARTED;
}
/**
* Starts the print job. You should call this method if {@link
* #isQueued()} returns true and you started printing.
*
* @return Whether the job as started.
*
* @see #isQueued()
*/
public boolean start() {
if (isQueued()) {
return setState(PrintJobInfo.STATE_STARTED);
}
return false;
}
/**
* Completes the print job. You should call this method if {@link
* #isStarted()} returns true and you are done printing.
*
* @return Whether the job as completed.
*
* @see #isStarted()
*/
public boolean complete() {
if (isStarted()) {
return setState(PrintJobInfo.STATE_COMPLETED);
}
return false;
}
/**
* Fails the print job. You should call this method if {@link
* #isStarted()} returns true you filed while printing.
*
* @return Whether the job as failed.
*
* @see #isStarted()
*/
public boolean fail(CharSequence error) {
// TODO: Propagate the error message to the UI.
if (isStarted()) {
return setState(PrintJobInfo.STATE_FAILED);
}
return false;
}
/**
* Cancels the print job. You should call this method if {@link
* #isStarted()} returns true and you canceled the print job as a
* response to a call to {@link PrintService#onRequestCancelPrintJob(
* PrintJob)}.
*
* @return Whether the job as canceled.
*
* @see #isStarted()
*/
public boolean cancel() {
if (isStarted()) {
return setState(PrintJobInfo.STATE_CANCELED);
}
return false;
}
/**
* Sets a tag that is valid in the context of a {@link PrintService}
* and is not interpreted by the system. For example, a print service
* may set as a tag the key of the print job returned by a remote
* print server, if the printing is off handed to a cloud based service.
*
* @param tag The tag.
* @return True if the tag was set, false otherwise.
*/
public boolean setTag(String tag) {
try {
return mPrintServiceClient.setPrintJobTag(mId, tag);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error setting tag for job:" + mId, re);
}
return false;
}
/**
* Gets the data associated with this print job. It is a responsibility of
* the print service to open a stream to the returned file descriptor
* and fully read the content.
*
* @return A file descriptor for reading the data or <code>null</code>.
*/
public final FileDescriptor getData() {
ParcelFileDescriptor source = null;
ParcelFileDescriptor sink = null;
try {
ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
source = fds[0];
sink = fds[1];
mPrintServiceClient.writePrintJobData(sink, mId);
return source.getFileDescriptor();
} catch (IOException ioe) {
Log.e(LOG_TAG, "Error calling getting print job data!", ioe);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling getting print job data!", re);
} finally {
if (sink != null) {
try {
sink.close();
} catch (IOException ioe) {
/* ignore */
}
}
}
return null;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PrintJob other = (PrintJob) obj;
return (mId == other.mId);
}
@Override
public int hashCode() {
return mId;
}
private boolean setState(int state) {
// Best effort - update the state of the cached info since
// we may not be able to re-fetch it later if the job gets
// removed from the spooler.
mCachedInfo.setState(state);
try {
return mPrintServiceClient.setPrintJobState(mId, state);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error setting the state of job:" + mId, re);
}
return false;
}
}

View File

@@ -0,0 +1,462 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.printservice;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.print.PrintJobInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* <p>
* This is the base class for implementing print services. A print service
* knows how to discover and interact one or more printers via one or more
* protocols.
* </p>
* <h3>Printer discovery</h3>
* <p>
* A print service is responsible for discovering and reporting printers.
* A printer discovery period starts with a call to
* {@link #onStartPrinterDiscovery()} and ends with a call to
* {@link #onStopPrinterDiscovery()}. During a printer discovery
* period the print service reports newly discovered printers by
* calling {@link #addDiscoveredPrinters(List)} and added printers
* that disappeared by calling {@link #removeDiscoveredPrinters(List)}.
* Calls to {@link #addDiscoveredPrinters(List)} and
* {@link #removeDiscoveredPrinters(List)} before a call to
* {@link #onStartPrinterDiscovery()} and after a call to
* {@link #onStopPrinterDiscovery()} is a no-op.
* </p>
* <p>
* For every printer discovery period all printers have to be added. Each
* printer known to this print service should be added only once during a
* discovery period, unless it was added and then removed before that.
* Only an already added printer can be removed.
* </p>
* <h3>Print jobs</h3>
* <p>
* When a new print job targeted to the printers managed by this print
* service is queued, i.e. ready for processing by the print service,
* a call to {@link #onPrintJobQueued(PrintJob)} is made and the print
* service may handle it immediately or schedule that for an appropriate
* time in the future. The list of all print jobs for this service
* are be available by calling {@link #getPrintJobs()}. A queued print
* job is in a {@link PrintJobInfo#STATE_QUEUED} state.
* </p>
* <p>
* A print service is responsible for setting the print job state as
* appropriate while processing it. Initially, a print job is in a
* {@link PrintJobInfo#STATE_QUEUED} state which means that the data to
* be printed is spooled by the system and the print service can obtain
* that data by calling {@link PrintJob#getData()}. After the print
* service starts printing the data it should set the print job state
* to {@link PrintJobInfo#STATE_STARTED}. Upon successful completion, the
* print job state has to be set to {@link PrintJobInfo#STATE_COMPLETED}.
* In a case of a failure, the print job state should be set to
* {@link PrintJobInfo#STATE_FAILED}. If a print job is in a
* {@link PrintJobInfo#STATE_STARTED} state and the user requests to
* cancel it, the print service will receive a call to
* {@link #onRequestCancelPrintJob(PrintJob)} which requests from the
* service to do a best effort in canceling the job. In case the job
* is successfully canceled, its state has to be set to
* {@link PrintJobInfo#STATE_CANCELED}.
* </p>
* <h3>Lifecycle</h3>
* <p>
* The lifecycle of a print service is managed exclusively by the system
* and follows the established service lifecycle. Additionally, starting
* or stopping a print service is triggered exclusively by an explicit
* user action through enabling or disabling it in the device settings.
* After the system binds to a print service, it calls {@link #onConnected()}.
* This method can be overriden by clients to perform post binding setup.
* Also after the system unbinds from a print service, it calls
* {@link #onDisconnected()}. This method can be overriden by clients to
* perform post unbinding cleanup.
* </p>
* <h3>Declaration</h3>
* <p>
* A print service is declared as any other service in an AndroidManifest.xml
* but it must also specify that it handles the {@link android.content.Intent}
* with action {@link #SERVICE_INTERFACE}. Failure to declare this intent
* will cause the system to ignore the print service. Additionally, a print
* service must request the {@link android.Manifest.permission#BIND_PRINT_SERVICE}
* permission to ensure that only the system can bind to it. Failure to
* declare this intent will cause the system to ignore the print service.
* Following is an example declaration:
* </p>
* <pre>
* &lt;service android:name=".MyPrintService"
* android:permission="android.permission.BIND_PRINT_SERVICE"&gt;
* &lt;intent-filter&gt;
* &lt;action android:name="android.printservice.PrintService" /&gt;
* &lt;/intent-filter&gt;
* . . .
* &lt;/service&gt;
* </pre>
* <h3>Configuration</h3>
* <p>
* A print service can be configured by specifying an optional settings
* activity which exposes service specific options, an optional add
* prints activity which is used for manual addition of printers, etc.
* It is a responsibility of the system to launch the settings and add
* printers activities when appropriate.
* </p>
* <p>
* A print service is configured by providing a
* {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring
* the service. A service declaration with a meta-data tag is presented
* below:
* <pre> &lt;service android:name=".MyPrintService"
* android:permission="android.permission.BIND_PRINT_SERVICE"&gt;
* &lt;intent-filter&gt;
* &lt;action android:name="android.printservice.PrintService" /&gt;
* &lt;/intent-filter&gt;
* &lt;meta-data android:name="android.printservice" android:resource="@xml/printservice" /&gt;
* &lt;/service&gt;</pre>
* </p>
* <p>
* For more details refer to {@link #SERVICE_META_DATA} and
* <code>&lt;{@link android.R.styleable#PrintService print-service}&gt;</code>.
* </p>
*/
public abstract class PrintService extends Service {
private static final String LOG_TAG = PrintService.class.getSimpleName();
/**
* The {@link Intent} action that must be declared as handled by a service
* in its manifest to allow the system to recognize it as a print service.
*/
public static final String SERVICE_INTERFACE = "android.printservice.PrintService";
/**
* Name under which a PrintService component publishes additional information
* about itself. This meta-data must reference an XML resource containing a
* <code>&lt;{@link android.R.styleable#PrintService print-service}&gt;</code>
* tag. This is a a sample XML file configuring a print service:
* <pre> &lt;print-service
* android:settingsActivity="foo.bar.MySettingsActivity"
* andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity."
* . . .
* /&gt;</pre>
*/
public static final String SERVICE_META_DATA = "android.printservice";
private final Object mLock = new Object();
private Handler mHandler;
private IPrintServiceClient mClient;
private boolean mDiscoveringPrinters;
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
mHandler = new MyHandler(base.getMainLooper());
}
/**
* The system has connected to this service.
*/
protected void onConnected() {
/* do nothing */
}
/**
* The system has disconnected from this service.
*/
protected void onDisconnected() {
/* do nothing */
}
/**
* Callback requesting from this service to start printer discovery.
* At the end of the printer discovery period the system will call
* {@link #onStopPrinterDiscovery(). Discovered printers should be
* reported by calling #addDiscoveredPrinters(List) and reported ones
* that disappear should be reported by calling
* {@link #removeDiscoveredPrinters(List)}.
*
* @see #onStopPrinterDiscovery()
* @see #addDiscoveredPrinters(List)
* @see #removeDiscoveredPrinters(List)
*/
protected abstract void onStartPrinterDiscovery();
/**
* Callback requesting from this service to stop printer discovery.
*
* @see #onStartPrinterDiscovery()
* @see #addDiscoveredPrinters(List)
* @see #removeDiscoveredPrinters(List)
*/
protected abstract void onStopPrinterDiscovery();
/**
* Adds discovered printers. This method should be called during a
* printer discovery period, i.e. after a call to
* {@link #onStartPrinterDiscovery()} and before the corresponding
* call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing.
* <p>
* <strong>Note:</strong> For every printer discovery period all
* printers have to be added. You can call this method as many times as
* necessary during the discovery period but should not pass in already
* added printers. If a printer is already added in the same printer
* discovery period, it will be ignored.
* </p>
*
* @param printers A list with discovered printers.
*
* @throws IllegalStateException If this service is not connected.
*
* @see #removeDiscoveredPrinters(List)
* @see #onStartPrinterDiscovery()
* @see #onStopPrinterDiscovery()
*/
public final void addDiscoveredPrinters(List<PrinterInfo> printers) {
synchronized (mLock) {
if (mClient == null) {
throw new IllegalStateException("Print serivice not connected!");
}
if (mDiscoveringPrinters) {
try {
// Calling with a lock into the system is fine.
mClient.addDiscoveredPrinters(printers);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error adding discovered printers!", re);
}
}
}
}
/**
* Removes discovered printers given their ids. This method should be called
* during a printer discovery period, i.e. after a call to
* {@link #onStartPrinterDiscovery()} and before the corresponding
* call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing.
* <p>
* For every printer discovery period all printers have to be added. You
* should remove only printers that were added in this printer discovery
* period by a call to {@link #addDiscoveredPrinters(List)}. You can call
* this method as many times as necessary during the discovery period
* but should not pass in already removed printer ids. If a printer with
* a given id is already removed in the same discovery period, it will
* be ignored.
* </p>
*
* @param printerIds A list with disappeared printer ids.
*
* @throws IllegalStateException If this service is not connected.
*
* @see #addDiscoveredPrinters(List)
* @see #onStartPrinterDiscovery()
* @see #onStopPrinterDiscovery()
*/
public final void removeDiscoveredPrinters(List<PrinterId> printerIds) {
synchronized (mLock) {
if (mClient == null) {
throw new IllegalStateException("Print serivice not connected!");
}
if (mDiscoveringPrinters) {
try {
// Calling with a lock into the system is fine.
mClient.removeDiscoveredPrinters(printerIds);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error removing discovered printers!", re);
}
}
}
}
/**
* Called when canceling of a print job is requested. The service
* should do best effort to fulfill the request. After the print
* job is canceled it state has to be set to
* {@link PrintJobInfo#STATE_CANCELED}.
*
* @param printJob The print job to be canceled.
*/
protected void onRequestCancelPrintJob(PrintJob printJob) {
}
/**
* Called when there is a queued print job for one of the printers
* managed by this print service. A queued print job is ready for
* processing by a print service which can get the data to be printed
* by calling {@link PrintJob#getData()}. This service may start
* processing the passed in print job or schedule handling of queued
* print jobs at a convenient time. The service can get the print
* jobs by a call to {@link #getPrintJobs()} and examine their state
* to find the ones with state {@link PrintJobInfo#STATE_QUEUED}.
*
* @param printJob The new queued print job.
*
* @see #getPrintJobs()
*/
protected abstract void onPrintJobQueued(PrintJob printJob);
/**
* Gets the print jobs for the printers managed by this service.
*
* @return The print jobs.
*
* @throws IllegalStateException If this service is not connected.
*/
public final List<PrintJob> getPrintJobs() {
synchronized (mLock) {
if (mClient == null) {
throw new IllegalStateException("Print serivice not connected!");
}
try {
List<PrintJob> printJobs = null;
List<PrintJobInfo> printJobInfos = mClient.getPrintJobs();
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), mClient));
}
}
if (printJobs != null) {
return printJobs;
}
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling getPrintJobs()", re);
}
return Collections.emptyList();
}
}
/**
* Generates a global printer id from a local id. The local id is unique
* only within this print service.
*
* @param localId The local id.
* @return Global printer id.
*/
public final PrinterId generatePrinterId(String localId) {
return new PrinterId(new ComponentName(getPackageName(),
getClass().getName()), localId);
}
@Override
public final IBinder onBind(Intent intent) {
return new IPrintService.Stub() {
@Override
public void setClient(IPrintServiceClient client) {
mHandler.obtainMessage(MyHandler.MESSAGE_SET_CLEINT, client).sendToTarget();
}
@Override
public void startPrinterDiscovery() {
mHandler.sendEmptyMessage(MyHandler.MESSAGE_START_PRINTER_DISCOVERY);
}
@Override
public void stopPrinterDiscovery() {
mHandler.sendEmptyMessage(MyHandler.MESSAGE_STOP_PRINTER_DISCOVERY);
}
@Override
public void requestCancelPrintJob(PrintJobInfo printJob) {
mHandler.obtainMessage(MyHandler.MESSAGE_CANCEL_PRINTJOB, printJob).sendToTarget();
}
@Override
public void onPrintJobQueued(PrintJobInfo printJob) {
mHandler.obtainMessage(MyHandler.MESSAGE_ON_PRINTJOB_QUEUED,
printJob).sendToTarget();
}
};
}
private final class MyHandler extends Handler {
public static final int MESSAGE_START_PRINTER_DISCOVERY = 1;
public static final int MESSAGE_STOP_PRINTER_DISCOVERY = 2;
public static final int MESSAGE_CANCEL_PRINTJOB = 3;
public static final int MESSAGE_ON_PRINTJOB_QUEUED = 4;
public static final int MESSAGE_SET_CLEINT = 5;
public MyHandler(Looper looper) {
super(looper, null, true);
}
@Override
public void handleMessage(Message message) {
final int action = message.what;
switch (action) {
case MESSAGE_START_PRINTER_DISCOVERY: {
synchronized (mLock) {
mDiscoveringPrinters = true;
}
onStartPrinterDiscovery();
} break;
case MESSAGE_STOP_PRINTER_DISCOVERY: {
synchronized (mLock) {
mDiscoveringPrinters = false;
}
onStopPrinterDiscovery();
} break;
case MESSAGE_CANCEL_PRINTJOB: {
PrintJobInfo printJob = (PrintJobInfo) message.obj;
onRequestCancelPrintJob(new PrintJob(printJob, mClient));
} break;
case MESSAGE_ON_PRINTJOB_QUEUED: {
PrintJobInfo printJob = (PrintJobInfo) message.obj;
onPrintJobQueued(new PrintJob(printJob, mClient));
} break;
case MESSAGE_SET_CLEINT: {
IPrintServiceClient client = (IPrintServiceClient) message.obj;
synchronized (mLock) {
mClient = client;
if (client == null) {
mDiscoveringPrinters = false;
}
}
if (client != null) {
onConnected();
} else {
onStopPrinterDiscovery();
onDisconnected();
}
} break;
default: {
throw new IllegalArgumentException("Unknown message: " + action);
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2013, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.printservice;
parcelable PrintServiceInfo;

View File

@@ -0,0 +1,267 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.printservice;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
/**
* This class describes a {@link PrintService}. A print service knows
* how to communicate with one or more printers over one or more protocols
* and exposes printers for use by the applications via the platform print
* APIs.
*
* @see PrintService
* @see android.print.PrintManager
*
* @hide
*/
public final class PrintServiceInfo implements Parcelable {
private static final boolean DEBUG = false;
private static final String LOG_TAG = PrintServiceInfo.class.getSimpleName();
private static final String TAG_PRINT_SERVICE = "print-service";
private final String mId;
private final ResolveInfo mResolveInfo;
private final String mSettingsActivityName;
private final String mAddPrintersActivityName;
/**
* Creates a new instance.
*
* @hide
*/
public PrintServiceInfo(Parcel parcel) {
mId = parcel.readString();
mResolveInfo = parcel.readParcelable(null);
mSettingsActivityName = parcel.readString();
mAddPrintersActivityName = parcel.readString();
}
/**
* Creates a new instance.
*
* @param resolveInfo The service resolve info.
* @param settingsActivityName Optional settings activity name.
* @param addPrintersActivityName Optional add printers activity name.
*/
public PrintServiceInfo(ResolveInfo resolveInfo, String settingsActivityName,
String addPrintersActivityName) {
mId = new ComponentName(resolveInfo.serviceInfo.packageName,
resolveInfo.serviceInfo.name).flattenToString();
mResolveInfo = resolveInfo;
mSettingsActivityName = settingsActivityName;
mAddPrintersActivityName = addPrintersActivityName;
}
/**
* Creates a new instance.
*
* @param resolveInfo The service resolve info.
* @param context Context for accessing resources.
* @throws XmlPullParserException If a XML parsing error occurs.
* @throws IOException If a I/O error occurs.
* @hide
*/
public static PrintServiceInfo create(ResolveInfo resolveInfo, Context context) {
String settingsActivityName = null;
String addPrintersActivityName = null;
XmlResourceParser parser = null;
PackageManager packageManager = context.getPackageManager();
parser = resolveInfo.serviceInfo.loadXmlMetaData(packageManager,
PrintService.SERVICE_META_DATA);
if (parser != null) {
try {
int type = 0;
while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
type = parser.next();
}
String nodeName = parser.getName();
if (!TAG_PRINT_SERVICE.equals(nodeName)) {
throw new XmlPullParserException(
"Meta-data does not start with" + TAG_PRINT_SERVICE + " tag");
}
Resources resources = packageManager.getResourcesForApplication(
resolveInfo.serviceInfo.applicationInfo);
AttributeSet allAttributes = Xml.asAttributeSet(parser);
TypedArray attributes = resources.obtainAttributes(allAttributes,
com.android.internal.R.styleable.PrintService);
settingsActivityName = attributes.getString(
com.android.internal.R.styleable.PrintService_settingsActivity);
addPrintersActivityName = attributes.getString(
com.android.internal.R.styleable.PrintService_addPrintersActivity);
attributes.recycle();
} catch (IOException ioe) {
Log.w(LOG_TAG, "Error reading meta-data:" + ioe);
} catch (XmlPullParserException xppe) {
Log.w(LOG_TAG, "Error reading meta-data:" + xppe);
} catch (NameNotFoundException e) {
Log.e(LOG_TAG, "Unable to load resources for: "
+ resolveInfo.serviceInfo.packageName);
} finally {
if (parser != null) {
parser.close();
}
}
}
return new PrintServiceInfo(resolveInfo, settingsActivityName, addPrintersActivityName);
}
/**
* The accessibility service id.
* <p>
* <strong>Generated by the system.</strong>
* </p>
*
* @return The id.
*/
public String getId() {
return mId;
}
/**
* The service {@link ResolveInfo}.
*
* @return The info.
*/
public ResolveInfo getResolveInfo() {
return mResolveInfo;
}
/**
* The settings activity name.
* <p>
* <strong>Statically set from
* {@link PrintService#SERVICE_META_DATA meta-data}.</strong>
* </p>
*
* @return The settings activity name.
*/
public String getSettingsActivityName() {
return mSettingsActivityName;
}
/**
* The add printers activity name.
* <p>
* <strong>Statically set from
* {@link PrintService#SERVICE_META_DATA meta-data}.</strong>
* </p>
*
* @return The add printers activity name.
*/
public String getAddPrintersActivityName() {
return mAddPrintersActivityName;
}
/**
* {@inheritDoc}
*/
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel parcel, int flagz) {
parcel.writeString(mId);
parcel.writeParcelable(mResolveInfo, 0);
parcel.writeString(mSettingsActivityName);
parcel.writeString(mAddPrintersActivityName);
}
@Override
public int hashCode() {
return 31 * 1 + ((mId == null) ? 0 : mId.hashCode());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PrintServiceInfo other = (PrintServiceInfo) obj;
if (mId == null) {
if (other.mId != null) {
return false;
}
} else if (!mId.equals(other.mId)) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("PrintServiceInfo{");
builder.append("id:").append(mId).append(", ");
builder.append("resolveInfo:").append(mResolveInfo).append(", ");
if (DEBUG) {
builder.append("settingsActivityName:").append(mSettingsActivityName);
builder.append("addPrintersActivityName:").append(mAddPrintersActivityName);
} else if (mSettingsActivityName != null || mAddPrintersActivityName != null) {
builder.append("<has meta-data>");
}
builder.append("}");
return builder.toString();
}
public static final Parcelable.Creator<PrintServiceInfo> CREATOR =
new Parcelable.Creator<PrintServiceInfo>() {
public PrintServiceInfo createFromParcel(Parcel parcel) {
return new PrintServiceInfo(parcel);
}
public PrintServiceInfo[] newArray(int size) {
return new PrintServiceInfo[size];
}
};
}

View File

@@ -3507,6 +3507,13 @@ public final class Settings {
*/
public static final String LONG_PRESS_TIMEOUT = "long_press_timeout";
/**
* List of the enabled print providers.
* @hide
*/
public static final String ENABLED_PRINT_SERVICES =
"enabled_print_services";
/**
* Setting to always use the default text-to-speech settings regardless
* of the application settings.

View File

@@ -0,0 +1,134 @@
/*
* 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.util;
import android.os.SystemClock;
import java.util.concurrent.TimeoutException;
/**
* This is a helper class for making an async one way call and
* its async one way response response in a sync fashion within
* a timeout. The key idea is to call the remote method with a
* sequence number and a callback and then starting to wait for
* the response. The remote method calls back with the result and
* the sequence number. If the response comes within the timeout
* and its sequence number is the one sent in the method invocation,
* then the call succeeded. If the response does not come within
* the timeout then the call failed. Older result received when
* waiting for the result are ignored.
* <p>
* Typical usage is:
* </p>
* <p><pre><code>
* public class MyMethodCaller extends TimeoutRemoteCallHelper<Object> {
* // The one way remote method to call.
* private final IRemoteInterface mTarget;
*
* // One way callback invoked when the remote method is done.
* private final IRemoteCallback mCallback = new IRemoteCallback.Stub() {
* public void onCompleted(Object result, int sequence) {
* onRemoteMethodResult(result, sequence);
* }
* };
*
* public MyMethodCaller(IRemoteInterface target) {
* mTarget = target;
* }
*
* public Object onCallMyMethod(Object arg) throws RemoteException {
* final int sequence = onBeforeRemoteCall();
* mTarget.myMethod(arg, sequence);
* return getResultTimed(sequence);
* }
* }
* </code></pre></p>
*
* @param <T> The type of the expected result.
*
* @hide
*/
public abstract class TimedRemoteCaller<T> {
public static final long DEFAULT_CALL_TIMEOUT_MILLIS = 5000;
private static final int UNDEFINED_SEQUENCE = -1;
private final Object mLock = new Object();
private final long mCallTimeoutMillis;
private int mSequenceCounter;
private int mReceivedSequence = UNDEFINED_SEQUENCE;
private int mAwaitedSequence = UNDEFINED_SEQUENCE;
private T mResult;
public TimedRemoteCaller(long callTimeoutMillis) {
mCallTimeoutMillis = callTimeoutMillis;
}
public final int onBeforeRemoteCall() {
synchronized (mLock) {
mAwaitedSequence = mSequenceCounter++;
return mAwaitedSequence;
}
}
public final T getResultTimed(int sequence) throws TimeoutException {
synchronized (mLock) {
final boolean success = waitForResultTimedLocked(sequence);
if (!success) {
throw new TimeoutException("No reponse for sequence: " + sequence);
}
T result = mResult;
mResult = null;
return result;
}
}
public final void onRemoteMethodResult(T result, int sequence) {
synchronized (mLock) {
if (sequence == mAwaitedSequence) {
mReceivedSequence = sequence;
mResult = result;
mLock.notifyAll();
}
}
}
private boolean waitForResultTimedLocked(int sequence) {
final long startMillis = SystemClock.uptimeMillis();
while (true) {
try {
if (mReceivedSequence == sequence) {
return true;
}
final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
final long waitMillis = mCallTimeoutMillis - elapsedMillis;
if (waitMillis <= 0) {
return false;
}
mLock.wait(waitMillis);
} catch (InterruptedException ie) {
/* ignore */
}
}
}
}

View File

@@ -1849,6 +1849,22 @@
android:description="@string/permdesc_bindAccessibilityService"
android:protectionLevel="signature" />
<!-- Must be required by an {@link android.printservice.PrintService},
to ensure that only the system can bind to it. -->
<permission android:name="android.permission.BIND_PRINT_SERVICE"
android:label="@string/permlab_bindPrintService"
android:description="@string/permdesc_bindPrintService"
android:protectionLevel="signature" />
<!-- Allows an application to call APIs that give it access to all print jobs
on the device. Usually an app can access only the print jobts it created.
This permission is not available to third party applications.
@hide -->
<permission android:name="android.permission.ACCESS_ALL_PRINT_JOBS"
android:label="@string/permlab_accessAllPrintJobs"
android:description="@string/permdesc_accessAllPrintJobs"
android:protectionLevel="signature" />
<!-- Must be required by a TextService (e.g. SpellCheckerService)
to ensure that only the system can bind to it. -->
<permission android:name="android.permission.BIND_TEXT_SERVICE"

View File

@@ -2562,6 +2562,21 @@
<attr name="description" />
</declare-styleable>
<!-- Use <code>print-service</code> as the root tag of the XML resource that
describes an {@link android.printservice.PrintService} service, which is
referenced from its {@link android.printservice.PrintService#SERVICE_META_DATA}
meta-data entry. -->
<declare-styleable name="PrintService">
<!-- Fully qualified class name of an activity that allows the user to modify
the settings for this service. -->
<attr name="settingsActivity" />
<!-- Fully qualified class name of an activity that allows the user to manually
add printers to this print service. -->
<attr name="addPrintersActivity" format="string"/>
<!-- The vendor name if this print service is vendor specific. -->
<attr name="vendor" format="string"/>
</declare-styleable>
<declare-styleable name="ActionMenuItemView">
<attr name="minWidth" />
</declare-styleable>

View File

@@ -2066,5 +2066,7 @@
<public type="attr" name="ssp" />
<public type="attr" name="sspPrefix" />
<public type="attr" name="sspPattern" />
<public type="attr" name="addPrintersActivity" />
<public type="attr" name="vendor" />
</resources>

View File

@@ -969,6 +969,18 @@
<string name="permdesc_bindAccessibilityService">Allows the holder to bind to the top-level
interface of an accessibility service. Should never be needed for normal apps.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_bindPrintService">bind to a print service</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_bindPrintService">Allows the holder to bind to the top-level
interface of a print service. Should never be needed for normal apps.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_accessAllPrintJobs">access all print jobs</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_accessAllPrintJobs">Allows the holder to access print jobs
created by another app. Should never be needed for normal apps.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_bindTextService">bind to a text service</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -4130,4 +4142,89 @@
<!-- Message informing user that the requested activity could not be found [CHAR LIMIT=none] -->
<string name="app_not_found">No application found to handle this action</string>
<string name="revoke">Revoke</string>
<!-- Printing -->
<!-- ISO A0 media size: 33.11" × 46.81" -->
<string name="mediaSize_iso_a0">ISO A0</string>
<!-- ISO A1 media size: 23.39" × 33.11" -->
<string name="mediaSize_iso_a1">ISO A1</string>
<!-- ISO A2 media size: 16.54" x 23.39" -->
<string name="mediaSize_iso_a2">ISO A2</string>
<!-- ISO A3 media size: 11.69" x 16.54" -->
<string name="mediaSize_iso_a3">ISO A3</string>
<!-- ISO A4 media size: 8.27" x 11.69" -->
<string name="mediaSize_iso_a4">ISO A4</string>
<!-- ISO A5 media size: 5.83" x 8.27" -->
<string name="mediaSize_iso_a5">ISO A5</string>
<!-- ISO A6 media size: 4.13" x 5.83" -->
<string name="mediaSize_iso_a6">ISO A6</string>
<!-- ISO A7 media size: 2.91" x 4.13" -->
<string name="mediaSize_iso_a7">ISO A7</string>
<!-- ISO A8 media size: 2.05" x 2.91" -->
<string name="mediaSize_iso_a8">ISO A8</string>
<!-- ISO A9 media size: 1.46" x 2.05" -->
<string name="mediaSize_iso_a9">ISO A9</string>
<!-- ISO A10 media size: 1.02" x 1.46" -->
<string name="mediaSize_iso_a10">ISO A10</string>
<!-- ISO B0 media size: 39.37" x 55.67" -->
<string name="mediaSize_iso_b0">ISO B0</string>
<!-- ISO B1 media size: 27.83" x 39.37" -->
<string name="mediaSize_iso_b1">ISO B1</string>
<!-- ISO B2 media size - 19.69" x 27.83" -->
<string name="mediaSize_iso_b2">ISO B2</string>
<!-- ISO B3 media size: 13.90" x 19.69" -->
<string name="mediaSize_iso_b3">ISO B3</string>
<!-- ISO B4 media size: 9.84" x 13.90" -->
<string name="mediaSize_iso_b4">ISO B4</string>
<!-- ISO B5 media size: 6.93" x 9.84" -->
<string name="mediaSize_iso_b5">ISO B5</string>
<!-- ISO B6 media size: 4.92" x 6.93" -->
<string name="mediaSize_iso_b6">ISO B6</string>
<!-- ISO B7 media size: 3.46" x 4.92" -->
<string name="mediaSize_iso_b7">ISO B7</string>
<!-- ISO B8 media size: 2.44" x 3.46" -->
<string name="mediaSize_iso_b8">ISO B8</string>
<!-- ISO B9 media size: 1.73" x 2.44" -->
<string name="mediaSize_iso_b9">ISO B9</string>
<!-- ISO B10 media size: 1.22" x 1.73" -->
<string name="mediaSize_iso_b10">ISO B10</string>
<!-- ISO C0 media size: 36.10" x 51.06" -->
<string name="mediaSize_iso_c0">ISO C0</string>
<!-- ISO C1 media size: 25.51" x 36.10" -->
<string name="mediaSize_iso_c1">ISO C1</string>
<!-- ISO C2 media size: 18.03" x 25.51" -->
<string name="mediaSize_iso_c2">ISO C2</string>
<!-- ISO C3 media size: 12.76" x 18.03" -->
<string name="mediaSize_iso_c3">ISO C3</string>
<!-- ISO C4 media size: 9.02" x 12.76" -->
<string name="mediaSize_iso_c4">ISO C4</string>
<!-- ISO C5 media size: 6.38" x 9.02" -->
<string name="mediaSize_iso_c5">ISO C5</string>
<!-- ISO C6 media size: 4.49" x 6.38" -->
<string name="mediaSize_iso_c6">ISO C6</string>
<!-- ISO C7 media size: 3.19" x 4.49" -->
<string name="mediaSize_iso_c7">ISO C7</string>
<!-- ISO ISO C8 media size: 2.24" x 3.19" -->
<string name="mediaSize_iso_c8">ISO C8</string>
<!-- ISO ISO C9 media size: 1.57" x 2.24" -->
<string name="mediaSize_iso_c9">ISO C9</string>
<!-- ISO C10 media size: 1.10" x 1.57" -->
<string name="mediaSize_iso_c10">ISO C10</string>
<!-- North America Letter media size: 8.5" × 11" -->
<string name="mediaSize_na_letter">Letter</string>
<!-- North America Government Letter media size: 8.0" × 10.5" -->
<string name="mediaSize_na_gvrnmt_letter">Government Letter</string>
<!-- North America Legal media size: 8.5" × 14" -->
<string name="mediaSize_na_legal">Legal</string>
<!-- North America Junior Legal media size: 8.0" × 5.0" -->
<string name="mediaSize_na_junior_legal">Junior Legal</string>
<!-- North America Ledger media size: 17" × 11" -->
<string name="mediaSize_na_ledger">Ledger</string>
<!-- North America Tabloid media size: 11" × 17" -->
<string name="mediaSize_na_tabloid">Tabloid</string>
</resources>

View File

@@ -858,6 +858,45 @@
<java-symbol type="string" name="action_bar_home_description_format" />
<java-symbol type="string" name="action_bar_home_subtitle_description_format" />
<java-symbol type="string" name="wireless_display_route_description" />
<java-symbol type="string" name="mediaSize_iso_a0" />
<java-symbol type="string" name="mediaSize_iso_a1" />
<java-symbol type="string" name="mediaSize_iso_a2" />
<java-symbol type="string" name="mediaSize_iso_a3" />
<java-symbol type="string" name="mediaSize_iso_a4" />
<java-symbol type="string" name="mediaSize_iso_a5" />
<java-symbol type="string" name="mediaSize_iso_a6" />
<java-symbol type="string" name="mediaSize_iso_a7" />
<java-symbol type="string" name="mediaSize_iso_a8" />
<java-symbol type="string" name="mediaSize_iso_a9" />
<java-symbol type="string" name="mediaSize_iso_a10" />
<java-symbol type="string" name="mediaSize_iso_b0" />
<java-symbol type="string" name="mediaSize_iso_b1" />
<java-symbol type="string" name="mediaSize_iso_b2" />
<java-symbol type="string" name="mediaSize_iso_b3" />
<java-symbol type="string" name="mediaSize_iso_b4" />
<java-symbol type="string" name="mediaSize_iso_b5" />
<java-symbol type="string" name="mediaSize_iso_b6" />
<java-symbol type="string" name="mediaSize_iso_b7" />
<java-symbol type="string" name="mediaSize_iso_b8" />
<java-symbol type="string" name="mediaSize_iso_b9" />
<java-symbol type="string" name="mediaSize_iso_b10" />
<java-symbol type="string" name="mediaSize_iso_c0" />
<java-symbol type="string" name="mediaSize_iso_c1" />
<java-symbol type="string" name="mediaSize_iso_c2" />
<java-symbol type="string" name="mediaSize_iso_c3" />
<java-symbol type="string" name="mediaSize_iso_c4" />
<java-symbol type="string" name="mediaSize_iso_c5" />
<java-symbol type="string" name="mediaSize_iso_c6" />
<java-symbol type="string" name="mediaSize_iso_c7" />
<java-symbol type="string" name="mediaSize_iso_c8" />
<java-symbol type="string" name="mediaSize_iso_c9" />
<java-symbol type="string" name="mediaSize_iso_c10" />
<java-symbol type="string" name="mediaSize_na_letter" />
<java-symbol type="string" name="mediaSize_na_gvrnmt_letter" />
<java-symbol type="string" name="mediaSize_na_legal" />
<java-symbol type="string" name="mediaSize_na_junior_legal" />
<java-symbol type="string" name="mediaSize_na_ledger" />
<java-symbol type="string" name="mediaSize_na_tabloid" />
<java-symbol type="plurals" name="abbrev_in_num_days" />
<java-symbol type="plurals" name="abbrev_in_num_hours" />

View File

@@ -0,0 +1,32 @@
# 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.
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := PrintSpooler
LOCAL_JAVA_LIBRARIES := framework
LOCAL_CERTIFICATE := platform
LOCAL_PROGUARD_ENABLED := disabled
include $(BUILD_PACKAGE)

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
* Copyright (c) 2013 Google Inc.
*
* 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.
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.printspooler"
android:sharedUserId="android.uid.printspooler"
android:versionName="1"
android:versionCode="1"
coreApp="true">
<uses-sdk android:minSdkVersion="17" android:targetSdkVersion="17"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<permission android:name="android.permission.BIND_PRINT_SPOOLER_SERVICE"
android:label="@string/permlab_bindPrintSpoolerService"
android:description="@string/permdesc_bindPrintSpoolerService"
android:protectionLevel="signature" />
<application
android:allowClearUserData="false"
android:label="@string/app_label"
android:allowBackup= "false">
<service
android:name=".PrintSpoolerService"
android:exported="true"
android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE">
</service>
<activity
android:name=".PrintJobConfigActivity"
android:exported="true">
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,190 @@
Copyright (c) 2005-2008, 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.
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.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -0,0 +1,261 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<GridLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:columnCount="2">
<EditText
android:id="@+id/copies_edittext"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="0"
android:layout_column="1"
android:minWidth="150dip"
android:inputType="number"
android:selectAllOnFocus="true">
</EditText>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="0"
android:layout_column="0"
android:text="@string/label_copies"
android:textAppearance="?android:attr/textAppearanceMedium"
android:labelFor="@id/copies_edittext">
</TextView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="1"
android:layout_column="0"
android:text="@string/label_destination"
android:textAppearance="?android:attr/textAppearanceMedium">
</TextView>
<Spinner
android:id="@+id/destination_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="1"
android:layout_column="1"
android:minWidth="150dip">
</Spinner>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="2"
android:layout_column="0"
android:text="@string/label_media_size"
android:textAppearance="?android:attr/textAppearanceMedium">
</TextView>
<Spinner
android:id="@+id/media_size_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="2"
android:layout_column="1"
android:minWidth="150dip">
</Spinner>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="3"
android:layout_column="0"
android:text="@string/label_resolution"
android:textAppearance="?android:attr/textAppearanceMedium">
</TextView>
<Spinner
android:id="@+id/resolution_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="3"
android:layout_column="1"
android:minWidth="150dip">
</Spinner>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="4"
android:layout_column="0"
android:text="@string/label_input_tray"
android:textAppearance="?android:attr/textAppearanceMedium">
</TextView>
<Spinner
android:id="@+id/input_tray_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="4"
android:layout_column="1"
android:minWidth="150dip">
</Spinner>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="5"
android:layout_column="0"
android:text="@string/label_output_tray"
android:textAppearance="?android:attr/textAppearanceMedium">
</TextView>
<Spinner
android:id="@+id/output_tray_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="5"
android:layout_column="1"
android:minWidth="150dip">
</Spinner>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="6"
android:layout_column="0"
android:text="@string/label_duplex_mode"
android:textAppearance="?android:attr/textAppearanceMedium">
</TextView>
<Spinner
android:id="@+id/duplex_mode_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="6"
android:layout_column="1"
android:minWidth="150dip">
</Spinner>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="7"
android:layout_column="0"
android:text="@string/label_color_mode"
android:textAppearance="?android:attr/textAppearanceMedium">
</TextView>
<Spinner
android:id="@+id/color_mode_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="7"
android:layout_column="1"
android:minWidth="150dip">
</Spinner>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="8"
android:layout_column="0"
android:text="@string/label_fitting_mode"
android:textAppearance="?android:attr/textAppearanceMedium">
</TextView>
<Spinner
android:id="@+id/fitting_mode_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="8"
android:layout_column="1"
android:minWidth="150dip">
</Spinner>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="9"
android:layout_column="0"
android:text="@string/label_orientation"
android:textAppearance="?android:attr/textAppearanceMedium">
</TextView>
<Spinner
android:id="@+id/orientation_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dip"
android:layout_marginRight="12dip"
android:layout_row="9"
android:layout_column="1"
android:minWidth="150dip">
</Spinner>
</GridLayout>
</ScrollView>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/print_button"
android:title="@string/print_button"
android:showAsAction="ifRoom">
</item>
</menu>

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<resources>
<!-- Title of the PrintSpooler application. [CHAR LIMIT=16] -->
<string name="app_label">Print Spooler</string>
<!-- Title of the print dialog. [CHAR LIMIT=10] -->
<string name="print_job_config_dialog_title">Print</string>
<!-- Label of the print dialog's print button. [CHAR LIMIT=16] -->
<string name="print_button">Print</string>
<!-- Label of the print dialog's cancel button. [CHAR LIMIT=16] -->
<string name="cancel_button">Cancel</string>
<!-- Label of the destination spinner. [CHAR LIMIT=16] -->
<string name="label_destination">Destination</string>
<!-- Label of the copies count edit text. [CHAR LIMIT=16] -->
<string name="label_copies">Copies</string>
<!-- Label of the media size spinner. [CHAR LIMIT=16] -->
<string name="label_media_size">Media size</string>
<!-- Label of the resolution spinner. [CHAR LIMIT=16] -->
<string name="label_resolution">Resolution</string>
<!-- Label of the input tray spinner. [CHAR LIMIT=16] -->
<string name="label_input_tray">Input tray</string>
<!-- Label of the output tray spinner. [CHAR LIMIT=16] -->
<string name="label_output_tray">Output tray</string>
<!-- Label of the duplex mode spinner. [CHAR LIMIT=16] -->
<string name="label_duplex_mode">Duplex mode</string>
<!-- Label of the color mode spinner. [CHAR LIMIT=16] -->
<string name="label_color_mode">Color mode</string>
<!-- Label of the fitting mode spinner. [CHAR LIMIT=16] -->
<string name="label_fitting_mode">Fitting mode</string>
<!-- Label of the orientation spinner. [CHAR LIMIT=16] -->
<string name="label_orientation">Orientation</string>
<!-- Duplex mode labels. -->
<string-array name="duplex_mode_labels">
<!-- Duplex mode label: No duplexing. [CHAR LIMIT=20] -->
<item>None</item>
<!-- Duplex mode label: Turn a page along its long edge, e.g. like a book. [CHAR LIMIT=20] -->
<item>Long edge</item>
<!-- Duplex mode label: Turn a page along its short edge, e.g. like a notepad. [CHAR LIMIT=20] -->
<item>Short edge</item>
</string-array>
<!-- Color mode labels. -->
<string-array name="color_mode_labels">
<!-- Color modelabel: Monochrome color scheme, e.g. one color is used. [CHAR LIMIT=20] -->
<item>Monochrome</item>
<!-- Color mode label: Color color scheme, e.g. many colors are used. [CHAR LIMIT=20] -->
<item>Color</item>
</string-array>
<!-- Fitting mode labels. -->
<string-array name="fitting_mode_labels">
<!-- Fitting mode label: No fitting. [CHAR LIMIT=30] -->
<item>None</item>
<!-- Fitting mode label: Fit the content to the page. [CHAR LIMIT=30] -->
<item>Fit to page</item>
</string-array>
<!-- Orientation labels. -->
<string-array name="orientation_labels">
<!-- Orientation label: Portrait page orientation. [CHAR LIMIT=30] -->
<item>Portrait</item>
<!-- Orientation label: Landscape page orientation [CHAR LIMIT=30] -->
<item>Landscape</item>
</string-array>
<!-- Title of an application permission, listed so the user can choose
whether they want to allow the application to do this. -->
<string name="permlab_bindPrintSpoolerService">bind to a print spooler service</string>
<!-- Description of an application permission, listed so the user can
choose whether they want to allow the application to do this. -->
<string name="permdesc_bindPrintSpoolerService">Allows the holder to bind to the top-level
interface of a print spooler service. Should never be needed for normal apps.</string>
</resources>

View File

@@ -0,0 +1,794 @@
/*
* 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.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.IBinder.DeathRecipient;
import android.print.IPrintAdapter;
import android.print.IPrintManager;
import android.print.IPrinterDiscoveryObserver;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Resolution;
import android.print.PrintAttributes.Tray;
import android.print.PrintJobInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Activity for configuring a print job.
*/
public class PrintJobConfigActivity extends Activity {
private static final boolean DEBUG = false;
private static final String LOG_TAG = PrintJobConfigActivity.class.getSimpleName();
public static final String EXTRA_PRINTABLE = "printable";
public static final String EXTRA_APP_ID = "appId";
public static final String EXTRA_ATTRIBUTES = "attributes";
public static final String EXTRA_PRINT_JOB_ID = "printJobId";
private static final int MIN_COPIES = 1;
private final List<QueuedAsyncTask<?>> mTaskQueue = new ArrayList<QueuedAsyncTask<?>>();
private IPrintManager mPrintManager;
private IPrinterDiscoveryObserver mPrinterDiscoveryObserver;
private int mAppId;
private int mPrintJobId;
private PrintAttributes mPrintAttributes;
private final PrintSpooler mPrintSpooler = PrintSpooler.getInstance(this);
private RemotePrintAdapter mRemotePrintAdapter;
// UI elements
private EditText mCopiesEditText;
private Spinner mDestinationSpinner;
public ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter;
private Spinner mMediaSizeSpinner;
public ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
private Spinner mResolutionSpinner;
public ArrayAdapter<SpinnerItem<Resolution>> mResolutionSpinnerAdapter;
private Spinner mInputTraySpinner;
public ArrayAdapter<SpinnerItem<Tray>> mInputTraySpinnerAdapter;
private Spinner mOutputTraySpinner;
public ArrayAdapter<SpinnerItem<Tray>> mOutputTraySpinnerAdapter;
private Spinner mDuplexModeSpinner;
public ArrayAdapter<SpinnerItem<Integer>> mDuplexModeSpinnerAdapter;
private Spinner mColorModeSpinner;
public ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
private Spinner mFittingModeSpinner;
public ArrayAdapter<SpinnerItem<Integer>> mFittingModeSpinnerAdapter;
private Spinner mOrientationSpinner;
public ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
private boolean mPrintStarted;
private boolean mPrintConfirmed;
private IBinder mPrinable;
// TODO: Implement store/restore state.
private final OnItemSelectedListener mOnItemSelectedListener =
new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
if (spinner == mDestinationSpinner) {
updateUi();
notifyPrintableStartIfNeeded();
} else if (spinner == mMediaSizeSpinner) {
SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
mPrintAttributes.setMediaSize(mediaItem.value);
updatePrintableContentIfNeeded();
} else if (spinner == mResolutionSpinner) {
SpinnerItem<Resolution> resolutionItem =
mResolutionSpinnerAdapter.getItem(position);
mPrintAttributes.setResolution(resolutionItem.value);
updatePrintableContentIfNeeded();
} else if (spinner == mInputTraySpinner) {
SpinnerItem<Tray> inputTrayItem =
mInputTraySpinnerAdapter.getItem(position);
mPrintAttributes.setInputTray(inputTrayItem.value);
} else if (spinner == mOutputTraySpinner) {
SpinnerItem<Tray> outputTrayItem =
mOutputTraySpinnerAdapter.getItem(position);
mPrintAttributes.setOutputTray(outputTrayItem.value);
} else if (spinner == mDuplexModeSpinner) {
SpinnerItem<Integer> duplexModeItem =
mDuplexModeSpinnerAdapter.getItem(position);
mPrintAttributes.setDuplexMode(duplexModeItem.value);
} else if (spinner == mColorModeSpinner) {
SpinnerItem<Integer> colorModeItem =
mColorModeSpinnerAdapter.getItem(position);
mPrintAttributes.setColorMode(colorModeItem.value);
} else if (spinner == mFittingModeSpinner) {
SpinnerItem<Integer> fittingModeItem =
mFittingModeSpinnerAdapter.getItem(position);
mPrintAttributes.setFittingMode(fittingModeItem.value);
} else if (spinner == mOrientationSpinner) {
SpinnerItem<Integer> orientationItem =
mOrientationSpinnerAdapter.getItem(position);
mPrintAttributes.setOrientation(orientationItem.value);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
/* do nothing*/
}
};
private final TextWatcher mTextWatcher = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
final int copies = Integer.parseInt(mCopiesEditText.getText().toString());
mPrintAttributes.setCopies(copies);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
/* do nothing */
}
@Override
public void afterTextChanged(Editable s) {
/* do nothing */
}
};
private final InputFilter mInputFilter = new InputFilter() {
@Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
StringBuffer text = new StringBuffer(dest.toString());
text.replace(dstart, dend, source.subSequence(start, end).toString());
if (TextUtils.isEmpty(text)) {
return dest;
}
final int copies = Integer.parseInt(text.toString());
if (copies < MIN_COPIES) {
return dest;
}
return null;
}
};
private final DeathRecipient mDeathRecipient = new DeathRecipient() {
@Override
public void binderDied() {
finish();
}
};
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.print_job_config_activity);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
| WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
mPrintManager = (IPrintManager) IPrintManager.Stub.asInterface(
ServiceManager.getService(PRINT_SERVICE));
Bundle extras = getIntent().getExtras();
mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1);
if (mPrintJobId < 0) {
throw new IllegalArgumentException("Invalid print job id: " + mPrintJobId);
}
mAppId = extras.getInt(EXTRA_APP_ID, -1);
if (mAppId < 0) {
throw new IllegalArgumentException("Invalid app id: " + mAppId);
}
mPrintAttributes = getIntent().getParcelableExtra(EXTRA_ATTRIBUTES);
if (mPrintAttributes == null) {
mPrintAttributes = new PrintAttributes.Builder().create();
}
mPrinable = extras.getBinder(EXTRA_PRINTABLE);
if (mPrinable == null) {
throw new IllegalArgumentException("Printable cannot be null");
}
mRemotePrintAdapter = new RemotePrintAdapter(IPrintAdapter.Stub.asInterface(mPrinable),
mPrintSpooler.generateFileForPrintJob(mPrintJobId));
try {
mPrinable.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException re) {
finish();
}
mPrinterDiscoveryObserver = new PrintDiscoveryObserver(getMainLooper());
bindUi();
}
@Override
protected void onDestroy() {
mPrinable.unlinkToDeath(mDeathRecipient, 0);
super.onDestroy();
}
private void bindUi() {
// Copies
mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
mCopiesEditText.setText(String.valueOf(MIN_COPIES));
mCopiesEditText.addTextChangedListener(mTextWatcher);
mCopiesEditText.setFilters(new InputFilter[] {mInputFilter});
// Destination.
mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
mDestinationSpinnerAdapter = new ArrayAdapter<SpinnerItem<PrinterInfo>>(this,
android.R.layout.simple_spinner_dropdown_item);
mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Media size.
mMediaSizeSpinner = (Spinner) findViewById(R.id.media_size_spinner);
mMediaSizeSpinnerAdapter = new ArrayAdapter<SpinnerItem<MediaSize>>(this,
android.R.layout.simple_spinner_dropdown_item);
mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Resolution.
mResolutionSpinner = (Spinner) findViewById(R.id.resolution_spinner);
mResolutionSpinnerAdapter = new ArrayAdapter<SpinnerItem<Resolution>>(this,
android.R.layout.simple_spinner_dropdown_item);
mResolutionSpinner.setAdapter(mResolutionSpinnerAdapter);
mResolutionSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Input tray.
mInputTraySpinner = (Spinner) findViewById(R.id.input_tray_spinner);
mInputTraySpinnerAdapter = new ArrayAdapter<SpinnerItem<Tray>>(this,
android.R.layout.simple_spinner_dropdown_item);
mInputTraySpinner.setAdapter(mInputTraySpinnerAdapter);
// Output tray.
mOutputTraySpinner = (Spinner) findViewById(R.id.output_tray_spinner);
mOutputTraySpinnerAdapter = new ArrayAdapter<SpinnerItem<Tray>>(this,
android.R.layout.simple_spinner_dropdown_item);
mOutputTraySpinner.setAdapter(mOutputTraySpinnerAdapter);
mOutputTraySpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Duplex mode.
mDuplexModeSpinner = (Spinner) findViewById(R.id.duplex_mode_spinner);
mDuplexModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
android.R.layout.simple_spinner_dropdown_item);
mDuplexModeSpinner.setAdapter(mDuplexModeSpinnerAdapter);
mDuplexModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Color mode.
mColorModeSpinner = (Spinner) findViewById(R.id.color_mode_spinner);
mColorModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
android.R.layout.simple_spinner_dropdown_item);
mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Color mode.
mFittingModeSpinner = (Spinner) findViewById(R.id.fitting_mode_spinner);
mFittingModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
android.R.layout.simple_spinner_dropdown_item);
mFittingModeSpinner.setAdapter(mFittingModeSpinnerAdapter);
mFittingModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Orientation
mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
mOrientationSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
android.R.layout.simple_spinner_dropdown_item);
mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
}
private void updateUi() {
final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
PrinterInfo printer = mDestinationSpinnerAdapter.getItem(selectedIndex).value;
printer.getDefaults(mPrintAttributes);
// Copies.
mCopiesEditText.setText(String.valueOf(
Math.max(mPrintAttributes.getCopies(), MIN_COPIES)));
// Media size.
mMediaSizeSpinnerAdapter.clear();
List<MediaSize> mediaSizes = printer.getMediaSizes();
final int mediaSizeCount = mediaSizes.size();
for (int i = 0; i < mediaSizeCount; i++) {
MediaSize mediaSize = mediaSizes.get(i);
mMediaSizeSpinnerAdapter.add(new SpinnerItem<MediaSize>(
mediaSize, mediaSize.getLabel(getPackageManager())));
}
final int selectedMediaSizeIndex = mediaSizes.indexOf(
mPrintAttributes.getMediaSize());
mMediaSizeSpinner.setOnItemSelectedListener(null);
mMediaSizeSpinner.setSelection(selectedMediaSizeIndex);
mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Resolution.
mResolutionSpinnerAdapter.clear();
List<Resolution> resolutions = printer.getResolutions();
final int resolutionCount = resolutions.size();
for (int i = 0; i < resolutionCount; i++) {
Resolution resolution = resolutions.get(i);
mResolutionSpinnerAdapter.add(new SpinnerItem<Resolution>(
resolution, resolution.getLabel(getPackageManager())));
}
final int selectedResolutionIndex = resolutions.indexOf(
mPrintAttributes.getResolution());
mResolutionSpinner.setOnItemSelectedListener(null);
mResolutionSpinner.setSelection(selectedResolutionIndex);
mResolutionSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Input tray.
mInputTraySpinnerAdapter.clear();
List<Tray> inputTrays = printer.getInputTrays();
final int inputTrayCount = inputTrays.size();
for (int i = 0; i < inputTrayCount; i++) {
Tray inputTray = inputTrays.get(i);
mInputTraySpinnerAdapter.add(new SpinnerItem<Tray>(
inputTray, inputTray.getLabel(getPackageManager())));
}
final int selectedInputTrayIndex = inputTrays.indexOf(
mPrintAttributes.getInputTray());
mInputTraySpinner.setSelection(selectedInputTrayIndex);
// Output tray.
mOutputTraySpinnerAdapter.clear();
List<Tray> outputTrays = printer.getOutputTrays();
final int outputTrayCount = outputTrays.size();
for (int i = 0; i < outputTrayCount; i++) {
Tray outputTray = outputTrays.get(i);
mOutputTraySpinnerAdapter.add(new SpinnerItem<Tray>(
outputTray, outputTray.getLabel(getPackageManager())));
}
final int selectedOutputTrayIndex = outputTrays.indexOf(
mPrintAttributes.getOutputTray());
mOutputTraySpinner.setSelection(selectedOutputTrayIndex);
// Duplex mode.
final int duplexModes = printer.getDuplexModes();
mDuplexModeSpinnerAdapter.clear();
String[] duplexModeLabels = getResources().getStringArray(
R.array.duplex_mode_labels);
int remainingDuplexModes = duplexModes;
while (remainingDuplexModes != 0) {
final int duplexBitOffset = Integer.numberOfTrailingZeros(remainingDuplexModes);
final int duplexMode = 1 << duplexBitOffset;
remainingDuplexModes &= ~duplexMode;
mDuplexModeSpinnerAdapter.add(new SpinnerItem<Integer>(duplexMode,
duplexModeLabels[duplexBitOffset]));
}
final int selectedDuplexModeIndex = Integer.numberOfTrailingZeros(
(duplexModes & mPrintAttributes.getDuplexMode()));
mDuplexModeSpinner.setSelection(selectedDuplexModeIndex);
// Color mode.
final int colorModes = printer.getColorModes();
mColorModeSpinnerAdapter.clear();
String[] colorModeLabels = getResources().getStringArray(
R.array.color_mode_labels);
int remainingColorModes = colorModes;
while (remainingColorModes != 0) {
final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
final int colorMode = 1 << colorBitOffset;
remainingColorModes &= ~colorMode;
mColorModeSpinnerAdapter.add(new SpinnerItem<Integer>(colorMode,
colorModeLabels[colorBitOffset]));
}
final int selectedColorModeIndex = Integer.numberOfTrailingZeros(
(colorModes & mPrintAttributes.getColorMode()));
mColorModeSpinner.setSelection(selectedColorModeIndex);
// Fitting mode.
final int fittingModes = printer.getFittingModes();
mFittingModeSpinnerAdapter.clear();
String[] fittingModeLabels = getResources().getStringArray(
R.array.fitting_mode_labels);
int remainingFittingModes = fittingModes;
while (remainingFittingModes != 0) {
final int fittingBitOffset = Integer.numberOfTrailingZeros(remainingFittingModes);
final int fittingMode = 1 << fittingBitOffset;
remainingFittingModes &= ~fittingMode;
mFittingModeSpinnerAdapter.add(new SpinnerItem<Integer>(fittingMode,
fittingModeLabels[fittingBitOffset]));
}
final int selectedFittingModeIndex = Integer.numberOfTrailingZeros(
(fittingModes & mPrintAttributes.getFittingMode()));
mFittingModeSpinner.setSelection(selectedFittingModeIndex);
// Orientation.
final int orientations = printer.getOrientations();
mOrientationSpinnerAdapter.clear();
String[] orientationLabels = getResources().getStringArray(
R.array.orientation_labels);
int remainingOrientations = orientations;
while (remainingOrientations != 0) {
final int orientationBitOffset = Integer.numberOfTrailingZeros(remainingOrientations);
final int orientation = 1 << orientationBitOffset;
remainingOrientations &= ~orientation;
mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>(orientation,
orientationLabels[orientationBitOffset]));
}
final int selectedOrientationIndex = Integer.numberOfTrailingZeros(
(orientations & mPrintAttributes.getOrientation()));
mOrientationSpinner.setSelection(selectedOrientationIndex);
}
@Override
protected void onResume() {
super.onResume();
try {
mPrintManager.startDiscoverPrinters(mPrinterDiscoveryObserver);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error starting printer discovery!", re);
}
notifyPrintableStartIfNeeded();
}
@Override
protected void onPause() {
super.onPause();
try {
mPrintManager.stopDiscoverPrinters();
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error starting printer discovery!", re);
}
notifyPrintableFinishIfNeeded();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.print_job_config_activity, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.print_button) {
mPrintConfirmed = true;
finish();
}
return super.onOptionsItemSelected(item);
}
private void notifyPrintableStartIfNeeded() {
if (mDestinationSpinner.getSelectedItemPosition() < 0
|| mPrintStarted) {
return;
}
mPrintStarted = true;
new QueuedAsyncTask<Void>(mTaskQueue) {
@Override
protected Void doInBackground(Void... params) {
try {
mRemotePrintAdapter.start();
} catch (IOException ioe) {
Log.e(LOG_TAG, "Error reading printed data!", ioe);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
updatePrintableContentIfNeeded();
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
private void updatePrintableContentIfNeeded() {
if (!mPrintStarted) {
return;
}
mPrintSpooler.setPrintJobAttributes(mPrintJobId, mPrintAttributes);
// TODO: Implement page selector.
final List<PageRange> pages = new ArrayList<PageRange>();
pages.add(PageRange.ALL_PAGES);
new QueuedAsyncTask<File>(mTaskQueue) {
@Override
protected File doInBackground(Void... params) {
try {
mRemotePrintAdapter.printAttributesChanged(mPrintAttributes);
mRemotePrintAdapter.cancelPrint();
mRemotePrintAdapter.print(pages);
return mRemotePrintAdapter.getFile();
} catch (IOException ioe) {
Log.e(LOG_TAG, "Error reading printed data!", ioe);
}
return null;
}
@Override
protected void onPostExecute(File file) {
super.onPostExecute(file);
updatePrintPreview(file);
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
private void notifyPrintableFinishIfNeeded() {
if (!mPrintStarted) {
return;
}
mPrintStarted = false;
// Cancel all pending async tasks if the activity was canceled.
if (!mPrintConfirmed) {
final int taskCount = mTaskQueue.size();
for (int i = taskCount - 1; i >= 0; i--) {
mTaskQueue.remove(i).cancel();
}
}
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
// Notify the app that printing completed.
try {
mRemotePrintAdapter.finish();
} catch (IOException ioe) {
Log.e(LOG_TAG, "Error reading printed data!", ioe);
}
// If canceled, nothing to do.
if (!mPrintConfirmed) {
mPrintSpooler.setPrintJobState(mPrintJobId,
PrintJobInfo.STATE_CANCELED);
return null;
}
// No printer, nothing to do.
final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
if (selectedIndex < 0) {
// Update the print job's status.
mPrintSpooler.setPrintJobState(mPrintJobId,
PrintJobInfo.STATE_CANCELED);
return null;
}
// Update the print job's printer.
SpinnerItem<PrinterInfo> printerItem =
mDestinationSpinnerAdapter.getItem(selectedIndex);
PrinterId printerId = printerItem.value.getId();
mPrintSpooler.setPrintJobPrinterId(mPrintJobId, printerId);
// Update the print job's status.
mPrintSpooler.setPrintJobState(mPrintJobId,
PrintJobInfo.STATE_QUEUED);
return null;
}
// Important: If we are canceling, then we do not wait for the write
// to complete since the result will be discarded anyway, we simply
// execute the finish immediately which will interrupt the write.
}.executeOnExecutor(mPrintConfirmed ? AsyncTask.SERIAL_EXECUTOR
: AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
if (DEBUG) {
if (mPrintConfirmed) {
File file = mRemotePrintAdapter.getFile();
if (file.exists()) {
new ViewSpooledFileAsyncTask(file).executeOnExecutor(
AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
}
}
}
private void updatePrintPreview(File file) {
// TODO: Implement
}
private void addPrinters(List<PrinterInfo> addedPrinters) {
final int addedPrinterCount = addedPrinters.size();
for (int i = 0; i < addedPrinterCount; i++) {
PrinterInfo addedPrinter = addedPrinters.get(i);
boolean duplicate = false;
final int existingPrinterCount = mDestinationSpinnerAdapter.getCount();
for (int j = 0; j < existingPrinterCount; j++) {
PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value;
if (addedPrinter.getId().equals(existingPrinter.getId())) {
duplicate = true;
break;
}
}
if (!duplicate) {
mDestinationSpinnerAdapter.add(new SpinnerItem<PrinterInfo>(
addedPrinter, addedPrinter.getLabel()));
} else {
Log.w(LOG_TAG, "Skipping a duplicate printer: " + addedPrinter);
}
}
}
private void removePrinters(List<PrinterId> pritnerIds) {
final int printerIdCount = pritnerIds.size();
for (int i = 0; i < printerIdCount; i++) {
PrinterId removedPrinterId = pritnerIds.get(i);
boolean removed = false;
final int existingPrinterCount = mDestinationSpinnerAdapter.getCount();
for (int j = 0; j < existingPrinterCount; j++) {
PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value;
if (removedPrinterId.equals(existingPrinter.getId())) {
mDestinationSpinnerAdapter.remove(mDestinationSpinnerAdapter.getItem(j));
removed = true;
break;
}
}
if (!removed) {
Log.w(LOG_TAG, "Ignoring not added printer with id: " + removedPrinterId);
}
}
}
private abstract class QueuedAsyncTask<T> extends AsyncTask<Void, Void, T> {
private final List<QueuedAsyncTask<?>> mPendingOrRunningTasks;
public QueuedAsyncTask(List<QueuedAsyncTask<?>> pendingOrRunningTasks) {
mPendingOrRunningTasks = pendingOrRunningTasks;
}
@Override
protected void onPreExecute() {
mPendingOrRunningTasks.add(this);
}
@Override
protected void onPostExecute(T result) {
mPendingOrRunningTasks.remove(this);
}
public void cancel() {
super.cancel(true);
mPendingOrRunningTasks.remove(this);
}
}
private final class ViewSpooledFileAsyncTask extends AsyncTask<Void, Void, Void> {
private final File mFile;
public ViewSpooledFileAsyncTask(File file) {
mFile = file;
}
@Override
protected Void doInBackground(Void... params) {
mFile.setExecutable(true, false);
mFile.setWritable(true, false);
mFile.setReadable(true, false);
final long identity = Binder.clearCallingIdentity();
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(mFile), "application/pdf");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityAsUser(intent, null, UserHandle.CURRENT);
Binder.restoreCallingIdentity(identity);
return null;
}
}
private final class PrintDiscoveryObserver extends IPrinterDiscoveryObserver.Stub {
private static final int MESSAGE_ADD_DICOVERED_PRINTERS = 1;
private static final int MESSAGE_REMOVE_DICOVERED_PRINTERS = 2;
private final Handler mHandler;
@SuppressWarnings("unchecked")
public PrintDiscoveryObserver(Looper looper) {
mHandler = new Handler(looper, null, true) {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MESSAGE_ADD_DICOVERED_PRINTERS: {
List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
addPrinters(printers);
// Just added the first printer, so select it and start printing.
if (mDestinationSpinnerAdapter.getCount() == 1) {
mDestinationSpinner.setSelection(0);
}
} break;
case MESSAGE_REMOVE_DICOVERED_PRINTERS: {
List<PrinterId> printerIds = (List<PrinterId>) message.obj;
removePrinters(printerIds);
// TODO: Handle removing the last printer.
} break;
}
}
};
}
@Override
public void addDiscoveredPrinters(List<PrinterInfo> printers) {
mHandler.obtainMessage(MESSAGE_ADD_DICOVERED_PRINTERS, printers).sendToTarget();
}
@Override
public void removeDiscoveredPrinters(List<PrinterId> printers) {
mHandler.obtainMessage(MESSAGE_REMOVE_DICOVERED_PRINTERS, printers).sendToTarget();
}
}
private final class SpinnerItem<T> {
final T value;
CharSequence label;
public SpinnerItem(T value, CharSequence label) {
this.value = value;
this.label = label;
}
public String toString() {
return label.toString();
}
}
}

View File

@@ -0,0 +1,734 @@
/*
* 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 java.io.Closeable;
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;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.content.ComponentName;
import android.content.Context;
import android.os.AsyncTask;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.print.IPrintClient;
import android.print.IPrintManager;
import android.print.PrintAttributes;
import android.print.PrintJobInfo;
import android.print.PrintManager;
import android.print.PrinterId;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.util.FastXmlSerializer;
public class PrintSpooler {
private static final String LOG_TAG = PrintSpooler.class.getSimpleName();
private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
private static final boolean DEBUG_PERSISTENCE = false;
private static final boolean PERSISTNECE_MANAGER_ENABLED = false;
private static final String PRINT_FILE_EXTENSION = "pdf";
private static int sPrintJobIdCounter;
private static final Object sLock = new Object();
private final Object mLock = new Object();
private static PrintSpooler sInstance;
private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
private final PersistenceManager mPersistanceManager;
private final Context mContext;
private final IPrintManager mPrintManager;
public static PrintSpooler getInstance(Context context) {
synchronized (sLock) {
if (sInstance == null) {
sInstance = new PrintSpooler(context);
}
return sInstance;
}
}
private PrintSpooler(Context context) {
mContext = context;
mPersistanceManager = new PersistenceManager();
mPersistanceManager.readStateLocked();
mPrintManager = IPrintManager.Stub.asInterface(
ServiceManager.getService("print"));
}
public List<PrintJobInfo> getPrintJobs(ComponentName componentName, int state, int appId) {
synchronized (mLock) {
List<PrintJobInfo> foundPrintJobs = null;
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.getServiceComponentName())));
final boolean sameAppId = appId == PrintManager.APP_ID_ANY
|| printJob.getAppId() == appId;
final boolean sameState = state == PrintJobInfo.STATE_ANY
|| state == printJob.getState();
if (sameComponent && sameAppId && sameState) {
if (foundPrintJobs == null) {
foundPrintJobs = new ArrayList<PrintJobInfo>();
}
foundPrintJobs.add(printJob);
}
}
return foundPrintJobs;
}
}
public PrintJobInfo getPrintJob(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 boolean cancelPrintJob(int printJobId, int appId) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJob(printJobId, appId);
if (printJob != null) {
switch (printJob.getState()) {
case PrintJobInfo.STATE_CREATED: {
removePrintJobLocked(printJob);
} return true;
case PrintJobInfo.STATE_QUEUED: {
removePrintJobLocked(printJob);
} return true;
default: {
return false;
}
}
}
return false;
}
}
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);
addPrintJobLocked(printJob);
setPrintJobState(printJobId, PrintJobInfo.STATE_CREATED);
return printJob;
}
}
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;
}
@SuppressWarnings("resource")
public boolean writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
synchronized (mLock) {
FileInputStream in = null;
FileOutputStream out = null;
try {
PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
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 true;
}
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 {
closeIfNotNullNoException(in);
closeIfNotNullNoException(out);
closeIfNotNullNoException(fd);
}
}
return false;
}
private void closeIfNotNullNoException(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException ioe) {
/* ignore */;
}
}
}
public File generateFileForPrintJob(int printJobId) {
return new File(mContext.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) {
boolean success = false;
PrintJobInfo queuedPrintJob = null;
synchronized (mLock) {
PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null && printJob.getState() < state) {
success = true;
printJob.setState(state);
// TODO: Update notifications.
switch (state) {
case PrintJobInfo.STATE_COMPLETED:
case PrintJobInfo.STATE_CANCELED: {
removePrintJobLocked(printJob);
} break;
case PrintJobInfo.STATE_QUEUED: {
queuedPrintJob = new PrintJobInfo(printJob);
} break;
}
if (DEBUG_PRINT_JOB_LIFECYCLE) {
Slog.i(LOG_TAG, "[STATUS CHANGED] " + printJob);
}
mPersistanceManager.writeStateLocked();
}
}
if (queuedPrintJob != null) {
try {
mPrintManager.onPrintJobQueued(queuedPrintJob.getPrinterId(),
queuedPrintJob);
} catch (RemoteException re) {
/* ignore */
}
}
return success;
}
public boolean setPrintJobTag(int printJobId, String tag) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setTag(tag);
mPersistanceManager.writeStateLocked();
return true;
}
}
return false;
}
public void setPrintJobAttributes(int printJobId, PrintAttributes attributes) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setAttributes(attributes);
mPersistanceManager.writeStateLocked();
}
}
}
public void setPrintJobPrinterId(int printJobId, PrinterId printerId) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setPrinterId(printerId);
mPersistanceManager.writeStateLocked();
}
}
}
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_ID = "id";
private static final String TAG_TAG = "tag";
private static final String TAG_APP_ID = "app-id";
private static final String TAG_STATE = "state";
private static final String TAG_ATTRIBUTES = "attributes";
private static final String TAG_LABEL = "label";
private static final String TAG_PRINTER = "printer";
private static final String ATTRIBUTE_MEDIA_SIZE = "mediaSize";
private static final String ATTRIBUTE_RESOLUTION = "resolution";
private static final String ATTRIBUTE_MARGINS = "margins";
private static final String ATTRIBUTE_INPUT_TRAY = "inputTray";
private static final String ATTRIBUTE_OUTPUT_TRAY = "outputTray";
private static final String ATTRIBUTE_DUPLEX_MODE = "duplexMode";
private static final String ATTRIBUTE_COLOR_MODE = "colorMode";
private static final String ATTRIBUTE_FITTING_MODE = "fittingMode";
private static final String ATTRIBUTE_ORIENTATION = "orientation";
private final AtomicFile mStatePersistFile;
private boolean mWriteStateScheduled;
private PersistenceManager() {
mStatePersistFile = new AtomicFile(new File(mContext.getFilesDir(),
PERSIST_FILE_NAME));
}
public void writeStateLocked() {
// TODO: Implement persistence of PrintableInfo
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() {
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_FAILED) {
continue;
}
serializer.startTag(null, TAG_JOB);
serializer.startTag(null, TAG_ID);
serializer.text(String.valueOf(printJob.getId()));
serializer.endTag(null, TAG_ID);
serializer.startTag(null, TAG_TAG);
serializer.text(printJob.getTag());
serializer.endTag(null, TAG_TAG);
serializer.startTag(null, TAG_APP_ID);
serializer.text(String.valueOf(printJob.getAppId()));
serializer.endTag(null, TAG_APP_ID);
serializer.startTag(null, TAG_LABEL);
serializer.text(printJob.getLabel().toString());
serializer.endTag(null, TAG_LABEL);
serializer.startTag(null, TAG_STATE);
serializer.text(String.valueOf(printJob.getState()));
serializer.endTag(null, TAG_STATE);
serializer.startTag(null, TAG_PRINTER);
serializer.text(printJob.getPrinterId().flattenToString());
serializer.endTag(null, TAG_PRINTER);
PrintAttributes attributes = printJob.getAttributes();
if (attributes != null) {
serializer.startTag(null, TAG_ATTRIBUTES);
//TODO: Implement persistence of the attributes below.
// MediaSize mediaSize = attributes.getMediaSize();
// if (mediaSize != null) {
// serializer.attribute(null, ATTRIBUTE_MEDIA_SIZE,
// mediaSize.flattenToString());
// }
//
// Resolution resolution = attributes.getResolution();
// if (resolution != null) {
// serializer.attribute(null, ATTRIBUTE_RESOLUTION,
// resolution.flattenToString());
// }
//
// Margins margins = attributes.getMargins();
// if (margins != null) {
// serializer.attribute(null, ATTRIBUTE_MARGINS,
// margins.flattenToString());
// }
//
// Tray inputTray = attributes.getInputTray();
// if (inputTray != null) {
// serializer.attribute(null, ATTRIBUTE_INPUT_TRAY,
// inputTray.flattenToString());
// }
//
// Tray outputTray = attributes.getOutputTray();
// if (outputTray != null) {
// serializer.attribute(null, ATTRIBUTE_OUTPUT_TRAY,
// outputTray.flattenToString());
// }
final int duplexMode = attributes.getDuplexMode();
if (duplexMode > 0) {
serializer.attribute(null, ATTRIBUTE_DUPLEX_MODE,
String.valueOf(duplexMode));
}
final int colorMode = attributes.getColorMode();
if (colorMode > 0) {
serializer.attribute(null, ATTRIBUTE_COLOR_MODE,
String.valueOf(colorMode));
}
final int fittingMode = attributes.getFittingMode();
if (fittingMode > 0) {
serializer.attribute(null, ATTRIBUTE_FITTING_MODE,
String.valueOf(fittingMode));
}
final int orientation = attributes.getOrientation();
if (orientation > 0) {
serializer.attribute(null, ATTRIBUTE_ORIENTATION,
String.valueOf(orientation));
}
serializer.endTag(null, TAG_ATTRIBUTES);
}
serializer.endTag(null, TAG_JOB);
if (DEBUG_PERSISTENCE) {
Log.i(LOG_TAG, "[PERSISTED] " + printJob);
}
}
serializer.endTag(null, TAG_SPOOLER);
serializer.endDocument();
mStatePersistFile.finishWrite(out);
} catch (IOException e) {
Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
mStatePersistFile.failWrite(out);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException ioe) {
/* ignore */
}
}
}
}
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 {
try {
in.close();
} catch (IOException ioe) {
/* ignore */
}
}
}
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;
}
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.START_TAG, TAG_ID);
parser.next();
final int printJobId = Integer.parseInt(parser.getText());
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.END_TAG, TAG_ID);
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.START_TAG, TAG_TAG);
parser.next();
String tag = parser.getText();
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.END_TAG, TAG_TAG);
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.START_TAG, TAG_APP_ID);
parser.next();
final int appId = Integer.parseInt(parser.getText());
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.END_TAG, TAG_APP_ID);
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.START_TAG, TAG_LABEL);
parser.next();
String label = parser.getText();
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.END_TAG, TAG_LABEL);
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.START_TAG, TAG_STATE);
parser.next();
final int state = Integer.parseInt(parser.getText());
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.END_TAG, TAG_STATE);
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.START_TAG, TAG_PRINTER);
parser.next();
PrinterId printerId = PrinterId.unflattenFromString(parser.getText());
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.END_TAG, TAG_PRINTER);
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES);
final int attributeCount = parser.getAttributeCount();
PrintAttributes attributes = null;
if (attributeCount > 0) {
PrintAttributes.Builder builder = new PrintAttributes.Builder();
// TODO: Implement reading of the attributes below.
// String mediaSize = parser.getAttributeValue(null, ATTRIBUTE_MEDIA_SIZE);
// if (mediaSize != null) {
// builder.setMediaSize(MediaSize.unflattenFromString(mediaSize));
// }
//
// String resolution = parser.getAttributeValue(null, ATTRIBUTE_RESOLUTION);
// if (resolution != null) {
// builder.setMediaSize(Resolution.unflattenFromString(resolution));
// }
//
// String margins = parser.getAttributeValue(null, ATTRIBUTE_MARGINS);
// if (margins != null) {
// builder.setMediaSize(Margins.unflattenFromString(margins));
// }
//
// String inputTray = parser.getAttributeValue(null, ATTRIBUTE_INPUT_TRAY);
// if (inputTray != null) {
// builder.setMediaSize(Tray.unflattenFromString(inputTray));
// }
//
// String outputTray = parser.getAttributeValue(null, ATTRIBUTE_OUTPUT_TRAY);
// if (outputTray != null) {
// builder.setMediaSize(Tray.unflattenFromString(outputTray));
// }
//
// String duplexMode = parser.getAttributeValue(null, ATTRIBUTE_DUPLEX_MODE);
// if (duplexMode != null) {
// builder.setDuplexMode(Integer.parseInt(duplexMode));
// }
String colorMode = parser.getAttributeValue(null, ATTRIBUTE_COLOR_MODE);
if (colorMode != null) {
builder.setColorMode(Integer.parseInt(colorMode));
}
String fittingMode = parser.getAttributeValue(null, ATTRIBUTE_COLOR_MODE);
if (fittingMode != null) {
builder.setFittingMode(Integer.parseInt(fittingMode));
}
}
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
parser.next();
PrintJobInfo printJob = new PrintJobInfo();
printJob.setId(printJobId);
printJob.setTag(tag);
printJob.setAppId(appId);
printJob.setLabel(label);
printJob.setState(state);
printJob.setAttributes(attributes);
printJob.setPrinterId(printerId);
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

@@ -0,0 +1,187 @@
/*
* 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 java.util.List;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.IPrintAdapter;
import android.print.IPrintClient;
import android.print.IPrintSpoolerService;
import android.print.IPrintSpoolerServiceCallbacks;
import android.print.PrintAttributes;
import android.print.PrintJobInfo;
import android.util.Slog;
import com.android.internal.os.SomeArgs;
/**
* Service for exposing some of the {@link PrintSpooler} functionality to
* another process.
*/
public final class PrintSpoolerService extends Service {
private static final String LOG_TAG = "PrintSpoolerService";
private Intent mStartPrintJobConfigActivityIntent;
private PrintSpooler mSpooler;
private Handler mHanlder;
@Override
public void onCreate() {
super.onCreate();
mStartPrintJobConfigActivityIntent = new Intent(PrintSpoolerService.this,
PrintJobConfigActivity.class);
mSpooler = PrintSpooler.getInstance(this);
mHanlder = new MyHandler(getMainLooper());
}
@Override
public IBinder onBind(Intent intent) {
return new IPrintSpoolerService.Stub() {
@Override
public void getPrintJobs(IPrintSpoolerServiceCallbacks callback,
ComponentName componentName, int state, int appId, int sequence)
throws RemoteException {
List<PrintJobInfo> printJobs = null;
try {
printJobs = mSpooler.getPrintJobs(componentName, state, appId);
} finally {
callback.onGetPrintJobsResult(printJobs, sequence);
}
}
@Override
public void getPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback,
int appId, int sequence) throws RemoteException {
PrintJobInfo printJob = null;
try {
printJob = mSpooler.getPrintJob(printJobId, appId);
} finally {
callback.onGetPrintJobInfoResult(printJob, sequence);
}
}
@Override
public void cancelPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback,
int appId, int sequence) throws RemoteException {
boolean success = false;
try {
success = mSpooler.cancelPrintJob(printJobId, appId);
} finally {
callback.onCancelPrintJobResult(success, sequence);
}
}
@SuppressWarnings("deprecation")
@Override
public void createPrintJob(String printJobName, IPrintClient client,
IPrintAdapter printAdapter, PrintAttributes attributes,
IPrintSpoolerServiceCallbacks callback, int appId, int sequence)
throws RemoteException {
PrintJobInfo printJob = null;
try {
printJob = mSpooler.createPrintJob(printJobName, client,
attributes, appId);
if (printJob != null) {
Intent intent = mStartPrintJobConfigActivityIntent;
intent.putExtra(PrintJobConfigActivity.EXTRA_PRINTABLE,
printAdapter.asBinder());
intent.putExtra(PrintJobConfigActivity.EXTRA_APP_ID, appId);
intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_JOB_ID,
printJob.getId());
intent.putExtra(PrintJobConfigActivity.EXTRA_ATTRIBUTES, attributes);
IntentSender sender = PendingIntent.getActivity(
PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
SomeArgs args = SomeArgs.obtain();
args.arg1 = client;
args.arg2 = sender;
mHanlder.obtainMessage(0, args).sendToTarget();
}
} finally {
callback.onCreatePrintJobResult(printJob, sequence);
}
}
@Override
public void setPrintJobState(int printJobId, int state,
IPrintSpoolerServiceCallbacks callback, int sequece)
throws RemoteException {
boolean success = false;
try {
// TODO: Make sure the clients (print services) can set the state
// only to acceptable ones, e.g. not settings STATE_CREATED.
success = mSpooler.setPrintJobState(printJobId, state);
} finally {
callback.onSetPrintJobStateResult(success, sequece);
}
}
@Override
public void setPrintJobTag(int printJobId, String tag,
IPrintSpoolerServiceCallbacks callback, int sequece)
throws RemoteException {
boolean success = false;
try {
success = mSpooler.setPrintJobTag(printJobId, tag);
} finally {
callback.onSetPrintJobTagResult(success, sequece);
}
}
@Override
public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
mSpooler.writePrintJobData(fd, printJobId);
}
};
}
private static final class MyHandler extends Handler {
public MyHandler(Looper looper) {
super(looper, null, true);
}
@Override
public void handleMessage(Message message) {
SomeArgs args = (SomeArgs) message.obj;
IPrintClient client = (IPrintClient) args.arg1;
IntentSender sender = (IntentSender) args.arg2;
args.recycle();
try {
client.startPrintJobConfigActivity(sender);
} catch (RemoteException re) {
Slog.i(LOG_TAG, "Error starting print job config activity!", re);
}
}
}
}

View File

@@ -0,0 +1,219 @@
/*
* 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.os.ICancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.IPrintAdapter;
import android.print.IPrintProgressListener;
import android.print.PageRange;
import android.print.PrintAdapterInfo;
import android.print.PrintAttributes;
import android.util.Log;
import libcore.io.IoUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
/**
* This class represents a remote print adapter instance.
*/
final class RemotePrintAdapter {
private static final String LOG_TAG = "RemotePrintAdapter";
private static final boolean DEBUG = true;
private final Object mLock = new Object();
private final IPrintAdapter mRemoteInterface;
private final File mFile;
private final IPrintProgressListener mIPrintProgressListener;
private PrintAdapterInfo mInfo;
private ICancellationSignal mCancellationSignal;
private Thread mWriteThread;
public RemotePrintAdapter(IPrintAdapter printAdatper, File file) {
mRemoteInterface = printAdatper;
mFile = file;
mIPrintProgressListener = new IPrintProgressListener.Stub() {
@Override
public void onWriteStarted(PrintAdapterInfo info,
ICancellationSignal cancellationSignal) {
if (DEBUG) {
Log.i(LOG_TAG, "IPrintProgressListener#onWriteStarted()");
}
synchronized (mLock) {
mInfo = info;
mCancellationSignal = cancellationSignal;
}
}
@Override
public void onWriteFinished(List<PageRange> pages) {
if (DEBUG) {
Log.i(LOG_TAG, "IPrintProgressListener#onWriteFinished(" + pages + ")");
}
synchronized (mLock) {
if (isPrintingLocked()) {
mWriteThread.interrupt();
mCancellationSignal = null;
}
}
}
@Override
public void onWriteFailed(CharSequence error) {
if (DEBUG) {
Log.i(LOG_TAG, "IPrintProgressListener#onWriteFailed(" + error + ")");
}
synchronized (mLock) {
if (isPrintingLocked()) {
mWriteThread.interrupt();
mCancellationSignal = null;
}
}
}
};
}
public File getFile() {
if (DEBUG) {
Log.i(LOG_TAG, "getFile()");
}
return mFile;
}
public void start() throws IOException {
if (DEBUG) {
Log.i(LOG_TAG, "start()");
}
try {
mRemoteInterface.start();
} catch (RemoteException re) {
throw new IOException("Error reading file", re);
}
}
public void printAttributesChanged(PrintAttributes attributes) throws IOException {
if (DEBUG) {
Log.i(LOG_TAG, "printAttributesChanged(" + attributes +")");
}
try {
mRemoteInterface.printAttributesChanged(attributes);
} catch (RemoteException re) {
throw new IOException("Error reading file", re);
}
}
public void print(List<PageRange> pages) throws IOException {
if (DEBUG) {
Log.i(LOG_TAG, "print(" + pages +")");
}
InputStream in = null;
OutputStream out = null;
ParcelFileDescriptor source = null;
ParcelFileDescriptor sink = null;
synchronized (mLock) {
mWriteThread = Thread.currentThread();
}
try {
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
source = pipe[0];
sink = pipe[1];
in = new FileInputStream(source.getFileDescriptor());
out = new FileOutputStream(mFile);
// Async call to initiate the other process writing the data.
mRemoteInterface.print(pages, sink, mIPrintProgressListener);
// Close the source. It is now held by the client.
sink.close();
sink = null;
final byte[] buffer = new byte[8192];
while (true) {
if (Thread.currentThread().isInterrupted()) {
Thread.currentThread().interrupt();
break;
}
final int readByteCount = in.read(buffer);
if (readByteCount < 0) {
break;
}
out.write(buffer, 0, readByteCount);
}
} catch (RemoteException re) {
throw new IOException("Error reading file", re);
} catch (IOException ioe) {
throw new IOException("Error reading file", ioe);
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(sink);
IoUtils.closeQuietly(source);
}
}
public void cancelPrint() throws IOException {
if (DEBUG) {
Log.i(LOG_TAG, "cancelPrint()");
}
synchronized (mLock) {
if (isPrintingLocked()) {
try {
mCancellationSignal.cancel();
} catch (RemoteException re) {
throw new IOException("Error cancelling print", re);
}
}
}
}
public void finish() throws IOException {
if (DEBUG) {
Log.i(LOG_TAG, "finish()");
}
try {
mRemoteInterface.finish();
} catch (RemoteException re) {
throw new IOException("Error reading file", re);
}
}
public PrintAdapterInfo getInfo() {
synchronized (mLock) {
return mInfo;
}
}
private boolean isPrintingLocked() {
return mCancellationSignal != null;
}
}

View File

@@ -63,6 +63,7 @@ import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerService;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
import com.android.server.print.PrintManagerService;
import com.android.server.search.SearchManagerService;
import com.android.server.usb.UsbService;
import com.android.server.wifi.WifiService;
@@ -789,6 +790,14 @@ class ServerThread {
} catch (Throwable e) {
reportWtf("starting IdleMaintenanceService", e);
}
try {
Slog.i(TAG, "Print Service");
ServiceManager.addService(Context.PRINT_SERVICE,
new PrintManagerService(context));
} catch (Throwable e) {
reportWtf("starting Print Service", e);
}
}
// Before things start rolling, be sure we have decided whether

View File

@@ -0,0 +1,789 @@
/*
* 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.server.print;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.print.IPrintAdapter;
import android.print.IPrintClient;
import android.print.IPrintManager;
import android.print.IPrinterDiscoveryObserver;
import android.print.PrintAttributes;
import android.print.PrintJobInfo;
import android.print.PrintManager;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.printservice.IPrintService;
import android.printservice.IPrintServiceClient;
import android.printservice.PrintServiceInfo;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
import android.util.Slog;
import com.android.internal.content.PackageMonitor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
public final class PrintManagerService extends IPrintManager.Stub {
private static final String LOG_TAG = PrintManagerService.class.getSimpleName();
private static final char COMPONENT_NAME_SEPARATOR = ':';
private final Object mLock = new Object();
private final SimpleStringSplitter mStringColonSplitter =
new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
private final Map<ComponentName, PrintServiceClient> mServices =
new HashMap<ComponentName, PrintServiceClient>();
private final List<PrintServiceInfo> mInstalledServices = new ArrayList<PrintServiceInfo>();
private final Set<ComponentName> mEnabledServiceNames = new HashSet<ComponentName>();
private final Context mContext;
private final RemoteSpooler mSpooler;
private final int mMyUid;
private int mCurrentUserId = UserHandle.USER_OWNER;
private IPrinterDiscoveryObserver mPrinterDiscoveryObserver;
public PrintManagerService(Context context) {
mContext = context;
mSpooler = new RemoteSpooler(context);
mMyUid = android.os.Process.myUid();
registerContentObservers();
registerBoradcastreceivers();
}
@Override
public PrintJobInfo print(String printJobName, IPrintClient client, IPrintAdapter printAdapter,
PrintAttributes attributes, int appId, int userId) {
final int resolvedAppId = resolveCallingAppEnforcingPermissionsLocked(appId);
final int resolvedUserId = resolveCallingUserEnforcingPermissionsIdLocked(userId);
final long identity = Binder.clearCallingIdentity();
try {
return mSpooler.createPrintJob(printJobName, client, printAdapter,
attributes, resolvedAppId, resolvedUserId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public List<PrintJobInfo> getPrintJobs(int appId, int userId) {
final int resolvedAppId = resolveCallingAppEnforcingPermissionsLocked(appId);
final int resolvedUserId = resolveCallingUserEnforcingPermissionsIdLocked(userId);
// TODO: Do we want to return jobs in STATE_CREATED? We should probably
// have additional argument for the types to get
final long identity = Binder.clearCallingIdentity();
try {
return mSpooler.getPrintJobs(null, PrintJobInfo.STATE_ANY,
resolvedAppId, resolvedUserId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public PrintJobInfo getPrintJob(int printJobId, int appId, int userId) {
final int resolvedAppId = resolveCallingAppEnforcingPermissionsLocked(appId);
final int resolvedUserId = resolveCallingUserEnforcingPermissionsIdLocked(userId);
final long identity = Binder.clearCallingIdentity();
try {
return mSpooler.getPrintJobInfo(printJobId, resolvedAppId, resolvedUserId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void cancelPrintJob(int printJobId, int appId, int userId) {
final int resolvedAppId = resolveCallingAppEnforcingPermissionsLocked(appId);
final int resolvedUserId = resolveCallingUserEnforcingPermissionsIdLocked(userId);
final long identity = Binder.clearCallingIdentity();
try {
if (mSpooler.cancelPrintJob(printJobId, resolvedAppId, resolvedUserId)) {
return;
}
PrintJobInfo printJob = getPrintJob(printJobId, resolvedAppId, resolvedUserId);
if (printJob == null) {
return;
}
ComponentName printServiceName = printJob.getPrinterId().getServiceComponentName();
PrintServiceClient printService = null;
synchronized (mLock) {
printService = mServices.get(printServiceName);
}
if (printService == null) {
return;
}
printService.requestCancelPrintJob(printJob);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
// Called only from the spooler.
@Override
public void onPrintJobQueued(PrinterId printerId, PrintJobInfo printJob) {
throwIfCallerNotSignedWithSystemKey();
PrintServiceClient printService = null;
synchronized (mLock) {
ComponentName printServiceName = printerId.getServiceComponentName();
printService = mServices.get(printServiceName);
}
if (printService != null) {
final long identity = Binder.clearCallingIdentity();
try {
printService.notifyPrintJobQueued(printJob);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
// Called only from the spooler.
@Override
public void startDiscoverPrinters(IPrinterDiscoveryObserver observer) {
throwIfCallerNotSignedWithSystemKey();
List<PrintServiceClient> services = new ArrayList<PrintServiceClient>();
synchronized (mLock) {
mPrinterDiscoveryObserver = observer;
services.addAll(mServices.values());
}
final int serviceCount = services.size();
if (serviceCount <= 0) {
return;
}
final long identity = Binder.clearCallingIdentity();
try {
for (int i = 0; i < serviceCount; i++) {
PrintServiceClient service = services.get(i);
service.startPrinterDiscovery();
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
// Called only from the spooler.
@Override
public void stopDiscoverPrinters() {
throwIfCallerNotSignedWithSystemKey();
List<PrintServiceClient> services = new ArrayList<PrintServiceClient>();
synchronized (mLock) {
mPrinterDiscoveryObserver = null;
services.addAll(mServices.values());
}
final int serviceCount = services.size();
if (serviceCount <= 0) {
return;
}
final long identity = Binder.clearCallingIdentity();
try {
for (int i = 0; i < serviceCount; i++) {
PrintServiceClient service = services.get(i);
service.stopPrintersDiscovery();
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
private void registerContentObservers() {
final Uri enabledPrintServicesUri = Settings.Secure.getUriFor(
Settings.Secure.ENABLED_PRINT_SERVICES);
ContentObserver observer = new ContentObserver(new Handler(mContext.getMainLooper())) {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (enabledPrintServicesUri.equals(uri)) {
synchronized (mLock) {
if (readEnabledPrintServicesChangedLocked()) {
onUserStateChangedLocked();
}
}
}
}
};
mContext.getContentResolver().registerContentObserver(enabledPrintServicesUri,
false, observer, UserHandle.USER_ALL);
}
private void registerBoradcastreceivers() {
PackageMonitor monitor = new PackageMonitor() {
@Override
public void onSomePackagesChanged() {
synchronized (mLock) {
if (getChangingUserId() != mCurrentUserId) {
return;
}
if (readConfigurationForUserStateLocked()) {
onUserStateChangedLocked();
}
}
}
@Override
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
if (getChangingUserId() != mCurrentUserId) {
return;
}
Iterator<ComponentName> iterator = mEnabledServiceNames.iterator();
while (iterator.hasNext()) {
ComponentName componentName = iterator.next();
if (packageName.equals(componentName.getPackageName())) {
iterator.remove();
onEnabledServiceNamesChangedLocked();
return;
}
}
}
}
@Override
public boolean onHandleForceStop(Intent intent, String[] stoppedPackages,
int uid, boolean doit) {
synchronized (mLock) {
if (getChangingUserId() != mCurrentUserId) {
return false;
}
Iterator<ComponentName> iterator = mEnabledServiceNames.iterator();
while (iterator.hasNext()) {
ComponentName componentName = iterator.next();
String componentPackage = componentName.getPackageName();
for (String stoppedPackage : stoppedPackages) {
if (componentPackage.equals(stoppedPackage)) {
if (!doit) {
return true;
}
iterator.remove();
onEnabledServiceNamesChangedLocked();
}
}
}
return false;
}
}
private void onEnabledServiceNamesChangedLocked() {
// Update the enabled services setting.
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_PRINT_SERVICES,
mEnabledServiceNames, mCurrentUserId);
// Update the current user state.
onUserStateChangedLocked();
}
};
// package changes
monitor.register(mContext, null, UserHandle.ALL, true);
// user changes
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_USER_SWITCHED.equals(action)) {
switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
}
}
}, UserHandle.ALL, intentFilter, null, null);
}
private void throwIfCallerNotSignedWithSystemKey() {
if (mContext.getPackageManager().checkSignatures(
mMyUid, Binder.getCallingUid()) != PackageManager.SIGNATURE_MATCH) {
throw new SecurityException("Caller must be signed with the system key!");
}
}
private void onUserStateChangedLocked() {
manageServicesLocked();
}
private void manageServicesLocked() {
final int installedCount = mInstalledServices.size();
for (int i = 0; i < installedCount; i++) {
ResolveInfo resolveInfo = mInstalledServices.get(i).getResolveInfo();
ComponentName serviceName = new ComponentName(resolveInfo.serviceInfo.packageName,
resolveInfo.serviceInfo.name);
if (mEnabledServiceNames.contains(serviceName)) {
if (!mServices.containsKey(serviceName)) {
new PrintServiceClient(serviceName, mCurrentUserId).ensureBoundLocked();
}
} else {
PrintServiceClient service = mServices.get(serviceName);
if (service != null) {
service.ensureUnboundLocked();
}
}
}
}
private boolean readConfigurationForUserStateLocked() {
boolean somethingChanged = false;
somethingChanged |= readInstalledPrintServiceLocked();
somethingChanged |= readEnabledPrintServicesChangedLocked();
return somethingChanged;
}
private boolean readEnabledPrintServicesChangedLocked() {
Set<ComponentName> tempEnabledServiceNameSet = new HashSet<ComponentName>();
readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES,
mCurrentUserId, tempEnabledServiceNameSet);
if (!tempEnabledServiceNameSet.equals(mEnabledServiceNames)) {
mEnabledServiceNames.clear();
mEnabledServiceNames.addAll(tempEnabledServiceNameSet);
return true;
}
return false;
}
private boolean readInstalledPrintServiceLocked() {
Set<PrintServiceInfo> tempPrintServices = new HashSet<PrintServiceInfo>();
List<ResolveInfo> installedServices = mContext.getPackageManager()
.queryIntentServicesAsUser(
new Intent(android.printservice.PrintService.SERVICE_INTERFACE),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
mCurrentUserId);
final int installedCount = installedServices.size();
for (int i = 0, count = installedCount; i < count; i++) {
ResolveInfo installedService = installedServices.get(i);
if (!android.Manifest.permission.BIND_PRINT_SERVICE.equals(
installedService.serviceInfo.permission)) {
ComponentName serviceName = new ComponentName(
installedService.serviceInfo.packageName,
installedService.serviceInfo.name);
Slog.w(LOG_TAG, "Skipping print service "
+ serviceName.flattenToShortString()
+ " since it does not require permission "
+ android.Manifest.permission.BIND_PRINT_SERVICE);
continue;
}
tempPrintServices.add(PrintServiceInfo.create(installedService, mContext));
}
if (!tempPrintServices.equals(mInstalledServices)) {
mInstalledServices.clear();
mInstalledServices.addAll(tempPrintServices);
return true;
}
return false;
}
private void readComponentNamesFromSettingLocked(String settingName, int userId,
Set<ComponentName> outComponentNames) {
String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
settingName, userId);
outComponentNames.clear();
if (!TextUtils.isEmpty(settingValue)) {
TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
splitter.setString(settingValue);
while (splitter.hasNext()) {
String string = splitter.next();
if (TextUtils.isEmpty(string)) {
continue;
}
ComponentName componentName = ComponentName.unflattenFromString(string);
if (componentName != null) {
outComponentNames.add(componentName);
}
}
}
}
private void persistComponentNamesToSettingLocked(String settingName,
Set<ComponentName> componentNames, int userId) {
StringBuilder builder = new StringBuilder();
for (ComponentName componentName : componentNames) {
if (builder.length() > 0) {
builder.append(COMPONENT_NAME_SEPARATOR);
}
builder.append(componentName.flattenToShortString());
}
Settings.Secure.putStringForUser(mContext.getContentResolver(),
settingName, builder.toString(), userId);
}
private void switchUser(int newUserId) {
synchronized (mLock) {
// Disconnect services for the old user.
mEnabledServiceNames.clear();
onUserStateChangedLocked();
// The user changed.
mCurrentUserId = newUserId;
// Update the user state based on current settings.
readConfigurationForUserStateLocked();
onUserStateChangedLocked();
}
// Unbind the spooler for the old user).
mSpooler.unbind();
// If we have queued jobs, advertise it, or we do
// not need the spooler for now.
if (notifyQueuedPrintJobs()) {
mSpooler.unbind();
}
}
private boolean notifyQueuedPrintJobs() {
Map<PrintServiceClient, List<PrintJobInfo>> notifications =
new HashMap<PrintServiceClient, List<PrintJobInfo>>();
synchronized (mLock) {
for (PrintServiceClient service : mServices.values()) {
List<PrintJobInfo> printJobs = mSpooler.getPrintJobs(
service.mComponentName, PrintJobInfo.STATE_QUEUED,
PrintManager.APP_ID_ANY, service.mUserId);
notifications.put(service, printJobs);
}
}
if (notifications.isEmpty()) {
return false;
}
for (Map.Entry<PrintServiceClient, List<PrintJobInfo>> notification
: notifications.entrySet()) {
PrintServiceClient service = notification.getKey();
List<PrintJobInfo> printJobs = notification.getValue();
final int printJobIdCount = printJobs.size();
for (int i = 0; i < printJobIdCount; i++) {
service.notifyPrintJobQueued(printJobs.get(i));
}
}
return true;
}
private int resolveCallingUserEnforcingPermissionsIdLocked(int userId) {
final int callingUid = Binder.getCallingUid();
if (callingUid == 0 || callingUid == Process.SYSTEM_UID
|| callingUid == Process.SHELL_UID) {
return userId;
}
final int callingUserId = UserHandle.getUserId(callingUid);
if (callingUserId == userId) {
return userId;
}
if (mContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED
|| mContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS)
!= PackageManager.PERMISSION_GRANTED) {
if (userId == UserHandle.USER_CURRENT_OR_SELF) {
return callingUserId;
}
throw new SecurityException("Call from user " + callingUserId + " as user "
+ userId + " without permission INTERACT_ACROSS_USERS or "
+ "INTERACT_ACROSS_USERS_FULL not allowed.");
}
if (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) {
return mCurrentUserId;
}
throw new IllegalArgumentException("Calling user can be changed to only "
+ "UserHandle.USER_CURRENT or UserHandle.USER_CURRENT_OR_SELF.");
}
private int resolveCallingAppEnforcingPermissionsLocked(int appId) {
final int callingUid = Binder.getCallingUid();
if (callingUid == 0 || callingUid == Process.SYSTEM_UID
|| callingUid == Process.SHELL_UID) {
return appId;
}
final int callingAppId = UserHandle.getAppId(callingUid);
if (appId == callingAppId) {
return appId;
}
if (mContext.checkCallingPermission(Manifest.permission.ACCESS_ALL_PRINT_JOBS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Call from app " + callingAppId + " as app "
+ appId + " without permission INTERACT_ACROSS_APPS");
}
return appId;
}
private final class PrintServiceClient extends IPrintServiceClient.Stub
implements ServiceConnection, DeathRecipient {
private final ComponentName mComponentName;
private final Intent mIntent;
private final int mUserId;
private IPrintService mInterface;
private boolean mBinding;
private boolean mWasConnectedAndDied;
public PrintServiceClient(ComponentName componentName, int userId) {
mComponentName = componentName;
mIntent = new Intent().setComponent(mComponentName);
mUserId = userId;
}
@Override
public List<PrintJobInfo> getPrintJobs() {
return mSpooler.getPrintJobs(mComponentName, PrintJobInfo.STATE_ANY,
PrintManager.APP_ID_ANY, mUserId);
}
@Override
public PrintJobInfo getPrintJob(int printJobId) {
return mSpooler.getPrintJobInfo(printJobId,
PrintManager.APP_ID_ANY, mUserId);
}
@Override
public boolean setPrintJobState(int printJobId, int state) {
return mSpooler.setPrintJobState(printJobId, state, mUserId);
}
@Override
public boolean setPrintJobTag(int printJobId, String tag) {
return mSpooler.setPrintJobTag(printJobId, tag, mUserId);
}
@Override
public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
mSpooler.writePrintJobData(fd, printJobId, mUserId);
}
@Override
public void addDiscoveredPrinters(List<PrinterInfo> printers) {
throwIfPrinterIdsForPrinterInfoTampered(printers);
synchronized (mLock) {
if (mPrinterDiscoveryObserver != null) {
try {
mPrinterDiscoveryObserver.addDiscoveredPrinters(printers);
} catch (RemoteException re) {
/* ignore */
}
}
}
}
@Override
public void removeDiscoveredPrinters(List<PrinterId> printerIds) {
throwIfPrinterIdsTampered(printerIds);
synchronized (mLock) {
if (mPrinterDiscoveryObserver != null) {
try {
mPrinterDiscoveryObserver.removeDiscoveredPrinters(printerIds);
} catch (RemoteException re) {
/* ignore */
}
}
}
}
public void requestCancelPrintJob(PrintJobInfo printJob) {
synchronized (mLock) {
try {
mInterface.requestCancelPrintJob(printJob);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error canceling pring job!", re);
}
}
}
public void notifyPrintJobQueued(PrintJobInfo printJob) {
IPrintService service = mInterface;
if (service != null) {
try {
service.onPrintJobQueued(printJob);
} catch (RemoteException re) {
/* ignore */
}
}
}
public void startPrinterDiscovery() {
IPrintService service = mInterface;
if (service != null) {
try {
service.startPrinterDiscovery();
} catch (RemoteException re) {
/* ignore */
}
}
}
public void stopPrintersDiscovery() {
IPrintService service = mInterface;
if (service != null) {
try {
service.stopPrinterDiscovery();
} catch (RemoteException re) {
/* ignore */
}
}
}
public void ensureBoundLocked() {
if (mBinding) {
return;
}
if (mInterface == null) {
mBinding = true;
mContext.bindServiceAsUser(mIntent, this,
Context.BIND_AUTO_CREATE, new UserHandle(mUserId));
}
}
public void ensureUnboundLocked() {
if (mBinding) {
mBinding = false;
return;
}
if (mInterface != null) {
mContext.unbindService(this);
destroyLocked();
}
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
mInterface = IPrintService.Stub.asInterface(service);
mServices.put(mComponentName, this);
try {
mInterface.asBinder().linkToDeath(this, 0);
} catch (RemoteException re) {
destroyLocked();
return;
}
if (mUserId != mCurrentUserId) {
destroyLocked();
return;
}
if (mBinding || mWasConnectedAndDied) {
mBinding = false;
mWasConnectedAndDied = false;
onUserStateChangedLocked();
try {
mInterface.setClient(this);
} catch (RemoteException re) {
Slog.w(LOG_TAG, "Error while setting client for service: "
+ service, re);
}
} else {
destroyLocked();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
/* do nothing - #binderDied takes care */
}
@Override
public void binderDied() {
synchronized (mLock) {
if (isConnectedLocked()) {
mWasConnectedAndDied = true;
}
destroyLocked();
}
}
private void destroyLocked() {
if (mServices.remove(mComponentName) == null) {
return;
}
if (isConnectedLocked()) {
try {
mInterface.asBinder().unlinkToDeath(this, 0);
} catch (NoSuchElementException nse) {
/* ignore */
}
try {
mInterface.setClient(null);
} catch (RemoteException re) {
/* ignore */
}
mInterface = null;
}
mBinding = false;
}
private boolean isConnectedLocked() {
return (mInterface != null);
}
private void throwIfPrinterIdsForPrinterInfoTampered(List<PrinterInfo> printerInfos) {
final int printerInfoCount = printerInfos.size();
for (int i = 0; i < printerInfoCount; i++) {
PrinterId printerId = printerInfos.get(i).getId();
throwIfPrinterIdTampered(printerId);
}
}
private void throwIfPrinterIdsTampered(List<PrinterId> printerIds) {
final int printerIdCount = printerIds.size();
for (int i = 0; i < printerIdCount; i++) {
PrinterId printerId = printerIds.get(i);
throwIfPrinterIdTampered(printerId);
}
}
private void throwIfPrinterIdTampered(PrinterId printerId) {
if (printerId == null || printerId.getServiceComponentName() == null
|| !printerId.getServiceComponentName().equals(mComponentName)) {
throw new IllegalArgumentException("Invalid printer id: " + printerId);
}
}
}
}

View File

@@ -0,0 +1,416 @@
/*
* 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.server.print;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.IBinder.DeathRecipient;
import android.print.IPrintAdapter;
import android.print.IPrintClient;
import android.print.IPrintSpoolerService;
import android.print.IPrintSpoolerServiceCallbacks;
import android.print.PrintAttributes;
import android.print.PrintJobInfo;
import android.util.Slog;
import android.util.TimedRemoteCaller;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeoutException;
/**
* This represents the remote print spooler as a local object to the
* PrintManagerSerivce. It is responsible to connecting to the remove
* spooler if needed, to make the timed out remote calls, and to handle
* remove exceptions.
*/
final class RemoteSpooler implements ServiceConnection, DeathRecipient {
private static final String LOG_TAG = "Spooler";
private static final long BIND_SPOOLER_SERVICE_TIMEOUT = 10000;
private final Object mLock = new Object();
private final Context mContext;
private final Intent mIntent;
private final GetPrintJobsCaller mGetPrintJobsCaller = new GetPrintJobsCaller();
private final CreatePrintJobCaller mCreatePrintJobCaller = new CreatePrintJobCaller();
private final CancelPrintJobCaller mCancelPrintJobCaller = new CancelPrintJobCaller();
private final GetPrintJobCaller mGetPrintJobCaller = new GetPrintJobCaller();
private final SetPrintJobStateCaller mSetPrintJobStatusCaller = new SetPrintJobStateCaller();
private final SetPrintJobTagCaller mSetPrintJobTagCaller = new SetPrintJobTagCaller();
private IPrintSpoolerService mRemoteInterface;
private int mUserId = UserHandle.USER_NULL;
public RemoteSpooler(Context context) {
mContext = context;
mIntent = new Intent();
mIntent.setComponent(new ComponentName("com.android.printspooler",
"com.android.printspooler.PrintSpoolerService"));
}
public List<PrintJobInfo> getPrintJobs(ComponentName componentName, int state, int appId,
int userId) {
try {
return mGetPrintJobsCaller.getPrintJobs(getRemoteInstance(userId),
componentName, state, appId);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error getting print jobs!", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error getting print jobs!", te);
}
return null;
}
public PrintJobInfo createPrintJob(String printJobName, IPrintClient client,
IPrintAdapter printAdapter, PrintAttributes attributes, int appId, int userId) {
try {
return mCreatePrintJobCaller.createPrintJob(getRemoteInstance(userId),
printJobName, client, printAdapter, attributes, appId);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error creating print job!", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error creating print job!", te);
}
return null;
}
public boolean cancelPrintJob(int printJobId, int appId, int userId) {
try {
return mCancelPrintJobCaller.cancelPrintJob(getRemoteInstance(userId),
printJobId, appId);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error canceling print job!", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error canceling print job!", te);
}
return false;
}
public void writePrintJobData(ParcelFileDescriptor fd, int printJobId, int userId) {
try {
getRemoteInstance(userId).writePrintJobData(fd, printJobId);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error writing print job data!", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error writing print job data!", te);
} finally {
// We passed the file descriptor across and now the other
// side is responsible to close it, so close the local copy.
try {
fd.close();
} catch (IOException ioe) {
/* ignore */
}
}
}
public PrintJobInfo getPrintJobInfo(int printJobId, int appId, int userId) {
try {
return mGetPrintJobCaller.getPrintJobInfo(getRemoteInstance(userId),
printJobId, appId);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error getting print job!", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error getting print job!", te);
}
return null;
}
public boolean setPrintJobState(int printJobId, int state, int userId) {
try {
return mSetPrintJobStatusCaller.setPrintJobState(getRemoteInstance(userId),
printJobId, state);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error setting print job status!", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error setting print job status!", te);
}
return false;
}
public boolean setPrintJobTag(int printJobId, String tag, int userId) {
try {
return mSetPrintJobTagCaller.setPrintJobTag(getRemoteInstance(userId),
printJobId, tag);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error setting print job tag!", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error setting print job tag!", te);
}
return false;
}
@Override
public void onServiceDisconnected(ComponentName name) {
binderDied();
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
try {
service.linkToDeath(this, 0);
mRemoteInterface = IPrintSpoolerService.Stub.asInterface(service);
} catch (RemoteException re) {
/* ignore */
}
}
}
private IPrintSpoolerService getRemoteInstance(int userId) throws TimeoutException {
synchronized (mLock) {
if (mRemoteInterface != null && mUserId == userId) {
return mRemoteInterface;
}
final long identity = Binder.clearCallingIdentity();
try {
if (mUserId != UserHandle.USER_NULL && mUserId != userId) {
unbind();
}
mContext.bindServiceAsUser(mIntent, this,
Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_OOM_MANAGEMENT,
UserHandle.CURRENT);
} finally {
Binder.restoreCallingIdentity(identity);
}
final long startMillis = SystemClock.uptimeMillis();
while (true) {
if (mRemoteInterface != null) {
break;
}
final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
final long remainingMillis = BIND_SPOOLER_SERVICE_TIMEOUT - elapsedMillis;
if (remainingMillis <= 0) {
throw new TimeoutException("Cannot get spooler!");
}
try {
mLock.wait(remainingMillis);
} catch (InterruptedException ie) {
/* ignore */
}
}
mUserId = userId;
return mRemoteInterface;
}
}
public void unbind() {
synchronized (mLock) {
if (mRemoteInterface != null) {
mContext.unbindService(this);
mRemoteInterface = null;
mUserId = UserHandle.USER_NULL;
}
}
}
@Override
public void binderDied() {
synchronized (mLock) {
if (mRemoteInterface != null) {
mRemoteInterface.asBinder().unlinkToDeath(this, 0);
mRemoteInterface = null;
}
}
}
private final class GetPrintJobsCaller extends TimedRemoteCaller<List<PrintJobInfo>> {
private final IPrintSpoolerServiceCallbacks mCallback;
public GetPrintJobsCaller() {
super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
mCallback = new BasePrintSpoolerServiceCallbacks() {
@Override
public void onGetPrintJobsResult(List<PrintJobInfo> printJobs, int sequence) {
onRemoteMethodResult(printJobs, sequence);
}
};
}
public List<PrintJobInfo> getPrintJobs(IPrintSpoolerService target,
ComponentName componentName, int state, int appId)
throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
target.getPrintJobs(mCallback, componentName, state, appId, sequence);
return getResultTimed(sequence);
}
}
private final class CreatePrintJobCaller extends TimedRemoteCaller<PrintJobInfo> {
private final IPrintSpoolerServiceCallbacks mCallback;
public CreatePrintJobCaller() {
super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
mCallback = new BasePrintSpoolerServiceCallbacks() {
@Override
public void onCreatePrintJobResult(PrintJobInfo printJob, int sequence) {
onRemoteMethodResult(printJob, sequence);
}
};
}
public PrintJobInfo createPrintJob(IPrintSpoolerService target, String printJobName,
IPrintClient client, IPrintAdapter printAdapter, PrintAttributes attributes,
int appId) throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
target.createPrintJob(printJobName, client, printAdapter, attributes,
mCallback, appId, sequence);
return getResultTimed(sequence);
}
}
private final class CancelPrintJobCaller extends TimedRemoteCaller<Boolean> {
private final IPrintSpoolerServiceCallbacks mCallback;
public CancelPrintJobCaller() {
super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
mCallback = new BasePrintSpoolerServiceCallbacks() {
@Override
public void onCancelPrintJobResult(boolean canceled, int sequence) {
onRemoteMethodResult(canceled, sequence);
}
};
}
public boolean cancelPrintJob(IPrintSpoolerService target, int printJobId,
int appId) throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
target.cancelPrintJob(printJobId, mCallback, appId, sequence);
return getResultTimed(sequence);
}
}
private final class GetPrintJobCaller extends TimedRemoteCaller<PrintJobInfo> {
private final IPrintSpoolerServiceCallbacks mCallback;
public GetPrintJobCaller() {
super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
mCallback = new BasePrintSpoolerServiceCallbacks() {
@Override
public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) {
onRemoteMethodResult(printJob, sequence);
}
};
}
public PrintJobInfo getPrintJobInfo(IPrintSpoolerService target, int printJobId,
int appId) throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
target.getPrintJob(printJobId, mCallback, appId, sequence);
return getResultTimed(sequence);
}
}
private final class SetPrintJobStateCaller extends TimedRemoteCaller<Boolean> {
private final IPrintSpoolerServiceCallbacks mCallback;
public SetPrintJobStateCaller() {
super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
mCallback = new BasePrintSpoolerServiceCallbacks() {
@Override
public void onSetPrintJobStateResult(boolean success, int sequence) {
onRemoteMethodResult(success, sequence);
}
};
}
public boolean setPrintJobState(IPrintSpoolerService target, int printJobId,
int status) throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
target.setPrintJobState(printJobId, status, mCallback, sequence);
return getResultTimed(sequence);
}
}
private final class SetPrintJobTagCaller extends TimedRemoteCaller<Boolean> {
private final IPrintSpoolerServiceCallbacks mCallback;
public SetPrintJobTagCaller() {
super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
mCallback = new BasePrintSpoolerServiceCallbacks() {
@Override
public void onSetPrintJobTagResult(boolean success, int sequence) {
onRemoteMethodResult(success, sequence);
}
};
}
public boolean setPrintJobTag(IPrintSpoolerService target, int printJobId,
String tag) throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
target.setPrintJobTag(printJobId, tag, mCallback, sequence);
return getResultTimed(sequence);
}
}
private abstract class BasePrintSpoolerServiceCallbacks
extends IPrintSpoolerServiceCallbacks.Stub {
@Override
public void onGetPrintJobsResult(List<PrintJobInfo> printJobIds, int sequence) {
/** do nothing */
}
@Override
public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) {
/** do nothing */
}
@Override
public void onCreatePrintJobResult(PrintJobInfo printJob, int sequence) {
/** do nothing */
}
@Override
public void onCancelPrintJobResult(boolean canceled, int sequence) {
/** do nothing */
}
@Override
public void onSetPrintJobStateResult(boolean success, int sequece) {
/** do nothing */
}
@Override
public void onSetPrintJobTagResult(boolean success, int sequence) {
/** do nothing */
}
}
}