/* * 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 foo.bar.printservice; import android.annotation.NonNull; import android.app.PendingIntent; import android.content.Intent; import android.graphics.BitmapFactory; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.print.PrintAttributes; import android.print.PrintAttributes.Margins; import android.print.PrintAttributes.MediaSize; import android.print.PrintAttributes.Resolution; import android.print.PrintJobId; import android.print.PrinterCapabilitiesInfo; import android.print.PrinterId; import android.print.PrinterInfo; import android.printservice.CustomPrinterIconCallback; import android.printservice.PrintJob; import android.printservice.PrintService; import android.printservice.PrinterDiscoverySession; import android.util.ArrayMap; import android.util.Log; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; public class MyPrintService extends PrintService { private static final String LOG_TAG = "MyPrintService"; private static final long STANDARD_DELAY_MILLIS = 10000000; static final String INTENT_EXTRA_ACTION_TYPE = "INTENT_EXTRA_ACTION_TYPE"; static final String INTENT_EXTRA_PRINT_JOB_ID = "INTENT_EXTRA_PRINT_JOB_ID"; static final int ACTION_TYPE_ON_PRINT_JOB_PENDING = 1; static final int ACTION_TYPE_ON_REQUEST_CANCEL_PRINT_JOB = 2; private static final Object sLock = new Object(); private static MyPrintService sInstance; private Handler mHandler; private AsyncTask mFakePrintTask; private FakePrinterDiscoverySession mSession; private final Map mProcessedPrintJobs = new ArrayMap(); public static MyPrintService peekInstance() { synchronized (sLock) { return sInstance; } } @Override protected void onConnected() { Log.i(LOG_TAG, "#onConnected()"); mHandler = new MyHandler(getMainLooper()); synchronized (sLock) { sInstance = this; } } @Override protected void onDisconnected() { Log.i(LOG_TAG, "#onDisconnected()"); if (mSession != null) { mSession.cancellAddingFakePrinters(); } synchronized (sLock) { sInstance = null; } } @Override protected PrinterDiscoverySession onCreatePrinterDiscoverySession() { Log.i(LOG_TAG, "#onCreatePrinterDiscoverySession()"); return new FakePrinterDiscoverySession(); } @Override protected void onRequestCancelPrintJob(final PrintJob printJob) { Log.i(LOG_TAG, "#onRequestCancelPrintJob()"); mProcessedPrintJobs.put(printJob.getId(), printJob); Intent intent = new Intent(this, MyDialogActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(INTENT_EXTRA_PRINT_JOB_ID, printJob.getId()); intent.putExtra(INTENT_EXTRA_ACTION_TYPE, ACTION_TYPE_ON_REQUEST_CANCEL_PRINT_JOB); startActivity(intent); } @Override public void onPrintJobQueued(final PrintJob printJob) { Log.i(LOG_TAG, "#onPrintJobQueued()"); mProcessedPrintJobs.put(printJob.getId(), printJob); if (printJob.isQueued()) { printJob.start(); } Intent intent = new Intent(this, MyDialogActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(INTENT_EXTRA_PRINT_JOB_ID, printJob.getId()); intent.putExtra(INTENT_EXTRA_ACTION_TYPE, ACTION_TYPE_ON_PRINT_JOB_PENDING); startActivity(intent); } void handleRequestCancelPrintJob(PrintJobId printJobId) { PrintJob printJob = mProcessedPrintJobs.get(printJobId); if (printJob == null) { return; } mProcessedPrintJobs.remove(printJobId); if (printJob.isQueued() || printJob.isStarted() || printJob.isBlocked()) { mHandler.removeMessages(MyHandler.MSG_HANDLE_DO_PRINT_JOB); mHandler.removeMessages(MyHandler.MSG_HANDLE_FAIL_PRINT_JOB); printJob.cancel(); } } void handleFailPrintJobDelayed(PrintJobId printJobId) { Message message = mHandler.obtainMessage( MyHandler.MSG_HANDLE_FAIL_PRINT_JOB, printJobId); mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS); } void handleFailPrintJob(PrintJobId printJobId) { PrintJob printJob = mProcessedPrintJobs.get(printJobId); if (printJob == null) { return; } mProcessedPrintJobs.remove(printJobId); if (printJob.isQueued() || printJob.isStarted()) { printJob.fail(getString(R.string.fail_reason)); } } void handleBlockPrintJobDelayed(PrintJobId printJobId) { Message message = mHandler.obtainMessage( MyHandler.MSG_HANDLE_BLOCK_PRINT_JOB, printJobId); mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS); } void handleBlockPrintJob(PrintJobId printJobId) { final PrintJob printJob = mProcessedPrintJobs.get(printJobId); if (printJob == null) { return; } if (printJob.isStarted()) { printJob.block("Gimme some rest, dude"); } } void handleBlockAndDelayedUnblockPrintJob(PrintJobId printJobId) { handleBlockPrintJob(printJobId); Message message = mHandler.obtainMessage( MyHandler.MSG_HANDLE_UNBLOCK_PRINT_JOB, printJobId); mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS); } void handleUnblockPrintJob(PrintJobId printJobId) { final PrintJob printJob = mProcessedPrintJobs.get(printJobId); if (printJob == null) { return; } if (printJob.isBlocked()) { printJob.start(); } } void handleQueuedPrintJobDelayed(PrintJobId printJobId) { final PrintJob printJob = mProcessedPrintJobs.get(printJobId); if (printJob == null) { return; } if (printJob.isQueued()) { printJob.start(); } Message message = mHandler.obtainMessage( MyHandler.MSG_HANDLE_DO_PRINT_JOB, printJobId); mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS); } /** * Pretend that the print job has progressed. * * @param printJobId ID of the print job to progress * @param progress the new value to progress to */ void handlePrintJobProgress(@NonNull PrintJobId printJobId, int progress) { final PrintJob printJob = mProcessedPrintJobs.get(printJobId); if (printJob == null) { return; } if (printJob.isQueued()) { printJob.start(); } if (progress == 100) { handleQueuedPrintJob(printJobId); } else { printJob.setProgress((float)progress / 100); printJob.setStatus("Printing progress: " + progress + "%"); Message message = mHandler.obtainMessage( MyHandler.MSG_HANDLE_PRINT_JOB_PROGRESS, progress + 10, 0, printJobId); mHandler.sendMessageDelayed(message, 1000); } } void handleQueuedPrintJob(PrintJobId printJobId) { final PrintJob printJob = mProcessedPrintJobs.get(printJobId); if (printJob == null) { return; } if (printJob.isQueued()) { printJob.start(); } try { final File file = File.createTempFile(this.getClass().getSimpleName(), ".pdf", getFilesDir()); mFakePrintTask = new AsyncTask() { @Override protected Void doInBackground(ParcelFileDescriptor... params) { InputStream in = new BufferedInputStream(new FileInputStream( params[0].getFileDescriptor())); OutputStream out = null; try { out = new BufferedOutputStream(new FileOutputStream(file)); final byte[] buffer = new byte[8192]; while (true) { if (isCancelled()) { break; } final int readByteCount = in.read(buffer); if (readByteCount < 0) { break; } out.write(buffer, 0, readByteCount); } } catch (IOException ioe) { throw new RuntimeException(ioe); } finally { if (in != null) { try { in.close(); } catch (IOException ioe) { /* ignore */ } } if (out != null) { try { out.close(); } catch (IOException ioe) { /* ignore */ } } if (isCancelled()) { file.delete(); } } return null; } @Override protected void onPostExecute(Void result) { if (printJob.isStarted()) { printJob.complete(); } file.setReadable(true, false); // Quick and dirty to show the file - use a content provider instead. Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file), "application/pdf"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent, null); mFakePrintTask = null; } @Override protected void onCancelled(Void result) { if (printJob.isStarted()) { printJob.cancel(); } } }; mFakePrintTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, printJob.getDocument().getData()); } catch (IOException e) { Log.e(LOG_TAG, "Could not create temporary file: %s", e); return; } } private final class MyHandler extends Handler { public static final int MSG_HANDLE_DO_PRINT_JOB = 1; public static final int MSG_HANDLE_FAIL_PRINT_JOB = 2; public static final int MSG_HANDLE_BLOCK_PRINT_JOB = 3; public static final int MSG_HANDLE_UNBLOCK_PRINT_JOB = 4; public static final int MSG_HANDLE_PRINT_JOB_PROGRESS = 5; public MyHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message message) { switch (message.what) { case MSG_HANDLE_DO_PRINT_JOB: { PrintJobId printJobId = (PrintJobId) message.obj; handleQueuedPrintJob(printJobId); } break; case MSG_HANDLE_FAIL_PRINT_JOB: { PrintJobId printJobId = (PrintJobId) message.obj; handleFailPrintJob(printJobId); } break; case MSG_HANDLE_BLOCK_PRINT_JOB: { PrintJobId printJobId = (PrintJobId) message.obj; handleBlockPrintJob(printJobId); } break; case MSG_HANDLE_UNBLOCK_PRINT_JOB: { PrintJobId printJobId = (PrintJobId) message.obj; handleUnblockPrintJob(printJobId); } break; case MSG_HANDLE_PRINT_JOB_PROGRESS: { PrintJobId printJobId = (PrintJobId) message.obj; handlePrintJobProgress(printJobId, message.arg1); } break; } } } private final class FakePrinterDiscoverySession extends PrinterDiscoverySession { private final Handler mSesionHandler = new SessionHandler(getMainLooper()); private final List mFakePrinters = new ArrayList(); public FakePrinterDiscoverySession() { for (int i = 0; i < 1; i++) { String name = "Printer " + i; Intent infoIntent = new Intent(MyPrintService.this, InfoActivity.class); infoIntent.putExtra(InfoActivity.PRINTER_NAME, name); PendingIntent infoPendingIntent = PendingIntent.getActivity(getApplicationContext(), i, infoIntent, PendingIntent.FLAG_UPDATE_CURRENT); PrinterInfo.Builder builder = new PrinterInfo.Builder(generatePrinterId(name), name, (i % 2 == 1) ? PrinterInfo.STATUS_UNAVAILABLE : PrinterInfo.STATUS_IDLE) .setDescription("Launch a menu to select behavior.") .setIconResourceId(R.drawable.printer) .setHasCustomPrinterIcon() .setInfoIntent(infoPendingIntent); mFakePrinters.add(builder.build()); } } @Override public void onDestroy() { Log.i(LOG_TAG, "FakePrinterDiscoverySession#onDestroy()"); mSesionHandler.removeMessages(SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS); } @Override public void onStartPrinterDiscovery(List priorityList) { Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStartPrinterDiscovery()"); Message message1 = mSesionHandler.obtainMessage( SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS, this); mSesionHandler.sendMessageDelayed(message1, 0); } @Override public void onStopPrinterDiscovery() { Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStopPrinterDiscovery()"); cancellAddingFakePrinters(); } @Override public void onStartPrinterStateTracking(PrinterId printerId) { Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStartPrinterStateTracking()"); final int printerCount = mFakePrinters.size(); for (int i = printerCount - 1; i >= 0; i--) { PrinterInfo printer = mFakePrinters.remove(i); if (printer.getId().equals(printerId)) { PrinterCapabilitiesInfo capabilities = new PrinterCapabilitiesInfo.Builder( printerId) .setMinMargins(new Margins(200, 200, 200, 200)) .addMediaSize(MediaSize.ISO_A4, true) .addMediaSize(MediaSize.NA_GOVT_LETTER, false) .addMediaSize(MediaSize.JPN_YOU4, false) .addResolution(new Resolution("R1", getString( R.string.resolution_200x200), 200, 200), false) .addResolution(new Resolution("R2", getString( R.string.resolution_300x300), 300, 300), true) .setColorModes(PrintAttributes.COLOR_MODE_COLOR | PrintAttributes.COLOR_MODE_MONOCHROME, PrintAttributes.COLOR_MODE_MONOCHROME) .setDuplexModes(PrintAttributes.DUPLEX_MODE_LONG_EDGE | PrintAttributes.DUPLEX_MODE_NONE, PrintAttributes.DUPLEX_MODE_LONG_EDGE) .build(); printer = new PrinterInfo.Builder(printer) .setCapabilities(capabilities) .build(); } mFakePrinters.add(printer); } addPrinters(mFakePrinters); } @Override public void onRequestCustomPrinterIcon(final PrinterId printerId, final CustomPrinterIconCallback callbacks) { Log.i(LOG_TAG, "FakePrinterDiscoverySession#onRequestCustomPrinterIcon() " + printerId); // Pretend the bitmap icon takes 5 seconds to load mSesionHandler.postDelayed(new Runnable() { @Override public void run() { final int printerCount = mFakePrinters.size(); for (int i = printerCount - 1; i >= 0; i--) { callbacks.onCustomPrinterIconLoaded(Icon.createWithBitmap( BitmapFactory.decodeResource(getResources(), R.raw.red_printer))); } } }, 5000); } @Override public void onValidatePrinters(List printerIds) { Log.i(LOG_TAG, "FakePrinterDiscoverySession#onValidatePrinters() " + printerIds); } @Override public void onStopPrinterStateTracking(PrinterId printerId) { Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStopPrinterStateTracking()"); } private void addFirstBatchFakePrinters() { List printers = mFakePrinters.subList(0, mFakePrinters.size()); addPrinters(printers); } private void cancellAddingFakePrinters() { mSesionHandler.removeMessages(SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS); } final class SessionHandler extends Handler { public static final int MSG_ADD_FIRST_BATCH_FAKE_PRINTERS = 1; public SessionHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message message) { switch (message.what) { case MSG_ADD_FIRST_BATCH_FAKE_PRINTERS: { addFirstBatchFakePrinters(); } break; } } } } }