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.content.Intent; 20 import android.net.Uri; 21 import android.os.AsyncTask; 22 import android.os.Handler; 23 import android.os.Looper; 24 import android.os.Message; 25 import android.os.ParcelFileDescriptor; 26 import android.print.PrintAttributes; 27 import android.print.PrintAttributes.Margins; 28 import android.print.PrintAttributes.MediaSize; 29 import android.print.PrintAttributes.Resolution; 30 import android.print.PrintJobId; 31 import android.print.PrintJobInfo; 32 import android.print.PrinterCapabilitiesInfo; 33 import android.print.PrinterId; 34 import android.print.PrinterInfo; 35 import android.printservice.PrintJob; 36 import android.printservice.PrintService; 37 import android.printservice.PrinterDiscoverySession; 38 import android.util.ArrayMap; 39 import android.util.Log; 40 41 import java.io.BufferedInputStream; 42 import java.io.BufferedOutputStream; 43 import java.io.File; 44 import java.io.FileInputStream; 45 import java.io.FileOutputStream; 46 import java.io.IOException; 47 import java.io.InputStream; 48 import java.io.OutputStream; 49 import java.util.ArrayList; 50 import java.util.List; 51 import java.util.Map; 52 53 public class MyPrintService extends PrintService { 54 55 private static final String LOG_TAG = "MyPrintService"; 56 57 private static final long STANDARD_DELAY_MILLIS = 10000000; 58 59 static final String INTENT_EXTRA_ACTION_TYPE = "INTENT_EXTRA_ACTION_TYPE"; 60 static final String INTENT_EXTRA_PRINT_JOB_ID = "INTENT_EXTRA_PRINT_JOB_ID"; 61 62 static final int ACTION_TYPE_ON_PRINT_JOB_PENDING = 1; 63 static final int ACTION_TYPE_ON_REQUEST_CANCEL_PRINT_JOB = 2; 64 65 private static final Object sLock = new Object(); 66 67 private static MyPrintService sInstance; 68 69 private Handler mHandler; 70 71 private AsyncTask<ParcelFileDescriptor, Void, Void> mFakePrintTask; 72 73 private FakePrinterDiscoverySession mSession; 74 75 private final Map<PrintJobId, PrintJob> mProcessedPrintJobs = 76 new ArrayMap<PrintJobId, PrintJob>(); 77 78 public static MyPrintService peekInstance() { 79 synchronized (sLock) { 80 return sInstance; 81 } 82 } 83 84 @Override 85 protected void onConnected() { 86 Log.i(LOG_TAG, "#onConnected()"); 87 mHandler = new MyHandler(getMainLooper()); 88 synchronized (sLock) { 89 sInstance = this; 90 } 91 } 92 93 @Override 94 protected void onDisconnected() { 95 Log.i(LOG_TAG, "#onDisconnected()"); 96 if (mSession != null) { 97 mSession.cancellAddingFakePrinters(); 98 } 99 synchronized (sLock) { 100 sInstance = null; 101 } 102 } 103 104 @Override 105 protected PrinterDiscoverySession onCreatePrinterDiscoverySession() { 106 Log.i(LOG_TAG, "#onCreatePrinterDiscoverySession()"); 107 return new FakePrinterDiscoverySession(); 108 } 109 110 @Override 111 protected void onRequestCancelPrintJob(final PrintJob printJob) { 112 Log.i(LOG_TAG, "#onRequestCancelPrintJob()"); 113 mProcessedPrintJobs.put(printJob.getId(), printJob); 114 Intent intent = new Intent(this, MyDialogActivity.class); 115 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 116 intent.putExtra(INTENT_EXTRA_PRINT_JOB_ID, printJob.getId()); 117 intent.putExtra(INTENT_EXTRA_ACTION_TYPE, ACTION_TYPE_ON_REQUEST_CANCEL_PRINT_JOB); 118 startActivity(intent); 119 } 120 121 @Override 122 public void onPrintJobQueued(final PrintJob printJob) { 123 Log.i(LOG_TAG, "#onPrintJobQueued()"); 124 mProcessedPrintJobs.put(printJob.getId(), printJob); 125 if (printJob.isQueued()) { 126 printJob.start(); 127 } 128 129 Intent intent = new Intent(this, MyDialogActivity.class); 130 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 131 intent.putExtra(INTENT_EXTRA_PRINT_JOB_ID, printJob.getId()); 132 intent.putExtra(INTENT_EXTRA_ACTION_TYPE, ACTION_TYPE_ON_PRINT_JOB_PENDING); 133 startActivity(intent); 134 } 135 136 void handleRequestCancelPrintJob(PrintJobId printJobId) { 137 PrintJob printJob = mProcessedPrintJobs.get(printJobId); 138 if (printJob == null) { 139 return; 140 } 141 mProcessedPrintJobs.remove(printJobId); 142 if (printJob.isQueued() || printJob.isStarted() || printJob.isBlocked()) { 143 mHandler.removeMessages(MyHandler.MSG_HANDLE_DO_PRINT_JOB); 144 mHandler.removeMessages(MyHandler.MSG_HANDLE_FAIL_PRINT_JOB); 145 printJob.cancel(); 146 } 147 } 148 149 void handleFailPrintJobDelayed(PrintJobId printJobId) { 150 Message message = mHandler.obtainMessage( 151 MyHandler.MSG_HANDLE_FAIL_PRINT_JOB, printJobId); 152 mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS); 153 } 154 155 void handleFailPrintJob(PrintJobId printJobId) { 156 PrintJob printJob = mProcessedPrintJobs.get(printJobId); 157 if (printJob == null) { 158 return; 159 } 160 mProcessedPrintJobs.remove(printJobId); 161 if (printJob.isQueued() || printJob.isStarted()) { 162 printJob.fail(getString(R.string.fail_reason)); 163 } 164 } 165 166 void handleBlockPrintJobDelayed(PrintJobId printJobId) { 167 Message message = mHandler.obtainMessage( 168 MyHandler.MSG_HANDLE_BLOCK_PRINT_JOB, printJobId); 169 mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS); 170 } 171 172 void handleBlockPrintJob(PrintJobId printJobId) { 173 final PrintJob printJob = mProcessedPrintJobs.get(printJobId); 174 if (printJob == null) { 175 return; 176 } 177 178 if (printJob.isStarted()) { 179 printJob.block("Gimme some rest, dude"); 180 } 181 } 182 183 void handleBlockAndDelayedUnblockPrintJob(PrintJobId printJobId) { 184 handleBlockPrintJob(printJobId); 185 186 Message message = mHandler.obtainMessage( 187 MyHandler.MSG_HANDLE_UNBLOCK_PRINT_JOB, printJobId); 188 mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS); 189 } 190 191 void handleUnblockPrintJob(PrintJobId printJobId) { 192 final PrintJob printJob = mProcessedPrintJobs.get(printJobId); 193 if (printJob == null) { 194 return; 195 } 196 197 if (printJob.isBlocked()) { 198 printJob.start(); 199 } 200 } 201 202 void handleQueuedPrintJobDelayed(PrintJobId printJobId) { 203 final PrintJob printJob = mProcessedPrintJobs.get(printJobId); 204 if (printJob == null) { 205 return; 206 } 207 208 if (printJob.isQueued()) { 209 printJob.start(); 210 } 211 Message message = mHandler.obtainMessage( 212 MyHandler.MSG_HANDLE_DO_PRINT_JOB, printJobId); 213 mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS); 214 } 215 216 void handleQueuedPrintJob(PrintJobId printJobId) { 217 final PrintJob printJob = mProcessedPrintJobs.get(printJobId); 218 if (printJob == null) { 219 return; 220 } 221 222 if (printJob.isQueued()) { 223 printJob.start(); 224 } 225 226 final PrintJobInfo info = printJob.getInfo(); 227 final File file = new File(getFilesDir(), info.getLabel() + ".pdf"); 228 229 mFakePrintTask = new AsyncTask<ParcelFileDescriptor, Void, Void>() { 230 @Override 231 protected Void doInBackground(ParcelFileDescriptor... params) { 232 InputStream in = new BufferedInputStream(new FileInputStream( 233 params[0].getFileDescriptor())); 234 OutputStream out = null; 235 try { 236 out = new BufferedOutputStream(new FileOutputStream(file)); 237 final byte[] buffer = new byte[8192]; 238 while (true) { 239 if (isCancelled()) { 240 break; 241 } 242 final int readByteCount = in.read(buffer); 243 if (readByteCount < 0) { 244 break; 245 } 246 out.write(buffer, 0, readByteCount); 247 } 248 } catch (IOException ioe) { 249 throw new RuntimeException(ioe); 250 } finally { 251 if (in != null) { 252 try { 253 in.close(); 254 } catch (IOException ioe) { 255 /* ignore */ 256 } 257 } 258 if (out != null) { 259 try { 260 out.close(); 261 } catch (IOException ioe) { 262 /* ignore */ 263 } 264 } 265 if (isCancelled()) { 266 file.delete(); 267 } 268 } 269 return null; 270 } 271 272 @Override 273 protected void onPostExecute(Void result) { 274 if (printJob.isStarted()) { 275 printJob.complete(); 276 } 277 278 file.setReadable(true, false); 279 280 // Quick and dirty to show the file - use a content provider instead. 281 Intent intent = new Intent(Intent.ACTION_VIEW); 282 intent.setDataAndType(Uri.fromFile(file), "application/pdf"); 283 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 284 startActivity(intent, null); 285 286 mFakePrintTask = null; 287 } 288 289 @Override 290 protected void onCancelled(Void result) { 291 if (printJob.isStarted()) { 292 printJob.cancel(); 293 } 294 } 295 }; 296 mFakePrintTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, 297 printJob.getDocument().getData()); 298 } 299 300 private final class MyHandler extends Handler { 301 public static final int MSG_HANDLE_DO_PRINT_JOB = 1; 302 public static final int MSG_HANDLE_FAIL_PRINT_JOB = 2; 303 public static final int MSG_HANDLE_BLOCK_PRINT_JOB = 3; 304 public static final int MSG_HANDLE_UNBLOCK_PRINT_JOB = 4; 305 306 public MyHandler(Looper looper) { 307 super(looper); 308 } 309 310 @Override 311 public void handleMessage(Message message) { 312 switch (message.what) { 313 case MSG_HANDLE_DO_PRINT_JOB: { 314 PrintJobId printJobId = (PrintJobId) message.obj; 315 handleQueuedPrintJob(printJobId); 316 } break; 317 318 case MSG_HANDLE_FAIL_PRINT_JOB: { 319 PrintJobId printJobId = (PrintJobId) message.obj; 320 handleFailPrintJob(printJobId); 321 } break; 322 323 case MSG_HANDLE_BLOCK_PRINT_JOB: { 324 PrintJobId printJobId = (PrintJobId) message.obj; 325 handleBlockPrintJob(printJobId); 326 } break; 327 328 case MSG_HANDLE_UNBLOCK_PRINT_JOB: { 329 PrintJobId printJobId = (PrintJobId) message.obj; 330 handleUnblockPrintJob(printJobId); 331 } break; 332 } 333 } 334 } 335 336 private final class FakePrinterDiscoverySession extends PrinterDiscoverySession { 337 private final Handler mSesionHandler = new SessionHandler(getMainLooper()); 338 339 private final List<PrinterInfo> mFakePrinters = new ArrayList<PrinterInfo>(); 340 341 public FakePrinterDiscoverySession() { 342 for (int i = 0; i < 10; i++) { 343 String name = "Printer " + i; 344 PrinterInfo printer = new PrinterInfo 345 .Builder(generatePrinterId(name), name, (i % 2 == 1) 346 ? PrinterInfo.STATUS_UNAVAILABLE : PrinterInfo.STATUS_IDLE) 347 .build(); 348 mFakePrinters.add(printer); 349 } 350 } 351 352 @Override 353 public void onDestroy() { 354 Log.i(LOG_TAG, "FakePrinterDiscoverySession#onDestroy()"); 355 mSesionHandler.removeMessages(SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS); 356 mSesionHandler.removeMessages(SessionHandler.MSG_ADD_SECOND_BATCH_FAKE_PRINTERS); 357 } 358 359 @Override 360 public void onStartPrinterDiscovery(List<PrinterId> priorityList) { 361 Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStartPrinterDiscovery()"); 362 Message message1 = mSesionHandler.obtainMessage( 363 SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS, this); 364 mSesionHandler.sendMessageDelayed(message1, 0); 365 366 Message message2 = mSesionHandler.obtainMessage( 367 SessionHandler.MSG_ADD_SECOND_BATCH_FAKE_PRINTERS, this); 368 mSesionHandler.sendMessageDelayed(message2, 10000); 369 } 370 371 @Override 372 public void onStopPrinterDiscovery() { 373 Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStopPrinterDiscovery()"); 374 cancellAddingFakePrinters(); 375 } 376 377 @Override 378 public void onStartPrinterStateTracking(PrinterId printerId) { 379 Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStartPrinterStateTracking()"); 380 PrinterInfo printer = findPrinterInfo(printerId); 381 if (printer != null) { 382 PrinterCapabilitiesInfo capabilities = 383 new PrinterCapabilitiesInfo.Builder(printerId) 384 .setMinMargins(new Margins(200, 200, 200, 200)) 385 .addMediaSize(MediaSize.ISO_A4, true) 386 .addMediaSize(MediaSize.ISO_A5, false) 387 .addResolution(new Resolution("R1", getString( 388 R.string.resolution_200x200), 200, 200), false) 389 .addResolution(new Resolution("R2", getString( 390 R.string.resolution_300x300), 300, 300), true) 391 .setColorModes(PrintAttributes.COLOR_MODE_COLOR 392 | PrintAttributes.COLOR_MODE_MONOCHROME, 393 PrintAttributes.COLOR_MODE_MONOCHROME) 394 .build(); 395 396 printer = new PrinterInfo.Builder(printer) 397 .setCapabilities(capabilities) 398 .build(); 399 400 List<PrinterInfo> printers = new ArrayList<PrinterInfo>(); 401 printers.add(printer); 402 addPrinters(printers); 403 } 404 } 405 406 @Override 407 public void onValidatePrinters(List<PrinterId> printerIds) { 408 Log.i(LOG_TAG, "FakePrinterDiscoverySession#onValidatePrinters()"); 409 } 410 411 @Override 412 public void onStopPrinterStateTracking(PrinterId printerId) { 413 Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStopPrinterStateTracking()"); 414 } 415 416 private void addFirstBatchFakePrinters() { 417 List<PrinterInfo> printers = mFakePrinters.subList(0, mFakePrinters.size() / 2); 418 addPrinters(printers); 419 } 420 421 private void addSecondBatchFakePrinters() { 422 List<PrinterInfo> printers = mFakePrinters.subList(0, mFakePrinters.size() / 2 423 /* mFakePrinters.size() / 2, mFakePrinters.size()*/); 424 final int printerCount = mFakePrinters.size(); 425 for (int i = printerCount - 1; i >= 0; i--) { 426 PrinterInfo printer = new PrinterInfo.Builder(mFakePrinters.get(i)) 427 .setStatus(PrinterInfo.STATUS_UNAVAILABLE).build(); 428 printers.add(printer); 429 } 430 addPrinters(printers); 431 } 432 433 private PrinterInfo findPrinterInfo(PrinterId printerId) { 434 List<PrinterInfo> printers = getPrinters(); 435 final int printerCount = getPrinters().size(); 436 for (int i = 0; i < printerCount; i++) { 437 PrinterInfo printer = printers.get(i); 438 if (printer.getId().equals(printerId)) { 439 return printer; 440 } 441 } 442 return null; 443 } 444 445 private void cancellAddingFakePrinters() { 446 mSesionHandler.removeMessages(SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS); 447 mSesionHandler.removeMessages(SessionHandler.MSG_ADD_SECOND_BATCH_FAKE_PRINTERS); 448 } 449 450 final class SessionHandler extends Handler { 451 public static final int MSG_ADD_FIRST_BATCH_FAKE_PRINTERS = 1; 452 public static final int MSG_ADD_SECOND_BATCH_FAKE_PRINTERS = 2; 453 454 public SessionHandler(Looper looper) { 455 super(looper); 456 } 457 458 @Override 459 public void handleMessage(Message message) { 460 switch (message.what) { 461 case MSG_ADD_FIRST_BATCH_FAKE_PRINTERS: { 462 addFirstBatchFakePrinters(); 463 } break; 464 465 case MSG_ADD_SECOND_BATCH_FAKE_PRINTERS: { 466 addSecondBatchFakePrinters(); 467 } break; 468 } 469 } 470 } 471 } 472 } 473