1 /* 2 * Copyright (C) 2014 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 com.android.printspooler.ui; 18 19 import android.app.Activity; 20 import android.app.Fragment; 21 import android.app.FragmentTransaction; 22 import android.content.ActivityNotFoundException; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.ServiceConnection; 27 import android.content.pm.PackageInfo; 28 import android.content.pm.PackageManager.NameNotFoundException; 29 import android.content.pm.ResolveInfo; 30 import android.content.res.Configuration; 31 import android.database.DataSetObserver; 32 import android.graphics.drawable.Drawable; 33 import android.net.Uri; 34 import android.os.AsyncTask; 35 import android.os.Bundle; 36 import android.os.Handler; 37 import android.os.IBinder; 38 import android.os.ParcelFileDescriptor; 39 import android.os.RemoteException; 40 import android.print.IPrintDocumentAdapter; 41 import android.print.PageRange; 42 import android.print.PrintAttributes; 43 import android.print.PrintAttributes.MediaSize; 44 import android.print.PrintAttributes.Resolution; 45 import android.print.PrintDocumentInfo; 46 import android.print.PrintJobInfo; 47 import android.print.PrintManager; 48 import android.print.PrinterCapabilitiesInfo; 49 import android.print.PrinterId; 50 import android.print.PrinterInfo; 51 import android.printservice.PrintService; 52 import android.provider.DocumentsContract; 53 import android.text.Editable; 54 import android.text.TextUtils; 55 import android.text.TextUtils.SimpleStringSplitter; 56 import android.text.TextWatcher; 57 import android.util.ArrayMap; 58 import android.util.Log; 59 import android.view.KeyEvent; 60 import android.view.View; 61 import android.view.View.OnClickListener; 62 import android.view.View.OnFocusChangeListener; 63 import android.view.ViewGroup; 64 import android.view.inputmethod.InputMethodManager; 65 import android.widget.AdapterView; 66 import android.widget.AdapterView.OnItemSelectedListener; 67 import android.widget.ArrayAdapter; 68 import android.widget.BaseAdapter; 69 import android.widget.Button; 70 import android.widget.EditText; 71 import android.widget.ImageView; 72 import android.widget.Spinner; 73 import android.widget.TextView; 74 75 import com.android.printspooler.R; 76 import com.android.printspooler.model.MutexFileProvider; 77 import com.android.printspooler.model.PrintSpoolerProvider; 78 import com.android.printspooler.model.PrintSpoolerService; 79 import com.android.printspooler.model.RemotePrintDocument; 80 import com.android.printspooler.model.RemotePrintDocument.RemotePrintDocumentInfo; 81 import com.android.printspooler.renderer.IPdfEditor; 82 import com.android.printspooler.renderer.PdfManipulationService; 83 import com.android.printspooler.util.MediaSizeUtils; 84 import com.android.printspooler.util.MediaSizeUtils.MediaSizeComparator; 85 import com.android.printspooler.util.PageRangeUtils; 86 import com.android.printspooler.util.PrintOptionUtils; 87 import com.android.printspooler.widget.PrintContentView; 88 import com.android.printspooler.widget.PrintContentView.OptionsStateChangeListener; 89 import com.android.printspooler.widget.PrintContentView.OptionsStateController; 90 import libcore.io.IoUtils; 91 import libcore.io.Streams; 92 93 import java.io.File; 94 import java.io.FileInputStream; 95 import java.io.FileOutputStream; 96 import java.io.IOException; 97 import java.io.InputStream; 98 import java.io.OutputStream; 99 import java.util.ArrayList; 100 import java.util.Arrays; 101 import java.util.Collection; 102 import java.util.Collections; 103 import java.util.List; 104 import java.util.regex.Matcher; 105 import java.util.regex.Pattern; 106 107 public class PrintActivity extends Activity implements RemotePrintDocument.UpdateResultCallbacks, 108 PrintErrorFragment.OnActionListener, PageAdapter.ContentCallbacks, 109 OptionsStateChangeListener, OptionsStateController { 110 private static final String LOG_TAG = "PrintActivity"; 111 112 private static final boolean DEBUG = false; 113 114 public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID"; 115 116 private static final String FRAGMENT_TAG = "FRAGMENT_TAG"; 117 118 private static final int ORIENTATION_PORTRAIT = 0; 119 private static final int ORIENTATION_LANDSCAPE = 1; 120 121 private static final int ACTIVITY_REQUEST_CREATE_FILE = 1; 122 private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2; 123 private static final int ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS = 3; 124 125 private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9; 126 127 private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE; 128 private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1; 129 130 private static final int STATE_INITIALIZING = 0; 131 private static final int STATE_CONFIGURING = 1; 132 private static final int STATE_PRINT_CONFIRMED = 2; 133 private static final int STATE_PRINT_CANCELED = 3; 134 private static final int STATE_UPDATE_FAILED = 4; 135 private static final int STATE_CREATE_FILE_FAILED = 5; 136 private static final int STATE_PRINTER_UNAVAILABLE = 6; 137 private static final int STATE_UPDATE_SLOW = 7; 138 private static final int STATE_PRINT_COMPLETED = 8; 139 140 private static final int UI_STATE_PREVIEW = 0; 141 private static final int UI_STATE_ERROR = 1; 142 private static final int UI_STATE_PROGRESS = 2; 143 144 private static final int MIN_COPIES = 1; 145 private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES); 146 147 private static final Pattern PATTERN_DIGITS = Pattern.compile("[\\d]+"); 148 149 private static final Pattern PATTERN_ESCAPE_SPECIAL_CHARS = Pattern.compile( 150 "(?=[]\\[+&|!(){}^\"~*?:\\\\])"); 151 152 private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile( 153 "[\\s]*[0-9]+[\\-]?[\\s]*[0-9]*[\\s]*?(([,])" 154 + "[\\s]*[0-9]+[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+"); 155 156 public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[]{PageRange.ALL_PAGES}; 157 158 private final PrinterAvailabilityDetector mPrinterAvailabilityDetector = 159 new PrinterAvailabilityDetector(); 160 161 private final SimpleStringSplitter mStringCommaSplitter = new SimpleStringSplitter(','); 162 163 private final OnFocusChangeListener mSelectAllOnFocusListener = new SelectAllOnFocusListener(); 164 165 private PrintSpoolerProvider mSpoolerProvider; 166 167 private PrintPreviewController mPrintPreviewController; 168 169 private PrintJobInfo mPrintJob; 170 private RemotePrintDocument mPrintedDocument; 171 private PrinterRegistry mPrinterRegistry; 172 173 private EditText mCopiesEditText; 174 175 private TextView mPageRangeTitle; 176 private EditText mPageRangeEditText; 177 178 private Spinner mDestinationSpinner; 179 private DestinationAdapter mDestinationSpinnerAdapter; 180 181 private Spinner mMediaSizeSpinner; 182 private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter; 183 184 private Spinner mColorModeSpinner; 185 private ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter; 186 187 private Spinner mOrientationSpinner; 188 private ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter; 189 190 private Spinner mRangeOptionsSpinner; 191 192 private PrintContentView mOptionsContent; 193 194 private View mSummaryContainer; 195 private TextView mSummaryCopies; 196 private TextView mSummaryPaperSize; 197 198 private Button mMoreOptionsButton; 199 200 private ImageView mPrintButton; 201 202 private ProgressMessageController mProgressMessageController; 203 private MutexFileProvider mFileProvider; 204 205 private MediaSizeComparator mMediaSizeComparator; 206 207 private PrinterInfo mCurrentPrinter; 208 209 private PageRange[] mSelectedPages; 210 211 private String mCallingPackageName; 212 213 private int mCurrentPageCount; 214 215 private int mState = STATE_INITIALIZING; 216 217 private int mUiState = UI_STATE_PREVIEW; 218 219 @Override onCreate(Bundle savedInstanceState)220 public void onCreate(Bundle savedInstanceState) { 221 super.onCreate(savedInstanceState); 222 223 Bundle extras = getIntent().getExtras(); 224 225 mPrintJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB); 226 if (mPrintJob == null) { 227 throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_JOB 228 + " cannot be null"); 229 } 230 mPrintJob.setAttributes(new PrintAttributes.Builder().build()); 231 232 final IBinder adapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER); 233 if (adapter == null) { 234 throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER 235 + " cannot be null"); 236 } 237 238 mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME); 239 240 // This will take just a few milliseconds, so just wait to 241 // bind to the local service before showing the UI. 242 mSpoolerProvider = new PrintSpoolerProvider(this, 243 new Runnable() { 244 @Override 245 public void run() { 246 onConnectedToPrintSpooler(adapter); 247 } 248 }); 249 } 250 onConnectedToPrintSpooler(final IBinder documentAdapter)251 private void onConnectedToPrintSpooler(final IBinder documentAdapter) { 252 // Now that we are bound to the print spooler service, 253 // create the printer registry and wait for it to get 254 // the first batch of results which will be delivered 255 // after reading historical data. This should be pretty 256 // fast, so just wait before showing the UI. 257 mPrinterRegistry = new PrinterRegistry(PrintActivity.this, 258 new Runnable() { 259 @Override 260 public void run() { 261 onPrinterRegistryReady(documentAdapter); 262 } 263 }); 264 } 265 onPrinterRegistryReady(IBinder documentAdapter)266 private void onPrinterRegistryReady(IBinder documentAdapter) { 267 // Now that we are bound to the local print spooler service 268 // and the printer registry loaded the historical printers 269 // we can show the UI without flickering. 270 setTitle(R.string.print_dialog); 271 setContentView(R.layout.print_activity); 272 273 try { 274 mFileProvider = new MutexFileProvider( 275 PrintSpoolerService.generateFileForPrintJob( 276 PrintActivity.this, mPrintJob.getId())); 277 } catch (IOException ioe) { 278 // At this point we cannot recover, so just take it down. 279 throw new IllegalStateException("Cannot create print job file", ioe); 280 } 281 282 mPrintPreviewController = new PrintPreviewController(PrintActivity.this, 283 mFileProvider); 284 mPrintedDocument = new RemotePrintDocument(PrintActivity.this, 285 IPrintDocumentAdapter.Stub.asInterface(documentAdapter), 286 mFileProvider, new RemotePrintDocument.RemoteAdapterDeathObserver() { 287 @Override 288 public void onDied() { 289 // If we are finishing or we are in a state that we do not need any 290 // data from the printing app, then no need to finish. 291 if (isFinishing() || (isFinalState(mState) && !mPrintedDocument.isUpdating())) { 292 return; 293 } 294 if (mPrintedDocument.isUpdating()) { 295 mPrintedDocument.cancel(); 296 } 297 setState(STATE_PRINT_CANCELED); 298 doFinish(); 299 } 300 }, PrintActivity.this); 301 mProgressMessageController = new ProgressMessageController( 302 PrintActivity.this); 303 mMediaSizeComparator = new MediaSizeComparator(PrintActivity.this); 304 mDestinationSpinnerAdapter = new DestinationAdapter(); 305 306 bindUi(); 307 updateOptionsUi(); 308 309 // Now show the updated UI to avoid flicker. 310 mOptionsContent.setVisibility(View.VISIBLE); 311 mSelectedPages = computeSelectedPages(); 312 mPrintedDocument.start(); 313 314 ensurePreviewUiShown(); 315 316 setState(STATE_CONFIGURING); 317 } 318 319 @Override onResume()320 public void onResume() { 321 super.onResume(); 322 if (mState != STATE_INITIALIZING && mCurrentPrinter != null) { 323 mPrinterRegistry.setTrackedPrinter(mCurrentPrinter.getId()); 324 } 325 } 326 327 @Override onPause()328 public void onPause() { 329 PrintSpoolerService spooler = mSpoolerProvider.getSpooler(); 330 331 if (mState == STATE_INITIALIZING) { 332 if (isFinishing()) { 333 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null); 334 } 335 super.onPause(); 336 return; 337 } 338 339 if (isFinishing()) { 340 spooler.updatePrintJobUserConfigurableOptionsNoPersistence(mPrintJob); 341 342 switch (mState) { 343 case STATE_PRINT_CONFIRMED: { 344 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_QUEUED, null); 345 } break; 346 347 case STATE_PRINT_COMPLETED: { 348 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_COMPLETED, null); 349 } break; 350 351 case STATE_CREATE_FILE_FAILED: { 352 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_FAILED, 353 getString(R.string.print_write_error_message)); 354 } break; 355 356 default: { 357 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null); 358 } break; 359 } 360 } 361 362 mPrinterAvailabilityDetector.cancel(); 363 mPrinterRegistry.setTrackedPrinter(null); 364 365 super.onPause(); 366 } 367 368 @Override onKeyDown(int keyCode, KeyEvent event)369 public boolean onKeyDown(int keyCode, KeyEvent event) { 370 if (keyCode == KeyEvent.KEYCODE_BACK) { 371 event.startTracking(); 372 return true; 373 } 374 return super.onKeyDown(keyCode, event); 375 } 376 377 @Override onKeyUp(int keyCode, KeyEvent event)378 public boolean onKeyUp(int keyCode, KeyEvent event) { 379 if (mState == STATE_INITIALIZING) { 380 doFinish(); 381 return true; 382 } 383 384 if (mState == STATE_PRINT_CANCELED || mState == STATE_PRINT_CONFIRMED 385 || mState == STATE_PRINT_COMPLETED) { 386 return true; 387 } 388 389 if (keyCode == KeyEvent.KEYCODE_BACK 390 && event.isTracking() && !event.isCanceled()) { 391 if (mPrintPreviewController != null && mPrintPreviewController.isOptionsOpened() 392 && !hasErrors()) { 393 mPrintPreviewController.closeOptions(); 394 } else { 395 cancelPrint(); 396 } 397 return true; 398 } 399 return super.onKeyUp(keyCode, event); 400 } 401 402 @Override onRequestContentUpdate()403 public void onRequestContentUpdate() { 404 if (canUpdateDocument()) { 405 updateDocument(false); 406 } 407 } 408 409 @Override onMalformedPdfFile()410 public void onMalformedPdfFile() { 411 onPrintDocumentError("Cannot print a malformed PDF file"); 412 } 413 414 @Override onSecurePdfFile()415 public void onSecurePdfFile() { 416 onPrintDocumentError("Cannot print a password protected PDF file"); 417 } 418 onPrintDocumentError(String message)419 private void onPrintDocumentError(String message) { 420 mProgressMessageController.cancel(); 421 ensureErrorUiShown(null, PrintErrorFragment.ACTION_RETRY); 422 423 setState(STATE_UPDATE_FAILED); 424 425 updateOptionsUi(); 426 427 mPrintedDocument.kill(message); 428 } 429 430 @Override onActionPerformed()431 public void onActionPerformed() { 432 if (mState == STATE_UPDATE_FAILED 433 && canUpdateDocument() && updateDocument(true)) { 434 ensurePreviewUiShown(); 435 setState(STATE_CONFIGURING); 436 updateOptionsUi(); 437 } 438 } 439 onUpdateCanceled()440 public void onUpdateCanceled() { 441 if (DEBUG) { 442 Log.i(LOG_TAG, "onUpdateCanceled()"); 443 } 444 445 mProgressMessageController.cancel(); 446 ensurePreviewUiShown(); 447 448 switch (mState) { 449 case STATE_PRINT_CONFIRMED: { 450 requestCreatePdfFileOrFinish(); 451 } break; 452 453 case STATE_PRINT_CANCELED: { 454 doFinish(); 455 } break; 456 } 457 } 458 459 @Override onUpdateCompleted(RemotePrintDocumentInfo document)460 public void onUpdateCompleted(RemotePrintDocumentInfo document) { 461 if (DEBUG) { 462 Log.i(LOG_TAG, "onUpdateCompleted()"); 463 } 464 465 mProgressMessageController.cancel(); 466 ensurePreviewUiShown(); 467 468 // Update the print job with the info for the written document. The page 469 // count we get from the remote document is the pages in the document from 470 // the app perspective but the print job should contain the page count from 471 // print service perspective which is the pages in the written PDF not the 472 // pages in the printed document. 473 PrintDocumentInfo info = document.info; 474 if (info != null) { 475 final int pageCount = PageRangeUtils.getNormalizedPageCount(document.writtenPages, 476 getAdjustedPageCount(info)); 477 PrintDocumentInfo adjustedInfo = new PrintDocumentInfo.Builder(info.getName()) 478 .setContentType(info.getContentType()) 479 .setPageCount(pageCount) 480 .build(); 481 mPrintJob.setDocumentInfo(adjustedInfo); 482 mPrintJob.setPages(document.printedPages); 483 } 484 485 switch (mState) { 486 case STATE_PRINT_CONFIRMED: { 487 requestCreatePdfFileOrFinish(); 488 } break; 489 490 case STATE_PRINT_CANCELED: { 491 updateOptionsUi(); 492 } break; 493 494 default: { 495 updatePrintPreviewController(document.changed); 496 497 setState(STATE_CONFIGURING); 498 updateOptionsUi(); 499 } break; 500 } 501 } 502 503 @Override onUpdateFailed(CharSequence error)504 public void onUpdateFailed(CharSequence error) { 505 if (DEBUG) { 506 Log.i(LOG_TAG, "onUpdateFailed()"); 507 } 508 509 mProgressMessageController.cancel(); 510 ensureErrorUiShown(error, PrintErrorFragment.ACTION_RETRY); 511 512 setState(STATE_UPDATE_FAILED); 513 514 updateOptionsUi(); 515 } 516 517 @Override onOptionsOpened()518 public void onOptionsOpened() { 519 updateSelectedPagesFromPreview(); 520 } 521 522 @Override onOptionsClosed()523 public void onOptionsClosed() { 524 PageRange[] selectedPages = computeSelectedPages(); 525 if (!Arrays.equals(mSelectedPages, selectedPages)) { 526 mSelectedPages = selectedPages; 527 528 // Update preview. 529 updatePrintPreviewController(false); 530 } 531 532 // Make sure the IME is not on the way of preview as 533 // the user may have used it to type copies or range. 534 InputMethodManager imm = (InputMethodManager) getSystemService( 535 Context.INPUT_METHOD_SERVICE); 536 imm.hideSoftInputFromWindow(mDestinationSpinner.getWindowToken(), 0); 537 } 538 updatePrintPreviewController(boolean contentUpdated)539 private void updatePrintPreviewController(boolean contentUpdated) { 540 // If we have not heard from the application, do nothing. 541 RemotePrintDocumentInfo documentInfo = mPrintedDocument.getDocumentInfo(); 542 if (!documentInfo.laidout) { 543 return; 544 } 545 546 // Update the preview controller. 547 mPrintPreviewController.onContentUpdated(contentUpdated, 548 getAdjustedPageCount(documentInfo.info), 549 mPrintedDocument.getDocumentInfo().writtenPages, 550 mSelectedPages, mPrintJob.getAttributes().getMediaSize(), 551 mPrintJob.getAttributes().getMinMargins()); 552 } 553 554 555 @Override canOpenOptions()556 public boolean canOpenOptions() { 557 return true; 558 } 559 560 @Override canCloseOptions()561 public boolean canCloseOptions() { 562 return !hasErrors(); 563 } 564 565 @Override onConfigurationChanged(Configuration newConfig)566 public void onConfigurationChanged(Configuration newConfig) { 567 super.onConfigurationChanged(newConfig); 568 if (mPrintPreviewController != null) { 569 mPrintPreviewController.onOrientationChanged(); 570 } 571 } 572 573 @Override onActivityResult(int requestCode, int resultCode, Intent data)574 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 575 switch (requestCode) { 576 case ACTIVITY_REQUEST_CREATE_FILE: { 577 onStartCreateDocumentActivityResult(resultCode, data); 578 } break; 579 580 case ACTIVITY_REQUEST_SELECT_PRINTER: { 581 onSelectPrinterActivityResult(resultCode, data); 582 } break; 583 584 case ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS: { 585 onAdvancedPrintOptionsActivityResult(resultCode, data); 586 } break; 587 } 588 } 589 startCreateDocumentActivity()590 private void startCreateDocumentActivity() { 591 if (!isResumed()) { 592 return; 593 } 594 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info; 595 if (info == null) { 596 return; 597 } 598 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); 599 intent.setType("application/pdf"); 600 intent.putExtra(Intent.EXTRA_TITLE, info.getName()); 601 intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName); 602 startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE); 603 } 604 onStartCreateDocumentActivityResult(int resultCode, Intent data)605 private void onStartCreateDocumentActivityResult(int resultCode, Intent data) { 606 if (resultCode == RESULT_OK && data != null) { 607 setState(STATE_PRINT_COMPLETED); 608 updateOptionsUi(); 609 final Uri uri = data.getData(); 610 // Calling finish here does not invoke lifecycle callbacks but we 611 // update the print job in onPause if finishing, hence post a message. 612 mDestinationSpinner.post(new Runnable() { 613 @Override 614 public void run() { 615 transformDocumentAndFinish(uri); 616 } 617 }); 618 } else if (resultCode == RESULT_CANCELED) { 619 mState = STATE_CONFIGURING; 620 updateOptionsUi(); 621 } else { 622 setState(STATE_CREATE_FILE_FAILED); 623 updateOptionsUi(); 624 // Calling finish here does not invoke lifecycle callbacks but we 625 // update the print job in onPause if finishing, hence post a message. 626 mDestinationSpinner.post(new Runnable() { 627 @Override 628 public void run() { 629 doFinish(); 630 } 631 }); 632 } 633 } 634 startSelectPrinterActivity()635 private void startSelectPrinterActivity() { 636 Intent intent = new Intent(this, SelectPrinterActivity.class); 637 startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER); 638 } 639 onSelectPrinterActivityResult(int resultCode, Intent data)640 private void onSelectPrinterActivityResult(int resultCode, Intent data) { 641 if (resultCode == RESULT_OK && data != null) { 642 PrinterId printerId = data.getParcelableExtra(INTENT_EXTRA_PRINTER_ID); 643 if (printerId != null) { 644 mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerId); 645 final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId); 646 if (index != AdapterView.INVALID_POSITION) { 647 mDestinationSpinner.setSelection(index); 648 return; 649 } 650 } 651 } 652 653 PrinterId printerId = mCurrentPrinter.getId(); 654 final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId); 655 mDestinationSpinner.setSelection(index); 656 } 657 startAdvancedPrintOptionsActivity(PrinterInfo printer)658 private void startAdvancedPrintOptionsActivity(PrinterInfo printer) { 659 ComponentName serviceName = printer.getId().getServiceName(); 660 661 String activityName = PrintOptionUtils.getAdvancedOptionsActivityName(this, serviceName); 662 if (TextUtils.isEmpty(activityName)) { 663 return; 664 } 665 666 Intent intent = new Intent(Intent.ACTION_MAIN); 667 intent.setComponent(new ComponentName(serviceName.getPackageName(), activityName)); 668 669 List<ResolveInfo> resolvedActivities = getPackageManager() 670 .queryIntentActivities(intent, 0); 671 if (resolvedActivities.isEmpty()) { 672 return; 673 } 674 675 // The activity is a component name, therefore it is one or none. 676 if (resolvedActivities.get(0).activityInfo.exported) { 677 intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, mPrintJob); 678 intent.putExtra(PrintService.EXTRA_PRINTER_INFO, printer); 679 680 // This is external activity and may not be there. 681 try { 682 startActivityForResult(intent, ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS); 683 } catch (ActivityNotFoundException anfe) { 684 Log.e(LOG_TAG, "Error starting activity for intent: " + intent, anfe); 685 } 686 } 687 } 688 onAdvancedPrintOptionsActivityResult(int resultCode, Intent data)689 private void onAdvancedPrintOptionsActivityResult(int resultCode, Intent data) { 690 if (resultCode != RESULT_OK || data == null) { 691 return; 692 } 693 694 PrintJobInfo printJobInfo = data.getParcelableExtra(PrintService.EXTRA_PRINT_JOB_INFO); 695 696 if (printJobInfo == null) { 697 return; 698 } 699 700 // Take the advanced options without interpretation. 701 mPrintJob.setAdvancedOptions(printJobInfo.getAdvancedOptions()); 702 703 // Take copies without interpretation as the advanced print dialog 704 // cannot create a print job info with invalid copies. 705 mCopiesEditText.setText(String.valueOf(printJobInfo.getCopies())); 706 mPrintJob.setCopies(printJobInfo.getCopies()); 707 708 PrintAttributes currAttributes = mPrintJob.getAttributes(); 709 PrintAttributes newAttributes = printJobInfo.getAttributes(); 710 711 if (newAttributes != null) { 712 // Take the media size only if the current printer supports is. 713 MediaSize oldMediaSize = currAttributes.getMediaSize(); 714 MediaSize newMediaSize = newAttributes.getMediaSize(); 715 if (!oldMediaSize.equals(newMediaSize)) { 716 final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount(); 717 MediaSize newMediaSizePortrait = newAttributes.getMediaSize().asPortrait(); 718 for (int i = 0; i < mediaSizeCount; i++) { 719 MediaSize supportedSizePortrait = mMediaSizeSpinnerAdapter.getItem(i) 720 .value.asPortrait(); 721 if (supportedSizePortrait.equals(newMediaSizePortrait)) { 722 currAttributes.setMediaSize(newMediaSize); 723 mMediaSizeSpinner.setSelection(i); 724 if (currAttributes.getMediaSize().isPortrait()) { 725 if (mOrientationSpinner.getSelectedItemPosition() != 0) { 726 mOrientationSpinner.setSelection(0); 727 } 728 } else { 729 if (mOrientationSpinner.getSelectedItemPosition() != 1) { 730 mOrientationSpinner.setSelection(1); 731 } 732 } 733 break; 734 } 735 } 736 } 737 738 // Take the resolution only if the current printer supports is. 739 Resolution oldResolution = currAttributes.getResolution(); 740 Resolution newResolution = newAttributes.getResolution(); 741 if (!oldResolution.equals(newResolution)) { 742 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities(); 743 if (capabilities != null) { 744 List<Resolution> resolutions = capabilities.getResolutions(); 745 final int resolutionCount = resolutions.size(); 746 for (int i = 0; i < resolutionCount; i++) { 747 Resolution resolution = resolutions.get(i); 748 if (resolution.equals(newResolution)) { 749 currAttributes.setResolution(resolution); 750 break; 751 } 752 } 753 } 754 } 755 756 // Take the color mode only if the current printer supports it. 757 final int currColorMode = currAttributes.getColorMode(); 758 final int newColorMode = newAttributes.getColorMode(); 759 if (currColorMode != newColorMode) { 760 final int colorModeCount = mColorModeSpinner.getCount(); 761 for (int i = 0; i < colorModeCount; i++) { 762 final int supportedColorMode = mColorModeSpinnerAdapter.getItem(i).value; 763 if (supportedColorMode == newColorMode) { 764 currAttributes.setColorMode(newColorMode); 765 mColorModeSpinner.setSelection(i); 766 break; 767 } 768 } 769 } 770 } 771 772 // Handle selected page changes making sure they are in the doc. 773 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info; 774 final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0; 775 PageRange[] pageRanges = printJobInfo.getPages(); 776 if (pageRanges != null && pageCount > 0) { 777 pageRanges = PageRangeUtils.normalize(pageRanges); 778 779 List<PageRange> validatedList = new ArrayList<>(); 780 final int rangeCount = pageRanges.length; 781 for (int i = 0; i < rangeCount; i++) { 782 PageRange pageRange = pageRanges[i]; 783 if (pageRange.getEnd() >= pageCount) { 784 final int rangeStart = pageRange.getStart(); 785 final int rangeEnd = pageCount - 1; 786 if (rangeStart <= rangeEnd) { 787 pageRange = new PageRange(rangeStart, rangeEnd); 788 validatedList.add(pageRange); 789 } 790 break; 791 } 792 validatedList.add(pageRange); 793 } 794 795 if (!validatedList.isEmpty()) { 796 PageRange[] validatedArray = new PageRange[validatedList.size()]; 797 validatedList.toArray(validatedArray); 798 updateSelectedPages(validatedArray, pageCount); 799 } 800 } 801 802 // Update the content if needed. 803 if (canUpdateDocument()) { 804 updateDocument(false); 805 } 806 } 807 setState(int state)808 private void setState(int state) { 809 if (isFinalState(mState)) { 810 if (isFinalState(state)) { 811 mState = state; 812 } 813 } else { 814 mState = state; 815 } 816 } 817 isFinalState(int state)818 private static boolean isFinalState(int state) { 819 return state == STATE_PRINT_CONFIRMED 820 || state == STATE_PRINT_CANCELED 821 || state == STATE_PRINT_COMPLETED; 822 } 823 updateSelectedPagesFromPreview()824 private void updateSelectedPagesFromPreview() { 825 PageRange[] selectedPages = mPrintPreviewController.getSelectedPages(); 826 if (!Arrays.equals(mSelectedPages, selectedPages)) { 827 updateSelectedPages(selectedPages, 828 getAdjustedPageCount(mPrintedDocument.getDocumentInfo().info)); 829 } 830 } 831 updateSelectedPages(PageRange[] selectedPages, int pageInDocumentCount)832 private void updateSelectedPages(PageRange[] selectedPages, int pageInDocumentCount) { 833 if (selectedPages == null || selectedPages.length <= 0) { 834 return; 835 } 836 837 selectedPages = PageRangeUtils.normalize(selectedPages); 838 839 // Handle the case where all pages are specified explicitly 840 // instead of the *all pages* constant. 841 if (PageRangeUtils.isAllPages(selectedPages, pageInDocumentCount)) { 842 selectedPages = new PageRange[] {PageRange.ALL_PAGES}; 843 } 844 845 if (Arrays.equals(mSelectedPages, selectedPages)) { 846 return; 847 } 848 849 mSelectedPages = selectedPages; 850 mPrintJob.setPages(selectedPages); 851 852 if (Arrays.equals(selectedPages, ALL_PAGES_ARRAY)) { 853 if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) { 854 mRangeOptionsSpinner.setSelection(0); 855 mPageRangeEditText.setText(""); 856 } 857 } else if (selectedPages[0].getStart() >= 0 858 && selectedPages[selectedPages.length - 1].getEnd() < pageInDocumentCount) { 859 if (mRangeOptionsSpinner.getSelectedItemPosition() != 1) { 860 mRangeOptionsSpinner.setSelection(1); 861 } 862 863 StringBuilder builder = new StringBuilder(); 864 final int pageRangeCount = selectedPages.length; 865 for (int i = 0; i < pageRangeCount; i++) { 866 if (builder.length() > 0) { 867 builder.append(','); 868 } 869 870 final int shownStartPage; 871 final int shownEndPage; 872 PageRange pageRange = selectedPages[i]; 873 if (pageRange.equals(PageRange.ALL_PAGES)) { 874 shownStartPage = 1; 875 shownEndPage = pageInDocumentCount; 876 } else { 877 shownStartPage = pageRange.getStart() + 1; 878 shownEndPage = pageRange.getEnd() + 1; 879 } 880 881 builder.append(shownStartPage); 882 883 if (shownStartPage != shownEndPage) { 884 builder.append('-'); 885 builder.append(shownEndPage); 886 } 887 } 888 889 mPageRangeEditText.setText(builder.toString()); 890 } 891 } 892 ensureProgressUiShown()893 private void ensureProgressUiShown() { 894 if (isFinishing()) { 895 return; 896 } 897 if (mUiState != UI_STATE_PROGRESS) { 898 mUiState = UI_STATE_PROGRESS; 899 mPrintPreviewController.setUiShown(false); 900 Fragment fragment = PrintProgressFragment.newInstance(); 901 showFragment(fragment); 902 } 903 } 904 ensurePreviewUiShown()905 private void ensurePreviewUiShown() { 906 if (isFinishing()) { 907 return; 908 } 909 if (mUiState != UI_STATE_PREVIEW) { 910 mUiState = UI_STATE_PREVIEW; 911 mPrintPreviewController.setUiShown(true); 912 showFragment(null); 913 } 914 } 915 ensureErrorUiShown(CharSequence message, int action)916 private void ensureErrorUiShown(CharSequence message, int action) { 917 if (isFinishing()) { 918 return; 919 } 920 if (mUiState != UI_STATE_ERROR) { 921 mUiState = UI_STATE_ERROR; 922 mPrintPreviewController.setUiShown(false); 923 Fragment fragment = PrintErrorFragment.newInstance(message, action); 924 showFragment(fragment); 925 } 926 } 927 showFragment(Fragment newFragment)928 private void showFragment(Fragment newFragment) { 929 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 930 Fragment oldFragment = getFragmentManager().findFragmentByTag(FRAGMENT_TAG); 931 if (oldFragment != null) { 932 transaction.remove(oldFragment); 933 } 934 if (newFragment != null) { 935 transaction.add(R.id.embedded_content_container, newFragment, FRAGMENT_TAG); 936 } 937 transaction.commit(); 938 getFragmentManager().executePendingTransactions(); 939 } 940 requestCreatePdfFileOrFinish()941 private void requestCreatePdfFileOrFinish() { 942 if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) { 943 startCreateDocumentActivity(); 944 } else { 945 transformDocumentAndFinish(null); 946 } 947 } 948 updatePrintAttributesFromCapabilities(PrinterCapabilitiesInfo capabilities)949 private void updatePrintAttributesFromCapabilities(PrinterCapabilitiesInfo capabilities) { 950 PrintAttributes defaults = capabilities.getDefaults(); 951 952 // Sort the media sizes based on the current locale. 953 List<MediaSize> sortedMediaSizes = new ArrayList<>(capabilities.getMediaSizes()); 954 Collections.sort(sortedMediaSizes, mMediaSizeComparator); 955 956 PrintAttributes attributes = mPrintJob.getAttributes(); 957 958 // Media size. 959 MediaSize currMediaSize = attributes.getMediaSize(); 960 if (currMediaSize == null) { 961 attributes.setMediaSize(defaults.getMediaSize()); 962 } else { 963 boolean foundCurrentMediaSize = false; 964 // Try to find the current media size in the capabilities as 965 // it may be in a different orientation. 966 MediaSize currMediaSizePortrait = currMediaSize.asPortrait(); 967 final int mediaSizeCount = sortedMediaSizes.size(); 968 for (int i = 0; i < mediaSizeCount; i++) { 969 MediaSize mediaSize = sortedMediaSizes.get(i); 970 if (currMediaSizePortrait.equals(mediaSize.asPortrait())) { 971 attributes.setMediaSize(currMediaSize); 972 foundCurrentMediaSize = true; 973 break; 974 } 975 } 976 // If we did not find the current media size fall back to default. 977 if (!foundCurrentMediaSize) { 978 attributes.setMediaSize(defaults.getMediaSize()); 979 } 980 } 981 982 // Color mode. 983 final int colorMode = attributes.getColorMode(); 984 if ((capabilities.getColorModes() & colorMode) == 0) { 985 attributes.setColorMode(defaults.getColorMode()); 986 } 987 988 // Resolution 989 Resolution resolution = attributes.getResolution(); 990 if (resolution == null || !capabilities.getResolutions().contains(resolution)) { 991 attributes.setResolution(defaults.getResolution()); 992 } 993 994 // Margins. 995 attributes.setMinMargins(defaults.getMinMargins()); 996 } 997 updateDocument(boolean clearLastError)998 private boolean updateDocument(boolean clearLastError) { 999 if (!clearLastError && mPrintedDocument.hasUpdateError()) { 1000 return false; 1001 } 1002 1003 if (clearLastError && mPrintedDocument.hasUpdateError()) { 1004 mPrintedDocument.clearUpdateError(); 1005 } 1006 1007 final boolean preview = mState != STATE_PRINT_CONFIRMED; 1008 final PageRange[] pages; 1009 if (preview) { 1010 pages = mPrintPreviewController.getRequestedPages(); 1011 } else { 1012 pages = mPrintPreviewController.getSelectedPages(); 1013 } 1014 1015 final boolean willUpdate = mPrintedDocument.update(mPrintJob.getAttributes(), 1016 pages, preview); 1017 1018 if (willUpdate && !mPrintedDocument.hasLaidOutPages()) { 1019 // When the update is done we update the print preview. 1020 mProgressMessageController.post(); 1021 return true; 1022 } else if (!willUpdate) { 1023 // Update preview. 1024 updatePrintPreviewController(false); 1025 } 1026 1027 return false; 1028 } 1029 addCurrentPrinterToHistory()1030 private void addCurrentPrinterToHistory() { 1031 if (mCurrentPrinter != null) { 1032 PrinterId fakePdfPrinterId = mDestinationSpinnerAdapter.getPdfPrinter().getId(); 1033 if (!mCurrentPrinter.getId().equals(fakePdfPrinterId)) { 1034 mPrinterRegistry.addHistoricalPrinter(mCurrentPrinter); 1035 } 1036 } 1037 } 1038 cancelPrint()1039 private void cancelPrint() { 1040 setState(STATE_PRINT_CANCELED); 1041 updateOptionsUi(); 1042 if (mPrintedDocument.isUpdating()) { 1043 mPrintedDocument.cancel(); 1044 } 1045 doFinish(); 1046 } 1047 confirmPrint()1048 private void confirmPrint() { 1049 setState(STATE_PRINT_CONFIRMED); 1050 1051 updateOptionsUi(); 1052 addCurrentPrinterToHistory(); 1053 1054 PageRange[] selectedPages = computeSelectedPages(); 1055 if (!Arrays.equals(mSelectedPages, selectedPages)) { 1056 mSelectedPages = selectedPages; 1057 // Update preview. 1058 updatePrintPreviewController(false); 1059 } 1060 1061 updateSelectedPagesFromPreview(); 1062 mPrintPreviewController.closeOptions(); 1063 1064 if (canUpdateDocument()) { 1065 updateDocument(false); 1066 } 1067 1068 if (!mPrintedDocument.isUpdating()) { 1069 requestCreatePdfFileOrFinish(); 1070 } 1071 } 1072 bindUi()1073 private void bindUi() { 1074 // Summary 1075 mSummaryContainer = findViewById(R.id.summary_content); 1076 mSummaryCopies = (TextView) findViewById(R.id.copies_count_summary); 1077 mSummaryPaperSize = (TextView) findViewById(R.id.paper_size_summary); 1078 1079 // Options container 1080 mOptionsContent = (PrintContentView) findViewById(R.id.options_content); 1081 mOptionsContent.setOptionsStateChangeListener(this); 1082 mOptionsContent.setOpenOptionsController(this); 1083 1084 OnItemSelectedListener itemSelectedListener = new MyOnItemSelectedListener(); 1085 OnClickListener clickListener = new MyClickListener(); 1086 1087 // Copies 1088 mCopiesEditText = (EditText) findViewById(R.id.copies_edittext); 1089 mCopiesEditText.setOnFocusChangeListener(mSelectAllOnFocusListener); 1090 mCopiesEditText.setText(MIN_COPIES_STRING); 1091 mCopiesEditText.setSelection(mCopiesEditText.getText().length()); 1092 mCopiesEditText.addTextChangedListener(new EditTextWatcher()); 1093 1094 // Destination. 1095 mDestinationSpinnerAdapter.registerDataSetObserver(new PrintersObserver()); 1096 mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner); 1097 mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter); 1098 mDestinationSpinner.setOnItemSelectedListener(itemSelectedListener); 1099 1100 // Media size. 1101 mMediaSizeSpinnerAdapter = new ArrayAdapter<>( 1102 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1); 1103 mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner); 1104 mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter); 1105 mMediaSizeSpinner.setOnItemSelectedListener(itemSelectedListener); 1106 1107 // Color mode. 1108 mColorModeSpinnerAdapter = new ArrayAdapter<>( 1109 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1); 1110 mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner); 1111 mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter); 1112 mColorModeSpinner.setOnItemSelectedListener(itemSelectedListener); 1113 1114 // Orientation 1115 mOrientationSpinnerAdapter = new ArrayAdapter<>( 1116 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1); 1117 String[] orientationLabels = getResources().getStringArray( 1118 R.array.orientation_labels); 1119 mOrientationSpinnerAdapter.add(new SpinnerItem<>( 1120 ORIENTATION_PORTRAIT, orientationLabels[0])); 1121 mOrientationSpinnerAdapter.add(new SpinnerItem<>( 1122 ORIENTATION_LANDSCAPE, orientationLabels[1])); 1123 mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner); 1124 mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter); 1125 mOrientationSpinner.setOnItemSelectedListener(itemSelectedListener); 1126 1127 // Range options 1128 ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter = new ArrayAdapter<>( 1129 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1); 1130 mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner); 1131 mRangeOptionsSpinner.setAdapter(rangeOptionsSpinnerAdapter); 1132 mRangeOptionsSpinner.setOnItemSelectedListener(itemSelectedListener); 1133 updatePageRangeOptions(PrintDocumentInfo.PAGE_COUNT_UNKNOWN); 1134 1135 // Page range 1136 mPageRangeTitle = (TextView) findViewById(R.id.page_range_title); 1137 mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext); 1138 mPageRangeEditText.setOnFocusChangeListener(mSelectAllOnFocusListener); 1139 mPageRangeEditText.addTextChangedListener(new RangeTextWatcher()); 1140 1141 // Advanced options button. 1142 mMoreOptionsButton = (Button) findViewById(R.id.more_options_button); 1143 mMoreOptionsButton.setOnClickListener(clickListener); 1144 1145 // Print button 1146 mPrintButton = (ImageView) findViewById(R.id.print_button); 1147 mPrintButton.setOnClickListener(clickListener); 1148 } 1149 1150 private final class MyClickListener implements OnClickListener { 1151 @Override onClick(View view)1152 public void onClick(View view) { 1153 if (view == mPrintButton) { 1154 if (mCurrentPrinter != null) { 1155 confirmPrint(); 1156 } else { 1157 cancelPrint(); 1158 } 1159 } else if (view == mMoreOptionsButton) { 1160 if (mCurrentPrinter != null) { 1161 startAdvancedPrintOptionsActivity(mCurrentPrinter); 1162 } 1163 } 1164 } 1165 } 1166 canPrint(PrinterInfo printer)1167 private static boolean canPrint(PrinterInfo printer) { 1168 return printer.getCapabilities() != null 1169 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; 1170 } 1171 updateOptionsUi()1172 void updateOptionsUi() { 1173 // Always update the summary. 1174 updateSummary(); 1175 1176 if (mState == STATE_PRINT_CONFIRMED 1177 || mState == STATE_PRINT_COMPLETED 1178 || mState == STATE_PRINT_CANCELED 1179 || mState == STATE_UPDATE_FAILED 1180 || mState == STATE_CREATE_FILE_FAILED 1181 || mState == STATE_PRINTER_UNAVAILABLE 1182 || mState == STATE_UPDATE_SLOW) { 1183 if (mState != STATE_PRINTER_UNAVAILABLE) { 1184 mDestinationSpinner.setEnabled(false); 1185 } 1186 mCopiesEditText.setEnabled(false); 1187 mCopiesEditText.setFocusable(false); 1188 mMediaSizeSpinner.setEnabled(false); 1189 mColorModeSpinner.setEnabled(false); 1190 mOrientationSpinner.setEnabled(false); 1191 mRangeOptionsSpinner.setEnabled(false); 1192 mPageRangeEditText.setEnabled(false); 1193 mPrintButton.setVisibility(View.GONE); 1194 mMoreOptionsButton.setEnabled(false); 1195 return; 1196 } 1197 1198 // If no current printer, or it has no capabilities, or it is not 1199 // available, we disable all print options except the destination. 1200 if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) { 1201 mCopiesEditText.setEnabled(false); 1202 mCopiesEditText.setFocusable(false); 1203 mMediaSizeSpinner.setEnabled(false); 1204 mColorModeSpinner.setEnabled(false); 1205 mOrientationSpinner.setEnabled(false); 1206 mRangeOptionsSpinner.setEnabled(false); 1207 mPageRangeEditText.setEnabled(false); 1208 mPrintButton.setVisibility(View.GONE); 1209 mMoreOptionsButton.setEnabled(false); 1210 return; 1211 } 1212 1213 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities(); 1214 PrintAttributes defaultAttributes = capabilities.getDefaults(); 1215 1216 // Destination. 1217 mDestinationSpinner.setEnabled(true); 1218 1219 // Media size. 1220 mMediaSizeSpinner.setEnabled(true); 1221 1222 List<MediaSize> mediaSizes = new ArrayList<>(capabilities.getMediaSizes()); 1223 // Sort the media sizes based on the current locale. 1224 Collections.sort(mediaSizes, mMediaSizeComparator); 1225 1226 PrintAttributes attributes = mPrintJob.getAttributes(); 1227 1228 // If the media sizes changed, we update the adapter and the spinner. 1229 boolean mediaSizesChanged = false; 1230 final int mediaSizeCount = mediaSizes.size(); 1231 if (mediaSizeCount != mMediaSizeSpinnerAdapter.getCount()) { 1232 mediaSizesChanged = true; 1233 } else { 1234 for (int i = 0; i < mediaSizeCount; i++) { 1235 if (!mediaSizes.get(i).equals(mMediaSizeSpinnerAdapter.getItem(i).value)) { 1236 mediaSizesChanged = true; 1237 break; 1238 } 1239 } 1240 } 1241 if (mediaSizesChanged) { 1242 // Remember the old media size to try selecting it again. 1243 int oldMediaSizeNewIndex = AdapterView.INVALID_POSITION; 1244 MediaSize oldMediaSize = attributes.getMediaSize(); 1245 1246 // Rebuild the adapter data. 1247 mMediaSizeSpinnerAdapter.clear(); 1248 for (int i = 0; i < mediaSizeCount; i++) { 1249 MediaSize mediaSize = mediaSizes.get(i); 1250 if (oldMediaSize != null 1251 && mediaSize.asPortrait().equals(oldMediaSize.asPortrait())) { 1252 // Update the index of the old selection. 1253 oldMediaSizeNewIndex = i; 1254 } 1255 mMediaSizeSpinnerAdapter.add(new SpinnerItem<>( 1256 mediaSize, mediaSize.getLabel(getPackageManager()))); 1257 } 1258 1259 if (oldMediaSizeNewIndex != AdapterView.INVALID_POSITION) { 1260 // Select the old media size - nothing really changed. 1261 if (mMediaSizeSpinner.getSelectedItemPosition() != oldMediaSizeNewIndex) { 1262 mMediaSizeSpinner.setSelection(oldMediaSizeNewIndex); 1263 } 1264 } else { 1265 // Select the first or the default. 1266 final int mediaSizeIndex = Math.max(mediaSizes.indexOf( 1267 defaultAttributes.getMediaSize()), 0); 1268 if (mMediaSizeSpinner.getSelectedItemPosition() != mediaSizeIndex) { 1269 mMediaSizeSpinner.setSelection(mediaSizeIndex); 1270 } 1271 // Respect the orientation of the old selection. 1272 if (oldMediaSize != null) { 1273 if (oldMediaSize.isPortrait()) { 1274 attributes.setMediaSize(mMediaSizeSpinnerAdapter 1275 .getItem(mediaSizeIndex).value.asPortrait()); 1276 } else { 1277 attributes.setMediaSize(mMediaSizeSpinnerAdapter 1278 .getItem(mediaSizeIndex).value.asLandscape()); 1279 } 1280 } 1281 } 1282 } 1283 1284 // Color mode. 1285 mColorModeSpinner.setEnabled(true); 1286 final int colorModes = capabilities.getColorModes(); 1287 1288 // If the color modes changed, we update the adapter and the spinner. 1289 boolean colorModesChanged = false; 1290 if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) { 1291 colorModesChanged = true; 1292 } else { 1293 int remainingColorModes = colorModes; 1294 int adapterIndex = 0; 1295 while (remainingColorModes != 0) { 1296 final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes); 1297 final int colorMode = 1 << colorBitOffset; 1298 remainingColorModes &= ~colorMode; 1299 if (colorMode != mColorModeSpinnerAdapter.getItem(adapterIndex).value) { 1300 colorModesChanged = true; 1301 break; 1302 } 1303 adapterIndex++; 1304 } 1305 } 1306 if (colorModesChanged) { 1307 // Remember the old color mode to try selecting it again. 1308 int oldColorModeNewIndex = AdapterView.INVALID_POSITION; 1309 final int oldColorMode = attributes.getColorMode(); 1310 1311 // Rebuild the adapter data. 1312 mColorModeSpinnerAdapter.clear(); 1313 String[] colorModeLabels = getResources().getStringArray(R.array.color_mode_labels); 1314 int remainingColorModes = colorModes; 1315 while (remainingColorModes != 0) { 1316 final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes); 1317 final int colorMode = 1 << colorBitOffset; 1318 if (colorMode == oldColorMode) { 1319 // Update the index of the old selection. 1320 oldColorModeNewIndex = colorBitOffset; 1321 } 1322 remainingColorModes &= ~colorMode; 1323 mColorModeSpinnerAdapter.add(new SpinnerItem<>(colorMode, 1324 colorModeLabels[colorBitOffset])); 1325 } 1326 if (oldColorModeNewIndex != AdapterView.INVALID_POSITION) { 1327 // Select the old color mode - nothing really changed. 1328 if (mColorModeSpinner.getSelectedItemPosition() != oldColorModeNewIndex) { 1329 mColorModeSpinner.setSelection(oldColorModeNewIndex); 1330 } 1331 } else { 1332 // Select the default. 1333 final int selectedColorMode = colorModes & defaultAttributes.getColorMode(); 1334 final int itemCount = mColorModeSpinnerAdapter.getCount(); 1335 for (int i = 0; i < itemCount; i++) { 1336 SpinnerItem<Integer> item = mColorModeSpinnerAdapter.getItem(i); 1337 if (selectedColorMode == item.value) { 1338 if (mColorModeSpinner.getSelectedItemPosition() != i) { 1339 mColorModeSpinner.setSelection(i); 1340 } 1341 attributes.setColorMode(selectedColorMode); 1342 } 1343 } 1344 } 1345 } 1346 1347 // Orientation 1348 mOrientationSpinner.setEnabled(true); 1349 MediaSize mediaSize = attributes.getMediaSize(); 1350 if (mediaSize != null) { 1351 if (mediaSize.isPortrait() 1352 && mOrientationSpinner.getSelectedItemPosition() != 0) { 1353 mOrientationSpinner.setSelection(0); 1354 } else if (!mediaSize.isPortrait() 1355 && mOrientationSpinner.getSelectedItemPosition() != 1) { 1356 mOrientationSpinner.setSelection(1); 1357 } 1358 } 1359 1360 // Range options 1361 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info; 1362 final int pageCount = getAdjustedPageCount(info); 1363 if (info != null && pageCount > 0) { 1364 if (pageCount == 1) { 1365 mRangeOptionsSpinner.setEnabled(false); 1366 } else { 1367 mRangeOptionsSpinner.setEnabled(true); 1368 if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) { 1369 if (!mPageRangeEditText.isEnabled()) { 1370 mPageRangeEditText.setEnabled(true); 1371 mPageRangeEditText.setVisibility(View.VISIBLE); 1372 mPageRangeTitle.setVisibility(View.VISIBLE); 1373 mPageRangeEditText.requestFocus(); 1374 InputMethodManager imm = (InputMethodManager) 1375 getSystemService(Context.INPUT_METHOD_SERVICE); 1376 imm.showSoftInput(mPageRangeEditText, 0); 1377 } 1378 } else { 1379 mPageRangeEditText.setEnabled(false); 1380 mPageRangeEditText.setVisibility(View.INVISIBLE); 1381 mPageRangeTitle.setVisibility(View.INVISIBLE); 1382 } 1383 } 1384 } else { 1385 if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) { 1386 mRangeOptionsSpinner.setSelection(0); 1387 mPageRangeEditText.setText(""); 1388 } 1389 mRangeOptionsSpinner.setEnabled(false); 1390 mPageRangeEditText.setEnabled(false); 1391 mPageRangeEditText.setVisibility(View.INVISIBLE); 1392 mPageRangeTitle.setVisibility(View.INVISIBLE); 1393 } 1394 1395 final int newPageCount = getAdjustedPageCount(info); 1396 if (newPageCount != mCurrentPageCount) { 1397 mCurrentPageCount = newPageCount; 1398 updatePageRangeOptions(newPageCount); 1399 } 1400 1401 // Advanced print options 1402 ComponentName serviceName = mCurrentPrinter.getId().getServiceName(); 1403 if (!TextUtils.isEmpty(PrintOptionUtils.getAdvancedOptionsActivityName( 1404 this, serviceName))) { 1405 mMoreOptionsButton.setVisibility(View.VISIBLE); 1406 mMoreOptionsButton.setEnabled(true); 1407 } else { 1408 mMoreOptionsButton.setVisibility(View.GONE); 1409 mMoreOptionsButton.setEnabled(false); 1410 } 1411 1412 // Print 1413 if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) { 1414 mPrintButton.setImageResource(com.android.internal.R.drawable.ic_print); 1415 mPrintButton.setContentDescription(getString(R.string.print_button)); 1416 } else { 1417 mPrintButton.setImageResource(R.drawable.ic_menu_savetopdf); 1418 mPrintButton.setContentDescription(getString(R.string.savetopdf_button)); 1419 } 1420 if (!mPrintedDocument.getDocumentInfo().laidout 1421 ||(mRangeOptionsSpinner.getSelectedItemPosition() == 1 1422 && (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors())) 1423 || (mRangeOptionsSpinner.getSelectedItemPosition() == 0 1424 && (mPrintedDocument.getDocumentInfo() == null || hasErrors()))) { 1425 mPrintButton.setVisibility(View.GONE); 1426 } else { 1427 mPrintButton.setVisibility(View.VISIBLE); 1428 } 1429 1430 // Copies 1431 if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) { 1432 mCopiesEditText.setEnabled(true); 1433 mCopiesEditText.setFocusableInTouchMode(true); 1434 } else { 1435 CharSequence text = mCopiesEditText.getText(); 1436 if (TextUtils.isEmpty(text) || !MIN_COPIES_STRING.equals(text.toString())) { 1437 mCopiesEditText.setText(MIN_COPIES_STRING); 1438 } 1439 mCopiesEditText.setEnabled(false); 1440 mCopiesEditText.setFocusable(false); 1441 } 1442 if (mCopiesEditText.getError() == null 1443 && TextUtils.isEmpty(mCopiesEditText.getText())) { 1444 mCopiesEditText.setText(MIN_COPIES_STRING); 1445 mCopiesEditText.requestFocus(); 1446 } 1447 } 1448 updateSummary()1449 private void updateSummary() { 1450 CharSequence copiesText = null; 1451 CharSequence mediaSizeText = null; 1452 1453 if (!TextUtils.isEmpty(mCopiesEditText.getText())) { 1454 copiesText = mCopiesEditText.getText(); 1455 mSummaryCopies.setText(copiesText); 1456 } 1457 1458 final int selectedMediaIndex = mMediaSizeSpinner.getSelectedItemPosition(); 1459 if (selectedMediaIndex >= 0) { 1460 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(selectedMediaIndex); 1461 mediaSizeText = mediaItem.label; 1462 mSummaryPaperSize.setText(mediaSizeText); 1463 } 1464 1465 if (!TextUtils.isEmpty(copiesText) && !TextUtils.isEmpty(mediaSizeText)) { 1466 String summaryText = getString(R.string.summary_template, copiesText, mediaSizeText); 1467 mSummaryContainer.setContentDescription(summaryText); 1468 } 1469 } 1470 updatePageRangeOptions(int pageCount)1471 private void updatePageRangeOptions(int pageCount) { 1472 ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter = 1473 (ArrayAdapter) mRangeOptionsSpinner.getAdapter(); 1474 rangeOptionsSpinnerAdapter.clear(); 1475 1476 final int[] rangeOptionsValues = getResources().getIntArray( 1477 R.array.page_options_values); 1478 1479 String pageCountLabel = (pageCount > 0) ? String.valueOf(pageCount) : ""; 1480 String[] rangeOptionsLabels = new String[] { 1481 getString(R.string.template_all_pages, pageCountLabel), 1482 getString(R.string.template_page_range, pageCountLabel) 1483 }; 1484 1485 final int rangeOptionsCount = rangeOptionsLabels.length; 1486 for (int i = 0; i < rangeOptionsCount; i++) { 1487 rangeOptionsSpinnerAdapter.add(new SpinnerItem<>( 1488 rangeOptionsValues[i], rangeOptionsLabels[i])); 1489 } 1490 } 1491 computeSelectedPages()1492 private PageRange[] computeSelectedPages() { 1493 if (hasErrors()) { 1494 return null; 1495 } 1496 1497 if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) { 1498 List<PageRange> pageRanges = new ArrayList<>(); 1499 mStringCommaSplitter.setString(mPageRangeEditText.getText().toString()); 1500 1501 while (mStringCommaSplitter.hasNext()) { 1502 String range = mStringCommaSplitter.next().trim(); 1503 if (TextUtils.isEmpty(range)) { 1504 continue; 1505 } 1506 final int dashIndex = range.indexOf('-'); 1507 final int fromIndex; 1508 final int toIndex; 1509 1510 if (dashIndex > 0) { 1511 fromIndex = Integer.parseInt(range.substring(0, dashIndex).trim()) - 1; 1512 // It is possible that the dash is at the end since the input 1513 // verification can has to allow the user to keep entering if 1514 // this would lead to a valid input. So we handle this. 1515 if (dashIndex < range.length() - 1) { 1516 String fromString = range.substring(dashIndex + 1, range.length()).trim(); 1517 toIndex = Integer.parseInt(fromString) - 1; 1518 } else { 1519 toIndex = fromIndex; 1520 } 1521 } else { 1522 fromIndex = toIndex = Integer.parseInt(range) - 1; 1523 } 1524 1525 PageRange pageRange = new PageRange(Math.min(fromIndex, toIndex), 1526 Math.max(fromIndex, toIndex)); 1527 pageRanges.add(pageRange); 1528 } 1529 1530 PageRange[] pageRangesArray = new PageRange[pageRanges.size()]; 1531 pageRanges.toArray(pageRangesArray); 1532 1533 return PageRangeUtils.normalize(pageRangesArray); 1534 } 1535 1536 return ALL_PAGES_ARRAY; 1537 } 1538 getAdjustedPageCount(PrintDocumentInfo info)1539 private int getAdjustedPageCount(PrintDocumentInfo info) { 1540 if (info != null) { 1541 final int pageCount = info.getPageCount(); 1542 if (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) { 1543 return pageCount; 1544 } 1545 } 1546 // If the app does not tell us how many pages are in the 1547 // doc we ask for all pages and use the document page count. 1548 return mPrintPreviewController.getFilePageCount(); 1549 } 1550 hasErrors()1551 private boolean hasErrors() { 1552 return (mCopiesEditText.getError() != null) 1553 || (mPageRangeEditText.getVisibility() == View.VISIBLE 1554 && mPageRangeEditText.getError() != null); 1555 } 1556 onPrinterAvailable(PrinterInfo printer)1557 public void onPrinterAvailable(PrinterInfo printer) { 1558 if (mCurrentPrinter.equals(printer)) { 1559 setState(STATE_CONFIGURING); 1560 if (canUpdateDocument()) { 1561 updateDocument(false); 1562 } 1563 ensurePreviewUiShown(); 1564 updateOptionsUi(); 1565 } 1566 } 1567 onPrinterUnavailable(PrinterInfo printer)1568 public void onPrinterUnavailable(PrinterInfo printer) { 1569 if (mCurrentPrinter.getId().equals(printer.getId())) { 1570 setState(STATE_PRINTER_UNAVAILABLE); 1571 if (mPrintedDocument.isUpdating()) { 1572 mPrintedDocument.cancel(); 1573 } 1574 ensureErrorUiShown(getString(R.string.print_error_printer_unavailable), 1575 PrintErrorFragment.ACTION_NONE); 1576 updateOptionsUi(); 1577 } 1578 } 1579 canUpdateDocument()1580 private boolean canUpdateDocument() { 1581 if (mPrintedDocument.isDestroyed()) { 1582 return false; 1583 } 1584 1585 if (hasErrors()) { 1586 return false; 1587 } 1588 1589 PrintAttributes attributes = mPrintJob.getAttributes(); 1590 1591 final int colorMode = attributes.getColorMode(); 1592 if (colorMode != PrintAttributes.COLOR_MODE_COLOR 1593 && colorMode != PrintAttributes.COLOR_MODE_MONOCHROME) { 1594 return false; 1595 } 1596 if (attributes.getMediaSize() == null) { 1597 return false; 1598 } 1599 if (attributes.getMinMargins() == null) { 1600 return false; 1601 } 1602 if (attributes.getResolution() == null) { 1603 return false; 1604 } 1605 1606 if (mCurrentPrinter == null) { 1607 return false; 1608 } 1609 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities(); 1610 if (capabilities == null) { 1611 return false; 1612 } 1613 if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) { 1614 return false; 1615 } 1616 1617 return true; 1618 } 1619 transformDocumentAndFinish(final Uri writeToUri)1620 private void transformDocumentAndFinish(final Uri writeToUri) { 1621 // If saving to PDF, apply the attibutes as we are acting as a print service. 1622 PrintAttributes attributes = mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter 1623 ? mPrintJob.getAttributes() : null; 1624 new DocumentTransformer(this, mPrintJob, mFileProvider, attributes, new Runnable() { 1625 @Override 1626 public void run() { 1627 if (writeToUri != null) { 1628 mPrintedDocument.writeContent(getContentResolver(), writeToUri); 1629 } 1630 doFinish(); 1631 } 1632 }).transform(); 1633 } 1634 doFinish()1635 private void doFinish() { 1636 if (mState != STATE_INITIALIZING) { 1637 mProgressMessageController.cancel(); 1638 mPrinterRegistry.setTrackedPrinter(null); 1639 mSpoolerProvider.destroy(); 1640 mPrintedDocument.finish(); 1641 mPrintedDocument.destroy(); 1642 mPrintPreviewController.destroy(new Runnable() { 1643 @Override 1644 public void run() { 1645 finish(); 1646 } 1647 }); 1648 } else { 1649 finish(); 1650 } 1651 } 1652 1653 private final class SpinnerItem<T> { 1654 final T value; 1655 final CharSequence label; 1656 SpinnerItem(T value, CharSequence label)1657 public SpinnerItem(T value, CharSequence label) { 1658 this.value = value; 1659 this.label = label; 1660 } 1661 toString()1662 public String toString() { 1663 return label.toString(); 1664 } 1665 } 1666 1667 private final class PrinterAvailabilityDetector implements Runnable { 1668 private static final long UNAVAILABLE_TIMEOUT_MILLIS = 10000; // 10sec 1669 1670 private boolean mPosted; 1671 1672 private boolean mPrinterUnavailable; 1673 1674 private PrinterInfo mPrinter; 1675 updatePrinter(PrinterInfo printer)1676 public void updatePrinter(PrinterInfo printer) { 1677 if (printer.equals(mDestinationSpinnerAdapter.getPdfPrinter())) { 1678 return; 1679 } 1680 1681 final boolean available = printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE 1682 && printer.getCapabilities() != null; 1683 final boolean notifyIfAvailable; 1684 1685 if (mPrinter == null || !mPrinter.getId().equals(printer.getId())) { 1686 notifyIfAvailable = true; 1687 unpostIfNeeded(); 1688 mPrinterUnavailable = false; 1689 mPrinter = new PrinterInfo.Builder(printer).build(); 1690 } else { 1691 notifyIfAvailable = 1692 (mPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE 1693 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) 1694 || (mPrinter.getCapabilities() == null 1695 && printer.getCapabilities() != null); 1696 mPrinter.copyFrom(printer); 1697 } 1698 1699 if (available) { 1700 unpostIfNeeded(); 1701 mPrinterUnavailable = false; 1702 if (notifyIfAvailable) { 1703 onPrinterAvailable(mPrinter); 1704 } 1705 } else { 1706 if (!mPrinterUnavailable) { 1707 postIfNeeded(); 1708 } 1709 } 1710 } 1711 cancel()1712 public void cancel() { 1713 unpostIfNeeded(); 1714 mPrinterUnavailable = false; 1715 } 1716 postIfNeeded()1717 private void postIfNeeded() { 1718 if (!mPosted) { 1719 mPosted = true; 1720 mDestinationSpinner.postDelayed(this, UNAVAILABLE_TIMEOUT_MILLIS); 1721 } 1722 } 1723 unpostIfNeeded()1724 private void unpostIfNeeded() { 1725 if (mPosted) { 1726 mPosted = false; 1727 mDestinationSpinner.removeCallbacks(this); 1728 } 1729 } 1730 1731 @Override run()1732 public void run() { 1733 mPosted = false; 1734 mPrinterUnavailable = true; 1735 onPrinterUnavailable(mPrinter); 1736 } 1737 } 1738 1739 private static final class PrinterHolder { 1740 PrinterInfo printer; 1741 boolean removed; 1742 PrinterHolder(PrinterInfo printer)1743 public PrinterHolder(PrinterInfo printer) { 1744 this.printer = printer; 1745 } 1746 } 1747 1748 private final class DestinationAdapter extends BaseAdapter 1749 implements PrinterRegistry.OnPrintersChangeListener { 1750 private final List<PrinterHolder> mPrinterHolders = new ArrayList<>(); 1751 1752 private final PrinterHolder mFakePdfPrinterHolder; 1753 1754 private boolean mHistoricalPrintersLoaded; 1755 DestinationAdapter()1756 public DestinationAdapter() { 1757 mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded(); 1758 if (mHistoricalPrintersLoaded) { 1759 addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters()); 1760 } 1761 mPrinterRegistry.setOnPrintersChangeListener(this); 1762 mFakePdfPrinterHolder = new PrinterHolder(createFakePdfPrinter()); 1763 } 1764 getPdfPrinter()1765 public PrinterInfo getPdfPrinter() { 1766 return mFakePdfPrinterHolder.printer; 1767 } 1768 getPrinterIndex(PrinterId printerId)1769 public int getPrinterIndex(PrinterId printerId) { 1770 for (int i = 0; i < getCount(); i++) { 1771 PrinterHolder printerHolder = (PrinterHolder) getItem(i); 1772 if (printerHolder != null && !printerHolder.removed 1773 && printerHolder.printer.getId().equals(printerId)) { 1774 return i; 1775 } 1776 } 1777 return AdapterView.INVALID_POSITION; 1778 } 1779 ensurePrinterInVisibleAdapterPosition(PrinterId printerId)1780 public void ensurePrinterInVisibleAdapterPosition(PrinterId printerId) { 1781 final int printerCount = mPrinterHolders.size(); 1782 for (int i = 0; i < printerCount; i++) { 1783 PrinterHolder printerHolder = mPrinterHolders.get(i); 1784 if (printerHolder.printer.getId().equals(printerId)) { 1785 // If already in the list - do nothing. 1786 if (i < getCount() - 2) { 1787 return; 1788 } 1789 // Else replace the last one (two items are not printers). 1790 final int lastPrinterIndex = getCount() - 3; 1791 mPrinterHolders.set(i, mPrinterHolders.get(lastPrinterIndex)); 1792 mPrinterHolders.set(lastPrinterIndex, printerHolder); 1793 notifyDataSetChanged(); 1794 return; 1795 } 1796 } 1797 } 1798 1799 @Override getCount()1800 public int getCount() { 1801 if (mHistoricalPrintersLoaded) { 1802 return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT); 1803 } 1804 return 0; 1805 } 1806 1807 @Override isEnabled(int position)1808 public boolean isEnabled(int position) { 1809 Object item = getItem(position); 1810 if (item instanceof PrinterHolder) { 1811 PrinterHolder printerHolder = (PrinterHolder) item; 1812 return !printerHolder.removed 1813 && printerHolder.printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; 1814 } 1815 return true; 1816 } 1817 1818 @Override getItem(int position)1819 public Object getItem(int position) { 1820 if (mPrinterHolders.isEmpty()) { 1821 if (position == 0) { 1822 return mFakePdfPrinterHolder; 1823 } 1824 } else { 1825 if (position < 1) { 1826 return mPrinterHolders.get(position); 1827 } 1828 if (position == 1) { 1829 return mFakePdfPrinterHolder; 1830 } 1831 if (position < getCount() - 1) { 1832 return mPrinterHolders.get(position - 1); 1833 } 1834 } 1835 return null; 1836 } 1837 1838 @Override getItemId(int position)1839 public long getItemId(int position) { 1840 if (mPrinterHolders.isEmpty()) { 1841 if (position == 0) { 1842 return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF; 1843 } else if (position == 1) { 1844 return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS; 1845 } 1846 } else { 1847 if (position == 1) { 1848 return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF; 1849 } 1850 if (position == getCount() - 1) { 1851 return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS; 1852 } 1853 } 1854 return position; 1855 } 1856 1857 @Override getDropDownView(int position, View convertView, ViewGroup parent)1858 public View getDropDownView(int position, View convertView, ViewGroup parent) { 1859 View view = getView(position, convertView, parent); 1860 view.setEnabled(isEnabled(position)); 1861 return view; 1862 } 1863 1864 @Override getView(int position, View convertView, ViewGroup parent)1865 public View getView(int position, View convertView, ViewGroup parent) { 1866 if (convertView == null) { 1867 convertView = getLayoutInflater().inflate( 1868 R.layout.printer_dropdown_item, parent, false); 1869 } 1870 1871 CharSequence title = null; 1872 CharSequence subtitle = null; 1873 Drawable icon = null; 1874 1875 if (mPrinterHolders.isEmpty()) { 1876 if (position == 0 && getPdfPrinter() != null) { 1877 PrinterHolder printerHolder = (PrinterHolder) getItem(position); 1878 title = printerHolder.printer.getName(); 1879 icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf); 1880 } else if (position == 1) { 1881 title = getString(R.string.all_printers); 1882 } 1883 } else { 1884 if (position == 1 && getPdfPrinter() != null) { 1885 PrinterHolder printerHolder = (PrinterHolder) getItem(position); 1886 title = printerHolder.printer.getName(); 1887 icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf); 1888 } else if (position == getCount() - 1) { 1889 title = getString(R.string.all_printers); 1890 } else { 1891 PrinterHolder printerHolder = (PrinterHolder) getItem(position); 1892 title = printerHolder.printer.getName(); 1893 try { 1894 PackageInfo packageInfo = getPackageManager().getPackageInfo( 1895 printerHolder.printer.getId().getServiceName().getPackageName(), 0); 1896 subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager()); 1897 icon = packageInfo.applicationInfo.loadIcon(getPackageManager()); 1898 } catch (NameNotFoundException nnfe) { 1899 /* ignore */ 1900 } 1901 } 1902 } 1903 1904 TextView titleView = (TextView) convertView.findViewById(R.id.title); 1905 titleView.setText(title); 1906 1907 TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle); 1908 if (!TextUtils.isEmpty(subtitle)) { 1909 subtitleView.setText(subtitle); 1910 subtitleView.setVisibility(View.VISIBLE); 1911 } else { 1912 subtitleView.setText(null); 1913 subtitleView.setVisibility(View.GONE); 1914 } 1915 1916 ImageView iconView = (ImageView) convertView.findViewById(R.id.icon); 1917 if (icon != null) { 1918 iconView.setImageDrawable(icon); 1919 iconView.setVisibility(View.VISIBLE); 1920 } else { 1921 iconView.setVisibility(View.INVISIBLE); 1922 } 1923 1924 return convertView; 1925 } 1926 1927 @Override onPrintersChanged(List<PrinterInfo> printers)1928 public void onPrintersChanged(List<PrinterInfo> printers) { 1929 // We rearrange the printers if the user selects a printer 1930 // not shown in the initial short list. Therefore, we have 1931 // to keep the printer order. 1932 1933 // Check if historical printers are loaded as this adapter is open 1934 // for busyness only if they are. This member is updated here and 1935 // when the adapter is created because the historical printers may 1936 // be loaded before or after the adapter is created. 1937 mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded(); 1938 1939 // No old printers - do not bother keeping their position. 1940 if (mPrinterHolders.isEmpty()) { 1941 addPrinters(mPrinterHolders, printers); 1942 notifyDataSetChanged(); 1943 return; 1944 } 1945 1946 // Add the new printers to a map. 1947 ArrayMap<PrinterId, PrinterInfo> newPrintersMap = new ArrayMap<>(); 1948 final int printerCount = printers.size(); 1949 for (int i = 0; i < printerCount; i++) { 1950 PrinterInfo printer = printers.get(i); 1951 newPrintersMap.put(printer.getId(), printer); 1952 } 1953 1954 List<PrinterHolder> newPrinterHolders = new ArrayList<>(); 1955 1956 // Update printers we already have which are either updated or removed. 1957 // We do not remove printers if the currently selected printer is removed 1958 // to prevent the user printing to a wrong printer. 1959 final int oldPrinterCount = mPrinterHolders.size(); 1960 for (int i = 0; i < oldPrinterCount; i++) { 1961 PrinterHolder printerHolder = mPrinterHolders.get(i); 1962 PrinterId oldPrinterId = printerHolder.printer.getId(); 1963 PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId); 1964 if (updatedPrinter != null) { 1965 printerHolder.printer = updatedPrinter; 1966 } else { 1967 printerHolder.removed = true; 1968 } 1969 newPrinterHolders.add(printerHolder); 1970 } 1971 1972 // Add the rest of the new printers, i.e. what is left. 1973 addPrinters(newPrinterHolders, newPrintersMap.values()); 1974 1975 mPrinterHolders.clear(); 1976 mPrinterHolders.addAll(newPrinterHolders); 1977 1978 notifyDataSetChanged(); 1979 } 1980 1981 @Override onPrintersInvalid()1982 public void onPrintersInvalid() { 1983 mPrinterHolders.clear(); 1984 notifyDataSetInvalidated(); 1985 } 1986 getPrinterHolder(PrinterId printerId)1987 public PrinterHolder getPrinterHolder(PrinterId printerId) { 1988 final int itemCount = getCount(); 1989 for (int i = 0; i < itemCount; i++) { 1990 Object item = getItem(i); 1991 if (item instanceof PrinterHolder) { 1992 PrinterHolder printerHolder = (PrinterHolder) item; 1993 if (printerId.equals(printerHolder.printer.getId())) { 1994 return printerHolder; 1995 } 1996 } 1997 } 1998 return null; 1999 } 2000 pruneRemovedPrinters()2001 public void pruneRemovedPrinters() { 2002 final int holderCounts = mPrinterHolders.size(); 2003 for (int i = holderCounts - 1; i >= 0; i--) { 2004 PrinterHolder printerHolder = mPrinterHolders.get(i); 2005 if (printerHolder.removed) { 2006 mPrinterHolders.remove(i); 2007 } 2008 } 2009 } 2010 addPrinters(List<PrinterHolder> list, Collection<PrinterInfo> printers)2011 private void addPrinters(List<PrinterHolder> list, Collection<PrinterInfo> printers) { 2012 for (PrinterInfo printer : printers) { 2013 PrinterHolder printerHolder = new PrinterHolder(printer); 2014 list.add(printerHolder); 2015 } 2016 } 2017 createFakePdfPrinter()2018 private PrinterInfo createFakePdfPrinter() { 2019 MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintActivity.this); 2020 2021 PrinterId printerId = new PrinterId(getComponentName(), "PDF printer"); 2022 2023 PrinterCapabilitiesInfo.Builder builder = 2024 new PrinterCapabilitiesInfo.Builder(printerId); 2025 2026 String[] mediaSizeIds = getResources().getStringArray(R.array.pdf_printer_media_sizes); 2027 final int mediaSizeIdCount = mediaSizeIds.length; 2028 for (int i = 0; i < mediaSizeIdCount; i++) { 2029 String id = mediaSizeIds[i]; 2030 MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id); 2031 builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize)); 2032 } 2033 2034 builder.addResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300), 2035 true); 2036 builder.setColorModes(PrintAttributes.COLOR_MODE_COLOR 2037 | PrintAttributes.COLOR_MODE_MONOCHROME, PrintAttributes.COLOR_MODE_COLOR); 2038 2039 return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf), 2040 PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build(); 2041 } 2042 } 2043 2044 private final class PrintersObserver extends DataSetObserver { 2045 @Override onChanged()2046 public void onChanged() { 2047 PrinterInfo oldPrinterState = mCurrentPrinter; 2048 if (oldPrinterState == null) { 2049 return; 2050 } 2051 2052 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder( 2053 oldPrinterState.getId()); 2054 if (printerHolder == null) { 2055 return; 2056 } 2057 PrinterInfo newPrinterState = printerHolder.printer; 2058 2059 if (!printerHolder.removed) { 2060 mDestinationSpinnerAdapter.pruneRemovedPrinters(); 2061 } else { 2062 onPrinterUnavailable(newPrinterState); 2063 } 2064 2065 if (oldPrinterState.equals(newPrinterState)) { 2066 return; 2067 } 2068 2069 PrinterCapabilitiesInfo oldCapab = oldPrinterState.getCapabilities(); 2070 PrinterCapabilitiesInfo newCapab = newPrinterState.getCapabilities(); 2071 2072 final boolean hasCapab = newCapab != null; 2073 final boolean gotCapab = oldCapab == null && newCapab != null; 2074 final boolean lostCapab = oldCapab != null && newCapab == null; 2075 final boolean capabChanged = capabilitiesChanged(oldCapab, newCapab); 2076 2077 final int oldStatus = oldPrinterState.getStatus(); 2078 final int newStatus = newPrinterState.getStatus(); 2079 2080 final boolean isActive = newStatus != PrinterInfo.STATUS_UNAVAILABLE; 2081 final boolean becameActive = (oldStatus == PrinterInfo.STATUS_UNAVAILABLE 2082 && oldStatus != newStatus); 2083 final boolean becameInactive = (newStatus == PrinterInfo.STATUS_UNAVAILABLE 2084 && oldStatus != newStatus); 2085 2086 mPrinterAvailabilityDetector.updatePrinter(newPrinterState); 2087 2088 oldPrinterState.copyFrom(newPrinterState); 2089 2090 if ((isActive && gotCapab) || (becameActive && hasCapab)) { 2091 if (hasCapab && capabChanged) { 2092 updatePrintAttributesFromCapabilities(newCapab); 2093 updatePrintPreviewController(false); 2094 } 2095 onPrinterAvailable(newPrinterState); 2096 } else if ((becameInactive && hasCapab) || (isActive && lostCapab)) { 2097 onPrinterUnavailable(newPrinterState); 2098 } 2099 2100 final boolean updateNeeded = ((capabChanged && hasCapab && isActive) 2101 || (becameActive && hasCapab) || (isActive && gotCapab)); 2102 2103 if (updateNeeded && canUpdateDocument()) { 2104 updateDocument(false); 2105 } 2106 2107 updateOptionsUi(); 2108 } 2109 capabilitiesChanged(PrinterCapabilitiesInfo oldCapabilities, PrinterCapabilitiesInfo newCapabilities)2110 private boolean capabilitiesChanged(PrinterCapabilitiesInfo oldCapabilities, 2111 PrinterCapabilitiesInfo newCapabilities) { 2112 if (oldCapabilities == null) { 2113 if (newCapabilities != null) { 2114 return true; 2115 } 2116 } else if (!oldCapabilities.equals(newCapabilities)) { 2117 return true; 2118 } 2119 return false; 2120 } 2121 } 2122 2123 private final class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener { 2124 @Override onItemSelected(AdapterView<?> spinner, View view, int position, long id)2125 public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) { 2126 if (spinner == mDestinationSpinner) { 2127 if (position == AdapterView.INVALID_POSITION) { 2128 return; 2129 } 2130 2131 if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) { 2132 startSelectPrinterActivity(); 2133 return; 2134 } 2135 2136 PrinterHolder currentItem = (PrinterHolder) mDestinationSpinner.getSelectedItem(); 2137 PrinterInfo currentPrinter = (currentItem != null) ? currentItem.printer : null; 2138 2139 // Why on earth item selected is called if no selection changed. 2140 if (mCurrentPrinter == currentPrinter) { 2141 return; 2142 } 2143 2144 mCurrentPrinter = currentPrinter; 2145 2146 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder( 2147 currentPrinter.getId()); 2148 if (!printerHolder.removed) { 2149 setState(STATE_CONFIGURING); 2150 mDestinationSpinnerAdapter.pruneRemovedPrinters(); 2151 ensurePreviewUiShown(); 2152 } 2153 2154 mPrintJob.setPrinterId(currentPrinter.getId()); 2155 mPrintJob.setPrinterName(currentPrinter.getName()); 2156 2157 mPrinterRegistry.setTrackedPrinter(currentPrinter.getId()); 2158 2159 PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities(); 2160 if (capabilities != null) { 2161 updatePrintAttributesFromCapabilities(capabilities); 2162 } 2163 2164 mPrinterAvailabilityDetector.updatePrinter(currentPrinter); 2165 } else if (spinner == mMediaSizeSpinner) { 2166 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position); 2167 PrintAttributes attributes = mPrintJob.getAttributes(); 2168 if (mOrientationSpinner.getSelectedItemPosition() == 0) { 2169 attributes.setMediaSize(mediaItem.value.asPortrait()); 2170 } else { 2171 attributes.setMediaSize(mediaItem.value.asLandscape()); 2172 } 2173 } else if (spinner == mColorModeSpinner) { 2174 SpinnerItem<Integer> colorModeItem = mColorModeSpinnerAdapter.getItem(position); 2175 mPrintJob.getAttributes().setColorMode(colorModeItem.value); 2176 } else if (spinner == mOrientationSpinner) { 2177 SpinnerItem<Integer> orientationItem = mOrientationSpinnerAdapter.getItem(position); 2178 PrintAttributes attributes = mPrintJob.getAttributes(); 2179 if (mMediaSizeSpinner.getSelectedItem() != null) { 2180 if (orientationItem.value == ORIENTATION_PORTRAIT) { 2181 attributes.copyFrom(attributes.asPortrait()); 2182 } else { 2183 attributes.copyFrom(attributes.asLandscape()); 2184 } 2185 } 2186 } else if (spinner == mRangeOptionsSpinner) { 2187 if (mRangeOptionsSpinner.getSelectedItemPosition() == 0) { 2188 mPageRangeEditText.setText(""); 2189 } else if (TextUtils.isEmpty(mPageRangeEditText.getText())) { 2190 mPageRangeEditText.setError(""); 2191 } 2192 } 2193 2194 if (canUpdateDocument()) { 2195 updateDocument(false); 2196 } 2197 2198 updateOptionsUi(); 2199 } 2200 2201 @Override onNothingSelected(AdapterView<?> parent)2202 public void onNothingSelected(AdapterView<?> parent) { 2203 /* do nothing*/ 2204 } 2205 } 2206 2207 private final class SelectAllOnFocusListener implements OnFocusChangeListener { 2208 @Override onFocusChange(View view, boolean hasFocus)2209 public void onFocusChange(View view, boolean hasFocus) { 2210 EditText editText = (EditText) view; 2211 if (!TextUtils.isEmpty(editText.getText())) { 2212 editText.setSelection(editText.getText().length()); 2213 } 2214 } 2215 } 2216 2217 private final class RangeTextWatcher implements TextWatcher { 2218 @Override onTextChanged(CharSequence s, int start, int before, int count)2219 public void onTextChanged(CharSequence s, int start, int before, int count) { 2220 /* do nothing */ 2221 } 2222 2223 @Override beforeTextChanged(CharSequence s, int start, int count, int after)2224 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 2225 /* do nothing */ 2226 } 2227 2228 @Override afterTextChanged(Editable editable)2229 public void afterTextChanged(Editable editable) { 2230 final boolean hadErrors = hasErrors(); 2231 2232 String text = editable.toString(); 2233 2234 if (TextUtils.isEmpty(text)) { 2235 mPageRangeEditText.setError(""); 2236 updateOptionsUi(); 2237 return; 2238 } 2239 2240 String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////"); 2241 if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) { 2242 mPageRangeEditText.setError(""); 2243 updateOptionsUi(); 2244 return; 2245 } 2246 2247 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info; 2248 final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0; 2249 2250 // The range 2251 Matcher matcher = PATTERN_DIGITS.matcher(text); 2252 while (matcher.find()) { 2253 String numericString = text.substring(matcher.start(), matcher.end()).trim(); 2254 if (TextUtils.isEmpty(numericString)) { 2255 continue; 2256 } 2257 final int pageIndex = Integer.parseInt(numericString); 2258 if (pageIndex < 1 || pageIndex > pageCount) { 2259 mPageRangeEditText.setError(""); 2260 updateOptionsUi(); 2261 return; 2262 } 2263 } 2264 2265 // We intentionally do not catch the case of the from page being 2266 // greater than the to page. When computing the requested pages 2267 // we just swap them if necessary. 2268 2269 mPageRangeEditText.setError(null); 2270 mPrintButton.setEnabled(true); 2271 updateOptionsUi(); 2272 2273 if (hadErrors && !hasErrors()) { 2274 updateOptionsUi(); 2275 } 2276 } 2277 } 2278 2279 private final class EditTextWatcher implements TextWatcher { 2280 @Override onTextChanged(CharSequence s, int start, int before, int count)2281 public void onTextChanged(CharSequence s, int start, int before, int count) { 2282 /* do nothing */ 2283 } 2284 2285 @Override beforeTextChanged(CharSequence s, int start, int count, int after)2286 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 2287 /* do nothing */ 2288 } 2289 2290 @Override afterTextChanged(Editable editable)2291 public void afterTextChanged(Editable editable) { 2292 final boolean hadErrors = hasErrors(); 2293 2294 if (editable.length() == 0) { 2295 mCopiesEditText.setError(""); 2296 updateOptionsUi(); 2297 return; 2298 } 2299 2300 int copies = 0; 2301 try { 2302 copies = Integer.parseInt(editable.toString()); 2303 } catch (NumberFormatException nfe) { 2304 /* ignore */ 2305 } 2306 2307 if (copies < MIN_COPIES) { 2308 mCopiesEditText.setError(""); 2309 updateOptionsUi(); 2310 return; 2311 } 2312 2313 mPrintJob.setCopies(copies); 2314 2315 mCopiesEditText.setError(null); 2316 2317 updateOptionsUi(); 2318 2319 if (hadErrors && canUpdateDocument()) { 2320 updateDocument(false); 2321 } 2322 } 2323 } 2324 2325 private final class ProgressMessageController implements Runnable { 2326 private static final long PROGRESS_TIMEOUT_MILLIS = 1000; 2327 2328 private final Handler mHandler; 2329 2330 private boolean mPosted; 2331 ProgressMessageController(Context context)2332 public ProgressMessageController(Context context) { 2333 mHandler = new Handler(context.getMainLooper(), null, false); 2334 } 2335 post()2336 public void post() { 2337 if (mPosted) { 2338 return; 2339 } 2340 mPosted = true; 2341 mHandler.postDelayed(this, PROGRESS_TIMEOUT_MILLIS); 2342 } 2343 cancel()2344 public void cancel() { 2345 if (!mPosted) { 2346 return; 2347 } 2348 mPosted = false; 2349 mHandler.removeCallbacks(this); 2350 } 2351 2352 @Override run()2353 public void run() { 2354 mPosted = false; 2355 setState(STATE_UPDATE_SLOW); 2356 ensureProgressUiShown(); 2357 updateOptionsUi(); 2358 } 2359 } 2360 2361 private static final class DocumentTransformer implements ServiceConnection { 2362 private static final String TEMP_FILE_PREFIX = "print_job"; 2363 private static final String TEMP_FILE_EXTENSION = ".pdf"; 2364 2365 private final Context mContext; 2366 2367 private final MutexFileProvider mFileProvider; 2368 2369 private final PrintJobInfo mPrintJob; 2370 2371 private final PageRange[] mPagesToShred; 2372 2373 private final PrintAttributes mAttributesToApply; 2374 2375 private final Runnable mCallback; 2376 DocumentTransformer(Context context, PrintJobInfo printJob, MutexFileProvider fileProvider, PrintAttributes attributes, Runnable callback)2377 public DocumentTransformer(Context context, PrintJobInfo printJob, 2378 MutexFileProvider fileProvider, PrintAttributes attributes, 2379 Runnable callback) { 2380 mContext = context; 2381 mPrintJob = printJob; 2382 mFileProvider = fileProvider; 2383 mCallback = callback; 2384 mPagesToShred = computePagesToShred(mPrintJob); 2385 mAttributesToApply = attributes; 2386 } 2387 transform()2388 public void transform() { 2389 // If we have only the pages we want, done. 2390 if (mPagesToShred.length <= 0 && mAttributesToApply == null) { 2391 mCallback.run(); 2392 return; 2393 } 2394 2395 // Bind to the manipulation service and the work 2396 // will be performed upon connection to the service. 2397 Intent intent = new Intent(PdfManipulationService.ACTION_GET_EDITOR); 2398 intent.setClass(mContext, PdfManipulationService.class); 2399 mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); 2400 } 2401 2402 @Override onServiceConnected(ComponentName name, IBinder service)2403 public void onServiceConnected(ComponentName name, IBinder service) { 2404 final IPdfEditor editor = IPdfEditor.Stub.asInterface(service); 2405 new AsyncTask<Void, Void, Void>() { 2406 @Override 2407 protected Void doInBackground(Void... params) { 2408 // It's OK to access the data members as they are 2409 // final and this code is the last one to touch 2410 // them as shredding is the very last step, so the 2411 // UI is not interactive at this point. 2412 doTransform(editor); 2413 updatePrintJob(); 2414 return null; 2415 } 2416 2417 @Override 2418 protected void onPostExecute(Void aVoid) { 2419 mContext.unbindService(DocumentTransformer.this); 2420 mCallback.run(); 2421 } 2422 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 2423 } 2424 2425 @Override onServiceDisconnected(ComponentName name)2426 public void onServiceDisconnected(ComponentName name) { 2427 /* do nothing */ 2428 } 2429 doTransform(IPdfEditor editor)2430 private void doTransform(IPdfEditor editor) { 2431 File tempFile = null; 2432 ParcelFileDescriptor src = null; 2433 ParcelFileDescriptor dst = null; 2434 InputStream in = null; 2435 OutputStream out = null; 2436 try { 2437 File jobFile = mFileProvider.acquireFile(null); 2438 src = ParcelFileDescriptor.open(jobFile, ParcelFileDescriptor.MODE_READ_WRITE); 2439 2440 // Open the document. 2441 editor.openDocument(src); 2442 2443 // We passed the fd over IPC, close this one. 2444 src.close(); 2445 2446 // Drop the pages. 2447 editor.removePages(mPagesToShred); 2448 2449 // Apply print attributes if needed. 2450 if (mAttributesToApply != null) { 2451 editor.applyPrintAttributes(mAttributesToApply); 2452 } 2453 2454 // Write the modified PDF to a temp file. 2455 tempFile = File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_EXTENSION, 2456 mContext.getCacheDir()); 2457 dst = ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_WRITE); 2458 editor.write(dst); 2459 dst.close(); 2460 2461 // Close the document. 2462 editor.closeDocument(); 2463 2464 // Copy the temp file over the print job file. 2465 jobFile.delete(); 2466 in = new FileInputStream(tempFile); 2467 out = new FileOutputStream(jobFile); 2468 Streams.copy(in, out); 2469 } catch (IOException|RemoteException e) { 2470 Log.e(LOG_TAG, "Error dropping pages", e); 2471 } finally { 2472 IoUtils.closeQuietly(src); 2473 IoUtils.closeQuietly(dst); 2474 IoUtils.closeQuietly(in); 2475 IoUtils.closeQuietly(out); 2476 if (tempFile != null) { 2477 tempFile.delete(); 2478 } 2479 mFileProvider.releaseFile(); 2480 } 2481 } 2482 updatePrintJob()2483 private void updatePrintJob() { 2484 // Update the print job pages. 2485 final int newPageCount = PageRangeUtils.getNormalizedPageCount( 2486 mPrintJob.getPages(), 0); 2487 mPrintJob.setPages(new PageRange[]{PageRange.ALL_PAGES}); 2488 2489 // Update the print job document info. 2490 PrintDocumentInfo oldDocInfo = mPrintJob.getDocumentInfo(); 2491 PrintDocumentInfo newDocInfo = new PrintDocumentInfo 2492 .Builder(oldDocInfo.getName()) 2493 .setContentType(oldDocInfo.getContentType()) 2494 .setPageCount(newPageCount) 2495 .build(); 2496 mPrintJob.setDocumentInfo(newDocInfo); 2497 } 2498 computePagesToShred(PrintJobInfo printJob)2499 private static PageRange[] computePagesToShred(PrintJobInfo printJob) { 2500 List<PageRange> rangesToShred = new ArrayList<>(); 2501 PageRange previousRange = null; 2502 2503 final int pageCount = printJob.getDocumentInfo().getPageCount(); 2504 2505 PageRange[] printedPages = printJob.getPages(); 2506 final int rangeCount = printedPages.length; 2507 for (int i = 0; i < rangeCount; i++) { 2508 PageRange range = PageRangeUtils.asAbsoluteRange(printedPages[i], pageCount); 2509 2510 if (previousRange == null) { 2511 final int startPageIdx = 0; 2512 final int endPageIdx = range.getStart() - 1; 2513 if (startPageIdx <= endPageIdx) { 2514 PageRange removedRange = new PageRange(startPageIdx, endPageIdx); 2515 rangesToShred.add(removedRange); 2516 } 2517 } else { 2518 final int startPageIdx = previousRange.getEnd() + 1; 2519 final int endPageIdx = range.getStart() - 1; 2520 if (startPageIdx <= endPageIdx) { 2521 PageRange removedRange = new PageRange(startPageIdx, endPageIdx); 2522 rangesToShred.add(removedRange); 2523 } 2524 } 2525 2526 if (i == rangeCount - 1) { 2527 final int startPageIdx = range.getEnd() + 1; 2528 final int endPageIdx = printJob.getDocumentInfo().getPageCount() - 1; 2529 if (startPageIdx <= endPageIdx) { 2530 PageRange removedRange = new PageRange(startPageIdx, endPageIdx); 2531 rangesToShred.add(removedRange); 2532 } 2533 } 2534 2535 previousRange = range; 2536 } 2537 2538 PageRange[] result = new PageRange[rangesToShred.size()]; 2539 rangesToShred.toArray(result); 2540 return result; 2541 } 2542 } 2543 } 2544