Android自定义后台打印服务

接前一篇文章(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>() {
        @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目录导进去了,接下来我们全文搜索一下这个字符串.


```
在这里插入代码片
<activity
        android: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() {
            @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<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);

        // Orientation
        mOrientationSpinnerAdapter = 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 options
        mRangeOptionsSpinnerAdapter = 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);
 }
 @Override
        public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
            if (id == LOADER_ID_PRINTERS_LOADER) {
                return new FusedPrintersProvider(PrintJobConfigActivity.this);
            }
            return null;
        }

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

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

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

    @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<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 printer
                PrinterRecord record = recordMap.get(printer.getId());
                if (record == null) {
                    record = new PrinterRecord(printer);
                    recordMap.put(printer.getId(), record);
                }
                record.weight += currentWeight;
                currentWeight *= WEIGHT_DECAY_COEFFICIENT;
            }

            // Soft the favorite printers.
            List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>(
                    recordMap.values());
            Collections.sort(favoriteRecords);

            // Write the favorites to the output.
            final int favoriteCount = 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;
            }

            @Override
            public int compareTo(PrinterRecord another) {
                return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
            }
        }

        private final class ReadTask extends AsyncTask<Void, Void, List<PrinterInfo>> {
            @Override
            protected List<PrinterInfo> doInBackground(Void... args) {
                return doReadPrinterHistory();
            }

            @Override
            protected void onPostExecute(List<PrinterInfo> printers) {
                if (DEBUG) {
                    Log.i(LOG_TAG, "read history completed "
                            + 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 cancellation
                    if (isCancelled()) {
                        return;
                    }
                    parser.next();
                }

                skipEmptyTextTags(parser);
                expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);
            }

            private boolean parsePrinter(XmlPullParser parser, List<PrinterInfo> outPrinters)
                    throws IOException, XmlPullParserException {
                skipEmptyTextTags(parser);
                if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) {
                    return false;
                }

                String name = parser.getAttributeValue(null, ATTR_NAME);
                String description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
                final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS));

                parser.next();

                skipEmptyTextTags(parser);
                expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID);
                String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
                ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
                        null, ATTR_SERVICE_NAME));
                PrinterId printerId =  new PrinterId(service, localId);
                parser.next();
                skipEmptyTextTags(parser);
                expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
                parser.next();

                PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status);
                builder.setDescription(description);
                PrinterInfo printer = builder.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> {
            @Override
            protected Void doInBackground(List<PrinterInfo>... printers) {
                doWritePrinterHistory(printers[0]);
                return null;
            }

            private void doWritePrinterHistory(List<PrinterInfo> printers) {
                FileOutputStream out = null;
                try {
                    out = mStatePersistFile.startWrite();

                    XmlSerializer serializer = new FastXmlSerializer();
                    serializer.setOutput(out, "utf-8");
                    serializer.startDocument(null, true);
                    serializer.startTag(null, TAG_PRINTERS);

                    final int printerCount = printers.size();
                    for (int i = 0; i < printerCount; i++) {
                        PrinterInfo printer = printers.get(i);

                        serializer.startTag(null, TAG_PRINTER);

                        serializer.attribute(null, ATTR_NAME, printer.getName());
                        // 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中注册


```
在这里插入代码片
<service
        android: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() {
        @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<PrintController.ControllerHandler> mWeakHandler;

        public LayoutResultCallback(PrintController.ControllerHandler handler) {
            mWeakHandler = new WeakReference<PrintController.ControllerHandler>(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<PrintController.ControllerHandler> mWeakHandler;

        public WriteResultCallback(PrintController.ControllerHandler handler) {
            mWeakHandler = new WeakReference<PrintController.ControllerHandler>(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<Void, Void, Void>() {
            @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<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);
            }

            // 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<PageRange> sComparator = new Comparator<PageRange>() {
            @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<PrintJobService> mWeakService;

        public PrintDocumentAdapterObserver(PrintJobService service) {
            mWeakService = new WeakReference<PrintJobService>(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实现的,其他版本没试过,应该大概相似吧。
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
Android提供了打印服务的API,可以通过自定义打印服务来实现特定的打印功能。要实现自定义打印服务,可以按照以下步骤进行操作: 1. 创建一个继承自PrintService的类,该类将作为自定义打印服务的主要入口点。在这个类中,你可以实现打印任务的管理和处理逻辑。 2. 在AndroidManifest.xml文件中注册自定义打印服务。在<application>标签内添加一个<service>标签,并指定android:name属性为你创建的自定义打印服务类的完整路径。 3. 在自定义打印服务类中,你可以重写onCreatePrinterDiscoverySession()方法来创建打印发现会话。在这个方法中,你可以添加打印机发现逻辑,以便用户可以选择可用的打印机。 4. 在自定义打印服务类中,你可以重写onPrintJobQueued(PrintJob printJob)方法来处理打印任务。在这个方法中,你可以获取打印任务的相关信息,并执行打印操作。 5. 在自定义打印服务类中,你可以重写onRequestCancelPrintJob(PrintJob printJob)方法来处理取消打印任务的请求。在这个方法中,你可以取消正在进行的打印任务。 6. 在自定义打印服务类中,你可以重写onPrintJobQueued(PrintJob printJob)方法来处理打印任务的状态变化。在这个方法中,你可以更新打印任务的状态,并通知用户打印进度。 以上是实现自定义打印服务的基本步骤。你可以根据具体需求进行扩展和定制。\[1\] 另外,如果你想在Android Gradle项目中使用自定义打印服务,可以按照以下步骤进行操作: 1. 创建一个Groovy文件,例如ClickPlugin.groovy,并在其中编写自定义打印服务的代码。你可以在这个文件中实现自定义打印服务的逻辑。 2. 在build.gradle文件中添加groovy插件和java插件的依赖。在plugins部分添加id 'groovy'和id 'java'。 3. 在repositories部分添加google()和mavenCentral(),以便获取所需的依赖。 4. 在apply plugin部分添加'maven-publish'插件,以便发布自定义打印服务。 5. 在publishing部分配置发布信息,包括groupId、artifactId和version等。 6. 在dependencies部分添加gradleApi()依赖,以便使用Gradle API。 通过以上步骤,你可以在Android Gradle项目中使用自定义打印服务。\[2\]\[3\] #### 引用[.reference_title] - *1* [Android自定义后台打印服务](https://blog.csdn.net/yan1348/article/details/90694730)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Android 自定义gradle插件](https://blog.csdn.net/l506945024/article/details/123870008)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Android 自定义插件](https://blog.csdn.net/sinat_41268473/article/details/127183982)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值