接前一篇文章(Android调用系统默认打印机并反射获取打印任务状态 https://blog.csdn.net/yan1348/article/details/90666657)所说,完全按照系统默认的打印流程是有缺陷的,所以我在这里又实现了一个自定义服务来实现后台打印的。
首先,我们先来看看PrintManager.print方法到底做了什么动作,先看看源码
```
在这里插入代码片
/**
* Creates a print job for printing a {@link PrintDocumentAdapter} with
* default print attributes.
*
* Calling this method brings the print UI allowing the user to customize
* the print job and returns a {@link PrintJob} object without waiting for the
* user to customize or confirm the print job. The returned print job instance
* is in a {@link PrintJobInfo#STATE_CREATED created} state.
*
* This method can be called only from an {@link Activity}. The rationale is that
* printing from a service will create an inconsistent user experience as the print
* UI would appear without any context.
*
*
* Also the passed in {@link PrintDocumentAdapter} will be considered invalid if
* your activity is finished. The rationale is that once the activity that
* initiated printing is finished, the provided adapter may be in an inconsistent
* state as it may depend on the UI presented by the activity.
*
*
* The default print attributes are a hint to the system how the data is to
* be printed. For example, a photo editor may look at the photo aspect ratio
* to determine the default orientation and provide a hint whether the printing
* should be in portrait or landscape. The system will do a best effort to
* selected the hinted options in the print dialog, given the current printer
* supports them.
*
*
* Note: Calling this method will bring the print dialog and
* the system will connect to the provided {@link PrintDocumentAdapter}. If a
* configuration change occurs that you application does not handle, for example
* a rotation change, the system will drop the connection to the adapter as the
* activity has to be recreated and the old adapter may be invalid in this context,
* hence a new adapter instance is required. As a consequence, if your activity
* does not handle configuration changes (default behavior), you have to save the
* state that you were printing and call this method again when your activity
* is recreated.
*
*
* @param printJobName A name for the new print job which is shown to the user.
* @param documentAdapter An adapter that emits the document to print.
* @param attributes The default print job attributes or null
.
* @return The created print job on success or null on failure.
* @throws IllegalStateException If not called from an {@link Activity}.
* @throws IllegalArgumentException If the print job name is empty or the
* document adapter is null.
*
* @see PrintJob
*/
public @NonNull PrintJob print(@NonNull String printJobName,
@NonNull PrintDocumentAdapter documentAdapter,
@Nullable PrintAttributes attributes) {
if (mService == null) {
Log.w(LOG_TAG, "Feature android.software.print not available");
return null;
}
if (!(mContext instanceof Activity)) {
throw new IllegalStateException("Can print only from an activity");
}
if (TextUtils.isEmpty(printJobName)) {
throw new IllegalArgumentException("printJobName cannot be empty");
}
if (documentAdapter == null) {
throw new IllegalArgumentException("documentAdapter cannot be null");
}
PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(
(Activity) mContext, documentAdapter);
try {
Bundle result = mService.print(printJobName, delegate,
attributes, mContext.getPackageName(), mAppId, mUserId);
if (result != null) {
PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB);
IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT);
if (printJob == null || intent == null) {
return null;
}
try {
mContext.startIntentSender(intent, null, 0, 0, 0);
return new PrintJob(printJob, this);
} catch (SendIntentException sie) {
Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
}
}
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
return null;
}
```
这个是print方法的源码,我们可以看看,最后是实现了一个intent操作,这个intent会跳转到哪里去,我们可以看下intent的参数,接下来我们看看这个参数的定义在哪里,我们可以看到,intent是由mService.print方法产生的,我们来看看PrintManagerService.print方法
```
在这里插入代码片
@Override
public Bundle print(String printJobName, IPrintDocumentAdapter adapter,
PrintAttributes attributes, String packageName, int appId, int userId) {
final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
String resolvedPackageName = resolveCallingPackageNameEnforcingSecurity(packageName);
final UserState userState;
synchronized (mLock) {
userState = getOrCreateUserStateLocked(resolvedUserId);
}
final long identity = Binder.clearCallingIdentity();
try {
return userState.print(printJobName, adapter, attributes,
resolvedPackageName, resolvedAppId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
```
可以看到,接下来我们要看的是UserState.print方法,我们跳到UserState.java
```
在这里插入代码片
@SuppressWarnings("deprecation")
public Bundle print(String printJobName, IPrintDocumentAdapter adapter,
PrintAttributes attributes, String packageName, int appId) {
// Create print job place holder.
final PrintJobInfo printJob = new PrintJobInfo();
printJob.setId(new PrintJobId());
printJob.setAppId(appId);
printJob.setLabel(printJobName);
printJob.setAttributes(attributes);
printJob.setState(PrintJobInfo.STATE_CREATED);
printJob.setCopies(1);
printJob.setCreationTime(System.currentTimeMillis());
// Track this job so we can forget it when the creator dies.
if (!mPrintJobForAppCache.onPrintJobCreated(adapter.asBinder(), appId,
printJob)) {
// Not adding a print job means the client is dead - done.
return null;
}
// Spin the spooler to add the job and show the config UI.
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
mSpooler.createPrintJob(printJob);
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
final long identity = Binder.clearCallingIdentity();
try {
Intent intent = new Intent(PrintManager.ACTION_PRINT_DIALOG);
intent.setData(Uri.fromParts("printjob", printJob.getId().flattenToString(), null));
intent.putExtra(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER, adapter.asBinder());
intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob);
intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, packageName);
IntentSender intentSender = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(mUserId))
.getIntentSender();
Bundle result = new Bundle();
result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob);
result.putParcelable(PrintManager.EXTRA_PRINT_DIALOG_INTENT, intentSender);
return result;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
```
在这里,我们可以看到,其实这个intent的action是PrintManager.ACTION_PRINT_DIALOG,接下来我们先看看这个action是怎么定义的,回到PrintManager.java文件。
```
在这里插入代码片
/**
* The action for launching the print dialog activity.
*
* @hide
*/
public static final String ACTION_PRINT_DIALOG = "android.print.PRINT_DIALOG";
```
接下来我们使用一种笨办法来查找一下这个action是在哪里定义的,就用全文搜索吧,查找这个字符串,我这里用的是Source Insight 4.0,已经把这个frameworks目录导进去了,接下来我们全文搜索一下这个字符串.
```
在这里插入代码片
```
我们可以找到,在frameworks\base\packages\PrintSpooler\AndroidManifest.xml里面有这个定义,说明上面那个intent就是跳转到这个PrintJobConfigActivity里面的,接下来我们分析一下这个\frameworks\base\packages\PrintSpooler\src\com\android\printspooler\PrintJobConfigActivity.
我们先来看看这个activity的onCreate方法,在这里,我们可以看到,这个activity使用了intent传递过来的两个参数,PrintJob和PrintDocumentAdapter。这个activity的逻辑比较复杂,在这里我就不一一的去解释,大家自己去看就行了,看不懂的自己去打log去看看流程怎么走的,我在这里主要介绍两个内部类,一个是Editor,一个是PrintController。
我们先来看看Editor。
先看看Editor的postCreate方法。
```
在这里插入代码片
public void postCreate() {
// Destination.
mMediaSizeComparator = new MediaSizeComparator(PrintJobConfigActivity.this);
mDestinationSpinnerAdapter = new DestinationAdapter();
mDestinationSpinnerAdapter.registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
Log.e(LOG_TAG, "postCreate-onChanged");
// Initially, we have only safe to PDF as a printer but after some
// printers are loaded we want to select the user's favorite one
// which is the first.
if (!mFavoritePrinterSelected && mDestinationSpinnerAdapter.getCount() > 2) {
mFavoritePrinterSelected = true;
mDestinationSpinner.setSelection(0);
// Workaround again the weird spinner behavior to notify for selection
// change on the next layout pass as the current printer is used below.
mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter.getItem(0);
}
// If there is a next printer to select and we succeed selecting
// it - done. Let the selection handling code make everything right.
if (mNextPrinterId != null && selectPrinter(mNextPrinterId)) {
mNextPrinterId = null;
return;
}
// If the current printer properties changed, we update the UI.
if (mCurrentPrinter != null) {
final int printerCount = mDestinationSpinnerAdapter.getCount();
for (int i = 0; i < printerCount; i++) {
Object item = mDestinationSpinnerAdapter.getItem(i);
// Some items are not printers
if (item instanceof PrinterInfo) {
PrinterInfo printer = (PrinterInfo) item;
if (!printer.getId().equals(mCurrentPrinter.getId())) {
continue;
}
// If nothing changed - done.
if (mCurrentPrinter.equals(printer)) {
return;
}
// If the current printer became available and has no
// capabilities, we refresh it.
if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
&& printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
&& printer.getCapabilities() == null) {
if (!mCapabilitiesTimeout.isPosted()) {
mCapabilitiesTimeout.post();
}
mCurrentPrinter.copyFrom(printer);
refreshCurrentPrinter();
return;
}
// If the current printer became unavailable or its
// capabilities go away, we update the UI and add a
// timeout to declare the printer as unavailable.
if ((mCurrentPrinter.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
&& printer.getStatus() == PrinterInfo.STATUS_UNAVAILABLE)
|| (mCurrentPrinter.getCapabilities() != null
&& printer.getCapabilities() == null)) {
if (!mCapabilitiesTimeout.isPosted()) {
mCapabilitiesTimeout.post();
}
mCurrentPrinter.copyFrom(printer);
updateUi();
return;
}
// We just refreshed the current printer.
if (printer.getCapabilities() != null
&& mCapabilitiesTimeout.isPosted()) {
mCapabilitiesTimeout.remove();
updatePrintAttributes(printer.getCapabilities());
updateUi();
mController.update();
}
// Update the UI if capabilities changed.
boolean capabilitiesChanged = false;
if (mCurrentPrinter.getCapabilities() == null) {
if (printer.getCapabilities() != null) {
capabilitiesChanged = true;
}
} else if (!mCurrentPrinter.getCapabilities().equals(
printer.getCapabilities())) {
capabilitiesChanged = true;
}
// Update the UI if the status changed.
final boolean statusChanged = mCurrentPrinter.getStatus()
!= printer.getStatus();
// Update the printer with the latest info.
if (!mCurrentPrinter.equals(printer)) {
mCurrentPrinter.copyFrom(printer);
}
if (capabilitiesChanged || statusChanged) {
// If something changed during update...
if (updateUi() || !mController.hasPerformedLayout()) {
// Update the document.
mController.update();
}
}
break;
}
}
}
}
@Override
public void onInvalidated() {
/* do nothing - we always have one fake PDF printer */
}
});
// Media size.
mMediaSizeSpinnerAdapter = new ArrayAdapter>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
// Color mode.
mColorModeSpinnerAdapter = new ArrayAdapter>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
// Orientation
mOrientationSpinnerAdapter = new ArrayAdapter>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
String[] orientationLabels = getResources().getStringArray(
R.array.orientation_labels);
mOrientationSpinnerAdapter.add(new SpinnerItem(
ORIENTATION_PORTRAIT, orientationLabels[0]));
mOrientationSpinnerAdapter.add(new SpinnerItem(
ORIENTATION_LANDSCAPE, orientationLabels[1]));
// Range options
mRangeOptionsSpinnerAdapter = new ArrayAdapter>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
final int[] rangeOptionsValues = getResources().getIntArray(
R.array.page_options_values);
String[] rangeOptionsLabels = getResources().getStringArray(
R.array.page_options_labels);
final int rangeOptionsCount = rangeOptionsLabels.length;
for (int i = 0; i < rangeOptionsCount; i++) {
mRangeOptionsSpinnerAdapter.add(new SpinnerItem(
rangeOptionsValues[i], rangeOptionsLabels[i]));
}
showUi(UI_EDITING_PRINT_JOB, null);
bindUi();
updateUi();
}
```
接下来看看DestinationAdapter,在这里我们主要看看这几个方法。
```
在这里插入代码片
public DestinationAdapter() {
getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
}
@Override
public Loader> onCreateLoader(int id, Bundle args) {
if (id == LOADER_ID_PRINTERS_LOADER) {
return new FusedPrintersProvider(PrintJobConfigActivity.this);
}
return null;
}
@Override
public void onLoadFinished(Loader> loader,
List printers) {
// If this is the first load, create the fake PDF printer.
// We do this to avoid flicker where the PDF printer is the
// only one and as soon as the loader loads the favorites
// it gets switched. Not a great user experience.
if (mFakePdfPrinter == null) {
mCurrentPrinter = mFakePdfPrinter = createFakePdfPrinter();
updatePrintAttributes(mCurrentPrinter.getCapabilities());
updateUi();
}
for(int i = 0,length = printers.size();i newPrintersMap =
new ArrayMap();
final int printerCount = printers.size();
for (int i = 0; i < printerCount; i++) {
PrinterInfo printer = printers.get(i);
newPrintersMap.put(printer.getId(), printer);
}
List newPrinters = new ArrayList();
// Update printers we already have.
final int oldPrinterCount = mPrinters.size();
for (int i = 0; i < oldPrinterCount; i++) {
PrinterId oldPrinterId = mPrinters.get(i).getId();
PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId);
if (updatedPrinter != null) {
newPrinters.add(updatedPrinter);
}
}
// Add the rest of the new printers, i.e. what is left.
newPrinters.addAll(newPrintersMap.values());
mPrinters.clear();
mPrinters.addAll(newPrinters);
mEditor.ensureCurrentPrinterSelected();
notifyDataSetChanged();
}
@Override
public void onLoaderReset(Loader> loader) {
mPrinters.clear();
notifyDataSetInvalidated();
}
```
这几行代码主要是扫描打印机的,在执行完onLoadFinished后如果找到了打印机,就执行adapter的notifyDataSetChanged,在上面postCreate方法里面,我们可以看到mDestinationSpinnerAdapter.registerDataSetObserver注册了数据变化监听,我们看看onChanged方法,前面几行代码是选择第一个打印机为默认打印机,接下来就是判断打印机的状态是否可用,以及打印机属性是否为空,如果两者状态都满足,就执行updateUi方法,updateUi主要是实现一些属性的配置,这个我们不细讲,我们主要关注这几行代码。
```
在这里插入代码片
if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
&& (TextUtils.isEmpty(mPageRangeEditText.getText())
|| hasErrors()))
|| (mRangeOptionsSpinner.getSelectedItemPosition() == 0
&& (!mController.hasPerformedLayout() || hasErrors()))) {
mPrintButton.setEnabled(false);
} else {
mPrintButton.setEnabled(true);
}
```
这几行代码主要判断打印机状态等等信息是否正常,打印按钮是否可用,接下来我们去看看打印按钮主要做了什么操作。
```
在这里插入代码片
final PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
Log.e(LOG_TAG, "[ymy]PrintButton is click--"+printer.toString());
if (printer != null) {
mEditor.confirmPrint();
mController.update();
if (!printer.equals(mDestinationSpinnerAdapter.mFakePdfPrinter)) {
mEditor.refreshCurrentPrinter();
}
} else {
mEditor.cancel();
PrintJobConfigActivity.this.finish();
}
```
接下来主要操作在mController.update()方法里面,我们就不一一介绍了,大概的流程就是第一步执行mRemotePrintAdapter.layout,接下来handleOnLayoutFinished里面mRemotePrintAdapter.write,接下来handleOnWriteFinished里面的requestCreatePdfFileOrFinish里面执行PrintJobConfigActivity.this.finish(),接下俩我们看看Activity生命周期方法onPause里面。
```
在这里插入代码片
if (isFinishing()) {
Log.e(LOG_TAG, "onPause");
if (mController != null && mController.hasStarted()) {
mController.finish();
}
if (mEditor != null && mEditor.isPrintConfirmed()
&& mController != null && mController.isFinished()) {
Log.e(LOG_TAG, "setPrintJobState--STATE_QUEUED");
mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId,
PrintJobInfo.STATE_QUEUED, null);
} else {
mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId,
PrintJobInfo.STATE_CANCELED, null);
}
if (mGeneratingPrintJobDialog != null) {
mGeneratingPrintJobDialog.dismiss();
mGeneratingPrintJobDialog = null;
}
mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
mSpoolerProvider.destroy();
}
super.onPause();
```
就是将打印状态修改为STATE_QUEUED,接下来就交给PrintSpoolerService来处理了,这个我们就不看了,有兴趣的自己看。
前面说了这么多,主要是为后面做个铺垫,我们自定义PrintJobService就是根据这个流程来写的,大家有兴趣的话可以自己去看下,我是给PrintjobConfigActivity的方法都打上log,看看他具体是怎么跑的,大家可以试试,有其他更好的方法也可以交流一下。
接下来我们来说下我们的service是怎么实现的,首先我们的service也是放在\frameworks\base\packages\PrintSpooler\src\com\android\printspooler目录下的,这个是为了避免我们调用一些类时找不到,其实我们这个service就是PrintjobConfigActivity的简化版本,删除了一些东西,把activity变成了service而已。我们先从PrintManager.print开始修改。
```
在这里插入代码片
public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter,
PrintAttributes attributes) {
isPrint = false;
if (!(mContext instanceof Activity)) {
throw new IllegalStateException("Can print only from an activity");
}
if (TextUtils.isEmpty(printJobName)) {
throw new IllegalArgumentException("printJobName cannot be empty");
}
if (documentAdapter == null) {
throw new IllegalArgumentException("documentAdapter cannot be null");
}
final PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(
(Activity) mContext, documentAdapter);
try {
final Bundle result = mService.print(printJobName, delegate,
attributes, mContext.getPackageName(), mAppId, mUserId);
if (result != null) {
PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB);
IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT);
if (printJob == null || intent == null) {
return null;
}
try {
final PrinterLoader loader = new PrinterLoader((Activity)mContext);
loader.getPrinters(new GetPrintersCallBack(){
public void receivePrinters(List printers){
for(int i = 0,length = printers.size();i
import android.content.Context;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.util.ArrayMap;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class PrinterLoader implements LoaderManager.LoaderCallbacks{
private static final int LOADER_ID_PRINTERS_LOADER = 1;
private final List mPrinters = new ArrayList();
private Activity activity;
private GetPrintersCallBack callBack;
public PrinterLoader(Activity activity) {
this.activity = activity;
}
public void getPrinters(GetPrintersCallBack callBack){
Log.e("PrinterLoader", "getPrinters");
this.callBack = callBack;
activity.getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
}
public void refreshPrinters(PrinterId printerId){
Log.e("PrinterLoader", "refreshPrinters");
PrintersProvider printersLoader = (PrintersProvider)
(Loader>) activity.getLoaderManager().getLoader(
LOADER_ID_PRINTERS_LOADER);
if (printersLoader != null){
printersLoader.setTrackedPrinter(printerId);
}
}
public void addHistoricalPrinter(PrinterInfo printer){
Log.e("PrinterLoader", "refreshPrinters");
PrintersProvider printersLoader = (PrintersProvider)
(Loader>) activity.getLoaderManager().getLoader(
LOADER_ID_PRINTERS_LOADER);
if (printersLoader != null){
printersLoader.addHistoricalPrinter(printer);
}
}
@Override
public Loader> onCreateLoader(int id, Bundle args) {
if (id == LOADER_ID_PRINTERS_LOADER) {
return new PrintersProvider(activity);
}
return null;
}
@Override
public void onLoadFinished(Loader> loader, List printers) {
for(int i = 0,length = printers.size();i newPrintersMap =
new ArrayMap();
final int printerCount = printers.size();
for (int i = 0; i < printerCount; i++) {
PrinterInfo printer = printers.get(i);
newPrintersMap.put(printer.getId(), printer);
}
List newPrinters = new ArrayList();
// Update printers we already have.
final int oldPrinterCount = mPrinters.size();
for (int i = 0; i < oldPrinterCount; i++) {
PrinterId oldPrinterId = mPrinters.get(i).getId();
PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId);
if (updatedPrinter != null) {
newPrinters.add(updatedPrinter);
}
}
// Add the rest of the new printers, i.e. what is left.
newPrinters.addAll(newPrintersMap.values());
callBack.receivePrinters(newPrinters);
}
@Override
public void onLoaderReset(Loader> loader) {
}
}
```
这里主要是仿造PrintJobConfigActivity.DestinationAdapter里面的查找打印机方法来的,里面的PrintersProvider类就是直接copyframeworks\base\packages\PrintSpooler\src\com\android\printspooler\FusedPrintersProvider.java文件来的,是为了能在外面调用这个类,里面什么代码都没改,就改了个包名和类名,具体代码在下面。
在这里插入代码片
package android.print;
import android.content.ComponentName;
import android.content.Context;
import android.content.Loader;
import android.content.pm.ServiceInfo;
import android.os.AsyncTask;
import android.print.PrintManager;
import android.print.PrinterDiscoverySession;
import android.print.PrinterDiscoverySession.OnPrintersChangeListener;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.printservice.PrintServiceInfo;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.util.FastXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import libcore.io.IoUtils;
/**
* This class is responsible for loading printers by doing discovery
* and merging the discovered printers with the previously used ones.
*/
public class PrintersProvider extends Loader> {
private static final String LOG_TAG = "PrintersProvider";
private static final boolean DEBUG = true;
private static final double WEIGHT_DECAY_COEFFICIENT = 0.95f;
private static final int MAX_HISTORY_LENGTH = 50;
private static final int MAX_FAVORITE_PRINTER_COUNT = 4;
private final List mPrinters =
new ArrayList();
private final List mFavoritePrinters =
new ArrayList();
private final PersistenceManager mPersistenceManager;
private PrinterDiscoverySession mDiscoverySession;
private PrinterId mTrackedPrinter;
private boolean mPrintersUpdatedBefore;
public PrintersProvider(Context context) {
super(context);
mPersistenceManager = new PersistenceManager(context);
}
public void addHistoricalPrinter(PrinterInfo printer) {
mPersistenceManager.addPrinterAndWritePrinterHistory(printer);
}
private void computeAndDeliverResult(ArrayMap discoveredPrinters,
ArrayMap favoritePrinters) {
List printers = new ArrayList();
// Add the updated favorite printers.
final int favoritePrinterCount = favoritePrinters.size();
for (int i = 0; i < favoritePrinterCount; i++) {
PrinterInfo favoritePrinter = favoritePrinters.valueAt(i);
PrinterInfo updatedPrinter = discoveredPrinters.remove(
favoritePrinter.getId());
if (updatedPrinter != null) {
printers.add(updatedPrinter);
} else {
printers.add(favoritePrinter);
}
}
// Add other updated printers.
final int printerCount = mPrinters.size();
for (int i = 0; i < printerCount; i++) {
PrinterInfo printer = mPrinters.get(i);
PrinterInfo updatedPrinter = discoveredPrinters.remove(
printer.getId());
if (updatedPrinter != null) {
printers.add(updatedPrinter);
}
}
// Add the new printers, i.e. what is left.
printers.addAll(discoveredPrinters.values());
// Update the list of printers.
mPrinters.clear();
mPrinters.addAll(printers);
if (isStarted()) {
// If stated deliver the new printers.
deliverResult(printers);
} else {
// Otherwise, take a note for the change.
onContentChanged();
}
}
@Override
protected void onStartLoading() {
if (DEBUG) {
Log.i(LOG_TAG, "onStartLoading() " + PrintersProvider.this.hashCode());
}
// The contract is that if we already have a valid,
// result the we have to deliver it immediately.
if (!mPrinters.isEmpty()) {
deliverResult(new ArrayList(mPrinters));
}
// Always load the data to ensure discovery period is
// started and to make sure obsolete printers are updated.
onForceLoad();
}
@Override
protected void onStopLoading() {
if (DEBUG) {
Log.i(LOG_TAG, "onStopLoading() " + PrintersProvider.this.hashCode());
}
onCancelLoad();
}
@Override
protected void onForceLoad() {
if (DEBUG) {
Log.i(LOG_TAG, "onForceLoad() " + PrintersProvider.this.hashCode());
}
loadInternal();
}
private void loadInternal() {
if (mDiscoverySession == null) {
PrintManager printManager = (PrintManager) getContext()
.getSystemService(Context.PRINT_SERVICE);
mDiscoverySession = printManager.createPrinterDiscoverySession();
mPersistenceManager.readPrinterHistory();
} else if (mPersistenceManager.isHistoryChanged()) {
mPersistenceManager.readPrinterHistory();
}
if (mPersistenceManager.isReadHistoryCompleted()
&& !mDiscoverySession.isPrinterDiscoveryStarted()) {
mDiscoverySession.setOnPrintersChangeListener(new OnPrintersChangeListener() {
@Override
public void onPrintersChanged() {
if (DEBUG) {
Log.i(LOG_TAG, "onPrintersChanged() count:"
+ mDiscoverySession.getPrinters().size()
+ " " + PrintersProvider.this.hashCode());
}
updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters);
}
});
final int favoriteCount = mFavoritePrinters.size();
List printerIds = new ArrayList(favoriteCount);
for (int i = 0; i < favoriteCount; i++) {
printerIds.add(mFavoritePrinters.get(i).getId());
}
mDiscoverySession.startPrinterDisovery(printerIds);
List printers = mDiscoverySession.getPrinters();
if (!printers.isEmpty()) {
updatePrinters(printers, mFavoritePrinters);
}
}
}
private void updatePrinters(List printers, List favoritePrinters) {
for(int i = 0,length = printers.size();i printersMap =
new ArrayMap();
final int printerCount = printers.size();
for (int i = 0; i < printerCount; i++) {
PrinterInfo printer = printers.get(i);
printersMap.put(printer.getId(), printer);
}
ArrayMap favoritePrintersMap =
new ArrayMap();
final int favoritePrinterCount = favoritePrinters.size();
for (int i = 0; i < favoritePrinterCount; i++) {
PrinterInfo favoritePrinter = favoritePrinters.get(i);
favoritePrintersMap.put(favoritePrinter.getId(), favoritePrinter);
}
computeAndDeliverResult(printersMap, favoritePrintersMap);
}
@Override
protected boolean onCancelLoad() {
if (DEBUG) {
Log.i(LOG_TAG, "onCancelLoad() " + PrintersProvider.this.hashCode());
}
return cancelInternal();
}
private boolean cancelInternal() {
if (mDiscoverySession != null
&& mDiscoverySession.isPrinterDiscoveryStarted()) {
if (mTrackedPrinter != null) {
mDiscoverySession.stopPrinterStateTracking(mTrackedPrinter);
mTrackedPrinter = null;
}
mDiscoverySession.stopPrinterDiscovery();
return true;
} else if (mPersistenceManager.isReadHistoryInProgress()) {
return mPersistenceManager.stopReadPrinterHistory();
}
return false;
}
@Override
protected void onReset() {
if (DEBUG) {
Log.i(LOG_TAG, "onReset() " + PrintersProvider.this.hashCode());
}
onStopLoading();
mPrinters.clear();
if (mDiscoverySession != null) {
mDiscoverySession.destroy();
mDiscoverySession = null;
}
}
@Override
protected void onAbandon() {
if (DEBUG) {
Log.i(LOG_TAG, "onAbandon() " + PrintersProvider.this.hashCode());
}
onStopLoading();
}
public void setTrackedPrinter(PrinterId printerId) {
Log.i(LOG_TAG, "[ymy]setTrackedPrinter");
if (isStarted() && mDiscoverySession != null
&& mDiscoverySession.isPrinterDiscoveryStarted()) {
if (mTrackedPrinter != null) {
if (mTrackedPrinter.equals(printerId)) {
return;
}
mDiscoverySession.stopPrinterStateTracking(mTrackedPrinter);
}
mTrackedPrinter = printerId;
mDiscoverySession.startPrinterStateTracking(printerId);
}
}
public boolean isFavoritePrinter(PrinterId printerId) {
final int printerCount = mFavoritePrinters.size();
for (int i = 0; i < printerCount; i++) {
PrinterInfo favoritePritner = mFavoritePrinters.get(i);
if (favoritePritner.getId().equals(printerId)) {
return true;
}
}
return false;
}
public void forgetFavoritePrinter(PrinterId printerId) {
List newFavoritePrinters = null;
// Remove the printer from the favorites.
final int favoritePrinterCount = mFavoritePrinters.size();
for (int i = 0; i < favoritePrinterCount; i++) {
PrinterInfo favoritePrinter = mFavoritePrinters.get(i);
if (favoritePrinter.getId().equals(printerId)) {
newFavoritePrinters = new ArrayList();
newFavoritePrinters.addAll(mPrinters);
newFavoritePrinters.remove(i);
break;
}
}
// If we removed a favorite printer, we have work to do.
if (newFavoritePrinters != null) {
// Remove the printer from history and persist the latter.
mPersistenceManager.removeHistoricalPrinterAndWritePrinterHistory(printerId);
// Recompute and deliver the printers.
updatePrinters(mDiscoverySession.getPrinters(), newFavoritePrinters);
}
}
private final class PersistenceManager {
private static final String PERSIST_FILE_NAME = "printer_history.xml";
private static final String TAG_PRINTERS = "printers";
private static final String TAG_PRINTER = "printer";
private static final String TAG_PRINTER_ID = "printerId";
private static final String ATTR_LOCAL_ID = "localId";
private static final String ATTR_SERVICE_NAME = "serviceName";
private static final String ATTR_NAME = "name";
private static final String ATTR_DESCRIPTION = "description";
private static final String ATTR_STATUS = "status";
private final AtomicFile mStatePersistFile;
private List mHistoricalPrinters = new ArrayList();
private boolean mReadHistoryCompleted;
private boolean mReadHistoryInProgress;
private ReadTask mReadTask;
private volatile long mLastReadHistoryTimestamp;
private PersistenceManager(Context context) {
mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
PERSIST_FILE_NAME));
}
public boolean isReadHistoryInProgress() {
return mReadHistoryInProgress;
}
public boolean isReadHistoryCompleted() {
return mReadHistoryCompleted;
}
public boolean stopReadPrinterHistory() {
final boolean cancelled = mReadTask.cancel(true);
mReadTask = null;
return cancelled;
}
public void readPrinterHistory() {
if (DEBUG) {
Log.i(LOG_TAG, "read history started "
+ PrintersProvider.this.hashCode());
}
mReadHistoryInProgress = true;
mReadTask = new ReadTask();
mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
@SuppressWarnings("unchecked")
public void addPrinterAndWritePrinterHistory(PrinterInfo printer) {
if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
mHistoricalPrinters.remove(0);
}
mHistoricalPrinters.add(printer);
new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
new ArrayList(mHistoricalPrinters));
}
@SuppressWarnings("unchecked")
public void removeHistoricalPrinterAndWritePrinterHistory(PrinterId printerId) {
boolean writeHistory = false;
final int printerCount = mHistoricalPrinters.size();
for (int i = printerCount - 1; i >= 0; i--) {
PrinterInfo historicalPrinter = mHistoricalPrinters.get(i);
if (historicalPrinter.getId().equals(printerId)) {
mHistoricalPrinters.remove(i);
writeHistory = true;
}
}
if (writeHistory) {
new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
new ArrayList(mHistoricalPrinters));
}
}
public boolean isHistoryChanged() {
return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified();
}
private List computeFavoritePrinters(List printers) {
Map recordMap =
new ArrayMap();
// Recompute the weights.
float currentWeight = 1.0f;
final int printerCount = printers.size();
for (int i = printerCount - 1; i >= 0; i--) {
PrinterInfo printer = printers.get(i);
// Aggregate weight for the same printer
PrinterRecord record = recordMap.get(printer.getId());
if (record == null) {
record = new PrinterRecord(printer);
recordMap.put(printer.getId(), record);
}
record.weight += currentWeight;
currentWeight *= WEIGHT_DECAY_COEFFICIENT;
}
// Soft the favorite printers.
List favoriteRecords = new ArrayList(
recordMap.values());
Collections.sort(favoriteRecords);
// Write the favorites to the output.
final int favoriteCount = Math.min(favoriteRecords.size(),
MAX_FAVORITE_PRINTER_COUNT);
List favoritePrinters = new ArrayList(favoriteCount);
for (int i = 0; i < favoriteCount; i++) {
PrinterInfo printer = favoriteRecords.get(i).printer;
favoritePrinters.add(printer);
}
return favoritePrinters;
}
private final class PrinterRecord implements Comparable {
public final PrinterInfo printer;
public float weight;
public PrinterRecord(PrinterInfo printer) {
this.printer = printer;
}
@Override
public int compareTo(PrinterRecord another) {
return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
}
}
private final class ReadTask extends AsyncTask> {
@Override
protected List doInBackground(Void... args) {
return doReadPrinterHistory();
}
@Override
protected void onPostExecute(List printers) {
if (DEBUG) {
Log.i(LOG_TAG, "read history completed "
+ PrintersProvider.this.hashCode()+"-printers.size()="+printers.size());
}
for(int i = 0,length = printers.size();i services = printManager
.getEnabledPrintServices();
Set enabledComponents = new ArraySet();
final int installedServiceCount = services.size();
for (int i = 0; i < installedServiceCount; i++) {
ServiceInfo serviceInfo = services.get(i).getResolveInfo().serviceInfo;
ComponentName componentName = new ComponentName(
serviceInfo.packageName, serviceInfo.name);
enabledComponents.add(componentName);
}
final int printerCount = printers.size();
for (int i = printerCount - 1; i >= 0; i--) {
ComponentName printerServiceName = printers.get(i).getId().getServiceName();
if (!enabledComponents.contains(printerServiceName)) {
printers.remove(i);
}
}
// Store the filtered list.
mHistoricalPrinters = printers;
// Compute the favorite printers.
mFavoritePrinters.clear();
mFavoritePrinters.addAll(computeFavoritePrinters(mHistoricalPrinters));
mReadHistoryInProgress = false;
mReadHistoryCompleted = true;
// Deliver the printers.
updatePrinters(mDiscoverySession.getPrinters(), mHistoricalPrinters);
// Loading the available printers if needed.
loadInternal();
// We are done.
mReadTask = null;
}
private List doReadPrinterHistory() {
FileInputStream in = null;
try {
in = mStatePersistFile.openRead();
} catch (FileNotFoundException fnfe) {
if (DEBUG) {
Log.i(LOG_TAG, "No existing printer history "
+ PrintersProvider.this.hashCode());
}
return new ArrayList();
}
try {
List printers = new ArrayList();
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, null);
parseState(parser, printers);
// Take a note which version of the history was read.
mLastReadHistoryTimestamp = mStatePersistFile.getBaseFile().lastModified();
return printers;
} catch (IllegalStateException ise) {
Slog.w(LOG_TAG, "Failed parsing ", ise);
} catch (NullPointerException npe) {
Slog.w(LOG_TAG, "Failed parsing ", npe);
} catch (NumberFormatException nfe) {
Slog.w(LOG_TAG, "Failed parsing ", nfe);
} catch (XmlPullParserException xppe) {
Slog.w(LOG_TAG, "Failed parsing ", xppe);
} catch (IOException ioe) {
Slog.w(LOG_TAG, "Failed parsing ", ioe);
} catch (IndexOutOfBoundsException iobe) {
Slog.w(LOG_TAG, "Failed parsing ", iobe);
} finally {
IoUtils.closeQuietly(in);
}
return Collections.emptyList();
}
private void parseState(XmlPullParser parser, List outPrinters)
throws IOException, XmlPullParserException {
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS);
parser.next();
while (parsePrinter(parser, outPrinters)) {
// Be nice and respond to cancellation
if (isCancelled()) {
return;
}
parser.next();
}
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);
}
private boolean parsePrinter(XmlPullParser parser, List outPrinters)
throws IOException, XmlPullParserException {
skipEmptyTextTags(parser);
if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) {
return false;
}
String name = parser.getAttributeValue(null, ATTR_NAME);
String description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS));
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID);
String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
null, ATTR_SERVICE_NAME));
PrinterId printerId = new PrinterId(service, localId);
parser.next();
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
parser.next();
PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status);
builder.setDescription(description);
PrinterInfo printer = builder.build();
outPrinters.add(printer);
if (DEBUG) {
Log.i(LOG_TAG, "[RESTORED] " + printer);
}
skipEmptyTextTags(parser);
expect(parser, XmlPullParser.END_TAG, TAG_PRINTER);
return true;
}
private void expect(XmlPullParser parser, int type, String tag)
throws IOException, XmlPullParserException {
if (!accept(parser, type, tag)) {
throw new XmlPullParserException("Exepected event: " + type
+ " and tag: " + tag + " but got event: " + parser.getEventType()
+ " and tag:" + parser.getName());
}
}
private void skipEmptyTextTags(XmlPullParser parser)
throws IOException, XmlPullParserException {
while (accept(parser, XmlPullParser.TEXT, null)
&& "\n".equals(parser.getText())) {
parser.next();
}
}
private boolean accept(XmlPullParser parser, int type, String tag)
throws IOException, XmlPullParserException {
if (parser.getEventType() != type) {
return false;
}
if (tag != null) {
if (!tag.equals(parser.getName())) {
return false;
}
} else if (parser.getName() != null) {
return false;
}
return true;
}
};
private final class WriteTask extends AsyncTask, Void, Void> {
@Override
protected Void doInBackground(List... printers) {
doWritePrinterHistory(printers[0]);
return null;
}
private void doWritePrinterHistory(List printers) {
FileOutputStream out = null;
try {
out = mStatePersistFile.startWrite();
XmlSerializer serializer = new FastXmlSerializer();
serializer.setOutput(out, "utf-8");
serializer.startDocument(null, true);
serializer.startTag(null, TAG_PRINTERS);
final int printerCount = printers.size();
for (int i = 0; i < printerCount; i++) {
PrinterInfo printer = printers.get(i);
serializer.startTag(null, TAG_PRINTER);
serializer.attribute(null, ATTR_NAME, printer.getName());
// Historical printers are always stored as unavailable.
serializer.attribute(null, ATTR_STATUS, String.valueOf(
PrinterInfo.STATUS_UNAVAILABLE));
String description = printer.getDescription();
if (description != null) {
serializer.attribute(null, ATTR_DESCRIPTION, description);
}
PrinterId printerId = printer.getId();
serializer.startTag(null, TAG_PRINTER_ID);
serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
.flattenToString());
serializer.endTag(null, TAG_PRINTER_ID);
serializer.endTag(null, TAG_PRINTER);
if (DEBUG) {
Log.i(LOG_TAG, "[PERSISTED] " + printer);
}
}
serializer.endTag(null, TAG_PRINTERS);
serializer.endDocument();
mStatePersistFile.finishWrite(out);
if (DEBUG) {
Log.i(LOG_TAG, "[PERSIST END]");
}
} catch (IOException ioe) {
Slog.w(LOG_TAG, "Failed to write printer history, restoring backup.", ioe);
mStatePersistFile.failWrite(out);
} finally {
IoUtils.closeQuietly(out);
}
}
};
}
}
在PrinterLoader中找到打印机,我们回调到PrintManger.print方法中我们GetPrintersCallBack自定义的回调中,选择我们自己的打印机,接下来我们将打印机信息和打印任务信息传递到PrintJobService中,action自己定义的,在frameworks\base\packages\PrintSpooler\AndroidManifest.xml中注册
```
在这里插入代码片
```
接下来我们来看看PrintJobService.java的实现
```
在这里插入代码片
/*
* 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.Service;
import android.app.LoaderManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.print.ILayoutResultCallback;
import android.print.IPrintDocumentAdapter;
import android.print.IPrintDocumentAdapterObserver;
import android.print.IWriteResultCallback;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintAttributes.Margins;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Resolution;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.PrintJobId;
import android.print.PrintJobInfo;
import android.print.PrintManager;
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.printservice.PrintService;
import android.printservice.PrintServiceInfo;
import android.provider.DocumentsContract;
import android.text.Editable;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
import com.android.printspooler.MediaSizeUtils.MediaSizeComparator;
import libcore.io.IoUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Activity for configuring a print job.
*/
public class PrintJobService extends Service {
private static final String LOG_TAG = "PrintJobService";
private static final boolean DEBUG = true;
public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
private static final int LOADER_ID_PRINTERS_LOADER = 1;
private static final int ORIENTATION_PORTRAIT = 0;
private static final int ORIENTATION_LANDSCAPE = 1;
private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1;
private static final int ACTIVITY_REQUEST_CREATE_FILE = 1;
private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2;
private static final int ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS = 3;
private static final int CONTROLLER_STATE_FINISHED = 1;
private static final int CONTROLLER_STATE_FAILED = 2;
private static final int CONTROLLER_STATE_CANCELLED = 3;
private static final int CONTROLLER_STATE_INITIALIZED = 4;
private static final int CONTROLLER_STATE_STARTED = 5;
private static final int CONTROLLER_STATE_LAYOUT_STARTED = 6;
private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 7;
private static final int CONTROLLER_STATE_WRITE_STARTED = 8;
private static final int CONTROLLER_STATE_WRITE_COMPLETED = 9;
private static final int EDITOR_STATE_INITIALIZED = 1;
private static final int EDITOR_STATE_CONFIRMED_PRINT = 2;
private static final int EDITOR_STATE_CANCELLED = 3;
private static final int MIN_COPIES = 1;
private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES);
private static final Pattern PATTERN_DIGITS = Pattern.compile("[\\d]+");
private static final Pattern PATTERN_ESCAPE_SPECIAL_CHARS = Pattern.compile(
"(?=[]\\[+&|!(){}^\"~*?:\\\\])");
private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
"[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
+ "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {PageRange.ALL_PAGES};
private final PrintAttributes mOldPrintAttributes = new PrintAttributes.Builder().build();
private final PrintAttributes mCurrPrintAttributes = new PrintAttributes.Builder().build();
private final DeathRecipient mDeathRecipient = new DeathRecipient() {
@Override
public void binderDied() {
PrintJobService.this.finish();
}
};
private Editor mEditor;
private Document mDocument;
private PrintController mController;
private PrintJobId mPrintJobId;
private IBinder mIPrintDocumentAdapter;
private PrintSpoolerProvider mSpoolerProvider;
private String mCallingPackageName;
private boolean isPrint = false;
private PrinterInfo mCurrentPrinter;
public void finish(){
super.stopSelf();
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
/**
* 服务启动的时候调用
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(LOG_TAG,"onStartCommand");
// TODO Auto-generated method stub
Bundle extras = intent.getExtras();
PrintJobInfo printJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB);
mCurrentPrinter = extras.getParcelable("ready_printer");
if (printJob == null) {
throw new IllegalArgumentException("printJob cannot be null");
}
mPrintJobId = printJob.getId();
mIPrintDocumentAdapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
if (mIPrintDocumentAdapter == null) {
throw new IllegalArgumentException("PrintDocumentAdapter cannot be null");
}
try {
IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter)
.setObserver(new PrintDocumentAdapterObserver(this));
} catch (RemoteException re) {
finish();
}
PrintAttributes attributes = printJob.getAttributes();
if (attributes != null) {
mCurrPrintAttributes.copyFrom(attributes);
}
mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
try {
mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException re) {
finish();
}
mDocument = new Document();
mEditor = new Editor();
mSpoolerProvider = new PrintSpoolerProvider(this,
new Runnable() {
@Override
public void run() {
// We got the spooler so unleash the UI.
mController = new PrintController(new RemotePrintDocumentAdapter(
IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
mSpoolerProvider.getSpooler().generateFileForPrintJob(mPrintJobId)));
mController.initialize();
mEditor.initialize();
mEditor.postCreate();
}
});
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.e(LOG_TAG,"onDestroy");
if (mController != null && mController.hasStarted()) {
mController.finish();
}
if (mEditor != null && mEditor.isPrintConfirmed()
&& mController != null && mController.isFinished()) {
Log.e(LOG_TAG,"setPrintJobState--STATE_QUEUED");
mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId,
PrintJobInfo.STATE_QUEUED, null);
} else {
mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId,
PrintJobInfo.STATE_CANCELED, null);
}
mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
mSpoolerProvider.destroy();
}
private boolean printAttributesChanged() {
return !mOldPrintAttributes.equals(mCurrPrintAttributes);
}
private class PrintController {
private final AtomicInteger mRequestCounter = new AtomicInteger();
private final RemotePrintDocumentAdapter mRemotePrintAdapter;
private final Bundle mMetadata;
private final ControllerHandler mHandler;
private final LayoutResultCallback mLayoutResultCallback;
private final WriteResultCallback mWriteResultCallback;
private int mControllerState = CONTROLLER_STATE_INITIALIZED;
private boolean mHasStarted;
private PageRange[] mRequestedPages;
public PrintController(RemotePrintDocumentAdapter adapter) {
mRemotePrintAdapter = adapter;
mMetadata = new Bundle();
mHandler = new ControllerHandler(getMainLooper());
mLayoutResultCallback = new LayoutResultCallback(mHandler);
mWriteResultCallback = new WriteResultCallback(mHandler);
}
public void initialize() {
mHasStarted = false;
mControllerState = CONTROLLER_STATE_INITIALIZED;
}
public void cancel() {
if (isWorking()) {
mRemotePrintAdapter.cancel();
}
mControllerState = CONTROLLER_STATE_CANCELLED;
}
public boolean isCancelled() {
return (mControllerState == CONTROLLER_STATE_CANCELLED);
}
public boolean isFinished() {
return (mControllerState == CONTROLLER_STATE_FINISHED);
}
public boolean hasStarted() {
return mHasStarted;
}
public boolean hasPerformedLayout() {
return mControllerState >= CONTROLLER_STATE_LAYOUT_COMPLETED;
}
public boolean isPerformingLayout() {
return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED;
}
public boolean isWorking() {
return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED
|| mControllerState == CONTROLLER_STATE_WRITE_STARTED;
}
public void start() {
Log.e(LOG_TAG, "PrintController-start");
mControllerState = CONTROLLER_STATE_STARTED;
mHasStarted = true;
mRemotePrintAdapter.start();
}
public void update() {
if (!mController.hasStarted()) {
mController.start();
}
// If the print attributes are the same and we are performing
// a layout, then we have to wait for it to completed which will
// trigger writing of the necessary pages.
final boolean printAttributesChanged = printAttributesChanged();
if (!printAttributesChanged && isPerformingLayout()) {
return;
}
// If print is confirmed we always do a layout since the previous
// ones were for preview and this one is for printing.
if (!printAttributesChanged && !mEditor.isPrintConfirmed()) {
if (mDocument.info == null) {
// We are waiting for the result of a layout, so do nothing.
return;
}
// If the attributes didn't change and we have done a layout, then
// we do not do a layout but may have to ask the app to write some
// pages. Hence, pretend layout completed and nothing changed, so
// we handle writing as usual.
handleOnLayoutFinished(mDocument.info, false, mRequestCounter.get());
} else {
mSpoolerProvider.getSpooler().setPrintJobAttributesNoPersistence(
mPrintJobId, mCurrPrintAttributes);
mMetadata.putBoolean(PrintDocumentAdapter.EXTRA_PRINT_PREVIEW,
!mEditor.isPrintConfirmed());
mControllerState = CONTROLLER_STATE_LAYOUT_STARTED;
Log.e(LOG_TAG, "PrintController-CONTROLLER_STATE_LAYOUT_STARTED");
mRemotePrintAdapter.layout(mOldPrintAttributes, mCurrPrintAttributes,
mLayoutResultCallback, mMetadata, mRequestCounter.incrementAndGet());
mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
}
}
public void finish() {
mControllerState = CONTROLLER_STATE_FINISHED;
mRemotePrintAdapter.finish();
}
private void handleOnLayoutFinished(PrintDocumentInfo info,
boolean layoutChanged, int sequence) {
if (mRequestCounter.get() != sequence) {
return;
}
if (isCancelled()) {
if (mEditor.isDone()) {
PrintJobService.this.finish();
}
return;
}
Log.e(LOG_TAG, "PrintController-CONTROLLER_STATE_LAYOUT_COMPLETED");
mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED;
// For layout purposes we care only whether the type or the page
// count changed. We still do not have the size since we did not
// call write. We use "layoutChanged" set by the application to
// know whether something else changed about the document.
final boolean infoChanged = !equalsIgnoreSize(info, mDocument.info);
Log.e(LOG_TAG, "PrintController-infoChanged="+infoChanged+",layoutChanged="+layoutChanged);
// If the info changed, we update the document and the print job.
if (infoChanged) {
mDocument.info = info;
// Set the info.
mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence(
mPrintJobId, info);
}
// If the document info or the layout changed, then
// drop the pages since we have to fetch them again.
if (infoChanged || layoutChanged) {
mDocument.pages = null;
mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(
mPrintJobId, null);
}
// No pages means that the user selected an invalid range while we
// were doing a layout or the layout returned a document info for
// which the selected range is invalid. In such a case we do not
// write anything and wait for the user to fix the range which will
// trigger an update.
mRequestedPages = mEditor.getRequestedPages();
if (mRequestedPages == null || mRequestedPages.length == 0) {
Log.e(LOG_TAG, "PrintController-mRequestedPages == null || mRequestedPages.length == 0");
if (mEditor.isDone()) {
PrintJobService.this.finish();
}
return;
} else {
// If print is not confirmed we just ask for the first of the
// selected pages to emulate a behavior that shows preview
// increasing the chances that apps will implement the APIs
// correctly.
if (!mEditor.isPrintConfirmed()) {
if (ALL_PAGES_ARRAY.equals(mRequestedPages)) {
mRequestedPages = new PageRange[] {new PageRange(0, 0)};
} else {
final int firstPage = mRequestedPages[0].getStart();
mRequestedPages = new PageRange[] {new PageRange(firstPage, firstPage)};
}
}
}
// If the info and the layout did not change and we already have
// the requested pages, then nothing else to do.
if (!infoChanged && !layoutChanged
&& PageRangeUtils.contains(mDocument.pages, mRequestedPages)) {
// Nothing interesting changed and we have all requested pages.
// Then update the print jobs's pages as we will not do a write
// and we usually update the pages in the write complete callback.
Log.e(LOG_TAG, "PrintController-PageRangeUtils.contains(mDocument.pages, mRequestedPages)");
updatePrintJobPages(mDocument.pages, mRequestedPages);
if (mEditor.isDone()) {
requestFinish();
}
return;
}
Log.e(LOG_TAG, "PrintController-CONTROLLER_STATE_WRITE_STARTED");
// Request a write of the pages of interest.
mControllerState = CONTROLLER_STATE_WRITE_STARTED;
mRemotePrintAdapter.write(mRequestedPages, mWriteResultCallback,
mRequestCounter.incrementAndGet());
}
private void handleOnLayoutFailed(final CharSequence error, int sequence) {
if (mRequestCounter.get() != sequence) {
return;
}
Log.e(LOG_TAG, "PrintController-CONTROLLER_STATE_FAILED");
mControllerState = CONTROLLER_STATE_FAILED;
}
private void handleOnWriteFinished(PageRange[] pages, int sequence) {
if (mRequestCounter.get() != sequence) {
return;
}
if (isCancelled()) {
if (mEditor.isDone()) {
PrintJobService.this.finish();
}
return;
}
Log.e(LOG_TAG, "PrintController-CONTROLLER_STATE_WRITE_COMPLETED");
mControllerState = CONTROLLER_STATE_WRITE_COMPLETED;
// Update the document size.
File file = mSpoolerProvider.getSpooler()
.generateFileForPrintJob(mPrintJobId);
mDocument.info.setDataSize(file.length());
// Update the print job with the updated info.
mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence(
mPrintJobId, mDocument.info);
// Update which pages we have fetched.
mDocument.pages = PageRangeUtils.normalize(pages);
if (DEBUG) {
Log.i(LOG_TAG, "Requested: " + Arrays.toString(mRequestedPages)
+ " and got: " + Arrays.toString(mDocument.pages));
}
updatePrintJobPages(mDocument.pages, mRequestedPages);
if (mEditor.isDone()) {
requestFinish();
}
}
private void updatePrintJobPages(PageRange[] writtenPages, PageRange[] requestedPages) {
// Adjust the print job pages based on what was requested and written.
// The cases are ordered in the most expected to the least expected.
Log.e(LOG_TAG, "PrintController-updatePrintJobPages");
if (Arrays.equals(writtenPages, requestedPages)) {
// We got a document with exactly the pages we wanted. Hence,
// the printer has to print all pages in the data.
mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
ALL_PAGES_ARRAY);
} else if (Arrays.equals(writtenPages, ALL_PAGES_ARRAY)) {
// We requested specific pages but got all of them. Hence,
// the printer has to print only the requested pages.
mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
requestedPages);
} else if (PageRangeUtils.contains(writtenPages, requestedPages)) {
// We requested specific pages and got more but not all pages.
// Hence, we have to offset appropriately the printed pages to
// be based off the start of the written ones instead of zero.
// The written pages are always non-null and not empty.
final int offset = -writtenPages[0].getStart();
PageRange[] offsetPages = Arrays.copyOf(requestedPages, requestedPages.length);
PageRangeUtils.offset(offsetPages, offset);
mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
offsetPages);
} else if (Arrays.equals(requestedPages, ALL_PAGES_ARRAY)
&& writtenPages.length == 1 && writtenPages[0].getStart() == 0
&& writtenPages[0].getEnd() == mDocument.info.getPageCount() - 1) {
// We requested all pages via the special constant and got all
// of them as an explicit enumeration. Hence, the printer has
// to print only the requested pages.
mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
writtenPages);
} else {
// We did not get the pages we requested, then the application
// misbehaves, so we fail quickly.
mControllerState = CONTROLLER_STATE_FAILED;
Log.e(LOG_TAG, "Received invalid pages from the app");
}
}
private void requestFinish() {
Log.e(LOG_TAG, "PrintController-requestFinish");
PrintJobService.this.finish();
}
private void handleOnWriteFailed(final CharSequence error, int sequence) {
if (mRequestCounter.get() != sequence) {
return;
}
Log.e(LOG_TAG, "PrintController-CONTROLLER_STATE_FAILED");
mControllerState = CONTROLLER_STATE_FAILED;
}
private boolean equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs) {
if (lhs == rhs) {
return true;
}
if (lhs == null) {
if (rhs != null) {
return false;
}
} else {
if (rhs == null) {
return false;
}
if (lhs.getContentType() != rhs.getContentType()
|| lhs.getPageCount() != rhs.getPageCount()) {
return false;
}
}
return true;
}
private final class ControllerHandler extends Handler {
public static final int MSG_ON_LAYOUT_FINISHED = 1;
public static final int MSG_ON_LAYOUT_FAILED = 2;
public static final int MSG_ON_WRITE_FINISHED = 3;
public static final int MSG_ON_WRITE_FAILED = 4;
public ControllerHandler(Looper looper) {
super(looper, null, false);
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_ON_LAYOUT_FINISHED: {
PrintDocumentInfo info = (PrintDocumentInfo) message.obj;
final boolean changed = (message.arg1 == 1);
final int sequence = message.arg2;
handleOnLayoutFinished(info, changed, sequence);
} break;
case MSG_ON_LAYOUT_FAILED: {
CharSequence error = (CharSequence) message.obj;
final int sequence = message.arg1;
handleOnLayoutFailed(error, sequence);
} break;
case MSG_ON_WRITE_FINISHED: {
PageRange[] pages = (PageRange[]) message.obj;
final int sequence = message.arg1;
handleOnWriteFinished(pages, sequence);
} break;
case MSG_ON_WRITE_FAILED: {
CharSequence error = (CharSequence) message.obj;
final int sequence = message.arg1;
handleOnWriteFailed(error, sequence);
} break;
}
}
}
}
private static final class LayoutResultCallback extends ILayoutResultCallback.Stub {
private final WeakReference mWeakHandler;
public LayoutResultCallback(PrintController.ControllerHandler handler) {
mWeakHandler = new WeakReference(handler);
}
@Override
public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) {
Handler handler = mWeakHandler.get();
if (handler != null) {
handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_LAYOUT_FINISHED,
changed ? 1 : 0, sequence, info).sendToTarget();
}
}
@Override
public void onLayoutFailed(CharSequence error, int sequence) {
Handler handler = mWeakHandler.get();
if (handler != null) {
handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_LAYOUT_FAILED,
sequence, 0, error).sendToTarget();
}
}
}
private static final class WriteResultCallback extends IWriteResultCallback.Stub {
private final WeakReference mWeakHandler;
public WriteResultCallback(PrintController.ControllerHandler handler) {
mWeakHandler = new WeakReference(handler);
}
@Override
public void onWriteFinished(PageRange[] pages, int sequence) {
Handler handler = mWeakHandler.get();
if (handler != null) {
handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_WRITE_FINISHED,
sequence, 0, pages).sendToTarget();
}
}
@Override
public void onWriteFailed(CharSequence error, int sequence) {
Handler handler = mWeakHandler.get();
if (handler != null) {
handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_WRITE_FAILED,
sequence, 0, error).sendToTarget();
}
}
}
private void writePrintJobDataAndFinish(final Uri uri) {
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
InputStream in = null;
OutputStream out = null;
try {
PrintJobInfo printJob = mSpoolerProvider.getSpooler()
.getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
if (printJob == null) {
return null;
}
File file = mSpoolerProvider.getSpooler()
.generateFileForPrintJob(mPrintJobId);
in = new FileInputStream(file);
out = getContentResolver().openOutputStream(uri);
final byte[] buffer = new byte[8192];
while (true) {
final int readByteCount = in.read(buffer);
if (readByteCount < 0) {
break;
}
out.write(buffer, 0, readByteCount);
}
} catch (FileNotFoundException fnfe) {
Log.e(LOG_TAG, "Error writing print job data!", fnfe);
} catch (IOException ioe) {
Log.e(LOG_TAG, "Error writing print job data!", ioe);
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
}
return null;
}
@Override
public void onPostExecute(Void result) {
mEditor.cancel();
PrintJobService.this.finish();
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
private final class Editor {
private PrinterId mNextPrinterId;
private MediaSizeComparator mMediaSizeComparator;
private void updatePrintAttributes(PrinterCapabilitiesInfo capabilities) {
Log.e(LOG_TAG, "updatePrintAttributes");
PrintAttributes defaults = capabilities.getDefaults();
// Sort the media sizes based on the current locale.
List sortedMediaSizes = new ArrayList(
capabilities.getMediaSizes());
Collections.sort(sortedMediaSizes, mMediaSizeComparator);
// Media size.
MediaSize currMediaSize = mCurrPrintAttributes.getMediaSize();
if (currMediaSize == null) {
mCurrPrintAttributes.setMediaSize(defaults.getMediaSize());
} else {
MediaSize currMediaSizePortrait = currMediaSize.asPortrait();
final int mediaSizeCount = sortedMediaSizes.size();
for (int i = 0; i < mediaSizeCount; i++) {
MediaSize mediaSize = sortedMediaSizes.get(i);
if (currMediaSizePortrait.equals(mediaSize.asPortrait())) {
mCurrPrintAttributes.setMediaSize(currMediaSize);
break;
}
}
}
// Color mode.
final int colorMode = mCurrPrintAttributes.getColorMode();
if ((capabilities.getColorModes() & colorMode) == 0) {
mCurrPrintAttributes.setColorMode(colorMode);
}
// Resolution
Resolution resolution = mCurrPrintAttributes.getResolution();
if (resolution == null || !capabilities.getResolutions().contains(resolution)) {
mCurrPrintAttributes.setResolution(defaults.getResolution());
}
// Margins.
Margins margins = mCurrPrintAttributes.getMinMargins();
if (margins == null) {
mCurrPrintAttributes.setMinMargins(defaults.getMinMargins());
} else {
Margins minMargins = capabilities.getMinMargins();
if (margins.getLeftMils() < minMargins.getLeftMils()
|| margins.getTopMils() < minMargins.getTopMils()
|| margins.getRightMils() > minMargins.getRightMils()
|| margins.getBottomMils() > minMargins.getBottomMils()) {
mCurrPrintAttributes.setMinMargins(defaults.getMinMargins());
}
}
}
private int mEditorState;
private boolean mIgnoreNextDestinationChange;
private int mOldMediaSizeSelectionIndex;
private int mOldColorModeSelectionIndex;
private boolean mIgnoreNextOrientationChange;
private boolean mIgnoreNextRangeOptionChange;
private boolean mIgnoreNextCopiesChange;
private boolean mIgnoreNextRangeChange;
private boolean mIgnoreNextMediaSizeChange;
private boolean mIgnoreNextColorChange;
private boolean mFavoritePrinterSelected;
public Editor() {
}
public void postCreate() {
// Destination.
mMediaSizeComparator = new MediaSizeComparator(PrintJobService.this);
updatePrintAttributes(mCurrentPrinter.getCapabilities());
mSpoolerProvider.getSpooler().setPrintJobPrinterNoPersistence(mPrintJobId, mCurrentPrinter);
printButtonClick();
}
public void reselectCurrentPrinter() {
Log.d(LOG_TAG, "reselectCurrentPrinter");
}
public void addCurrentPrinterToHistory() {
}
private void printButtonClick() {
Log.e(LOG_TAG, "[ymy]printButtonClick--"+mCurrentPrinter.toString());
mController.update();
if (mCurrentPrinter != null) {
isPrint = true;
new Handler().postDelayed(new Runnable(){
@Override
public void run() {
mEditor.confirmPrint();
mController.update();
}
}, 5000);
} else {
mEditor.cancel();
PrintJobService.this.finish();
}
}
public void initialize() {
mEditorState = EDITOR_STATE_INITIALIZED;
}
public boolean isCancelled() {
return mEditorState == EDITOR_STATE_CANCELLED;
}
public void cancel() {
mEditorState = EDITOR_STATE_CANCELLED;
mController.cancel();
}
public boolean isDone() {
return isPrintConfirmed() || isCancelled();
}
public boolean isPrintConfirmed() {
return mEditorState == EDITOR_STATE_CONFIRMED_PRINT;
}
public void confirmPrint() {
Log.e(LOG_TAG, "confirmPrint");
addCurrentPrinterToHistory();
mEditorState = EDITOR_STATE_CONFIRMED_PRINT;
}
public PageRange[] getRequestedPages() {
if (hasErrors()) {
return null;
}
return ALL_PAGES_ARRAY;
}
private boolean hasErrors() {
return false;
}
}
private static final class Document {
public PrintDocumentInfo info;
public PageRange[] pages;
}
private static final class PageRangeUtils {
private static final Comparator sComparator = new Comparator() {
@Override
public int compare(PageRange lhs, PageRange rhs) {
return lhs.getStart() - rhs.getStart();
}
};
private PageRangeUtils() {
throw new UnsupportedOperationException();
}
public static boolean contains(PageRange[] ourRanges, PageRange[] otherRanges) {
if (ourRanges == null || otherRanges == null) {
return false;
}
if (ourRanges.length == 1
&& PageRange.ALL_PAGES.equals(ourRanges[0])) {
return true;
}
ourRanges = normalize(ourRanges);
otherRanges = normalize(otherRanges);
// Note that the code below relies on the ranges being normalized
// which is they contain monotonically increasing non-intersecting
// subranges whose start is less that or equal to the end.
int otherRangeIdx = 0;
final int ourRangeCount = ourRanges.length;
final int otherRangeCount = otherRanges.length;
for (int ourRangeIdx = 0; ourRangeIdx < ourRangeCount; ourRangeIdx++) {
PageRange ourRange = ourRanges[ourRangeIdx];
for (; otherRangeIdx < otherRangeCount; otherRangeIdx++) {
PageRange otherRange = otherRanges[otherRangeIdx];
if (otherRange.getStart() > ourRange.getEnd()) {
break;
}
if (otherRange.getStart() < ourRange.getStart()
|| otherRange.getEnd() > ourRange.getEnd()) {
return false;
}
}
}
if (otherRangeIdx < otherRangeCount) {
return false;
}
return true;
}
public static PageRange[] normalize(PageRange[] pageRanges) {
if (pageRanges == null) {
return null;
}
final int oldRangeCount = pageRanges.length;
if (oldRangeCount <= 1) {
return pageRanges;
}
Arrays.sort(pageRanges, sComparator);
int newRangeCount = 1;
for (int i = 0; i < oldRangeCount - 1; i++) {
newRangeCount++;
PageRange currentRange = pageRanges[i];
PageRange nextRange = pageRanges[i + 1];
if (currentRange.getEnd() + 1 >= nextRange.getStart()) {
newRangeCount--;
pageRanges[i] = null;
pageRanges[i + 1] = new PageRange(currentRange.getStart(),
Math.max(currentRange.getEnd(), nextRange.getEnd()));
}
}
if (newRangeCount == oldRangeCount) {
return pageRanges;
}
return Arrays.copyOfRange(pageRanges, oldRangeCount - newRangeCount,
oldRangeCount);
}
public static void offset(PageRange[] pageRanges, int offset) {
if (offset == 0) {
return;
}
final int pageRangeCount = pageRanges.length;
for (int i = 0; i < pageRangeCount; i++) {
final int start = pageRanges[i].getStart() + offset;
final int end = pageRanges[i].getEnd() + offset;
pageRanges[i] = new PageRange(start, end);
}
}
}
private static final class PrintSpoolerProvider implements ServiceConnection {
private final Context mContext;
private final Runnable mCallback;
private PrintSpoolerService mSpooler;
public PrintSpoolerProvider(Context context, Runnable callback) {
mContext = context;
mCallback = callback;
Intent intent = new Intent(mContext, PrintSpoolerService.class);
mContext.bindService(intent, this, 0);
}
public PrintSpoolerService getSpooler() {
return mSpooler;
}
public void destroy() {
if (mSpooler != null) {
mContext.unbindService(this);
}
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mSpooler = ((PrintSpoolerService.PrintSpooler) service).getService();
if (mSpooler != null) {
mCallback.run();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
/* do noting - we are in the same process */
}
}
private static final class PrintDocumentAdapterObserver
extends IPrintDocumentAdapterObserver.Stub {
private final WeakReference mWeakService;
public PrintDocumentAdapterObserver(PrintJobService service) {
mWeakService = new WeakReference(service);
}
@Override
public void onDestroy() {
final PrintJobService service = mWeakService.get();
if (service != null) {
service.mController.mHandler.post(new Runnable() {
@Override
public void run() {
if (service.mController != null) {
service.mController.cancel();
}
if (service.mEditor != null) {
service.mEditor.cancel();
}
service.finish();
}
});
}
}
}
}
```
接下来的实现主要在onStartCommand,这个实现主要是跟之前的onCreate方法大概相同,接下来是mEditor.postCreate方法,这里实现方式跟之前大概相同,只是没有了查找打印机流程,这里就不细说了,大家自己看吧,注意这些流程是在Android4.4实现的,其他版本没试过,应该大概相似吧。