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