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