接前一篇文章(Android调用系统默认打印机并反射获取打印任务状态 https://blog.csdn.net/yan1348/article/details/90666657)所说,完全按照系统默认的打印流程是有缺陷的,所以我在这里又实现了一个自定义服务来实现后台打印的。
首先,我们先来看看PrintManager.print方法到底做了什么动作,先看看源码```
在这里插入代码片
/*** Creates a print job for printing a {@link PrintDocumentAdapter} with* default print attributes.* <p>* 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.* <p>* 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.* </p>* <p>* 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.* </p>* <p>* 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.* </p>* <p>* <strong>Note:</strong> 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.* </p>** @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 <code>null</code>.* @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<Void, Void, Void>() {@Overrideprotected 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目录导进去了,接下来我们全文搜索一下这个字符串.```
在这里插入代码片
<activityandroid:name=".PrintJobConfigActivity"android:configChanges="orientation|screenSize"android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE"android:theme="@style/PrintJobConfigActivityTheme"><intent-filter><action android:name="android.print.PRINT_DIALOG" /><category android:name="android.intent.category.DEFAULT" /><data android:scheme="printjob" android:pathPattern="*" /></intent-filter></activity>
```
我们可以找到,在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() {@Overridepublic 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 printersif (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;}}}}@Overridepublic void onInvalidated() {/* do nothing - we always have one fake PDF printer */}});// Media size.mMediaSizeSpinnerAdapter = new ArrayAdapter<SpinnerItem<MediaSize>>(PrintJobConfigActivity.this,R.layout.spinner_dropdown_item, R.id.title);// Color mode.mColorModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(PrintJobConfigActivity.this,R.layout.spinner_dropdown_item, R.id.title);// OrientationmOrientationSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(PrintJobConfigActivity.this,R.layout.spinner_dropdown_item, R.id.title);String[] orientationLabels = getResources().getStringArray(R.array.orientation_labels);mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>(ORIENTATION_PORTRAIT, orientationLabels[0]));mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>(ORIENTATION_LANDSCAPE, orientationLabels[1]));// Range optionsmRangeOptionsSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(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<Integer>(rangeOptionsValues[i], rangeOptionsLabels[i]));}showUi(UI_EDITING_PRINT_JOB, null);bindUi();updateUi();}```
接下来看看DestinationAdapter,在这里我们主要看看这几个方法。```
在这里插入代码片
public DestinationAdapter() {getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);}@Overridepublic Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {if (id == LOADER_ID_PRINTERS_LOADER) {return new FusedPrintersProvider(PrintJobConfigActivity.this);}return null;}@Overridepublic void onLoadFinished(Loader<List<PrinterInfo>> loader,List<PrinterInfo> 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<length;i++){Log.e("PrintJobConfigActivity", "[ymy]print_index="+i+","+printers.get(i).toString());}// We rearrange the printers if the user selects a printer// not shown in the initial short list. Therefore, we have// to keep the printer order.// No old printers - do not bother keeping their position.if (mPrinters.isEmpty()) {mPrinters.addAll(printers);mEditor.ensureCurrentPrinterSelected();notifyDataSetChanged();return;}// Add the new printers to a map.ArrayMap<PrinterId, PrinterInfo> newPrintersMap =new ArrayMap<PrinterId, PrinterInfo>();final int printerCount = printers.size();for (int i = 0; i < printerCount; i++) {PrinterInfo printer = printers.get(i);newPrintersMap.put(printer.getId(), printer);}List<PrinterInfo> newPrinters = new ArrayList<PrinterInfo>();// 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();}@Overridepublic void onLoaderReset(Loader<List<PrinterInfo>> 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<PrinterInfo> printers){for(int i = 0,length = printers.size();i<length;i++){PrinterInfo printer = printers.get(i);Log.e("PrintManager", "[ymy]receivePrinters--print_index="+i+","+printer.toString());if(printer.getName().startsWith("L805") && printer.getStatus() == PrinterInfo.STATUS_IDLE && null != printer.getCapabilities() && !isPrint){loader.addHistoricalPrinter(printer);Intent intent = new Intent("android.print.PRINT_JOB_SERVICE");result.putParcelable(KEY_READY_PRINTER, (Parcelable)printer);result.putBinder(EXTRA_PRINT_DOCUMENT_ADAPTER,delegate.asBinder());intent.putExtras(result);mContext.startService(intent);isPrint = true;Log.e(LOG_TAG,"startService");return;}else if(printer.getName().startsWith("L805")){loader.refreshPrinters(printer.getId());}}}});}return new PrintJob(printJob, this);} catch (Exception sie) {Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);}}} catch (RemoteException re) {Log.e(LOG_TAG, "Error creating a print job", re);}return null;
}
```
这我们主要流程是先查找打印机,再将找到的打印机传递到service中,这里我们自定义一个PrintLoader类用来实现查找打印机的功能,
下面是PrinterLoader.java```
在这里插入代码片
package android.print;

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<List>{
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<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {if (id == LOADER_ID_PRINTERS_LOADER) {return new PrintersProvider(activity);}return null;
}@Override
public void onLoadFinished(Loader<List<PrinterInfo>> loader, List<PrinterInfo> printers) {for(int i = 0,length = printers.size();i<length;i++){Log.e("PrinterLoader", "[ymy]print_index="+i+","+printers.get(i).toString());}if (mPrinters.isEmpty()) {callBack.receivePrinters(printers);return;}// Add the new printers to a map.ArrayMap<PrinterId, PrinterInfo> newPrintersMap =new ArrayMap<PrinterId, PrinterInfo>();final int printerCount = printers.size();for (int i = 0; i < printerCount; i++) {PrinterInfo printer = printers.get(i);newPrintersMap.put(printer.getId(), printer);}List<PrinterInfo> newPrinters = new ArrayList<PrinterInfo>();// 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<List<PrinterInfo>> 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<List<PrinterInfo>> {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<PrinterInfo> mPrinters =new ArrayList<PrinterInfo>();private final List<PrinterInfo> mFavoritePrinters =new ArrayList<PrinterInfo>();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<PrinterId, PrinterInfo> discoveredPrinters,ArrayMap<PrinterId, PrinterInfo> favoritePrinters) {List<PrinterInfo> printers = new ArrayList<PrinterInfo>();// 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();}}@Overrideprotected 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<PrinterInfo>(mPrinters));}// Always load the data to ensure discovery period is// started and to make sure obsolete printers are updated.onForceLoad();}@Overrideprotected void onStopLoading() {if (DEBUG) {Log.i(LOG_TAG, "onStopLoading() " + PrintersProvider.this.hashCode());}onCancelLoad();}@Overrideprotected 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() {@Overridepublic 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<PrinterId> printerIds = new ArrayList<PrinterId>(favoriteCount);for (int i = 0; i < favoriteCount; i++) {printerIds.add(mFavoritePrinters.get(i).getId());}mDiscoverySession.startPrinterDisovery(printerIds);List<PrinterInfo> printers = mDiscoverySession.getPrinters();if (!printers.isEmpty()) {updatePrinters(printers, mFavoritePrinters);}}}private void updatePrinters(List<PrinterInfo> printers, List<PrinterInfo> favoritePrinters) {for(int i = 0,length = printers.size();i<length;i++){Log.i(LOG_TAG, "[ymy]updatePrinters--printers="+i+","+printers.get(i).toString());}for(int i = 0,length = favoritePrinters.size();i<length;i++){Log.i(LOG_TAG, "[ymy]updatePrinters--favoritePrinters="+i+","+favoritePrinters.get(i).toString());}if (mPrintersUpdatedBefore && mPrinters.equals(printers)&& mFavoritePrinters.equals(favoritePrinters)) {return;}mPrintersUpdatedBefore = true;ArrayMap<PrinterId, PrinterInfo> printersMap =new ArrayMap<PrinterId, PrinterInfo>();final int printerCount = printers.size();for (int i = 0; i < printerCount; i++) {PrinterInfo printer = printers.get(i);printersMap.put(printer.getId(), printer);}ArrayMap<PrinterId, PrinterInfo> favoritePrintersMap =new ArrayMap<PrinterId, PrinterInfo>();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);}@Overrideprotected 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;}@Overrideprotected void onReset() {if (DEBUG) {Log.i(LOG_TAG, "onReset() " + PrintersProvider.this.hashCode());}onStopLoading();mPrinters.clear();if (mDiscoverySession != null) {mDiscoverySession.destroy();mDiscoverySession = null;}}@Overrideprotected 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<PrinterInfo> 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<PrinterInfo>();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<PrinterInfo> mHistoricalPrinters = new ArrayList<PrinterInfo>();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<PrinterInfo>(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<PrinterInfo>(mHistoricalPrinters));}}public boolean isHistoryChanged() {return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified();}private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) {Map<PrinterId, PrinterRecord> recordMap =new ArrayMap<PrinterId, PrinterRecord>();// Recompute the weights.float currentWeight = 1.0f;final int printerCount = printers.size();for (int i = printerCount - 1; i >= 0; i--) {PrinterInfo printer = printers.get(i);// Aggregate weight for the same printerPrinterRecord record = recordMap.get(printer.getId());if (record == null) {record = new PrinterRecord(printer);recordMap.put(printer.getId(), record);}record.weight += currentWeight;currentWeight *= WEIGHT_DECAY_COEFFICIENT;}// Soft the favorite printers.List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>(recordMap.values());Collections.sort(favoriteRecords);// Write the favorites to the output.final int favoriteCount = Math.min(favoriteRecords.size(),MAX_FAVORITE_PRINTER_COUNT);List<PrinterInfo> favoritePrinters = new ArrayList<PrinterInfo>(favoriteCount);for (int i = 0; i < favoriteCount; i++) {PrinterInfo printer = favoriteRecords.get(i).printer;favoritePrinters.add(printer);}return favoritePrinters;}private final class PrinterRecord implements Comparable<PrinterRecord> {public final PrinterInfo printer;public float weight;public PrinterRecord(PrinterInfo printer) {this.printer = printer;}@Overridepublic int compareTo(PrinterRecord another) {return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);}}private final class ReadTask extends AsyncTask<Void, Void, List<PrinterInfo>> {@Overrideprotected List<PrinterInfo> doInBackground(Void... args) {return doReadPrinterHistory();}@Overrideprotected void onPostExecute(List<PrinterInfo> 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<length;i++){Log.i(LOG_TAG, "[ymy]print_index="+i+","+printers.get(i).toString());}// Ignore printer records whose target services are not enabled.PrintManager printManager = (PrintManager) getContext().getSystemService(Context.PRINT_SERVICE);List<PrintServiceInfo> services = printManager.getEnabledPrintServices();Set<ComponentName> enabledComponents = new ArraySet<ComponentName>();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<PrinterInfo> 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<PrinterInfo>();}try {List<PrinterInfo> printers = new ArrayList<PrinterInfo>();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<PrinterInfo> outPrinters)throws IOException, XmlPullParserException {parser.next();skipEmptyTextTags(parser);expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS);parser.next();while (parsePrinter(parser, outPrinters)) {// Be nice and respond to cancellationif (isCancelled()) {return;}parser.next();}skipEmptyTextTags(parser);expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);}private boolean parsePrinter(XmlPullParser parser, List<PrinterInfo> outPrinters)throws IOException, XmlPullParserException {skipEmptyTextTags(parser);if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) {return false;}String name = parser.getAttributeValue(null, ATTR_NAME);String description = parser.getAttributeValue(null, ATTR_DESCRIPTION);final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS));parser.next();skipEmptyTextTags(parser);expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID);String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(null, ATTR_SERVICE_NAME));PrinterId printerId =  new PrinterId(service, localId);parser.next();skipEmptyTextTags(parser);expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);parser.next();PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status);builder.setDescription(description);PrinterInfo printer = builder.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<List<PrinterInfo>, Void, Void> {@Overrideprotected Void doInBackground(List<PrinterInfo>... printers) {doWritePrinterHistory(printers[0]);return null;}private void doWritePrinterHistory(List<PrinterInfo> printers) {FileOutputStream out = null;try {out = mStatePersistFile.startWrite();XmlSerializer serializer = new FastXmlSerializer();serializer.setOutput(out, "utf-8");serializer.startDocument(null, true);serializer.startTag(null, TAG_PRINTERS);final int printerCount = printers.size();for (int i = 0; i < printerCount; i++) {PrinterInfo printer = printers.get(i);serializer.startTag(null, TAG_PRINTER);serializer.attribute(null, ATTR_NAME, printer.getName());// 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中注册```
在这里插入代码片
<serviceandroid:name=".PrintSpoolerService"android:exported="true"android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE"></service>
```
接下来我们来看看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() {@Overridepublic 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();}@Overridepublic IBinder onBind(Intent intent) {// TODO Auto-generated method stubreturn null;}/*** 服务启动的时候调用*/@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.e(LOG_TAG,"onStartCommand");// TODO Auto-generated method stubBundle 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() {@Overridepublic 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);}@Overridepublic 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);}@Overridepublic 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<PrintController.ControllerHandler> mWeakHandler;public LayoutResultCallback(PrintController.ControllerHandler handler) {mWeakHandler = new WeakReference<PrintController.ControllerHandler>(handler);}@Overridepublic 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();}}@Overridepublic 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<PrintController.ControllerHandler> mWeakHandler;public WriteResultCallback(PrintController.ControllerHandler handler) {mWeakHandler = new WeakReference<PrintController.ControllerHandler>(handler);}@Overridepublic 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();}}@Overridepublic 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<Void, Void, Void>() {@Overrideprotected 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;}@Overridepublic 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<MediaSize> sortedMediaSizes = new ArrayList<MediaSize>(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);}// ResolutionResolution 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(){@Overridepublic 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<PageRange> sComparator = new Comparator<PageRange>() {@Overridepublic 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);}}@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {mSpooler = ((PrintSpoolerService.PrintSpooler) service).getService();if (mSpooler != null) {mCallback.run();}}@Overridepublic void onServiceDisconnected(ComponentName name) {/* do noting - we are in the same process */}}private static final class PrintDocumentAdapterObserverextends IPrintDocumentAdapterObserver.Stub {private final WeakReference<PrintJobService> mWeakService;public PrintDocumentAdapterObserver(PrintJobService service) {mWeakService = new WeakReference<PrintJobService>(service);}@Overridepublic void onDestroy() {final PrintJobService service = mWeakService.get();if (service != null) {service.mController.mHandler.post(new Runnable() {@Overridepublic void run() {if (service.mController != null) {service.mController.cancel();}if (service.mEditor != null) {service.mEditor.cancel();}service.finish();}});}}}
}```
接下来的实现主要在onStartCommand,这个实现主要是跟之前的onCreate方法大概相同,接下来是mEditor.postCreate方法,这里实现方式跟之前大概相同,只是没有了查找打印机流程,这里就不细说了,大家自己看吧,注意这些流程是在Android4.4实现的,其他版本没试过,应该大概相似吧。

Android自定义后台打印服务相关推荐

  1. Android 系统(215)---Android O_GO后台启动服务改动

    Android O_GO后台启动服务改动 Android O_GO后台启动服务改动 1. 问题现象 应用在适配Android O/GO的系统时,会发现后台启动不了服务,会报出如下异常,并强退: jav ...

  2. android 后台服务拍照,Android实现后台开启服务默默拍照功能

    本文实例为大家分享了Android后台开启服务默默拍照的具体代码,供大家参考,具体内容如下 最近项目原因,需要编写一后台运行的程序,在给定时间间隔下进行拍照,关键技术主要是:1.开启服务:2.在不不预 ...

  3. android 后台自动拍照,Android实现后台开启服务默默拍照功能

    本文实例为大家分享了Android后台开启服务默默拍照的具体代码,供大家参考,具体内容如下 最近项目原因,需要编写一后台运行的程序,在给定时间间隔下进行拍照,关键技术主要是:1.开启服务:2.在不不预 ...

  4. 无法启动计算机打印机服务程序,安装驱动程序时电脑弹出错误窗口“无法启动Windows打印后台程序服务” (适用于Windows OS)...

    文档标题:安装驱动程序时电脑弹出错误窗口"无法启动Windows打印后台程序服务" (适用于Windows OS) 文档代码:CHN-FP0117-1 最近修改日期:2020年11 ...

  5. 打印后台程序服务没有运行的解决方法

    症状描述:在局域网中添加共享打印机或启动打印作业时,任务栏右下角提示"操作无法完成,打印后台程序服务没有运行",无法进行打印操作:类似问题是由于"Print Spoole ...

  6. 打印机后台程序服务没有运行该怎么办

    第1页:办法一:后台打印服务无法结束问题 [中关村在线办公打印频道原创]用户有时在安装使用打印产品时,电脑添加打印机时系统提示"打印机后台程序服务没有运行"或者在使用打印机打印时提 ...

  7. 打印服务的乱码故障处理

    这种方法一般适用于打印机长时间工作之后继续打印时出现的乱码故障,由于长时间工作后打印机的缓存中保存了不少垃圾任务,这些任务如果超过一定的数值,就会导致打印机反应迟钝或者反应出错,从而有可能引发打印乱码 ...

  8. ArcGIS打印服务PrintingTools解决方案总结

    打印服务若干问题汇总 ArcGIS打印服务若干问题 解决方案 打印报错 自签名证书导致的错误 打印服务获取时间较长,可能存在被阻塞的情况 可打印,但设置中文字体如微软雅黑后乱码 可打印,但对于缓存服务 ...

  9. 打印机后台程序服务没有运行

    办法一:后台打印服务无法结束问题 [中关村在线办公打印频道原创]用户有时在安装使用打印产品时,电脑添加打印机时系统提示"打印机后台程序服务没有运行"或者在使用打印机打印 时提示&q ...

最新文章

  1. 缺陷检测算法汇总(传统+深度学习方式)|综述、源码
  2. php过滤html标签截取部分内容
  3. 转:使用 PHP 直接在共享内存中存储数据集
  4. python stm32-python学习(一)
  5. Extjs 树菜单的自动展开数据的请求
  6. idea 升级到2020后 无法启动_启动崩盘!IDEA 2020 无法启动的解决办法|赠送 IDEA 2020 新功能...
  7. ElasticSearch入门 第一篇:Windows下安装ElasticSearch
  8. 2017/3/8 函数指针/事件/委托....
  9. 企业使用云计算低效益怎么办?区块链或成良药
  10. python使用sphinx自动生成文档
  11. 【Elasticsearch】es shard split 使用分析
  12. 基于JAVA+SpringMVC+Mybatis+MYSQL的考勤管理系统
  13. 一个可解释的植物胁迫表型的深度机器视觉框架(大豆叶片胁迫程度估算)
  14. 如何自建云存储平台?
  15. BZOJ 2821: 作诗(Poetize) [分块]
  16. [分块][离散化] Bzoj P2724 蒲公英
  17. SSM常用面试题整理一
  18. 【Python编程】《Python学习手册》思维导图
  19. 在任意文件夹下以管理员的身份运行powershell
  20. 360 ie8兼容模式 网页兼容问题

热门文章

  1. JQuery实现的后台框架(动易+Slashdot Menu)
  2. 计算机毕业设计:基于html制作大学生网上报到系统响应式模板项目源码
  3. 2020下半年,值得关注的10本机器学习、深度学习好书
  4. 使用火蜘蛛采集器Firespider采集天猫商品数据并上传到微店
  5. 程序员因违反《竞业协议》赔偿腾讯近百万
  6. 国产ARM核心板推荐
  7. 基于VB和AutoCAD的齿轮滚刀鉴定系统研究
  8. oracle请求输出全部都是fndwrr,[Alert]Oracle发出三个漏洞警报 两个被确定为最高级...
  9. error C2491: 不允许 dllimport 函数 的定义
  10. 腾讯云备案授权码常见问题及解决方法