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