MyPrintService.java revision c02ac3775cd61bba799dbf70a707507410b3ff13
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package foo.bar.printservice; 18 19import android.annotation.NonNull; 20import android.app.PendingIntent; 21import android.content.Intent; 22import android.graphics.BitmapFactory; 23import android.graphics.drawable.Icon; 24import android.net.Uri; 25import android.os.AsyncTask; 26import android.os.Handler; 27import android.os.Looper; 28import android.os.Message; 29import android.os.ParcelFileDescriptor; 30import android.print.PrintAttributes; 31import android.print.PrintAttributes.Margins; 32import android.print.PrintAttributes.MediaSize; 33import android.print.PrintAttributes.Resolution; 34import android.print.PrintJobId; 35import android.print.PrinterCapabilitiesInfo; 36import android.print.PrinterId; 37import android.print.PrinterInfo; 38import android.printservice.CustomPrinterIconCallback; 39import android.printservice.PrintJob; 40import android.printservice.PrintService; 41import android.printservice.PrinterDiscoverySession; 42import android.util.ArrayMap; 43import android.util.Log; 44 45import java.io.BufferedInputStream; 46import java.io.BufferedOutputStream; 47import java.io.File; 48import java.io.FileInputStream; 49import java.io.FileOutputStream; 50import java.io.IOException; 51import java.io.InputStream; 52import java.io.OutputStream; 53import java.util.ArrayList; 54import java.util.List; 55import java.util.Map; 56 57public class MyPrintService extends PrintService { 58 59 private static final String LOG_TAG = "MyPrintService"; 60 61 private static final long STANDARD_DELAY_MILLIS = 10000000; 62 63 static final String INTENT_EXTRA_ACTION_TYPE = "INTENT_EXTRA_ACTION_TYPE"; 64 static final String INTENT_EXTRA_PRINT_JOB_ID = "INTENT_EXTRA_PRINT_JOB_ID"; 65 66 static final int ACTION_TYPE_ON_PRINT_JOB_PENDING = 1; 67 static final int ACTION_TYPE_ON_REQUEST_CANCEL_PRINT_JOB = 2; 68 69 private static final Object sLock = new Object(); 70 71 private static MyPrintService sInstance; 72 73 private Handler mHandler; 74 75 private AsyncTask<ParcelFileDescriptor, Void, Void> mFakePrintTask; 76 77 private FakePrinterDiscoverySession mSession; 78 79 private final Map<PrintJobId, PrintJob> mProcessedPrintJobs = 80 new ArrayMap<PrintJobId, PrintJob>(); 81 82 public static MyPrintService peekInstance() { 83 synchronized (sLock) { 84 return sInstance; 85 } 86 } 87 88 @Override 89 protected void onConnected() { 90 Log.i(LOG_TAG, "#onConnected()"); 91 mHandler = new MyHandler(getMainLooper()); 92 synchronized (sLock) { 93 sInstance = this; 94 } 95 } 96 97 @Override 98 protected void onDisconnected() { 99 Log.i(LOG_TAG, "#onDisconnected()"); 100 if (mSession != null) { 101 mSession.cancellAddingFakePrinters(); 102 } 103 synchronized (sLock) { 104 sInstance = null; 105 } 106 } 107 108 @Override 109 protected PrinterDiscoverySession onCreatePrinterDiscoverySession() { 110 Log.i(LOG_TAG, "#onCreatePrinterDiscoverySession()"); 111 return new FakePrinterDiscoverySession(); 112 } 113 114 @Override 115 protected void onRequestCancelPrintJob(final PrintJob printJob) { 116 Log.i(LOG_TAG, "#onRequestCancelPrintJob()"); 117 mProcessedPrintJobs.put(printJob.getId(), printJob); 118 Intent intent = new Intent(this, MyDialogActivity.class); 119 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 120 intent.putExtra(INTENT_EXTRA_PRINT_JOB_ID, printJob.getId()); 121 intent.putExtra(INTENT_EXTRA_ACTION_TYPE, ACTION_TYPE_ON_REQUEST_CANCEL_PRINT_JOB); 122 startActivity(intent); 123 } 124 125 @Override 126 public void onPrintJobQueued(final PrintJob printJob) { 127 Log.i(LOG_TAG, "#onPrintJobQueued()"); 128 mProcessedPrintJobs.put(printJob.getId(), printJob); 129 if (printJob.isQueued()) { 130 printJob.start(); 131 } 132 133 Intent intent = new Intent(this, MyDialogActivity.class); 134 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 135 intent.putExtra(INTENT_EXTRA_PRINT_JOB_ID, printJob.getId()); 136 intent.putExtra(INTENT_EXTRA_ACTION_TYPE, ACTION_TYPE_ON_PRINT_JOB_PENDING); 137 startActivity(intent); 138 } 139 140 void handleRequestCancelPrintJob(PrintJobId printJobId) { 141 PrintJob printJob = mProcessedPrintJobs.get(printJobId); 142 if (printJob == null) { 143 return; 144 } 145 mProcessedPrintJobs.remove(printJobId); 146 if (printJob.isQueued() || printJob.isStarted() || printJob.isBlocked()) { 147 mHandler.removeMessages(MyHandler.MSG_HANDLE_DO_PRINT_JOB); 148 mHandler.removeMessages(MyHandler.MSG_HANDLE_FAIL_PRINT_JOB); 149 printJob.cancel(); 150 } 151 } 152 153 void handleFailPrintJobDelayed(PrintJobId printJobId) { 154 Message message = mHandler.obtainMessage( 155 MyHandler.MSG_HANDLE_FAIL_PRINT_JOB, printJobId); 156 mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS); 157 } 158 159 void handleFailPrintJob(PrintJobId printJobId) { 160 PrintJob printJob = mProcessedPrintJobs.get(printJobId); 161 if (printJob == null) { 162 return; 163 } 164 mProcessedPrintJobs.remove(printJobId); 165 if (printJob.isQueued() || printJob.isStarted()) { 166 printJob.fail(getString(R.string.fail_reason)); 167 } 168 } 169 170 void handleBlockPrintJobDelayed(PrintJobId printJobId) { 171 Message message = mHandler.obtainMessage( 172 MyHandler.MSG_HANDLE_BLOCK_PRINT_JOB, printJobId); 173 mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS); 174 } 175 176 void handleBlockPrintJob(PrintJobId printJobId) { 177 final PrintJob printJob = mProcessedPrintJobs.get(printJobId); 178 if (printJob == null) { 179 return; 180 } 181 182 if (printJob.isStarted()) { 183 printJob.block("Gimme some rest, dude"); 184 } 185 } 186 187 void handleBlockAndDelayedUnblockPrintJob(PrintJobId printJobId) { 188 handleBlockPrintJob(printJobId); 189 190 Message message = mHandler.obtainMessage( 191 MyHandler.MSG_HANDLE_UNBLOCK_PRINT_JOB, printJobId); 192 mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS); 193 } 194 195 void handleUnblockPrintJob(PrintJobId printJobId) { 196 final PrintJob printJob = mProcessedPrintJobs.get(printJobId); 197 if (printJob == null) { 198 return; 199 } 200 201 if (printJob.isBlocked()) { 202 printJob.start(); 203 } 204 } 205 206 void handleQueuedPrintJobDelayed(PrintJobId printJobId) { 207 final PrintJob printJob = mProcessedPrintJobs.get(printJobId); 208 if (printJob == null) { 209 return; 210 } 211 212 if (printJob.isQueued()) { 213 printJob.start(); 214 } 215 Message message = mHandler.obtainMessage( 216 MyHandler.MSG_HANDLE_DO_PRINT_JOB, printJobId); 217 mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS); 218 } 219 220 /** 221 * Pretend that the print job has progressed. 222 * 223 * @param printJobId ID of the print job to progress 224 * @param progress the new value to progress to 225 */ 226 void handlePrintJobProgress(@NonNull PrintJobId printJobId, int progress) { 227 final PrintJob printJob = mProcessedPrintJobs.get(printJobId); 228 if (printJob == null) { 229 return; 230 } 231 232 if (printJob.isQueued()) { 233 printJob.start(); 234 } 235 236 if (progress == 100) { 237 handleQueuedPrintJob(printJobId); 238 } else { 239 printJob.setProgress((float)progress / 100); 240 printJob.setStatus("Printing progress: " + progress + "%"); 241 242 Message message = mHandler.obtainMessage( 243 MyHandler.MSG_HANDLE_PRINT_JOB_PROGRESS, progress + 10, 0, printJobId); 244 mHandler.sendMessageDelayed(message, 1000); 245 } 246 } 247 248 void handleQueuedPrintJob(PrintJobId printJobId) { 249 final PrintJob printJob = mProcessedPrintJobs.get(printJobId); 250 if (printJob == null) { 251 return; 252 } 253 254 if (printJob.isQueued()) { 255 printJob.start(); 256 } 257 258 try { 259 final File file = File.createTempFile(this.getClass().getSimpleName(), ".pdf", 260 getFilesDir()); 261 mFakePrintTask = new AsyncTask<ParcelFileDescriptor, Void, Void>() { 262 @Override 263 protected Void doInBackground(ParcelFileDescriptor... params) { 264 InputStream in = new BufferedInputStream(new FileInputStream( 265 params[0].getFileDescriptor())); 266 OutputStream out = null; 267 try { 268 out = new BufferedOutputStream(new FileOutputStream(file)); 269 final byte[] buffer = new byte[8192]; 270 while (true) { 271 if (isCancelled()) { 272 break; 273 } 274 final int readByteCount = in.read(buffer); 275 if (readByteCount < 0) { 276 break; 277 } 278 out.write(buffer, 0, readByteCount); 279 } 280 } catch (IOException ioe) { 281 throw new RuntimeException(ioe); 282 } finally { 283 if (in != null) { 284 try { 285 in.close(); 286 } catch (IOException ioe) { 287 /* ignore */ 288 } 289 } 290 if (out != null) { 291 try { 292 out.close(); 293 } catch (IOException ioe) { 294 /* ignore */ 295 } 296 } 297 if (isCancelled()) { 298 file.delete(); 299 } 300 } 301 return null; 302 } 303 304 @Override 305 protected void onPostExecute(Void result) { 306 if (printJob.isStarted()) { 307 printJob.complete(); 308 } 309 310 file.setReadable(true, false); 311 312 // Quick and dirty to show the file - use a content provider instead. 313 Intent intent = new Intent(Intent.ACTION_VIEW); 314 intent.setDataAndType(Uri.fromFile(file), "application/pdf"); 315 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 316 startActivity(intent, null); 317 318 mFakePrintTask = null; 319 } 320 321 @Override 322 protected void onCancelled(Void result) { 323 if (printJob.isStarted()) { 324 printJob.cancel(); 325 } 326 } 327 }; 328 mFakePrintTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, 329 printJob.getDocument().getData()); 330 } catch (IOException e) { 331 Log.e(LOG_TAG, "Could not create temporary file: %s", e); 332 return; 333 } 334 } 335 336 private final class MyHandler extends Handler { 337 public static final int MSG_HANDLE_DO_PRINT_JOB = 1; 338 public static final int MSG_HANDLE_FAIL_PRINT_JOB = 2; 339 public static final int MSG_HANDLE_BLOCK_PRINT_JOB = 3; 340 public static final int MSG_HANDLE_UNBLOCK_PRINT_JOB = 4; 341 public static final int MSG_HANDLE_PRINT_JOB_PROGRESS = 5; 342 343 public MyHandler(Looper looper) { 344 super(looper); 345 } 346 347 @Override 348 public void handleMessage(Message message) { 349 switch (message.what) { 350 case MSG_HANDLE_DO_PRINT_JOB: { 351 PrintJobId printJobId = (PrintJobId) message.obj; 352 handleQueuedPrintJob(printJobId); 353 } break; 354 355 case MSG_HANDLE_FAIL_PRINT_JOB: { 356 PrintJobId printJobId = (PrintJobId) message.obj; 357 handleFailPrintJob(printJobId); 358 } break; 359 360 case MSG_HANDLE_BLOCK_PRINT_JOB: { 361 PrintJobId printJobId = (PrintJobId) message.obj; 362 handleBlockPrintJob(printJobId); 363 } break; 364 365 case MSG_HANDLE_UNBLOCK_PRINT_JOB: { 366 PrintJobId printJobId = (PrintJobId) message.obj; 367 handleUnblockPrintJob(printJobId); 368 } break; 369 370 case MSG_HANDLE_PRINT_JOB_PROGRESS: { 371 PrintJobId printJobId = (PrintJobId) message.obj; 372 handlePrintJobProgress(printJobId, message.arg1); 373 } break; 374 } 375 } 376 } 377 378 private final class FakePrinterDiscoverySession extends PrinterDiscoverySession { 379 private final Handler mSesionHandler = new SessionHandler(getMainLooper()); 380 381 private final List<PrinterInfo> mFakePrinters = new ArrayList<PrinterInfo>(); 382 383 public FakePrinterDiscoverySession() { 384 for (int i = 0; i < 6; i++) { 385 String name = "Printer " + i; 386 387 PrinterInfo.Builder builder = new PrinterInfo.Builder(generatePrinterId(name), name, 388 (i == 1 || i == 2) ? PrinterInfo.STATUS_UNAVAILABLE 389 : PrinterInfo.STATUS_IDLE); 390 391 if (i != 3) { 392 builder.setDescription("Launch a menu to select behavior."); 393 } 394 395 if (i != 4) { 396 builder.setIconResourceId(R.drawable.printer); 397 } 398 399 if (i % 2 == 0) { 400 Intent infoIntent = new Intent(MyPrintService.this, InfoActivity.class); 401 infoIntent.putExtra(InfoActivity.PRINTER_NAME, name); 402 PendingIntent infoPendingIntent = PendingIntent.getActivity( 403 getApplicationContext(), 404 i, infoIntent, PendingIntent.FLAG_UPDATE_CURRENT); 405 406 builder.setInfoIntent(infoPendingIntent); 407 } 408 409 if (i == 5) { 410 builder.setHasCustomPrinterIcon(); 411 } 412 413 mFakePrinters.add(builder.build()); 414 } 415 } 416 417 @Override 418 public void onDestroy() { 419 Log.i(LOG_TAG, "FakePrinterDiscoverySession#onDestroy()"); 420 mSesionHandler.removeMessages(SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS); 421 } 422 423 @Override 424 public void onStartPrinterDiscovery(List<PrinterId> priorityList) { 425 Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStartPrinterDiscovery()"); 426 Message message1 = mSesionHandler.obtainMessage( 427 SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS, this); 428 mSesionHandler.sendMessageDelayed(message1, 0); 429 } 430 431 @Override 432 public void onStopPrinterDiscovery() { 433 Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStopPrinterDiscovery()"); 434 cancellAddingFakePrinters(); 435 } 436 437 @Override 438 public void onStartPrinterStateTracking(PrinterId printerId) { 439 Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStartPrinterStateTracking()"); 440 441 final int printerCount = mFakePrinters.size(); 442 for (int i = printerCount - 1; i >= 0; i--) { 443 PrinterInfo printer = mFakePrinters.remove(i); 444 445 if (printer.getId().equals(printerId)) { 446 PrinterCapabilitiesInfo capabilities = new PrinterCapabilitiesInfo.Builder( 447 printerId) 448 .setMinMargins(new Margins(200, 200, 200, 200)) 449 .addMediaSize(MediaSize.ISO_A4, true) 450 .addMediaSize(MediaSize.NA_GOVT_LETTER, false) 451 .addMediaSize(MediaSize.JPN_YOU4, false) 452 .addResolution(new Resolution("R1", getString( 453 R.string.resolution_200x200), 200, 200), false) 454 .addResolution(new Resolution("R2", getString( 455 R.string.resolution_300x300), 300, 300), true) 456 .setColorModes(PrintAttributes.COLOR_MODE_COLOR 457 | PrintAttributes.COLOR_MODE_MONOCHROME, 458 PrintAttributes.COLOR_MODE_MONOCHROME) 459 .setDuplexModes(PrintAttributes.DUPLEX_MODE_LONG_EDGE 460 | PrintAttributes.DUPLEX_MODE_NONE, 461 PrintAttributes.DUPLEX_MODE_LONG_EDGE) 462 .build(); 463 464 printer = new PrinterInfo.Builder(printer) 465 .setCapabilities(capabilities) 466 .build(); 467 } 468 469 mFakePrinters.add(printer); 470 } 471 472 addPrinters(mFakePrinters); 473 } 474 475 @Override 476 public void onRequestCustomPrinterIcon(final PrinterId printerId, 477 final CustomPrinterIconCallback callbacks) { 478 Log.i(LOG_TAG, "FakePrinterDiscoverySession#onRequestCustomPrinterIcon() " + printerId); 479 480 // Pretend the bitmap icon takes 5 seconds to load 481 mSesionHandler.postDelayed(new Runnable() { 482 @Override 483 public void run() { 484 final int printerCount = mFakePrinters.size(); 485 for (int i = printerCount - 1; i >= 0; i--) { 486 callbacks.onCustomPrinterIconLoaded(Icon.createWithBitmap( 487 BitmapFactory.decodeResource(getResources(), R.raw.red_printer))); 488 } 489 } 490 }, 5000); 491 } 492 493 @Override 494 public void onValidatePrinters(List<PrinterId> printerIds) { 495 Log.i(LOG_TAG, "FakePrinterDiscoverySession#onValidatePrinters() " + printerIds); 496 } 497 498 @Override 499 public void onStopPrinterStateTracking(PrinterId printerId) { 500 Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStopPrinterStateTracking()"); 501 } 502 503 private void addFirstBatchFakePrinters() { 504 List<PrinterInfo> printers = mFakePrinters.subList(0, mFakePrinters.size()); 505 addPrinters(printers); 506 } 507 508 private void cancellAddingFakePrinters() { 509 mSesionHandler.removeMessages(SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS); 510 } 511 512 final class SessionHandler extends Handler { 513 public static final int MSG_ADD_FIRST_BATCH_FAKE_PRINTERS = 1; 514 515 public SessionHandler(Looper looper) { 516 super(looper); 517 } 518 519 @Override 520 public void handleMessage(Message message) { 521 switch (message.what) { 522 case MSG_ADD_FIRST_BATCH_FAKE_PRINTERS: { 523 addFirstBatchFakePrinters(); 524 } break; 525 } 526 } 527 } 528 } 529} 530