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.AlertDialog; 21 import android.app.Dialog; 22 import android.app.DialogFragment; 23 import android.app.Fragment; 24 import android.app.FragmentTransaction; 25 import android.app.LoaderManager; 26 import android.content.ActivityNotFoundException; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.DialogInterface; 30 import android.content.Intent; 31 import android.content.Loader; 32 import android.content.ServiceConnection; 33 import android.content.SharedPreferences; 34 import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 35 import android.content.pm.PackageManager; 36 import android.content.pm.PackageManager.NameNotFoundException; 37 import android.content.pm.ResolveInfo; 38 import android.content.res.Configuration; 39 import android.database.DataSetObserver; 40 import android.graphics.drawable.Drawable; 41 import android.net.Uri; 42 import android.os.AsyncTask; 43 import android.os.Bundle; 44 import android.os.Handler; 45 import android.os.IBinder; 46 import android.os.ParcelFileDescriptor; 47 import android.os.RemoteException; 48 import android.print.IPrintDocumentAdapter; 49 import android.print.PageRange; 50 import android.print.PrintAttributes; 51 import android.print.PrintAttributes.MediaSize; 52 import android.print.PrintAttributes.Resolution; 53 import android.print.PrintDocumentInfo; 54 import android.print.PrintJobInfo; 55 import android.print.PrintManager; 56 import android.print.PrintServicesLoader; 57 import android.print.PrinterCapabilitiesInfo; 58 import android.print.PrinterId; 59 import android.print.PrinterInfo; 60 import android.printservice.PrintService; 61 import android.printservice.PrintServiceInfo; 62 import android.provider.DocumentsContract; 63 import android.text.Editable; 64 import android.text.TextUtils; 65 import android.text.TextWatcher; 66 import android.util.ArrayMap; 67 import android.util.ArraySet; 68 import android.util.Log; 69 import android.util.TypedValue; 70 import android.view.KeyEvent; 71 import android.view.MotionEvent; 72 import android.view.View; 73 import android.view.View.OnClickListener; 74 import android.view.View.OnFocusChangeListener; 75 import android.view.ViewGroup; 76 import android.view.inputmethod.InputMethodManager; 77 import android.widget.AdapterView; 78 import android.widget.AdapterView.OnItemSelectedListener; 79 import android.widget.ArrayAdapter; 80 import android.widget.BaseAdapter; 81 import android.widget.Button; 82 import android.widget.EditText; 83 import android.widget.ImageView; 84 import android.widget.Spinner; 85 import android.widget.TextView; 86 87 import android.widget.Toast; 88 import com.android.internal.logging.MetricsLogger; 89 import com.android.printspooler.R; 90 import com.android.printspooler.model.MutexFileProvider; 91 import com.android.printspooler.model.PrintSpoolerProvider; 92 import com.android.printspooler.model.PrintSpoolerService; 93 import com.android.printspooler.model.RemotePrintDocument; 94 import com.android.printspooler.model.RemotePrintDocument.RemotePrintDocumentInfo; 95 import com.android.printspooler.renderer.IPdfEditor; 96 import com.android.printspooler.renderer.PdfManipulationService; 97 import com.android.printspooler.util.ApprovedPrintServices; 98 import com.android.printspooler.util.MediaSizeUtils; 99 import com.android.printspooler.util.MediaSizeUtils.MediaSizeComparator; 100 import com.android.printspooler.util.PageRangeUtils; 101 import com.android.printspooler.widget.PrintContentView; 102 import com.android.printspooler.widget.PrintContentView.OptionsStateChangeListener; 103 import com.android.printspooler.widget.PrintContentView.OptionsStateController; 104 105 import libcore.io.IoUtils; 106 import libcore.io.Streams; 107 108 import java.io.File; 109 import java.io.FileInputStream; 110 import java.io.FileOutputStream; 111 import java.io.IOException; 112 import java.io.InputStream; 113 import java.io.OutputStream; 114 import java.util.ArrayList; 115 import java.util.Arrays; 116 import java.util.Collection; 117 import java.util.Collections; 118 import java.util.List; 119 import java.util.Objects; 120 121 public class PrintActivity extends Activity implements RemotePrintDocument.UpdateResultCallbacks, 122 PrintErrorFragment.OnActionListener, PageAdapter.ContentCallbacks, 123 OptionsStateChangeListener, OptionsStateController, 124 LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> { 125 private static final String LOG_TAG = "PrintActivity"; 126 127 private static final boolean DEBUG = false; 128 129 private static final String FRAGMENT_TAG = "FRAGMENT_TAG"; 130 131 private static final String HAS_PRINTED_PREF = "has_printed"; 132 133 private static final int LOADER_ID_ENABLED_PRINT_SERVICES = 1; 134 private static final int LOADER_ID_PRINT_REGISTRY = 2; 135 private static final int LOADER_ID_PRINT_REGISTRY_INT = 3; 136 137 private static final int ORIENTATION_PORTRAIT = 0; 138 private static final int ORIENTATION_LANDSCAPE = 1; 139 140 private static final int ACTIVITY_REQUEST_CREATE_FILE = 1; 141 private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2; 142 private static final int ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS = 3; 143 144 private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9; 145 146 private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE; 147 private static final int DEST_ADAPTER_ITEM_ID_MORE = Integer.MAX_VALUE - 1; 148 149 private static final int STATE_INITIALIZING = 0; 150 private static final int STATE_CONFIGURING = 1; 151 private static final int STATE_PRINT_CONFIRMED = 2; 152 private static final int STATE_PRINT_CANCELED = 3; 153 private static final int STATE_UPDATE_FAILED = 4; 154 private static final int STATE_CREATE_FILE_FAILED = 5; 155 private static final int STATE_PRINTER_UNAVAILABLE = 6; 156 private static final int STATE_UPDATE_SLOW = 7; 157 private static final int STATE_PRINT_COMPLETED = 8; 158 159 private static final int UI_STATE_PREVIEW = 0; 160 private static final int UI_STATE_ERROR = 1; 161 private static final int UI_STATE_PROGRESS = 2; 162 163 private static final int MIN_COPIES = 1; 164 private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES); 165 166 private boolean mIsOptionsUiBound = false; 167 168 private final PrinterAvailabilityDetector mPrinterAvailabilityDetector = 169 new PrinterAvailabilityDetector(); 170 171 private final OnFocusChangeListener mSelectAllOnFocusListener = new SelectAllOnFocusListener(); 172 173 private PrintSpoolerProvider mSpoolerProvider; 174 175 private PrintPreviewController mPrintPreviewController; 176 177 private PrintJobInfo mPrintJob; 178 private RemotePrintDocument mPrintedDocument; 179 private PrinterRegistry mPrinterRegistry; 180 181 private EditText mCopiesEditText; 182 183 private TextView mPageRangeTitle; 184 private EditText mPageRangeEditText; 185 186 private Spinner mDestinationSpinner; 187 private DestinationAdapter mDestinationSpinnerAdapter; 188 private boolean mShowDestinationPrompt; 189 190 private Spinner mMediaSizeSpinner; 191 private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter; 192 193 private Spinner mColorModeSpinner; 194 private ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter; 195 196 private Spinner mDuplexModeSpinner; 197 private ArrayAdapter<SpinnerItem<Integer>> mDuplexModeSpinnerAdapter; 198 199 private Spinner mOrientationSpinner; 200 private ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter; 201 202 private Spinner mRangeOptionsSpinner; 203 204 private PrintContentView mOptionsContent; 205 206 private View mSummaryContainer; 207 private TextView mSummaryCopies; 208 private TextView mSummaryPaperSize; 209 210 private Button mMoreOptionsButton; 211 212 private ImageView mPrintButton; 213 214 private ProgressMessageController mProgressMessageController; 215 private MutexFileProvider mFileProvider; 216 217 private MediaSizeComparator mMediaSizeComparator; 218 219 private PrinterInfo mCurrentPrinter; 220 221 private PageRange[] mSelectedPages; 222 223 private String mCallingPackageName; 224 225 private int mCurrentPageCount; 226 227 private int mState = STATE_INITIALIZING; 228 229 private int mUiState = UI_STATE_PREVIEW; 230 231 /** Observer for changes to the printers */ 232 private PrintersObserver mPrintersObserver; 233 234 /** Advances options activity name for current printer */ 235 private ComponentName mAdvancedPrintOptionsActivity; 236 237 /** Whether at least one print services is enabled or not */ 238 private boolean mArePrintServicesEnabled; 239 240 /** Is doFinish() already in progress */ 241 private boolean mIsFinishing; 242 243 @Override onCreate(Bundle savedInstanceState)244 public void onCreate(Bundle savedInstanceState) { 245 super.onCreate(savedInstanceState); 246 247 Bundle extras = getIntent().getExtras(); 248 249 mPrintJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB); 250 if (mPrintJob == null) { 251 throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_JOB 252 + " cannot be null"); 253 } 254 if (mPrintJob.getAttributes() == null) { 255 mPrintJob.setAttributes(new PrintAttributes.Builder().build()); 256 } 257 258 final IBinder adapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER); 259 if (adapter == null) { 260 throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER 261 + " cannot be null"); 262 } 263 264 mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME); 265 266 // This will take just a few milliseconds, so just wait to 267 // bind to the local service before showing the UI. 268 mSpoolerProvider = new PrintSpoolerProvider(this, 269 new Runnable() { 270 @Override 271 public void run() { 272 if (isFinishing() || isDestroyed()) { 273 // onPause might have not been able to cancel the job, see PrintActivity#onPause 274 // To be sure, cancel the job again. Double canceling does no harm. 275 mSpoolerProvider.getSpooler().setPrintJobState(mPrintJob.getId(), 276 PrintJobInfo.STATE_CANCELED, null); 277 } else { 278 onConnectedToPrintSpooler(adapter); 279 } 280 } 281 }); 282 283 getLoaderManager().initLoader(LOADER_ID_ENABLED_PRINT_SERVICES, null, this); 284 } 285 onConnectedToPrintSpooler(final IBinder documentAdapter)286 private void onConnectedToPrintSpooler(final IBinder documentAdapter) { 287 // Now that we are bound to the print spooler service, 288 // create the printer registry and wait for it to get 289 // the first batch of results which will be delivered 290 // after reading historical data. This should be pretty 291 // fast, so just wait before showing the UI. 292 mPrinterRegistry = new PrinterRegistry(PrintActivity.this, () -> { 293 (new Handler(getMainLooper())).post(() -> onPrinterRegistryReady(documentAdapter)); 294 }, LOADER_ID_PRINT_REGISTRY, LOADER_ID_PRINT_REGISTRY_INT); 295 } 296 onPrinterRegistryReady(IBinder documentAdapter)297 private void onPrinterRegistryReady(IBinder documentAdapter) { 298 // Now that we are bound to the local print spooler service 299 // and the printer registry loaded the historical printers 300 // we can show the UI without flickering. 301 setTitle(R.string.print_dialog); 302 setContentView(R.layout.print_activity); 303 304 try { 305 mFileProvider = new MutexFileProvider( 306 PrintSpoolerService.generateFileForPrintJob( 307 PrintActivity.this, mPrintJob.getId())); 308 } catch (IOException ioe) { 309 // At this point we cannot recover, so just take it down. 310 throw new IllegalStateException("Cannot create print job file", ioe); 311 } 312 313 mPrintPreviewController = new PrintPreviewController(PrintActivity.this, 314 mFileProvider); 315 mPrintedDocument = new RemotePrintDocument(PrintActivity.this, 316 IPrintDocumentAdapter.Stub.asInterface(documentAdapter), 317 mFileProvider, new RemotePrintDocument.RemoteAdapterDeathObserver() { 318 @Override 319 public void onDied() { 320 Log.w(LOG_TAG, "Printing app died unexpectedly"); 321 322 // If we are finishing or we are in a state that we do not need any 323 // data from the printing app, then no need to finish. 324 if (isFinishing() || isDestroyed() || 325 (isFinalState(mState) && !mPrintedDocument.isUpdating())) { 326 return; 327 } 328 setState(STATE_PRINT_CANCELED); 329 mPrintedDocument.cancel(true); 330 doFinish(); 331 } 332 }, PrintActivity.this); 333 mProgressMessageController = new ProgressMessageController( 334 PrintActivity.this); 335 mMediaSizeComparator = new MediaSizeComparator(PrintActivity.this); 336 mDestinationSpinnerAdapter = new DestinationAdapter(); 337 338 bindUi(); 339 updateOptionsUi(); 340 341 // Now show the updated UI to avoid flicker. 342 mOptionsContent.setVisibility(View.VISIBLE); 343 mSelectedPages = computeSelectedPages(); 344 mPrintedDocument.start(); 345 346 ensurePreviewUiShown(); 347 348 setState(STATE_CONFIGURING); 349 } 350 351 @Override onStart()352 public void onStart() { 353 super.onStart(); 354 if (mPrinterRegistry != null && mCurrentPrinter != null) { 355 mPrinterRegistry.setTrackedPrinter(mCurrentPrinter.getId()); 356 } 357 MetricsLogger.count(this, "print_preview", 1); 358 } 359 360 @Override onPause()361 public void onPause() { 362 PrintSpoolerService spooler = mSpoolerProvider.getSpooler(); 363 364 if (mState == STATE_INITIALIZING) { 365 if (isFinishing()) { 366 if (spooler != null) { 367 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null); 368 } 369 } 370 super.onPause(); 371 return; 372 } 373 374 if (isFinishing()) { 375 spooler.updatePrintJobUserConfigurableOptionsNoPersistence(mPrintJob); 376 377 switch (mState) { 378 case STATE_PRINT_COMPLETED: { 379 if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) { 380 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_COMPLETED, 381 null); 382 } else { 383 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_QUEUED, 384 null); 385 } 386 } break; 387 388 case STATE_CREATE_FILE_FAILED: { 389 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_FAILED, 390 getString(R.string.print_write_error_message)); 391 } break; 392 393 default: { 394 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null); 395 } break; 396 } 397 } 398 399 super.onPause(); 400 } 401 402 @Override onStop()403 protected void onStop() { 404 mPrinterAvailabilityDetector.cancel(); 405 406 if (mPrinterRegistry != null) { 407 mPrinterRegistry.setTrackedPrinter(null); 408 } 409 410 super.onStop(); 411 } 412 413 @Override onKeyDown(int keyCode, KeyEvent event)414 public boolean onKeyDown(int keyCode, KeyEvent event) { 415 if (keyCode == KeyEvent.KEYCODE_BACK) { 416 event.startTracking(); 417 return true; 418 } 419 return super.onKeyDown(keyCode, event); 420 } 421 422 @Override onKeyUp(int keyCode, KeyEvent event)423 public boolean onKeyUp(int keyCode, KeyEvent event) { 424 if (mState == STATE_INITIALIZING) { 425 doFinish(); 426 return true; 427 } 428 429 if (mState == STATE_PRINT_CANCELED || mState == STATE_PRINT_CONFIRMED 430 || mState == STATE_PRINT_COMPLETED) { 431 return true; 432 } 433 434 if (keyCode == KeyEvent.KEYCODE_BACK 435 && event.isTracking() && !event.isCanceled()) { 436 if (mPrintPreviewController != null && mPrintPreviewController.isOptionsOpened() 437 && !hasErrors()) { 438 mPrintPreviewController.closeOptions(); 439 } else { 440 cancelPrint(); 441 } 442 return true; 443 } 444 return super.onKeyUp(keyCode, event); 445 } 446 447 @Override onRequestContentUpdate()448 public void onRequestContentUpdate() { 449 if (canUpdateDocument()) { 450 updateDocument(false); 451 } 452 } 453 454 @Override onMalformedPdfFile()455 public void onMalformedPdfFile() { 456 onPrintDocumentError("Cannot print a malformed PDF file"); 457 } 458 459 @Override onSecurePdfFile()460 public void onSecurePdfFile() { 461 onPrintDocumentError("Cannot print a password protected PDF file"); 462 } 463 onPrintDocumentError(String message)464 private void onPrintDocumentError(String message) { 465 setState(mProgressMessageController.cancel()); 466 ensureErrorUiShown(null, PrintErrorFragment.ACTION_RETRY); 467 468 setState(STATE_UPDATE_FAILED); 469 470 updateOptionsUi(); 471 472 mPrintedDocument.kill(message); 473 } 474 475 @Override onActionPerformed()476 public void onActionPerformed() { 477 if (mState == STATE_UPDATE_FAILED 478 && canUpdateDocument() && updateDocument(true)) { 479 ensurePreviewUiShown(); 480 setState(STATE_CONFIGURING); 481 updateOptionsUi(); 482 } 483 } 484 485 @Override onUpdateCanceled()486 public void onUpdateCanceled() { 487 if (DEBUG) { 488 Log.i(LOG_TAG, "onUpdateCanceled()"); 489 } 490 491 setState(mProgressMessageController.cancel()); 492 ensurePreviewUiShown(); 493 494 switch (mState) { 495 case STATE_PRINT_CONFIRMED: { 496 requestCreatePdfFileOrFinish(); 497 } break; 498 499 case STATE_CREATE_FILE_FAILED: 500 case STATE_PRINT_COMPLETED: 501 case STATE_PRINT_CANCELED: { 502 doFinish(); 503 } break; 504 } 505 } 506 507 @Override onUpdateCompleted(RemotePrintDocumentInfo document)508 public void onUpdateCompleted(RemotePrintDocumentInfo document) { 509 if (DEBUG) { 510 Log.i(LOG_TAG, "onUpdateCompleted()"); 511 } 512 513 setState(mProgressMessageController.cancel()); 514 ensurePreviewUiShown(); 515 516 // Update the print job with the info for the written document. The page 517 // count we get from the remote document is the pages in the document from 518 // the app perspective but the print job should contain the page count from 519 // print service perspective which is the pages in the written PDF not the 520 // pages in the printed document. 521 PrintDocumentInfo info = document.info; 522 if (info != null) { 523 final int pageCount = PageRangeUtils.getNormalizedPageCount(document.writtenPages, 524 getAdjustedPageCount(info)); 525 PrintDocumentInfo adjustedInfo = new PrintDocumentInfo.Builder(info.getName()) 526 .setContentType(info.getContentType()) 527 .setPageCount(pageCount) 528 .build(); 529 mPrintJob.setDocumentInfo(adjustedInfo); 530 mPrintJob.setPages(document.printedPages); 531 } 532 533 switch (mState) { 534 case STATE_PRINT_CONFIRMED: { 535 requestCreatePdfFileOrFinish(); 536 } break; 537 538 case STATE_CREATE_FILE_FAILED: 539 case STATE_PRINT_COMPLETED: 540 case STATE_PRINT_CANCELED: { 541 updateOptionsUi(); 542 543 doFinish(); 544 } break; 545 546 default: { 547 updatePrintPreviewController(document.changed); 548 549 setState(STATE_CONFIGURING); 550 updateOptionsUi(); 551 } break; 552 } 553 } 554 555 @Override onUpdateFailed(CharSequence error)556 public void onUpdateFailed(CharSequence error) { 557 if (DEBUG) { 558 Log.i(LOG_TAG, "onUpdateFailed()"); 559 } 560 561 setState(mProgressMessageController.cancel()); 562 ensureErrorUiShown(error, PrintErrorFragment.ACTION_RETRY); 563 564 if (mState == STATE_CREATE_FILE_FAILED 565 || mState == STATE_PRINT_COMPLETED 566 || mState == STATE_PRINT_CANCELED) { 567 doFinish(); 568 } 569 570 setState(STATE_UPDATE_FAILED); 571 572 updateOptionsUi(); 573 } 574 575 @Override onOptionsOpened()576 public void onOptionsOpened() { 577 updateSelectedPagesFromPreview(); 578 } 579 580 @Override onOptionsClosed()581 public void onOptionsClosed() { 582 // Make sure the IME is not on the way of preview as 583 // the user may have used it to type copies or range. 584 InputMethodManager imm = getSystemService(InputMethodManager.class); 585 imm.hideSoftInputFromWindow(mDestinationSpinner.getWindowToken(), 0); 586 } 587 updatePrintPreviewController(boolean contentUpdated)588 private void updatePrintPreviewController(boolean contentUpdated) { 589 // If we have not heard from the application, do nothing. 590 RemotePrintDocumentInfo documentInfo = mPrintedDocument.getDocumentInfo(); 591 if (!documentInfo.laidout) { 592 return; 593 } 594 595 // Update the preview controller. 596 mPrintPreviewController.onContentUpdated(contentUpdated, 597 getAdjustedPageCount(documentInfo.info), 598 mPrintedDocument.getDocumentInfo().writtenPages, 599 mSelectedPages, mPrintJob.getAttributes().getMediaSize(), 600 mPrintJob.getAttributes().getMinMargins()); 601 } 602 603 604 @Override canOpenOptions()605 public boolean canOpenOptions() { 606 return true; 607 } 608 609 @Override canCloseOptions()610 public boolean canCloseOptions() { 611 return !hasErrors(); 612 } 613 614 @Override onConfigurationChanged(Configuration newConfig)615 public void onConfigurationChanged(Configuration newConfig) { 616 super.onConfigurationChanged(newConfig); 617 618 mMediaSizeComparator.onConfigurationChanged(newConfig); 619 620 if (mPrintPreviewController != null) { 621 mPrintPreviewController.onOrientationChanged(); 622 } 623 } 624 625 @Override onDestroy()626 protected void onDestroy() { 627 if (mPrintedDocument != null) { 628 mPrintedDocument.cancel(true); 629 } 630 631 doFinish(); 632 633 super.onDestroy(); 634 } 635 636 @Override onActivityResult(int requestCode, int resultCode, Intent data)637 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 638 switch (requestCode) { 639 case ACTIVITY_REQUEST_CREATE_FILE: { 640 onStartCreateDocumentActivityResult(resultCode, data); 641 } break; 642 643 case ACTIVITY_REQUEST_SELECT_PRINTER: { 644 onSelectPrinterActivityResult(resultCode, data); 645 } break; 646 647 case ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS: { 648 onAdvancedPrintOptionsActivityResult(resultCode, data); 649 } break; 650 } 651 } 652 startCreateDocumentActivity()653 private void startCreateDocumentActivity() { 654 if (!isResumed()) { 655 return; 656 } 657 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info; 658 if (info == null) { 659 return; 660 } 661 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); 662 intent.setType("application/pdf"); 663 intent.putExtra(Intent.EXTRA_TITLE, info.getName()); 664 intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName); 665 666 try { 667 startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE); 668 } catch (Exception e) { 669 Log.e(LOG_TAG, "Could not create file", e); 670 Toast.makeText(this, getString(R.string.could_not_create_file), 671 Toast.LENGTH_SHORT).show(); 672 onStartCreateDocumentActivityResult(RESULT_CANCELED, null); 673 } 674 } 675 onStartCreateDocumentActivityResult(int resultCode, Intent data)676 private void onStartCreateDocumentActivityResult(int resultCode, Intent data) { 677 if (resultCode == RESULT_OK && data != null) { 678 updateOptionsUi(); 679 final Uri uri = data.getData(); 680 // Calling finish here does not invoke lifecycle callbacks but we 681 // update the print job in onPause if finishing, hence post a message. 682 mDestinationSpinner.post(new Runnable() { 683 @Override 684 public void run() { 685 transformDocumentAndFinish(uri); 686 } 687 }); 688 } else if (resultCode == RESULT_CANCELED) { 689 if (DEBUG) { 690 Log.i(LOG_TAG, "[state]" + STATE_CONFIGURING); 691 } 692 693 mState = STATE_CONFIGURING; 694 695 // The previous update might have been canceled 696 updateDocument(false); 697 698 updateOptionsUi(); 699 } else { 700 setState(STATE_CREATE_FILE_FAILED); 701 updateOptionsUi(); 702 // Calling finish here does not invoke lifecycle callbacks but we 703 // update the print job in onPause if finishing, hence post a message. 704 mDestinationSpinner.post(new Runnable() { 705 @Override 706 public void run() { 707 doFinish(); 708 } 709 }); 710 } 711 } 712 startSelectPrinterActivity()713 private void startSelectPrinterActivity() { 714 Intent intent = new Intent(this, SelectPrinterActivity.class); 715 startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER); 716 } 717 onSelectPrinterActivityResult(int resultCode, Intent data)718 private void onSelectPrinterActivityResult(int resultCode, Intent data) { 719 if (resultCode == RESULT_OK && data != null) { 720 PrinterInfo printerInfo = data.getParcelableExtra( 721 SelectPrinterActivity.INTENT_EXTRA_PRINTER); 722 if (printerInfo != null) { 723 mCurrentPrinter = printerInfo; 724 mPrintJob.setPrinterId(printerInfo.getId()); 725 mPrintJob.setPrinterName(printerInfo.getName()); 726 727 mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerInfo); 728 } 729 } 730 731 if (mCurrentPrinter != null) { 732 // Trigger PrintersObserver.onChanged() to adjust selection back to current printer 733 mDestinationSpinnerAdapter.notifyDataSetChanged(); 734 } 735 } 736 startAdvancedPrintOptionsActivity(PrinterInfo printer)737 private void startAdvancedPrintOptionsActivity(PrinterInfo printer) { 738 if (mAdvancedPrintOptionsActivity == null) { 739 return; 740 } 741 742 Intent intent = new Intent(Intent.ACTION_MAIN); 743 intent.setComponent(mAdvancedPrintOptionsActivity); 744 745 List<ResolveInfo> resolvedActivities = getPackageManager() 746 .queryIntentActivities(intent, 0); 747 if (resolvedActivities.isEmpty()) { 748 return; 749 } 750 751 // The activity is a component name, therefore it is one or none. 752 if (resolvedActivities.get(0).activityInfo.exported) { 753 PrintJobInfo.Builder printJobBuilder = new PrintJobInfo.Builder(mPrintJob); 754 printJobBuilder.setPages(mSelectedPages); 755 756 intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, printJobBuilder.build()); 757 intent.putExtra(PrintService.EXTRA_PRINTER_INFO, printer); 758 intent.putExtra(PrintService.EXTRA_PRINT_DOCUMENT_INFO, 759 mPrintedDocument.getDocumentInfo().info); 760 761 // This is external activity and may not be there. 762 try { 763 startActivityForResult(intent, ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS); 764 } catch (ActivityNotFoundException anfe) { 765 Log.e(LOG_TAG, "Error starting activity for intent: " + intent, anfe); 766 } 767 } 768 } 769 onAdvancedPrintOptionsActivityResult(int resultCode, Intent data)770 private void onAdvancedPrintOptionsActivityResult(int resultCode, Intent data) { 771 if (resultCode != RESULT_OK || data == null) { 772 return; 773 } 774 775 PrintJobInfo printJobInfo = data.getParcelableExtra(PrintService.EXTRA_PRINT_JOB_INFO); 776 777 if (printJobInfo == null) { 778 return; 779 } 780 781 // Take the advanced options without interpretation. 782 mPrintJob.setAdvancedOptions(printJobInfo.getAdvancedOptions()); 783 784 if (printJobInfo.getCopies() < 1) { 785 Log.w(LOG_TAG, "Cannot apply return value from advanced options activity. Copies " + 786 "must be 1 or more. Actual value is: " + printJobInfo.getCopies() + ". " + 787 "Ignoring."); 788 } else { 789 mCopiesEditText.setText(String.valueOf(printJobInfo.getCopies())); 790 mPrintJob.setCopies(printJobInfo.getCopies()); 791 } 792 793 PrintAttributes currAttributes = mPrintJob.getAttributes(); 794 PrintAttributes newAttributes = printJobInfo.getAttributes(); 795 796 if (newAttributes != null) { 797 // Take the media size only if the current printer supports is. 798 MediaSize oldMediaSize = currAttributes.getMediaSize(); 799 MediaSize newMediaSize = newAttributes.getMediaSize(); 800 if (newMediaSize != null && !oldMediaSize.equals(newMediaSize)) { 801 final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount(); 802 MediaSize newMediaSizePortrait = newAttributes.getMediaSize().asPortrait(); 803 for (int i = 0; i < mediaSizeCount; i++) { 804 MediaSize supportedSizePortrait = mMediaSizeSpinnerAdapter.getItem(i) 805 .value.asPortrait(); 806 if (supportedSizePortrait.equals(newMediaSizePortrait)) { 807 currAttributes.setMediaSize(newMediaSize); 808 mMediaSizeSpinner.setSelection(i); 809 if (currAttributes.getMediaSize().isPortrait()) { 810 if (mOrientationSpinner.getSelectedItemPosition() != 0) { 811 mOrientationSpinner.setSelection(0); 812 } 813 } else { 814 if (mOrientationSpinner.getSelectedItemPosition() != 1) { 815 mOrientationSpinner.setSelection(1); 816 } 817 } 818 break; 819 } 820 } 821 } 822 823 // Take the resolution only if the current printer supports is. 824 Resolution oldResolution = currAttributes.getResolution(); 825 Resolution newResolution = newAttributes.getResolution(); 826 if (!oldResolution.equals(newResolution)) { 827 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities(); 828 if (capabilities != null) { 829 List<Resolution> resolutions = capabilities.getResolutions(); 830 final int resolutionCount = resolutions.size(); 831 for (int i = 0; i < resolutionCount; i++) { 832 Resolution resolution = resolutions.get(i); 833 if (resolution.equals(newResolution)) { 834 currAttributes.setResolution(resolution); 835 break; 836 } 837 } 838 } 839 } 840 841 // Take the color mode only if the current printer supports it. 842 final int currColorMode = currAttributes.getColorMode(); 843 final int newColorMode = newAttributes.getColorMode(); 844 if (currColorMode != newColorMode) { 845 final int colorModeCount = mColorModeSpinner.getCount(); 846 for (int i = 0; i < colorModeCount; i++) { 847 final int supportedColorMode = mColorModeSpinnerAdapter.getItem(i).value; 848 if (supportedColorMode == newColorMode) { 849 currAttributes.setColorMode(newColorMode); 850 mColorModeSpinner.setSelection(i); 851 break; 852 } 853 } 854 } 855 856 // Take the duplex mode only if the current printer supports it. 857 final int currDuplexMode = currAttributes.getDuplexMode(); 858 final int newDuplexMode = newAttributes.getDuplexMode(); 859 if (currDuplexMode != newDuplexMode) { 860 final int duplexModeCount = mDuplexModeSpinner.getCount(); 861 for (int i = 0; i < duplexModeCount; i++) { 862 final int supportedDuplexMode = mDuplexModeSpinnerAdapter.getItem(i).value; 863 if (supportedDuplexMode == newDuplexMode) { 864 currAttributes.setDuplexMode(newDuplexMode); 865 mDuplexModeSpinner.setSelection(i); 866 break; 867 } 868 } 869 } 870 } 871 872 // Handle selected page changes making sure they are in the doc. 873 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info; 874 final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0; 875 PageRange[] pageRanges = printJobInfo.getPages(); 876 if (pageRanges != null && pageCount > 0) { 877 pageRanges = PageRangeUtils.normalize(pageRanges); 878 879 List<PageRange> validatedList = new ArrayList<>(); 880 final int rangeCount = pageRanges.length; 881 for (int i = 0; i < rangeCount; i++) { 882 PageRange pageRange = pageRanges[i]; 883 if (pageRange.getEnd() >= pageCount) { 884 final int rangeStart = pageRange.getStart(); 885 final int rangeEnd = pageCount - 1; 886 if (rangeStart <= rangeEnd) { 887 pageRange = new PageRange(rangeStart, rangeEnd); 888 validatedList.add(pageRange); 889 } 890 break; 891 } 892 validatedList.add(pageRange); 893 } 894 895 if (!validatedList.isEmpty()) { 896 PageRange[] validatedArray = new PageRange[validatedList.size()]; 897 validatedList.toArray(validatedArray); 898 updateSelectedPages(validatedArray, pageCount); 899 } 900 } 901 902 // Update the content if needed. 903 if (canUpdateDocument()) { 904 updateDocument(false); 905 } 906 } 907 setState(int state)908 private void setState(int state) { 909 if (isFinalState(mState)) { 910 if (isFinalState(state)) { 911 if (DEBUG) { 912 Log.i(LOG_TAG, "[state]" + state); 913 } 914 mState = state; 915 } 916 } else { 917 if (DEBUG) { 918 Log.i(LOG_TAG, "[state]" + state); 919 } 920 mState = state; 921 } 922 } 923 isFinalState(int state)924 private static boolean isFinalState(int state) { 925 return state == STATE_PRINT_CANCELED 926 || state == STATE_PRINT_COMPLETED 927 || state == STATE_CREATE_FILE_FAILED; 928 } 929 updateSelectedPagesFromPreview()930 private void updateSelectedPagesFromPreview() { 931 PageRange[] selectedPages = mPrintPreviewController.getSelectedPages(); 932 if (!Arrays.equals(mSelectedPages, selectedPages)) { 933 updateSelectedPages(selectedPages, 934 getAdjustedPageCount(mPrintedDocument.getDocumentInfo().info)); 935 } 936 } 937 updateSelectedPages(PageRange[] selectedPages, int pageInDocumentCount)938 private void updateSelectedPages(PageRange[] selectedPages, int pageInDocumentCount) { 939 if (selectedPages == null || selectedPages.length <= 0) { 940 return; 941 } 942 943 selectedPages = PageRangeUtils.normalize(selectedPages); 944 945 // Handle the case where all pages are specified explicitly 946 // instead of the *all pages* constant. 947 if (PageRangeUtils.isAllPages(selectedPages, pageInDocumentCount)) { 948 selectedPages = new PageRange[] {PageRange.ALL_PAGES}; 949 } 950 951 if (Arrays.equals(mSelectedPages, selectedPages)) { 952 return; 953 } 954 955 mSelectedPages = selectedPages; 956 mPrintJob.setPages(selectedPages); 957 958 if (Arrays.equals(selectedPages, PageRange.ALL_PAGES_ARRAY)) { 959 if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) { 960 mRangeOptionsSpinner.setSelection(0); 961 mPageRangeEditText.setText(""); 962 } 963 } else if (selectedPages[0].getStart() >= 0 964 && selectedPages[selectedPages.length - 1].getEnd() < pageInDocumentCount) { 965 if (mRangeOptionsSpinner.getSelectedItemPosition() != 1) { 966 mRangeOptionsSpinner.setSelection(1); 967 } 968 969 StringBuilder builder = new StringBuilder(); 970 final int pageRangeCount = selectedPages.length; 971 for (int i = 0; i < pageRangeCount; i++) { 972 if (builder.length() > 0) { 973 builder.append(','); 974 } 975 976 final int shownStartPage; 977 final int shownEndPage; 978 PageRange pageRange = selectedPages[i]; 979 if (pageRange.equals(PageRange.ALL_PAGES)) { 980 shownStartPage = 1; 981 shownEndPage = pageInDocumentCount; 982 } else { 983 shownStartPage = pageRange.getStart() + 1; 984 shownEndPage = pageRange.getEnd() + 1; 985 } 986 987 builder.append(shownStartPage); 988 989 if (shownStartPage != shownEndPage) { 990 builder.append('-'); 991 builder.append(shownEndPage); 992 } 993 } 994 995 mPageRangeEditText.setText(builder.toString()); 996 } 997 } 998 ensureProgressUiShown()999 private void ensureProgressUiShown() { 1000 if (isFinishing() || isDestroyed()) { 1001 return; 1002 } 1003 if (mUiState != UI_STATE_PROGRESS) { 1004 mUiState = UI_STATE_PROGRESS; 1005 mPrintPreviewController.setUiShown(false); 1006 Fragment fragment = PrintProgressFragment.newInstance(); 1007 showFragment(fragment); 1008 } 1009 } 1010 ensurePreviewUiShown()1011 private void ensurePreviewUiShown() { 1012 if (isFinishing() || isDestroyed()) { 1013 return; 1014 } 1015 if (mUiState != UI_STATE_PREVIEW) { 1016 mUiState = UI_STATE_PREVIEW; 1017 mPrintPreviewController.setUiShown(true); 1018 showFragment(null); 1019 } 1020 } 1021 ensureErrorUiShown(CharSequence message, int action)1022 private void ensureErrorUiShown(CharSequence message, int action) { 1023 if (isFinishing() || isDestroyed()) { 1024 return; 1025 } 1026 if (mUiState != UI_STATE_ERROR) { 1027 mUiState = UI_STATE_ERROR; 1028 mPrintPreviewController.setUiShown(false); 1029 Fragment fragment = PrintErrorFragment.newInstance(message, action); 1030 showFragment(fragment); 1031 } 1032 } 1033 showFragment(Fragment newFragment)1034 private void showFragment(Fragment newFragment) { 1035 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 1036 Fragment oldFragment = getFragmentManager().findFragmentByTag(FRAGMENT_TAG); 1037 if (oldFragment != null) { 1038 transaction.remove(oldFragment); 1039 } 1040 if (newFragment != null) { 1041 transaction.add(R.id.embedded_content_container, newFragment, FRAGMENT_TAG); 1042 } 1043 transaction.commitAllowingStateLoss(); 1044 getFragmentManager().executePendingTransactions(); 1045 } 1046 requestCreatePdfFileOrFinish()1047 private void requestCreatePdfFileOrFinish() { 1048 mPrintedDocument.cancel(false); 1049 1050 if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) { 1051 startCreateDocumentActivity(); 1052 } else { 1053 transformDocumentAndFinish(null); 1054 } 1055 } 1056 1057 /** 1058 * Clear the selected page range and update the preview if needed. 1059 */ clearPageRanges()1060 private void clearPageRanges() { 1061 mRangeOptionsSpinner.setSelection(0); 1062 mPageRangeEditText.setError(null); 1063 mPageRangeEditText.setText(""); 1064 mSelectedPages = PageRange.ALL_PAGES_ARRAY; 1065 1066 if (!Arrays.equals(mSelectedPages, mPrintPreviewController.getSelectedPages())) { 1067 updatePrintPreviewController(false); 1068 } 1069 } 1070 updatePrintAttributesFromCapabilities(PrinterCapabilitiesInfo capabilities)1071 private void updatePrintAttributesFromCapabilities(PrinterCapabilitiesInfo capabilities) { 1072 boolean clearRanges = false; 1073 PrintAttributes defaults = capabilities.getDefaults(); 1074 1075 // Sort the media sizes based on the current locale. 1076 List<MediaSize> sortedMediaSizes = new ArrayList<>(capabilities.getMediaSizes()); 1077 Collections.sort(sortedMediaSizes, mMediaSizeComparator); 1078 1079 PrintAttributes attributes = mPrintJob.getAttributes(); 1080 1081 // Media size. 1082 MediaSize currMediaSize = attributes.getMediaSize(); 1083 if (currMediaSize == null) { 1084 clearRanges = true; 1085 attributes.setMediaSize(defaults.getMediaSize()); 1086 } else { 1087 MediaSize newMediaSize = null; 1088 boolean isPortrait = currMediaSize.isPortrait(); 1089 1090 // Try to find the current media size in the capabilities as 1091 // it may be in a different orientation. 1092 MediaSize currMediaSizePortrait = currMediaSize.asPortrait(); 1093 final int mediaSizeCount = sortedMediaSizes.size(); 1094 for (int i = 0; i < mediaSizeCount; i++) { 1095 MediaSize mediaSize = sortedMediaSizes.get(i); 1096 if (currMediaSizePortrait.equals(mediaSize.asPortrait())) { 1097 newMediaSize = mediaSize; 1098 break; 1099 } 1100 } 1101 // If we did not find the current media size fall back to default. 1102 if (newMediaSize == null) { 1103 clearRanges = true; 1104 newMediaSize = defaults.getMediaSize(); 1105 } 1106 1107 if (newMediaSize != null) { 1108 if (isPortrait) { 1109 attributes.setMediaSize(newMediaSize.asPortrait()); 1110 } else { 1111 attributes.setMediaSize(newMediaSize.asLandscape()); 1112 } 1113 } 1114 } 1115 1116 // Color mode. 1117 final int colorMode = attributes.getColorMode(); 1118 if ((capabilities.getColorModes() & colorMode) == 0) { 1119 attributes.setColorMode(defaults.getColorMode()); 1120 } 1121 1122 // Duplex mode. 1123 final int duplexMode = attributes.getDuplexMode(); 1124 if ((capabilities.getDuplexModes() & duplexMode) == 0) { 1125 attributes.setDuplexMode(defaults.getDuplexMode()); 1126 } 1127 1128 // Resolution 1129 Resolution resolution = attributes.getResolution(); 1130 if (resolution == null || !capabilities.getResolutions().contains(resolution)) { 1131 attributes.setResolution(defaults.getResolution()); 1132 } 1133 1134 // Margins. 1135 if (!Objects.equals(attributes.getMinMargins(), defaults.getMinMargins())) { 1136 clearRanges = true; 1137 } 1138 attributes.setMinMargins(defaults.getMinMargins()); 1139 1140 if (clearRanges) { 1141 clearPageRanges(); 1142 } 1143 } 1144 updateDocument(boolean clearLastError)1145 private boolean updateDocument(boolean clearLastError) { 1146 if (!clearLastError && mPrintedDocument.hasUpdateError()) { 1147 return false; 1148 } 1149 1150 if (clearLastError && mPrintedDocument.hasUpdateError()) { 1151 mPrintedDocument.clearUpdateError(); 1152 } 1153 1154 final boolean preview = mState != STATE_PRINT_CONFIRMED; 1155 final PageRange[] pages; 1156 if (preview) { 1157 pages = mPrintPreviewController.getRequestedPages(); 1158 } else { 1159 pages = mPrintPreviewController.getSelectedPages(); 1160 } 1161 1162 final boolean willUpdate = mPrintedDocument.update(mPrintJob.getAttributes(), 1163 pages, preview); 1164 1165 if (willUpdate && !mPrintedDocument.hasLaidOutPages()) { 1166 // When the update is done we update the print preview. 1167 mProgressMessageController.post(); 1168 return true; 1169 } else if (!willUpdate) { 1170 // Update preview. 1171 updatePrintPreviewController(false); 1172 } 1173 1174 return false; 1175 } 1176 addCurrentPrinterToHistory()1177 private void addCurrentPrinterToHistory() { 1178 if (mCurrentPrinter != null) { 1179 PrinterId fakePdfPrinterId = mDestinationSpinnerAdapter.getPdfPrinter().getId(); 1180 if (!mCurrentPrinter.getId().equals(fakePdfPrinterId)) { 1181 mPrinterRegistry.addHistoricalPrinter(mCurrentPrinter); 1182 } 1183 } 1184 } 1185 cancelPrint()1186 private void cancelPrint() { 1187 setState(STATE_PRINT_CANCELED); 1188 updateOptionsUi(); 1189 mPrintedDocument.cancel(true); 1190 doFinish(); 1191 } 1192 1193 /** 1194 * Update the selected pages from the text field. 1195 */ updateSelectedPagesFromTextField()1196 private void updateSelectedPagesFromTextField() { 1197 PageRange[] selectedPages = computeSelectedPages(); 1198 if (!Arrays.equals(mSelectedPages, selectedPages)) { 1199 mSelectedPages = selectedPages; 1200 // Update preview. 1201 updatePrintPreviewController(false); 1202 } 1203 } 1204 confirmPrint()1205 private void confirmPrint() { 1206 setState(STATE_PRINT_CONFIRMED); 1207 1208 MetricsLogger.count(this, "print_confirmed", 1); 1209 1210 updateOptionsUi(); 1211 addCurrentPrinterToHistory(); 1212 setUserPrinted(); 1213 1214 // updateSelectedPagesFromTextField migth update the preview, hence apply the preview first 1215 updateSelectedPagesFromPreview(); 1216 updateSelectedPagesFromTextField(); 1217 1218 mPrintPreviewController.closeOptions(); 1219 1220 if (canUpdateDocument()) { 1221 updateDocument(false); 1222 } 1223 1224 if (!mPrintedDocument.isUpdating()) { 1225 requestCreatePdfFileOrFinish(); 1226 } 1227 } 1228 bindUi()1229 private void bindUi() { 1230 // Summary 1231 mSummaryContainer = findViewById(R.id.summary_content); 1232 mSummaryCopies = (TextView) findViewById(R.id.copies_count_summary); 1233 mSummaryPaperSize = (TextView) findViewById(R.id.paper_size_summary); 1234 1235 // Options container 1236 mOptionsContent = (PrintContentView) findViewById(R.id.options_content); 1237 mOptionsContent.setOptionsStateChangeListener(this); 1238 mOptionsContent.setOpenOptionsController(this); 1239 1240 OnItemSelectedListener itemSelectedListener = new MyOnItemSelectedListener(); 1241 OnClickListener clickListener = new MyClickListener(); 1242 1243 // Copies 1244 mCopiesEditText = (EditText) findViewById(R.id.copies_edittext); 1245 mCopiesEditText.setOnFocusChangeListener(mSelectAllOnFocusListener); 1246 mCopiesEditText.setText(MIN_COPIES_STRING); 1247 mCopiesEditText.setSelection(mCopiesEditText.getText().length()); 1248 mCopiesEditText.addTextChangedListener(new EditTextWatcher()); 1249 1250 // Destination. 1251 mPrintersObserver = new PrintersObserver(); 1252 mDestinationSpinnerAdapter.registerDataSetObserver(mPrintersObserver); 1253 mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner); 1254 mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter); 1255 mDestinationSpinner.setOnItemSelectedListener(itemSelectedListener); 1256 1257 // Media size. 1258 mMediaSizeSpinnerAdapter = new ArrayAdapter<>( 1259 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1); 1260 mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner); 1261 mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter); 1262 mMediaSizeSpinner.setOnItemSelectedListener(itemSelectedListener); 1263 1264 // Color mode. 1265 mColorModeSpinnerAdapter = new ArrayAdapter<>( 1266 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1); 1267 mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner); 1268 mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter); 1269 mColorModeSpinner.setOnItemSelectedListener(itemSelectedListener); 1270 1271 // Duplex mode. 1272 mDuplexModeSpinnerAdapter = new ArrayAdapter<>( 1273 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1); 1274 mDuplexModeSpinner = (Spinner) findViewById(R.id.duplex_spinner); 1275 mDuplexModeSpinner.setAdapter(mDuplexModeSpinnerAdapter); 1276 mDuplexModeSpinner.setOnItemSelectedListener(itemSelectedListener); 1277 1278 // Orientation 1279 mOrientationSpinnerAdapter = new ArrayAdapter<>( 1280 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1); 1281 String[] orientationLabels = getResources().getStringArray( 1282 R.array.orientation_labels); 1283 mOrientationSpinnerAdapter.add(new SpinnerItem<>( 1284 ORIENTATION_PORTRAIT, orientationLabels[0])); 1285 mOrientationSpinnerAdapter.add(new SpinnerItem<>( 1286 ORIENTATION_LANDSCAPE, orientationLabels[1])); 1287 mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner); 1288 mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter); 1289 mOrientationSpinner.setOnItemSelectedListener(itemSelectedListener); 1290 1291 // Range options 1292 ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter = new ArrayAdapter<>( 1293 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1); 1294 mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner); 1295 mRangeOptionsSpinner.setAdapter(rangeOptionsSpinnerAdapter); 1296 mRangeOptionsSpinner.setOnItemSelectedListener(itemSelectedListener); 1297 updatePageRangeOptions(PrintDocumentInfo.PAGE_COUNT_UNKNOWN); 1298 1299 // Page range 1300 mPageRangeTitle = (TextView) findViewById(R.id.page_range_title); 1301 mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext); 1302 mPageRangeEditText.setVisibility(View.INVISIBLE); 1303 mPageRangeTitle.setVisibility(View.INVISIBLE); 1304 mPageRangeEditText.setOnFocusChangeListener(mSelectAllOnFocusListener); 1305 mPageRangeEditText.addTextChangedListener(new RangeTextWatcher()); 1306 1307 // Advanced options button. 1308 mMoreOptionsButton = (Button) findViewById(R.id.more_options_button); 1309 mMoreOptionsButton.setOnClickListener(clickListener); 1310 1311 // Print button 1312 mPrintButton = (ImageView) findViewById(R.id.print_button); 1313 mPrintButton.setOnClickListener(clickListener); 1314 1315 // The UI is now initialized 1316 mIsOptionsUiBound = true; 1317 1318 // Special prompt instead of destination spinner for the first time the user printed 1319 if (!hasUserEverPrinted()) { 1320 mShowDestinationPrompt = true; 1321 1322 mSummaryCopies.setEnabled(false); 1323 mSummaryPaperSize.setEnabled(false); 1324 1325 mDestinationSpinner.setOnTouchListener(new View.OnTouchListener() { 1326 @Override 1327 public boolean onTouch(View v, MotionEvent event) { 1328 mShowDestinationPrompt = false; 1329 mSummaryCopies.setEnabled(true); 1330 mSummaryPaperSize.setEnabled(true); 1331 updateOptionsUi(); 1332 1333 mDestinationSpinner.setOnTouchListener(null); 1334 mDestinationSpinnerAdapter.notifyDataSetChanged(); 1335 1336 return false; 1337 } 1338 }); 1339 } 1340 } 1341 1342 @Override onCreateLoader(int id, Bundle args)1343 public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) { 1344 return new PrintServicesLoader((PrintManager) getSystemService(Context.PRINT_SERVICE), this, 1345 PrintManager.ENABLED_SERVICES); 1346 } 1347 1348 @Override onLoadFinished(Loader<List<PrintServiceInfo>> loader, List<PrintServiceInfo> services)1349 public void onLoadFinished(Loader<List<PrintServiceInfo>> loader, 1350 List<PrintServiceInfo> services) { 1351 ComponentName newAdvancedPrintOptionsActivity = null; 1352 if (mCurrentPrinter != null && services != null) { 1353 final int numServices = services.size(); 1354 for (int i = 0; i < numServices; i++) { 1355 PrintServiceInfo service = services.get(i); 1356 1357 if (service.getComponentName().equals(mCurrentPrinter.getId().getServiceName())) { 1358 String advancedOptionsActivityName = service.getAdvancedOptionsActivityName(); 1359 1360 if (!TextUtils.isEmpty(advancedOptionsActivityName)) { 1361 newAdvancedPrintOptionsActivity = new ComponentName( 1362 service.getComponentName().getPackageName(), 1363 advancedOptionsActivityName); 1364 1365 break; 1366 } 1367 } 1368 } 1369 } 1370 1371 if (!Objects.equals(newAdvancedPrintOptionsActivity, mAdvancedPrintOptionsActivity)) { 1372 mAdvancedPrintOptionsActivity = newAdvancedPrintOptionsActivity; 1373 updateOptionsUi(); 1374 } 1375 1376 boolean newArePrintServicesEnabled = services != null && !services.isEmpty(); 1377 if (mArePrintServicesEnabled != newArePrintServicesEnabled) { 1378 mArePrintServicesEnabled = newArePrintServicesEnabled; 1379 1380 // Reload mDestinationSpinnerAdapter as mArePrintServicesEnabled changed and the adapter 1381 // reads that in DestinationAdapter#getMoreItemTitle 1382 if (mDestinationSpinnerAdapter != null) { 1383 mDestinationSpinnerAdapter.notifyDataSetChanged(); 1384 } 1385 } 1386 } 1387 1388 @Override onLoaderReset(Loader<List<PrintServiceInfo>> loader)1389 public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) { 1390 if (!(isFinishing() || isDestroyed())) { 1391 onLoadFinished(loader, null); 1392 } 1393 } 1394 1395 /** 1396 * A dialog that asks the user to approve a {@link PrintService}. This dialog is automatically 1397 * dismissed if the same {@link PrintService} gets approved by another 1398 * {@link PrintServiceApprovalDialog}. 1399 */ 1400 private static final class PrintServiceApprovalDialog extends DialogFragment 1401 implements OnSharedPreferenceChangeListener { 1402 private static final String PRINTSERVICE_KEY = "PRINTSERVICE"; 1403 private ApprovedPrintServices mApprovedServices; 1404 1405 /** 1406 * Create a new {@link PrintServiceApprovalDialog} that ask the user to approve a 1407 * {@link PrintService}. 1408 * 1409 * @param printService The {@link ComponentName} of the service to approve 1410 * @return A new {@link PrintServiceApprovalDialog} that might approve the service 1411 */ newInstance(ComponentName printService)1412 static PrintServiceApprovalDialog newInstance(ComponentName printService) { 1413 PrintServiceApprovalDialog dialog = new PrintServiceApprovalDialog(); 1414 1415 Bundle args = new Bundle(); 1416 args.putParcelable(PRINTSERVICE_KEY, printService); 1417 dialog.setArguments(args); 1418 1419 return dialog; 1420 } 1421 1422 @Override onStop()1423 public void onStop() { 1424 super.onStop(); 1425 1426 mApprovedServices.unregisterChangeListener(this); 1427 } 1428 1429 @Override onStart()1430 public void onStart() { 1431 super.onStart(); 1432 1433 ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY); 1434 synchronized (ApprovedPrintServices.sLock) { 1435 if (mApprovedServices.isApprovedService(printService)) { 1436 dismiss(); 1437 } else { 1438 mApprovedServices.registerChangeListenerLocked(this); 1439 } 1440 } 1441 } 1442 1443 @Override onCreateDialog(Bundle savedInstanceState)1444 public Dialog onCreateDialog(Bundle savedInstanceState) { 1445 super.onCreateDialog(savedInstanceState); 1446 1447 mApprovedServices = new ApprovedPrintServices(getActivity()); 1448 1449 PackageManager packageManager = getActivity().getPackageManager(); 1450 CharSequence serviceLabel; 1451 try { 1452 ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY); 1453 1454 serviceLabel = packageManager.getApplicationInfo(printService.getPackageName(), 0) 1455 .loadLabel(packageManager); 1456 } catch (NameNotFoundException e) { 1457 serviceLabel = null; 1458 } 1459 1460 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 1461 builder.setTitle(getString(R.string.print_service_security_warning_title, 1462 serviceLabel)) 1463 .setMessage(getString(R.string.print_service_security_warning_summary, 1464 serviceLabel)) 1465 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1466 @Override 1467 public void onClick(DialogInterface dialog, int id) { 1468 ComponentName printService = 1469 getArguments().getParcelable(PRINTSERVICE_KEY); 1470 // Prevent onSharedPreferenceChanged from getting triggered 1471 mApprovedServices 1472 .unregisterChangeListener(PrintServiceApprovalDialog.this); 1473 1474 mApprovedServices.addApprovedService(printService); 1475 ((PrintActivity) getActivity()).confirmPrint(); 1476 } 1477 }) 1478 .setNegativeButton(android.R.string.cancel, null); 1479 1480 return builder.create(); 1481 } 1482 1483 @Override onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)1484 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 1485 ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY); 1486 1487 synchronized (ApprovedPrintServices.sLock) { 1488 if (mApprovedServices.isApprovedService(printService)) { 1489 dismiss(); 1490 } 1491 } 1492 } 1493 } 1494 1495 private final class MyClickListener implements OnClickListener { 1496 @Override onClick(View view)1497 public void onClick(View view) { 1498 if (view == mPrintButton) { 1499 if (mCurrentPrinter != null) { 1500 if (mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter) { 1501 confirmPrint(); 1502 } else { 1503 ApprovedPrintServices approvedServices = 1504 new ApprovedPrintServices(PrintActivity.this); 1505 1506 ComponentName printService = mCurrentPrinter.getId().getServiceName(); 1507 if (approvedServices.isApprovedService(printService)) { 1508 confirmPrint(); 1509 } else { 1510 PrintServiceApprovalDialog.newInstance(printService) 1511 .show(getFragmentManager(), "approve"); 1512 } 1513 } 1514 } else { 1515 cancelPrint(); 1516 } 1517 } else if (view == mMoreOptionsButton) { 1518 if (mPageRangeEditText.getError() == null) { 1519 // The selected pages is only applied once the user leaves the text field. A click 1520 // on this button, does not count as leaving. 1521 updateSelectedPagesFromTextField(); 1522 } 1523 1524 if (mCurrentPrinter != null) { 1525 startAdvancedPrintOptionsActivity(mCurrentPrinter); 1526 } 1527 } 1528 } 1529 } 1530 canPrint(PrinterInfo printer)1531 private static boolean canPrint(PrinterInfo printer) { 1532 return printer.getCapabilities() != null 1533 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; 1534 } 1535 1536 /** 1537 * Disable all options UI elements, beside the {@link #mDestinationSpinner} 1538 */ disableOptionsUi()1539 private void disableOptionsUi() { 1540 mCopiesEditText.setEnabled(false); 1541 mCopiesEditText.setFocusable(false); 1542 mMediaSizeSpinner.setEnabled(false); 1543 mColorModeSpinner.setEnabled(false); 1544 mDuplexModeSpinner.setEnabled(false); 1545 mOrientationSpinner.setEnabled(false); 1546 mRangeOptionsSpinner.setEnabled(false); 1547 mPageRangeEditText.setEnabled(false); 1548 mPrintButton.setVisibility(View.GONE); 1549 mMoreOptionsButton.setEnabled(false); 1550 } 1551 updateOptionsUi()1552 void updateOptionsUi() { 1553 if (!mIsOptionsUiBound) { 1554 return; 1555 } 1556 1557 // Always update the summary. 1558 updateSummary(); 1559 1560 if (mState == STATE_PRINT_CONFIRMED 1561 || mState == STATE_PRINT_COMPLETED 1562 || mState == STATE_PRINT_CANCELED 1563 || mState == STATE_UPDATE_FAILED 1564 || mState == STATE_CREATE_FILE_FAILED 1565 || mState == STATE_PRINTER_UNAVAILABLE 1566 || mState == STATE_UPDATE_SLOW) { 1567 if (mState != STATE_PRINTER_UNAVAILABLE) { 1568 mDestinationSpinner.setEnabled(false); 1569 } 1570 disableOptionsUi(); 1571 return; 1572 } 1573 1574 // If no current printer, or it has no capabilities, or it is not 1575 // available, we disable all print options except the destination. 1576 if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) { 1577 disableOptionsUi(); 1578 return; 1579 } 1580 1581 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities(); 1582 PrintAttributes defaultAttributes = capabilities.getDefaults(); 1583 1584 // Destination. 1585 mDestinationSpinner.setEnabled(true); 1586 1587 // Media size. 1588 mMediaSizeSpinner.setEnabled(true); 1589 1590 List<MediaSize> mediaSizes = new ArrayList<>(capabilities.getMediaSizes()); 1591 // Sort the media sizes based on the current locale. 1592 Collections.sort(mediaSizes, mMediaSizeComparator); 1593 1594 PrintAttributes attributes = mPrintJob.getAttributes(); 1595 1596 // If the media sizes changed, we update the adapter and the spinner. 1597 boolean mediaSizesChanged = false; 1598 final int mediaSizeCount = mediaSizes.size(); 1599 if (mediaSizeCount != mMediaSizeSpinnerAdapter.getCount()) { 1600 mediaSizesChanged = true; 1601 } else { 1602 for (int i = 0; i < mediaSizeCount; i++) { 1603 if (!mediaSizes.get(i).equals(mMediaSizeSpinnerAdapter.getItem(i).value)) { 1604 mediaSizesChanged = true; 1605 break; 1606 } 1607 } 1608 } 1609 if (mediaSizesChanged) { 1610 // Remember the old media size to try selecting it again. 1611 int oldMediaSizeNewIndex = AdapterView.INVALID_POSITION; 1612 MediaSize oldMediaSize = attributes.getMediaSize(); 1613 1614 // Rebuild the adapter data. 1615 mMediaSizeSpinnerAdapter.clear(); 1616 for (int i = 0; i < mediaSizeCount; i++) { 1617 MediaSize mediaSize = mediaSizes.get(i); 1618 if (oldMediaSize != null 1619 && mediaSize.asPortrait().equals(oldMediaSize.asPortrait())) { 1620 // Update the index of the old selection. 1621 oldMediaSizeNewIndex = i; 1622 } 1623 mMediaSizeSpinnerAdapter.add(new SpinnerItem<>( 1624 mediaSize, mediaSize.getLabel(getPackageManager()))); 1625 } 1626 1627 if (oldMediaSizeNewIndex != AdapterView.INVALID_POSITION) { 1628 // Select the old media size - nothing really changed. 1629 if (mMediaSizeSpinner.getSelectedItemPosition() != oldMediaSizeNewIndex) { 1630 mMediaSizeSpinner.setSelection(oldMediaSizeNewIndex); 1631 } 1632 } else { 1633 // Select the first or the default. 1634 final int mediaSizeIndex = Math.max(mediaSizes.indexOf( 1635 defaultAttributes.getMediaSize()), 0); 1636 if (mMediaSizeSpinner.getSelectedItemPosition() != mediaSizeIndex) { 1637 mMediaSizeSpinner.setSelection(mediaSizeIndex); 1638 } 1639 // Respect the orientation of the old selection. 1640 if (oldMediaSize != null) { 1641 if (oldMediaSize.isPortrait()) { 1642 attributes.setMediaSize(mMediaSizeSpinnerAdapter 1643 .getItem(mediaSizeIndex).value.asPortrait()); 1644 } else { 1645 attributes.setMediaSize(mMediaSizeSpinnerAdapter 1646 .getItem(mediaSizeIndex).value.asLandscape()); 1647 } 1648 } 1649 } 1650 } 1651 1652 // Color mode. 1653 mColorModeSpinner.setEnabled(true); 1654 final int colorModes = capabilities.getColorModes(); 1655 1656 // If the color modes changed, we update the adapter and the spinner. 1657 boolean colorModesChanged = false; 1658 if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) { 1659 colorModesChanged = true; 1660 } else { 1661 int remainingColorModes = colorModes; 1662 int adapterIndex = 0; 1663 while (remainingColorModes != 0) { 1664 final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes); 1665 final int colorMode = 1 << colorBitOffset; 1666 remainingColorModes &= ~colorMode; 1667 if (colorMode != mColorModeSpinnerAdapter.getItem(adapterIndex).value) { 1668 colorModesChanged = true; 1669 break; 1670 } 1671 adapterIndex++; 1672 } 1673 } 1674 if (colorModesChanged) { 1675 // Remember the old color mode to try selecting it again. 1676 int oldColorModeNewIndex = AdapterView.INVALID_POSITION; 1677 final int oldColorMode = attributes.getColorMode(); 1678 1679 // Rebuild the adapter data. 1680 mColorModeSpinnerAdapter.clear(); 1681 String[] colorModeLabels = getResources().getStringArray(R.array.color_mode_labels); 1682 int remainingColorModes = colorModes; 1683 while (remainingColorModes != 0) { 1684 final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes); 1685 final int colorMode = 1 << colorBitOffset; 1686 if (colorMode == oldColorMode) { 1687 // Update the index of the old selection. 1688 oldColorModeNewIndex = mColorModeSpinnerAdapter.getCount(); 1689 } 1690 remainingColorModes &= ~colorMode; 1691 mColorModeSpinnerAdapter.add(new SpinnerItem<>(colorMode, 1692 colorModeLabels[colorBitOffset])); 1693 } 1694 if (oldColorModeNewIndex != AdapterView.INVALID_POSITION) { 1695 // Select the old color mode - nothing really changed. 1696 if (mColorModeSpinner.getSelectedItemPosition() != oldColorModeNewIndex) { 1697 mColorModeSpinner.setSelection(oldColorModeNewIndex); 1698 } 1699 } else { 1700 // Select the default. 1701 final int selectedColorMode = colorModes & defaultAttributes.getColorMode(); 1702 final int itemCount = mColorModeSpinnerAdapter.getCount(); 1703 for (int i = 0; i < itemCount; i++) { 1704 SpinnerItem<Integer> item = mColorModeSpinnerAdapter.getItem(i); 1705 if (selectedColorMode == item.value) { 1706 if (mColorModeSpinner.getSelectedItemPosition() != i) { 1707 mColorModeSpinner.setSelection(i); 1708 } 1709 attributes.setColorMode(selectedColorMode); 1710 break; 1711 } 1712 } 1713 } 1714 } 1715 1716 // Duplex mode. 1717 mDuplexModeSpinner.setEnabled(true); 1718 final int duplexModes = capabilities.getDuplexModes(); 1719 1720 // If the duplex modes changed, we update the adapter and the spinner. 1721 // Note that we use bit count +1 to account for the no duplex option. 1722 boolean duplexModesChanged = false; 1723 if (Integer.bitCount(duplexModes) != mDuplexModeSpinnerAdapter.getCount()) { 1724 duplexModesChanged = true; 1725 } else { 1726 int remainingDuplexModes = duplexModes; 1727 int adapterIndex = 0; 1728 while (remainingDuplexModes != 0) { 1729 final int duplexBitOffset = Integer.numberOfTrailingZeros(remainingDuplexModes); 1730 final int duplexMode = 1 << duplexBitOffset; 1731 remainingDuplexModes &= ~duplexMode; 1732 if (duplexMode != mDuplexModeSpinnerAdapter.getItem(adapterIndex).value) { 1733 duplexModesChanged = true; 1734 break; 1735 } 1736 adapterIndex++; 1737 } 1738 } 1739 if (duplexModesChanged) { 1740 // Remember the old duplex mode to try selecting it again. Also the fallback 1741 // is no duplexing which is always the first item in the dropdown. 1742 int oldDuplexModeNewIndex = AdapterView.INVALID_POSITION; 1743 final int oldDuplexMode = attributes.getDuplexMode(); 1744 1745 // Rebuild the adapter data. 1746 mDuplexModeSpinnerAdapter.clear(); 1747 String[] duplexModeLabels = getResources().getStringArray(R.array.duplex_mode_labels); 1748 int remainingDuplexModes = duplexModes; 1749 while (remainingDuplexModes != 0) { 1750 final int duplexBitOffset = Integer.numberOfTrailingZeros(remainingDuplexModes); 1751 final int duplexMode = 1 << duplexBitOffset; 1752 if (duplexMode == oldDuplexMode) { 1753 // Update the index of the old selection. 1754 oldDuplexModeNewIndex = mDuplexModeSpinnerAdapter.getCount(); 1755 } 1756 remainingDuplexModes &= ~duplexMode; 1757 mDuplexModeSpinnerAdapter.add(new SpinnerItem<>(duplexMode, 1758 duplexModeLabels[duplexBitOffset])); 1759 } 1760 1761 if (oldDuplexModeNewIndex != AdapterView.INVALID_POSITION) { 1762 // Select the old duplex mode - nothing really changed. 1763 if (mDuplexModeSpinner.getSelectedItemPosition() != oldDuplexModeNewIndex) { 1764 mDuplexModeSpinner.setSelection(oldDuplexModeNewIndex); 1765 } 1766 } else { 1767 // Select the default. 1768 final int selectedDuplexMode = defaultAttributes.getDuplexMode(); 1769 final int itemCount = mDuplexModeSpinnerAdapter.getCount(); 1770 for (int i = 0; i < itemCount; i++) { 1771 SpinnerItem<Integer> item = mDuplexModeSpinnerAdapter.getItem(i); 1772 if (selectedDuplexMode == item.value) { 1773 if (mDuplexModeSpinner.getSelectedItemPosition() != i) { 1774 mDuplexModeSpinner.setSelection(i); 1775 } 1776 attributes.setDuplexMode(selectedDuplexMode); 1777 break; 1778 } 1779 } 1780 } 1781 } 1782 1783 mDuplexModeSpinner.setEnabled(mDuplexModeSpinnerAdapter.getCount() > 1); 1784 1785 // Orientation 1786 mOrientationSpinner.setEnabled(true); 1787 MediaSize mediaSize = attributes.getMediaSize(); 1788 if (mediaSize != null) { 1789 if (mediaSize.isPortrait() 1790 && mOrientationSpinner.getSelectedItemPosition() != 0) { 1791 mOrientationSpinner.setSelection(0); 1792 } else if (!mediaSize.isPortrait() 1793 && mOrientationSpinner.getSelectedItemPosition() != 1) { 1794 mOrientationSpinner.setSelection(1); 1795 } 1796 } 1797 1798 // Range options 1799 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info; 1800 final int pageCount = getAdjustedPageCount(info); 1801 if (pageCount > 0) { 1802 if (info != null) { 1803 if (pageCount == 1) { 1804 mRangeOptionsSpinner.setEnabled(false); 1805 } else { 1806 mRangeOptionsSpinner.setEnabled(true); 1807 if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) { 1808 if (!mPageRangeEditText.isEnabled()) { 1809 mPageRangeEditText.setEnabled(true); 1810 mPageRangeEditText.setVisibility(View.VISIBLE); 1811 mPageRangeTitle.setVisibility(View.VISIBLE); 1812 mPageRangeEditText.requestFocus(); 1813 InputMethodManager imm = (InputMethodManager) 1814 getSystemService(Context.INPUT_METHOD_SERVICE); 1815 imm.showSoftInput(mPageRangeEditText, 0); 1816 } 1817 } else { 1818 mPageRangeEditText.setEnabled(false); 1819 mPageRangeEditText.setVisibility(View.INVISIBLE); 1820 mPageRangeTitle.setVisibility(View.INVISIBLE); 1821 } 1822 } 1823 } else { 1824 if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) { 1825 mRangeOptionsSpinner.setSelection(0); 1826 mPageRangeEditText.setText(""); 1827 } 1828 mRangeOptionsSpinner.setEnabled(false); 1829 mPageRangeEditText.setEnabled(false); 1830 mPageRangeEditText.setVisibility(View.INVISIBLE); 1831 mPageRangeTitle.setVisibility(View.INVISIBLE); 1832 } 1833 } 1834 1835 final int newPageCount = getAdjustedPageCount(info); 1836 if (newPageCount != mCurrentPageCount) { 1837 mCurrentPageCount = newPageCount; 1838 updatePageRangeOptions(newPageCount); 1839 } 1840 1841 // Advanced print options 1842 if (mAdvancedPrintOptionsActivity != null) { 1843 mMoreOptionsButton.setVisibility(View.VISIBLE); 1844 mMoreOptionsButton.setEnabled(true); 1845 } else { 1846 mMoreOptionsButton.setVisibility(View.GONE); 1847 mMoreOptionsButton.setEnabled(false); 1848 } 1849 1850 // Print 1851 if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) { 1852 mPrintButton.setImageResource(com.android.internal.R.drawable.ic_print); 1853 mPrintButton.setContentDescription(getString(R.string.print_button)); 1854 } else { 1855 mPrintButton.setImageResource(R.drawable.ic_menu_savetopdf); 1856 mPrintButton.setContentDescription(getString(R.string.savetopdf_button)); 1857 } 1858 if (!mPrintedDocument.getDocumentInfo().laidout 1859 ||(mRangeOptionsSpinner.getSelectedItemPosition() == 1 1860 && (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors())) 1861 || (mRangeOptionsSpinner.getSelectedItemPosition() == 0 1862 && (mPrintedDocument.getDocumentInfo() == null || hasErrors()))) { 1863 mPrintButton.setVisibility(View.GONE); 1864 } else { 1865 mPrintButton.setVisibility(View.VISIBLE); 1866 } 1867 1868 // Copies 1869 if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) { 1870 mCopiesEditText.setEnabled(true); 1871 mCopiesEditText.setFocusableInTouchMode(true); 1872 } else { 1873 CharSequence text = mCopiesEditText.getText(); 1874 if (TextUtils.isEmpty(text) || !MIN_COPIES_STRING.equals(text.toString())) { 1875 mCopiesEditText.setText(MIN_COPIES_STRING); 1876 } 1877 mCopiesEditText.setEnabled(false); 1878 mCopiesEditText.setFocusable(false); 1879 } 1880 if (mCopiesEditText.getError() == null 1881 && TextUtils.isEmpty(mCopiesEditText.getText())) { 1882 mCopiesEditText.setText(MIN_COPIES_STRING); 1883 mCopiesEditText.requestFocus(); 1884 } 1885 1886 if (mShowDestinationPrompt) { 1887 disableOptionsUi(); 1888 } 1889 } 1890 updateSummary()1891 private void updateSummary() { 1892 if (!mIsOptionsUiBound) { 1893 return; 1894 } 1895 1896 CharSequence copiesText = null; 1897 CharSequence mediaSizeText = null; 1898 1899 if (!TextUtils.isEmpty(mCopiesEditText.getText())) { 1900 copiesText = mCopiesEditText.getText(); 1901 mSummaryCopies.setText(copiesText); 1902 } 1903 1904 final int selectedMediaIndex = mMediaSizeSpinner.getSelectedItemPosition(); 1905 if (selectedMediaIndex >= 0) { 1906 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(selectedMediaIndex); 1907 mediaSizeText = mediaItem.label; 1908 mSummaryPaperSize.setText(mediaSizeText); 1909 } 1910 1911 if (!TextUtils.isEmpty(copiesText) && !TextUtils.isEmpty(mediaSizeText)) { 1912 String summaryText = getString(R.string.summary_template, copiesText, mediaSizeText); 1913 mSummaryContainer.setContentDescription(summaryText); 1914 } 1915 } 1916 updatePageRangeOptions(int pageCount)1917 private void updatePageRangeOptions(int pageCount) { 1918 @SuppressWarnings("unchecked") 1919 ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter = 1920 (ArrayAdapter<SpinnerItem<Integer>>) mRangeOptionsSpinner.getAdapter(); 1921 rangeOptionsSpinnerAdapter.clear(); 1922 1923 final int[] rangeOptionsValues = getResources().getIntArray( 1924 R.array.page_options_values); 1925 1926 String pageCountLabel = (pageCount > 0) ? String.valueOf(pageCount) : ""; 1927 String[] rangeOptionsLabels = new String[] { 1928 getString(R.string.template_all_pages, pageCountLabel), 1929 getString(R.string.template_page_range, pageCountLabel) 1930 }; 1931 1932 final int rangeOptionsCount = rangeOptionsLabels.length; 1933 for (int i = 0; i < rangeOptionsCount; i++) { 1934 rangeOptionsSpinnerAdapter.add(new SpinnerItem<>( 1935 rangeOptionsValues[i], rangeOptionsLabels[i])); 1936 } 1937 } 1938 computeSelectedPages()1939 private PageRange[] computeSelectedPages() { 1940 if (hasErrors()) { 1941 return null; 1942 } 1943 1944 if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) { 1945 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info; 1946 final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0; 1947 1948 return PageRangeUtils.parsePageRanges(mPageRangeEditText.getText(), pageCount); 1949 } 1950 1951 return PageRange.ALL_PAGES_ARRAY; 1952 } 1953 getAdjustedPageCount(PrintDocumentInfo info)1954 private int getAdjustedPageCount(PrintDocumentInfo info) { 1955 if (info != null) { 1956 final int pageCount = info.getPageCount(); 1957 if (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) { 1958 return pageCount; 1959 } 1960 } 1961 // If the app does not tell us how many pages are in the 1962 // doc we ask for all pages and use the document page count. 1963 return mPrintPreviewController.getFilePageCount(); 1964 } 1965 hasErrors()1966 private boolean hasErrors() { 1967 return (mCopiesEditText.getError() != null) 1968 || (mPageRangeEditText.getVisibility() == View.VISIBLE 1969 && mPageRangeEditText.getError() != null); 1970 } 1971 onPrinterAvailable(PrinterInfo printer)1972 public void onPrinterAvailable(PrinterInfo printer) { 1973 if (mCurrentPrinter != null && mCurrentPrinter.equals(printer)) { 1974 setState(STATE_CONFIGURING); 1975 if (canUpdateDocument()) { 1976 updateDocument(false); 1977 } 1978 ensurePreviewUiShown(); 1979 updateOptionsUi(); 1980 } 1981 } 1982 onPrinterUnavailable(PrinterInfo printer)1983 public void onPrinterUnavailable(PrinterInfo printer) { 1984 if (mCurrentPrinter.getId().equals(printer.getId())) { 1985 setState(STATE_PRINTER_UNAVAILABLE); 1986 mPrintedDocument.cancel(false); 1987 ensureErrorUiShown(getString(R.string.print_error_printer_unavailable), 1988 PrintErrorFragment.ACTION_NONE); 1989 updateOptionsUi(); 1990 } 1991 } 1992 canUpdateDocument()1993 private boolean canUpdateDocument() { 1994 if (mPrintedDocument.isDestroyed()) { 1995 return false; 1996 } 1997 1998 if (hasErrors()) { 1999 return false; 2000 } 2001 2002 PrintAttributes attributes = mPrintJob.getAttributes(); 2003 2004 final int colorMode = attributes.getColorMode(); 2005 if (colorMode != PrintAttributes.COLOR_MODE_COLOR 2006 && colorMode != PrintAttributes.COLOR_MODE_MONOCHROME) { 2007 return false; 2008 } 2009 if (attributes.getMediaSize() == null) { 2010 return false; 2011 } 2012 if (attributes.getMinMargins() == null) { 2013 return false; 2014 } 2015 if (attributes.getResolution() == null) { 2016 return false; 2017 } 2018 2019 if (mCurrentPrinter == null) { 2020 return false; 2021 } 2022 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities(); 2023 if (capabilities == null) { 2024 return false; 2025 } 2026 if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) { 2027 return false; 2028 } 2029 2030 return true; 2031 } 2032 transformDocumentAndFinish(final Uri writeToUri)2033 private void transformDocumentAndFinish(final Uri writeToUri) { 2034 // If saving to PDF, apply the attibutes as we are acting as a print service. 2035 PrintAttributes attributes = mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter 2036 ? mPrintJob.getAttributes() : null; 2037 new DocumentTransformer(this, mPrintJob, mFileProvider, attributes, new Runnable() { 2038 @Override 2039 public void run() { 2040 if (writeToUri != null) { 2041 mPrintedDocument.writeContent(getContentResolver(), writeToUri); 2042 } 2043 setState(STATE_PRINT_COMPLETED); 2044 doFinish(); 2045 } 2046 }).transform(); 2047 } 2048 doFinish()2049 private void doFinish() { 2050 if (mPrintedDocument != null && mPrintedDocument.isUpdating()) { 2051 // The printedDocument will call doFinish() when the current command finishes 2052 return; 2053 } 2054 2055 if (mIsFinishing) { 2056 return; 2057 } 2058 2059 mIsFinishing = true; 2060 2061 if (mPrinterRegistry != null) { 2062 mPrinterRegistry.setTrackedPrinter(null); 2063 } 2064 2065 if (mPrintersObserver != null) { 2066 mDestinationSpinnerAdapter.unregisterDataSetObserver(mPrintersObserver); 2067 } 2068 2069 if (mSpoolerProvider != null) { 2070 mSpoolerProvider.destroy(); 2071 } 2072 2073 if (mProgressMessageController != null) { 2074 setState(mProgressMessageController.cancel()); 2075 } 2076 2077 if (mState != STATE_INITIALIZING) { 2078 mPrintedDocument.finish(); 2079 mPrintedDocument.destroy(); 2080 mPrintPreviewController.destroy(new Runnable() { 2081 @Override 2082 public void run() { 2083 finish(); 2084 } 2085 }); 2086 } else { 2087 finish(); 2088 } 2089 } 2090 2091 private final class SpinnerItem<T> { 2092 final T value; 2093 final CharSequence label; 2094 SpinnerItem(T value, CharSequence label)2095 public SpinnerItem(T value, CharSequence label) { 2096 this.value = value; 2097 this.label = label; 2098 } 2099 2100 @Override toString()2101 public String toString() { 2102 return label.toString(); 2103 } 2104 } 2105 2106 private final class PrinterAvailabilityDetector implements Runnable { 2107 private static final long UNAVAILABLE_TIMEOUT_MILLIS = 10000; // 10sec 2108 2109 private boolean mPosted; 2110 2111 private boolean mPrinterUnavailable; 2112 2113 private PrinterInfo mPrinter; 2114 updatePrinter(PrinterInfo printer)2115 public void updatePrinter(PrinterInfo printer) { 2116 if (printer.equals(mDestinationSpinnerAdapter.getPdfPrinter())) { 2117 return; 2118 } 2119 2120 final boolean available = printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE 2121 && printer.getCapabilities() != null; 2122 final boolean notifyIfAvailable; 2123 2124 if (mPrinter == null || !mPrinter.getId().equals(printer.getId())) { 2125 notifyIfAvailable = true; 2126 unpostIfNeeded(); 2127 mPrinterUnavailable = false; 2128 mPrinter = new PrinterInfo.Builder(printer).build(); 2129 } else { 2130 notifyIfAvailable = 2131 (mPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE 2132 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) 2133 || (mPrinter.getCapabilities() == null 2134 && printer.getCapabilities() != null); 2135 mPrinter = printer; 2136 } 2137 2138 if (available) { 2139 unpostIfNeeded(); 2140 mPrinterUnavailable = false; 2141 if (notifyIfAvailable) { 2142 onPrinterAvailable(mPrinter); 2143 } 2144 } else { 2145 if (!mPrinterUnavailable) { 2146 postIfNeeded(); 2147 } 2148 } 2149 } 2150 cancel()2151 public void cancel() { 2152 unpostIfNeeded(); 2153 mPrinterUnavailable = false; 2154 } 2155 postIfNeeded()2156 private void postIfNeeded() { 2157 if (!mPosted) { 2158 mPosted = true; 2159 mDestinationSpinner.postDelayed(this, UNAVAILABLE_TIMEOUT_MILLIS); 2160 } 2161 } 2162 unpostIfNeeded()2163 private void unpostIfNeeded() { 2164 if (mPosted) { 2165 mPosted = false; 2166 mDestinationSpinner.removeCallbacks(this); 2167 } 2168 } 2169 2170 @Override run()2171 public void run() { 2172 mPosted = false; 2173 mPrinterUnavailable = true; 2174 onPrinterUnavailable(mPrinter); 2175 } 2176 } 2177 2178 private static final class PrinterHolder { 2179 PrinterInfo printer; 2180 boolean removed; 2181 PrinterHolder(PrinterInfo printer)2182 public PrinterHolder(PrinterInfo printer) { 2183 this.printer = printer; 2184 } 2185 } 2186 2187 2188 /** 2189 * Check if the user has ever printed a document 2190 * 2191 * @return true iff the user has ever printed a document 2192 */ hasUserEverPrinted()2193 private boolean hasUserEverPrinted() { 2194 SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE); 2195 2196 return preferences.getBoolean(HAS_PRINTED_PREF, false); 2197 } 2198 2199 /** 2200 * Remember that the user printed a document 2201 */ setUserPrinted()2202 private void setUserPrinted() { 2203 SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE); 2204 2205 if (!preferences.getBoolean(HAS_PRINTED_PREF, false)) { 2206 SharedPreferences.Editor edit = preferences.edit(); 2207 2208 edit.putBoolean(HAS_PRINTED_PREF, true); 2209 edit.apply(); 2210 } 2211 } 2212 2213 private final class DestinationAdapter extends BaseAdapter 2214 implements PrinterRegistry.OnPrintersChangeListener { 2215 private final List<PrinterHolder> mPrinterHolders = new ArrayList<>(); 2216 2217 private final PrinterHolder mFakePdfPrinterHolder; 2218 2219 private boolean mHistoricalPrintersLoaded; 2220 2221 /** 2222 * Has the {@link #mDestinationSpinner} ever used a view from printer_dropdown_prompt 2223 */ 2224 private boolean hadPromptView; 2225 DestinationAdapter()2226 public DestinationAdapter() { 2227 mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded(); 2228 if (mHistoricalPrintersLoaded) { 2229 addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters()); 2230 } 2231 mPrinterRegistry.setOnPrintersChangeListener(this); 2232 mFakePdfPrinterHolder = new PrinterHolder(createFakePdfPrinter()); 2233 } 2234 getPdfPrinter()2235 public PrinterInfo getPdfPrinter() { 2236 return mFakePdfPrinterHolder.printer; 2237 } 2238 getPrinterIndex(PrinterId printerId)2239 public int getPrinterIndex(PrinterId printerId) { 2240 for (int i = 0; i < getCount(); i++) { 2241 PrinterHolder printerHolder = (PrinterHolder) getItem(i); 2242 if (printerHolder != null && !printerHolder.removed 2243 && printerHolder.printer.getId().equals(printerId)) { 2244 return i; 2245 } 2246 } 2247 return AdapterView.INVALID_POSITION; 2248 } 2249 ensurePrinterInVisibleAdapterPosition(PrinterInfo printer)2250 public void ensurePrinterInVisibleAdapterPosition(PrinterInfo printer) { 2251 final int printerCount = mPrinterHolders.size(); 2252 boolean isKnownPrinter = false; 2253 for (int i = 0; i < printerCount; i++) { 2254 PrinterHolder printerHolder = mPrinterHolders.get(i); 2255 2256 if (printerHolder.printer.getId().equals(printer.getId())) { 2257 isKnownPrinter = true; 2258 2259 // If already in the list - do nothing. 2260 if (i < getCount() - 2) { 2261 break; 2262 } 2263 // Else replace the last one (two items are not printers). 2264 final int lastPrinterIndex = getCount() - 3; 2265 mPrinterHolders.set(i, mPrinterHolders.get(lastPrinterIndex)); 2266 mPrinterHolders.set(lastPrinterIndex, printerHolder); 2267 break; 2268 } 2269 } 2270 2271 if (!isKnownPrinter) { 2272 PrinterHolder printerHolder = new PrinterHolder(printer); 2273 printerHolder.removed = true; 2274 2275 mPrinterHolders.add(Math.max(0, getCount() - 3), printerHolder); 2276 } 2277 2278 // Force reload to adjust selection in PrintersObserver.onChanged() 2279 notifyDataSetChanged(); 2280 } 2281 2282 @Override getCount()2283 public int getCount() { 2284 if (mHistoricalPrintersLoaded) { 2285 return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT); 2286 } 2287 return 0; 2288 } 2289 2290 @Override isEnabled(int position)2291 public boolean isEnabled(int position) { 2292 Object item = getItem(position); 2293 if (item instanceof PrinterHolder) { 2294 PrinterHolder printerHolder = (PrinterHolder) item; 2295 return !printerHolder.removed 2296 && printerHolder.printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; 2297 } 2298 return true; 2299 } 2300 2301 @Override getItem(int position)2302 public Object getItem(int position) { 2303 if (mPrinterHolders.isEmpty()) { 2304 if (position == 0) { 2305 return mFakePdfPrinterHolder; 2306 } 2307 } else { 2308 if (position < 1) { 2309 return mPrinterHolders.get(position); 2310 } 2311 if (position == 1) { 2312 return mFakePdfPrinterHolder; 2313 } 2314 if (position < getCount() - 1) { 2315 return mPrinterHolders.get(position - 1); 2316 } 2317 } 2318 return null; 2319 } 2320 2321 @Override getItemId(int position)2322 public long getItemId(int position) { 2323 if (mPrinterHolders.isEmpty()) { 2324 if (position == 0) { 2325 return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF; 2326 } else if (position == 1) { 2327 return DEST_ADAPTER_ITEM_ID_MORE; 2328 } 2329 } else { 2330 if (position == 1) { 2331 return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF; 2332 } 2333 if (position == getCount() - 1) { 2334 return DEST_ADAPTER_ITEM_ID_MORE; 2335 } 2336 } 2337 return position; 2338 } 2339 2340 @Override getDropDownView(int position, View convertView, ViewGroup parent)2341 public View getDropDownView(int position, View convertView, ViewGroup parent) { 2342 View view = getView(position, convertView, parent); 2343 view.setEnabled(isEnabled(position)); 2344 return view; 2345 } 2346 getMoreItemTitle()2347 private String getMoreItemTitle() { 2348 if (mArePrintServicesEnabled) { 2349 return getString(R.string.all_printers); 2350 } else { 2351 return getString(R.string.print_add_printer); 2352 } 2353 } 2354 2355 @Override getView(int position, View convertView, ViewGroup parent)2356 public View getView(int position, View convertView, ViewGroup parent) { 2357 if (mShowDestinationPrompt) { 2358 if (convertView == null) { 2359 convertView = getLayoutInflater().inflate( 2360 R.layout.printer_dropdown_prompt, parent, false); 2361 hadPromptView = true; 2362 } 2363 2364 return convertView; 2365 } else { 2366 // We don't know if we got an recyled printer_dropdown_prompt, hence do not use it 2367 if (hadPromptView || convertView == null) { 2368 convertView = getLayoutInflater().inflate( 2369 R.layout.printer_dropdown_item, parent, false); 2370 } 2371 } 2372 2373 CharSequence title = null; 2374 CharSequence subtitle = null; 2375 Drawable icon = null; 2376 2377 if (mPrinterHolders.isEmpty()) { 2378 if (position == 0 && getPdfPrinter() != null) { 2379 PrinterHolder printerHolder = (PrinterHolder) getItem(position); 2380 title = printerHolder.printer.getName(); 2381 icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf, null); 2382 } else if (position == 1) { 2383 title = getMoreItemTitle(); 2384 } 2385 } else { 2386 if (position == 1 && getPdfPrinter() != null) { 2387 PrinterHolder printerHolder = (PrinterHolder) getItem(position); 2388 title = printerHolder.printer.getName(); 2389 icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf, null); 2390 } else if (position == getCount() - 1) { 2391 title = getMoreItemTitle(); 2392 } else { 2393 PrinterHolder printerHolder = (PrinterHolder) getItem(position); 2394 PrinterInfo printInfo = printerHolder.printer; 2395 2396 title = printInfo.getName(); 2397 icon = printInfo.loadIcon(PrintActivity.this); 2398 subtitle = printInfo.getDescription(); 2399 } 2400 } 2401 2402 TextView titleView = (TextView) convertView.findViewById(R.id.title); 2403 titleView.setText(title); 2404 2405 TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle); 2406 if (!TextUtils.isEmpty(subtitle)) { 2407 subtitleView.setText(subtitle); 2408 subtitleView.setVisibility(View.VISIBLE); 2409 } else { 2410 subtitleView.setText(null); 2411 subtitleView.setVisibility(View.GONE); 2412 } 2413 2414 ImageView iconView = (ImageView) convertView.findViewById(R.id.icon); 2415 if (icon != null) { 2416 iconView.setVisibility(View.VISIBLE); 2417 if (!isEnabled(position)) { 2418 icon.mutate(); 2419 2420 TypedValue value = new TypedValue(); 2421 getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true); 2422 icon.setAlpha((int)(value.getFloat() * 255)); 2423 } 2424 iconView.setImageDrawable(icon); 2425 } else { 2426 iconView.setVisibility(View.INVISIBLE); 2427 } 2428 2429 return convertView; 2430 } 2431 2432 @Override onPrintersChanged(List<PrinterInfo> printers)2433 public void onPrintersChanged(List<PrinterInfo> printers) { 2434 // We rearrange the printers if the user selects a printer 2435 // not shown in the initial short list. Therefore, we have 2436 // to keep the printer order. 2437 2438 // Check if historical printers are loaded as this adapter is open 2439 // for busyness only if they are. This member is updated here and 2440 // when the adapter is created because the historical printers may 2441 // be loaded before or after the adapter is created. 2442 mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded(); 2443 2444 // No old printers - do not bother keeping their position. 2445 if (mPrinterHolders.isEmpty()) { 2446 addPrinters(mPrinterHolders, printers); 2447 notifyDataSetChanged(); 2448 return; 2449 } 2450 2451 // Add the new printers to a map. 2452 ArrayMap<PrinterId, PrinterInfo> newPrintersMap = new ArrayMap<>(); 2453 final int printerCount = printers.size(); 2454 for (int i = 0; i < printerCount; i++) { 2455 PrinterInfo printer = printers.get(i); 2456 newPrintersMap.put(printer.getId(), printer); 2457 } 2458 2459 List<PrinterHolder> newPrinterHolders = new ArrayList<>(); 2460 2461 // Update printers we already have which are either updated or removed. 2462 // We do not remove the currently selected printer. 2463 final int oldPrinterCount = mPrinterHolders.size(); 2464 for (int i = 0; i < oldPrinterCount; i++) { 2465 PrinterHolder printerHolder = mPrinterHolders.get(i); 2466 PrinterId oldPrinterId = printerHolder.printer.getId(); 2467 PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId); 2468 2469 if (updatedPrinter != null) { 2470 printerHolder.printer = updatedPrinter; 2471 printerHolder.removed = false; 2472 onPrinterAvailable(printerHolder.printer); 2473 newPrinterHolders.add(printerHolder); 2474 } else if (mCurrentPrinter != null && mCurrentPrinter.getId().equals(oldPrinterId)){ 2475 printerHolder.removed = true; 2476 onPrinterUnavailable(printerHolder.printer); 2477 newPrinterHolders.add(printerHolder); 2478 } 2479 } 2480 2481 // Add the rest of the new printers, i.e. what is left. 2482 addPrinters(newPrinterHolders, newPrintersMap.values()); 2483 2484 mPrinterHolders.clear(); 2485 mPrinterHolders.addAll(newPrinterHolders); 2486 2487 notifyDataSetChanged(); 2488 } 2489 2490 @Override onPrintersInvalid()2491 public void onPrintersInvalid() { 2492 mPrinterHolders.clear(); 2493 notifyDataSetInvalidated(); 2494 } 2495 getPrinterHolder(PrinterId printerId)2496 public PrinterHolder getPrinterHolder(PrinterId printerId) { 2497 final int itemCount = getCount(); 2498 for (int i = 0; i < itemCount; i++) { 2499 Object item = getItem(i); 2500 if (item instanceof PrinterHolder) { 2501 PrinterHolder printerHolder = (PrinterHolder) item; 2502 if (printerId.equals(printerHolder.printer.getId())) { 2503 return printerHolder; 2504 } 2505 } 2506 } 2507 return null; 2508 } 2509 2510 /** 2511 * Remove a printer from the holders if it is marked as removed. 2512 * 2513 * @param printerId the id of the printer to remove. 2514 * 2515 * @return true iff the printer was removed. 2516 */ pruneRemovedPrinter(PrinterId printerId)2517 public boolean pruneRemovedPrinter(PrinterId printerId) { 2518 final int holderCounts = mPrinterHolders.size(); 2519 for (int i = holderCounts - 1; i >= 0; i--) { 2520 PrinterHolder printerHolder = mPrinterHolders.get(i); 2521 2522 if (printerHolder.printer.getId().equals(printerId) && printerHolder.removed) { 2523 mPrinterHolders.remove(i); 2524 return true; 2525 } 2526 } 2527 2528 return false; 2529 } 2530 addPrinters(List<PrinterHolder> list, Collection<PrinterInfo> printers)2531 private void addPrinters(List<PrinterHolder> list, Collection<PrinterInfo> printers) { 2532 for (PrinterInfo printer : printers) { 2533 PrinterHolder printerHolder = new PrinterHolder(printer); 2534 list.add(printerHolder); 2535 } 2536 } 2537 createFakePdfPrinter()2538 private PrinterInfo createFakePdfPrinter() { 2539 ArraySet<MediaSize> allMediaSizes = MediaSize.getAllPredefinedSizes(); 2540 MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintActivity.this); 2541 2542 PrinterId printerId = new PrinterId(getComponentName(), "PDF printer"); 2543 2544 PrinterCapabilitiesInfo.Builder builder = 2545 new PrinterCapabilitiesInfo.Builder(printerId); 2546 2547 final int mediaSizeCount = allMediaSizes.size(); 2548 for (int i = 0; i < mediaSizeCount; i++) { 2549 MediaSize mediaSize = allMediaSizes.valueAt(i); 2550 builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize)); 2551 } 2552 2553 builder.addResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300), 2554 true); 2555 builder.setColorModes(PrintAttributes.COLOR_MODE_COLOR 2556 | PrintAttributes.COLOR_MODE_MONOCHROME, PrintAttributes.COLOR_MODE_COLOR); 2557 2558 return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf), 2559 PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build(); 2560 } 2561 } 2562 2563 private final class PrintersObserver extends DataSetObserver { 2564 @Override onChanged()2565 public void onChanged() { 2566 PrinterInfo oldPrinterState = mCurrentPrinter; 2567 if (oldPrinterState == null) { 2568 return; 2569 } 2570 2571 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder( 2572 oldPrinterState.getId()); 2573 PrinterInfo newPrinterState = printerHolder.printer; 2574 2575 if (printerHolder.removed) { 2576 onPrinterUnavailable(newPrinterState); 2577 } 2578 2579 if (mDestinationSpinner.getSelectedItem() != printerHolder) { 2580 mDestinationSpinner.setSelection( 2581 mDestinationSpinnerAdapter.getPrinterIndex(newPrinterState.getId())); 2582 } 2583 2584 if (oldPrinterState.equals(newPrinterState)) { 2585 return; 2586 } 2587 2588 PrinterCapabilitiesInfo oldCapab = oldPrinterState.getCapabilities(); 2589 PrinterCapabilitiesInfo newCapab = newPrinterState.getCapabilities(); 2590 2591 final boolean hadCabab = oldCapab != null; 2592 final boolean hasCapab = newCapab != null; 2593 final boolean gotCapab = oldCapab == null && newCapab != null; 2594 final boolean lostCapab = oldCapab != null && newCapab == null; 2595 final boolean capabChanged = capabilitiesChanged(oldCapab, newCapab); 2596 2597 final int oldStatus = oldPrinterState.getStatus(); 2598 final int newStatus = newPrinterState.getStatus(); 2599 2600 final boolean isActive = newStatus != PrinterInfo.STATUS_UNAVAILABLE; 2601 final boolean becameActive = (oldStatus == PrinterInfo.STATUS_UNAVAILABLE 2602 && oldStatus != newStatus); 2603 final boolean becameInactive = (newStatus == PrinterInfo.STATUS_UNAVAILABLE 2604 && oldStatus != newStatus); 2605 2606 mPrinterAvailabilityDetector.updatePrinter(newPrinterState); 2607 2608 mCurrentPrinter = newPrinterState; 2609 2610 final boolean updateNeeded = ((capabChanged && hasCapab && isActive) 2611 || (becameActive && hasCapab) || (isActive && gotCapab)); 2612 2613 if (capabChanged && hasCapab) { 2614 updatePrintAttributesFromCapabilities(newCapab); 2615 } 2616 2617 if (updateNeeded) { 2618 updatePrintPreviewController(false); 2619 } 2620 2621 if ((isActive && gotCapab) || (becameActive && hasCapab)) { 2622 onPrinterAvailable(newPrinterState); 2623 } else if ((becameInactive && hadCabab) || (isActive && lostCapab)) { 2624 onPrinterUnavailable(newPrinterState); 2625 } 2626 2627 if (updateNeeded && canUpdateDocument()) { 2628 updateDocument(false); 2629 } 2630 2631 // Force a reload of the enabled print services to update mAdvancedPrintOptionsActivity 2632 // in onLoadFinished(); 2633 getLoaderManager().getLoader(LOADER_ID_ENABLED_PRINT_SERVICES).forceLoad(); 2634 2635 updateOptionsUi(); 2636 updateSummary(); 2637 } 2638 capabilitiesChanged(PrinterCapabilitiesInfo oldCapabilities, PrinterCapabilitiesInfo newCapabilities)2639 private boolean capabilitiesChanged(PrinterCapabilitiesInfo oldCapabilities, 2640 PrinterCapabilitiesInfo newCapabilities) { 2641 if (oldCapabilities == null) { 2642 if (newCapabilities != null) { 2643 return true; 2644 } 2645 } else if (!oldCapabilities.equals(newCapabilities)) { 2646 return true; 2647 } 2648 return false; 2649 } 2650 } 2651 2652 private final class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener { 2653 @Override onItemSelected(AdapterView<?> spinner, View view, int position, long id)2654 public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) { 2655 boolean clearRanges = false; 2656 2657 if (spinner == mDestinationSpinner) { 2658 if (position == AdapterView.INVALID_POSITION) { 2659 return; 2660 } 2661 2662 if (id == DEST_ADAPTER_ITEM_ID_MORE) { 2663 startSelectPrinterActivity(); 2664 return; 2665 } 2666 2667 PrinterHolder currentItem = (PrinterHolder) mDestinationSpinner.getSelectedItem(); 2668 PrinterInfo currentPrinter = (currentItem != null) ? currentItem.printer : null; 2669 2670 // Why on earth item selected is called if no selection changed. 2671 if (mCurrentPrinter == currentPrinter) { 2672 return; 2673 } 2674 2675 PrinterId oldId = null; 2676 if (mCurrentPrinter != null) { 2677 oldId = mCurrentPrinter.getId(); 2678 } 2679 2680 mCurrentPrinter = currentPrinter; 2681 2682 if (oldId != null) { 2683 boolean printerRemoved = mDestinationSpinnerAdapter.pruneRemovedPrinter(oldId); 2684 2685 if (printerRemoved) { 2686 // Trigger PrinterObserver.onChanged to adjust selection. This will call 2687 // this function again. 2688 mDestinationSpinnerAdapter.notifyDataSetChanged(); 2689 return; 2690 } 2691 } 2692 2693 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder( 2694 currentPrinter.getId()); 2695 if (!printerHolder.removed) { 2696 setState(STATE_CONFIGURING); 2697 ensurePreviewUiShown(); 2698 } 2699 2700 mPrintJob.setPrinterId(currentPrinter.getId()); 2701 mPrintJob.setPrinterName(currentPrinter.getName()); 2702 2703 mPrinterRegistry.setTrackedPrinter(currentPrinter.getId()); 2704 2705 PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities(); 2706 if (capabilities != null) { 2707 updatePrintAttributesFromCapabilities(capabilities); 2708 } 2709 2710 mPrinterAvailabilityDetector.updatePrinter(currentPrinter); 2711 2712 // Force a reload of the enabled print services to update 2713 // mAdvancedPrintOptionsActivity in onLoadFinished(); 2714 getLoaderManager().getLoader(LOADER_ID_ENABLED_PRINT_SERVICES).forceLoad(); 2715 } else if (spinner == mMediaSizeSpinner) { 2716 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position); 2717 PrintAttributes attributes = mPrintJob.getAttributes(); 2718 2719 MediaSize newMediaSize; 2720 if (mOrientationSpinner.getSelectedItemPosition() == 0) { 2721 newMediaSize = mediaItem.value.asPortrait(); 2722 } else { 2723 newMediaSize = mediaItem.value.asLandscape(); 2724 } 2725 2726 if (newMediaSize != attributes.getMediaSize()) { 2727 clearRanges = true; 2728 attributes.setMediaSize(newMediaSize); 2729 } 2730 } else if (spinner == mColorModeSpinner) { 2731 SpinnerItem<Integer> colorModeItem = mColorModeSpinnerAdapter.getItem(position); 2732 mPrintJob.getAttributes().setColorMode(colorModeItem.value); 2733 } else if (spinner == mDuplexModeSpinner) { 2734 SpinnerItem<Integer> duplexModeItem = mDuplexModeSpinnerAdapter.getItem(position); 2735 mPrintJob.getAttributes().setDuplexMode(duplexModeItem.value); 2736 } else if (spinner == mOrientationSpinner) { 2737 SpinnerItem<Integer> orientationItem = mOrientationSpinnerAdapter.getItem(position); 2738 PrintAttributes attributes = mPrintJob.getAttributes(); 2739 if (mMediaSizeSpinner.getSelectedItem() != null) { 2740 boolean isPortrait = attributes.isPortrait(); 2741 2742 if (isPortrait != (orientationItem.value == ORIENTATION_PORTRAIT)) { 2743 clearRanges = true; 2744 if (orientationItem.value == ORIENTATION_PORTRAIT) { 2745 attributes.copyFrom(attributes.asPortrait()); 2746 } else { 2747 attributes.copyFrom(attributes.asLandscape()); 2748 } 2749 } 2750 } 2751 } else if (spinner == mRangeOptionsSpinner) { 2752 if (mRangeOptionsSpinner.getSelectedItemPosition() == 0) { 2753 clearRanges = true; 2754 mPageRangeEditText.setText(""); 2755 } else if (TextUtils.isEmpty(mPageRangeEditText.getText())) { 2756 mPageRangeEditText.setError(""); 2757 } 2758 } 2759 2760 if (clearRanges) { 2761 clearPageRanges(); 2762 } 2763 2764 updateOptionsUi(); 2765 2766 if (canUpdateDocument()) { 2767 updateDocument(false); 2768 } 2769 } 2770 2771 @Override onNothingSelected(AdapterView<?> parent)2772 public void onNothingSelected(AdapterView<?> parent) { 2773 /* do nothing*/ 2774 } 2775 } 2776 2777 private final class SelectAllOnFocusListener implements OnFocusChangeListener { 2778 @Override onFocusChange(View view, boolean hasFocus)2779 public void onFocusChange(View view, boolean hasFocus) { 2780 EditText editText = (EditText) view; 2781 if (!TextUtils.isEmpty(editText.getText())) { 2782 editText.setSelection(editText.getText().length()); 2783 } 2784 2785 if (view == mPageRangeEditText && !hasFocus && mPageRangeEditText.getError() == null) { 2786 updateSelectedPagesFromTextField(); 2787 } 2788 } 2789 } 2790 2791 private final class RangeTextWatcher implements TextWatcher { 2792 @Override onTextChanged(CharSequence s, int start, int before, int count)2793 public void onTextChanged(CharSequence s, int start, int before, int count) { 2794 /* do nothing */ 2795 } 2796 2797 @Override beforeTextChanged(CharSequence s, int start, int count, int after)2798 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 2799 /* do nothing */ 2800 } 2801 2802 @Override afterTextChanged(Editable editable)2803 public void afterTextChanged(Editable editable) { 2804 final boolean hadErrors = hasErrors(); 2805 2806 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info; 2807 final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0; 2808 PageRange[] ranges = PageRangeUtils.parsePageRanges(editable, pageCount); 2809 2810 if (ranges.length == 0) { 2811 if (mPageRangeEditText.getError() == null) { 2812 mPageRangeEditText.setError(""); 2813 updateOptionsUi(); 2814 } 2815 return; 2816 } 2817 2818 if (mPageRangeEditText.getError() != null) { 2819 mPageRangeEditText.setError(null); 2820 updateOptionsUi(); 2821 } 2822 2823 if (hadErrors && canUpdateDocument()) { 2824 updateDocument(false); 2825 } 2826 } 2827 } 2828 2829 private final class EditTextWatcher implements TextWatcher { 2830 @Override onTextChanged(CharSequence s, int start, int before, int count)2831 public void onTextChanged(CharSequence s, int start, int before, int count) { 2832 /* do nothing */ 2833 } 2834 2835 @Override beforeTextChanged(CharSequence s, int start, int count, int after)2836 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 2837 /* do nothing */ 2838 } 2839 2840 @Override afterTextChanged(Editable editable)2841 public void afterTextChanged(Editable editable) { 2842 final boolean hadErrors = hasErrors(); 2843 2844 if (editable.length() == 0) { 2845 if (mCopiesEditText.getError() == null) { 2846 mCopiesEditText.setError(""); 2847 updateOptionsUi(); 2848 } 2849 return; 2850 } 2851 2852 int copies = 0; 2853 try { 2854 copies = Integer.parseInt(editable.toString()); 2855 } catch (NumberFormatException nfe) { 2856 /* ignore */ 2857 } 2858 2859 if (copies < MIN_COPIES) { 2860 if (mCopiesEditText.getError() == null) { 2861 mCopiesEditText.setError(""); 2862 updateOptionsUi(); 2863 } 2864 return; 2865 } 2866 2867 mPrintJob.setCopies(copies); 2868 2869 if (mCopiesEditText.getError() != null) { 2870 mCopiesEditText.setError(null); 2871 updateOptionsUi(); 2872 } 2873 2874 if (hadErrors && canUpdateDocument()) { 2875 updateDocument(false); 2876 } 2877 } 2878 } 2879 2880 private final class ProgressMessageController implements Runnable { 2881 private static final long PROGRESS_TIMEOUT_MILLIS = 1000; 2882 2883 private final Handler mHandler; 2884 2885 private boolean mPosted; 2886 2887 /** State before run was executed */ 2888 private int mPreviousState = -1; 2889 ProgressMessageController(Context context)2890 public ProgressMessageController(Context context) { 2891 mHandler = new Handler(context.getMainLooper(), null, false); 2892 } 2893 post()2894 public void post() { 2895 if (mState == STATE_UPDATE_SLOW) { 2896 setState(STATE_UPDATE_SLOW); 2897 ensureProgressUiShown(); 2898 updateOptionsUi(); 2899 2900 return; 2901 } else if (mPosted) { 2902 return; 2903 } 2904 mPreviousState = -1; 2905 mPosted = true; 2906 mHandler.postDelayed(this, PROGRESS_TIMEOUT_MILLIS); 2907 } 2908 getStateAfterCancel()2909 private int getStateAfterCancel() { 2910 if (mPreviousState == -1) { 2911 return mState; 2912 } else { 2913 return mPreviousState; 2914 } 2915 } 2916 cancel()2917 public int cancel() { 2918 int state; 2919 2920 if (!mPosted) { 2921 state = getStateAfterCancel(); 2922 } else { 2923 mPosted = false; 2924 mHandler.removeCallbacks(this); 2925 2926 state = getStateAfterCancel(); 2927 } 2928 2929 mPreviousState = -1; 2930 2931 return state; 2932 } 2933 2934 @Override run()2935 public void run() { 2936 mPosted = false; 2937 mPreviousState = mState; 2938 setState(STATE_UPDATE_SLOW); 2939 ensureProgressUiShown(); 2940 updateOptionsUi(); 2941 } 2942 } 2943 2944 private static final class DocumentTransformer implements ServiceConnection { 2945 private static final String TEMP_FILE_PREFIX = "print_job"; 2946 private static final String TEMP_FILE_EXTENSION = ".pdf"; 2947 2948 private final Context mContext; 2949 2950 private final MutexFileProvider mFileProvider; 2951 2952 private final PrintJobInfo mPrintJob; 2953 2954 private final PageRange[] mPagesToShred; 2955 2956 private final PrintAttributes mAttributesToApply; 2957 2958 private final Runnable mCallback; 2959 DocumentTransformer(Context context, PrintJobInfo printJob, MutexFileProvider fileProvider, PrintAttributes attributes, Runnable callback)2960 public DocumentTransformer(Context context, PrintJobInfo printJob, 2961 MutexFileProvider fileProvider, PrintAttributes attributes, 2962 Runnable callback) { 2963 mContext = context; 2964 mPrintJob = printJob; 2965 mFileProvider = fileProvider; 2966 mCallback = callback; 2967 mPagesToShred = computePagesToShred(mPrintJob); 2968 mAttributesToApply = attributes; 2969 } 2970 transform()2971 public void transform() { 2972 // If we have only the pages we want, done. 2973 if (mPagesToShred.length <= 0 && mAttributesToApply == null) { 2974 mCallback.run(); 2975 return; 2976 } 2977 2978 // Bind to the manipulation service and the work 2979 // will be performed upon connection to the service. 2980 Intent intent = new Intent(PdfManipulationService.ACTION_GET_EDITOR); 2981 intent.setClass(mContext, PdfManipulationService.class); 2982 mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); 2983 } 2984 2985 @Override onServiceConnected(ComponentName name, IBinder service)2986 public void onServiceConnected(ComponentName name, IBinder service) { 2987 final IPdfEditor editor = IPdfEditor.Stub.asInterface(service); 2988 new AsyncTask<Void, Void, Void>() { 2989 @Override 2990 protected Void doInBackground(Void... params) { 2991 // It's OK to access the data members as they are 2992 // final and this code is the last one to touch 2993 // them as shredding is the very last step, so the 2994 // UI is not interactive at this point. 2995 doTransform(editor); 2996 updatePrintJob(); 2997 return null; 2998 } 2999 3000 @Override 3001 protected void onPostExecute(Void aVoid) { 3002 mContext.unbindService(DocumentTransformer.this); 3003 mCallback.run(); 3004 } 3005 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 3006 } 3007 3008 @Override onServiceDisconnected(ComponentName name)3009 public void onServiceDisconnected(ComponentName name) { 3010 /* do nothing */ 3011 } 3012 doTransform(IPdfEditor editor)3013 private void doTransform(IPdfEditor editor) { 3014 File tempFile = null; 3015 ParcelFileDescriptor src = null; 3016 ParcelFileDescriptor dst = null; 3017 InputStream in = null; 3018 OutputStream out = null; 3019 try { 3020 File jobFile = mFileProvider.acquireFile(null); 3021 src = ParcelFileDescriptor.open(jobFile, ParcelFileDescriptor.MODE_READ_WRITE); 3022 3023 // Open the document. 3024 editor.openDocument(src); 3025 3026 // We passed the fd over IPC, close this one. 3027 src.close(); 3028 3029 // Drop the pages. 3030 editor.removePages(mPagesToShred); 3031 3032 // Apply print attributes if needed. 3033 if (mAttributesToApply != null) { 3034 editor.applyPrintAttributes(mAttributesToApply); 3035 } 3036 3037 // Write the modified PDF to a temp file. 3038 tempFile = File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_EXTENSION, 3039 mContext.getCacheDir()); 3040 dst = ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_WRITE); 3041 editor.write(dst); 3042 dst.close(); 3043 3044 // Close the document. 3045 editor.closeDocument(); 3046 3047 // Copy the temp file over the print job file. 3048 jobFile.delete(); 3049 in = new FileInputStream(tempFile); 3050 out = new FileOutputStream(jobFile); 3051 Streams.copy(in, out); 3052 } catch (IOException|RemoteException e) { 3053 Log.e(LOG_TAG, "Error dropping pages", e); 3054 } finally { 3055 IoUtils.closeQuietly(src); 3056 IoUtils.closeQuietly(dst); 3057 IoUtils.closeQuietly(in); 3058 IoUtils.closeQuietly(out); 3059 if (tempFile != null) { 3060 tempFile.delete(); 3061 } 3062 mFileProvider.releaseFile(); 3063 } 3064 } 3065 updatePrintJob()3066 private void updatePrintJob() { 3067 // Update the print job pages. 3068 final int newPageCount = PageRangeUtils.getNormalizedPageCount( 3069 mPrintJob.getPages(), 0); 3070 mPrintJob.setPages(new PageRange[]{PageRange.ALL_PAGES}); 3071 3072 // Update the print job document info. 3073 PrintDocumentInfo oldDocInfo = mPrintJob.getDocumentInfo(); 3074 PrintDocumentInfo newDocInfo = new PrintDocumentInfo 3075 .Builder(oldDocInfo.getName()) 3076 .setContentType(oldDocInfo.getContentType()) 3077 .setPageCount(newPageCount) 3078 .build(); 3079 mPrintJob.setDocumentInfo(newDocInfo); 3080 } 3081 computePagesToShred(PrintJobInfo printJob)3082 private static PageRange[] computePagesToShred(PrintJobInfo printJob) { 3083 List<PageRange> rangesToShred = new ArrayList<>(); 3084 PageRange previousRange = null; 3085 3086 PageRange[] printedPages = printJob.getPages(); 3087 final int rangeCount = printedPages.length; 3088 for (int i = 0; i < rangeCount; i++) { 3089 PageRange range = printedPages[i]; 3090 3091 if (previousRange == null) { 3092 final int startPageIdx = 0; 3093 final int endPageIdx = range.getStart() - 1; 3094 if (startPageIdx <= endPageIdx) { 3095 PageRange removedRange = new PageRange(startPageIdx, endPageIdx); 3096 rangesToShred.add(removedRange); 3097 } 3098 } else { 3099 final int startPageIdx = previousRange.getEnd() + 1; 3100 final int endPageIdx = range.getStart() - 1; 3101 if (startPageIdx <= endPageIdx) { 3102 PageRange removedRange = new PageRange(startPageIdx, endPageIdx); 3103 rangesToShred.add(removedRange); 3104 } 3105 } 3106 3107 if (i == rangeCount - 1) { 3108 if (range.getEnd() != Integer.MAX_VALUE) { 3109 rangesToShred.add(new PageRange(range.getEnd() + 1, Integer.MAX_VALUE)); 3110 } 3111 } 3112 3113 previousRange = range; 3114 } 3115 3116 PageRange[] result = new PageRange[rangesToShred.size()]; 3117 rangesToShred.toArray(result); 3118 return result; 3119 } 3120 } 3121 } 3122