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