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