2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.printspooler.ui;
19 import android.app.Activity;
20 import android.app.Fragment;
21 import android.app.FragmentTransaction;
22 import android.content.ActivityNotFoundException;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.ServiceConnection;
27 import android.content.pm.PackageInfo;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.pm.ResolveInfo;
30 import android.content.res.Configuration;
31 import android.database.DataSetObserver;
32 import android.graphics.drawable.Drawable;
33 import android.net.Uri;
34 import android.os.AsyncTask;
35 import android.os.Bundle;
36 import android.os.Handler;
37 import android.os.IBinder;
38 import android.os.ParcelFileDescriptor;
39 import android.os.RemoteException;
40 import android.print.IPrintDocumentAdapter;
41 import android.print.PageRange;
42 import android.print.PrintAttributes;
43 import android.print.PrintAttributes.MediaSize;
44 import android.print.PrintAttributes.Resolution;
45 import android.print.PrintDocumentInfo;
46 import android.print.PrintJobInfo;
47 import android.print.PrintManager;
48 import android.print.PrinterCapabilitiesInfo;
49 import android.print.PrinterId;
50 import android.print.PrinterInfo;
51 import android.printservice.PrintService;
52 import android.provider.DocumentsContract;
53 import android.text.Editable;
54 import android.text.TextUtils;
55 import android.text.TextUtils.SimpleStringSplitter;
56 import android.text.TextWatcher;
57 import android.util.ArrayMap;
58 import android.util.Log;
59 import android.view.KeyEvent;
60 import android.view.View;
61 import android.view.View.OnClickListener;
62 import android.view.View.OnFocusChangeListener;
63 import android.view.ViewGroup;
64 import android.view.inputmethod.InputMethodManager;
65 import android.widget.AdapterView;
66 import android.widget.AdapterView.OnItemSelectedListener;
67 import android.widget.ArrayAdapter;
68 import android.widget.BaseAdapter;
69 import android.widget.Button;
70 import android.widget.EditText;
71 import android.widget.ImageView;
72 import android.widget.Spinner;
73 import android.widget.TextView;
75 import com.android.printspooler.R;
76 import com.android.printspooler.model.MutexFileProvider;
77 import com.android.printspooler.model.PrintSpoolerProvider;
78 import com.android.printspooler.model.PrintSpoolerService;
79 import com.android.printspooler.model.RemotePrintDocument;
80 import com.android.printspooler.model.RemotePrintDocument.RemotePrintDocumentInfo;
81 import com.android.printspooler.renderer.IPdfEditor;
82 import com.android.printspooler.renderer.PdfManipulationService;
83 import com.android.printspooler.util.MediaSizeUtils;
84 import com.android.printspooler.util.MediaSizeUtils.MediaSizeComparator;
85 import com.android.printspooler.util.PageRangeUtils;
86 import com.android.printspooler.util.PrintOptionUtils;
87 import com.android.printspooler.widget.PrintContentView;
88 import com.android.printspooler.widget.PrintContentView.OptionsStateChangeListener;
89 import com.android.printspooler.widget.PrintContentView.OptionsStateController;
90 import libcore.io.IoUtils;
91 import libcore.io.Streams;
94 import java.io.FileInputStream;
95 import java.io.FileOutputStream;
96 import java.io.IOException;
97 import java.io.InputStream;
98 import java.io.OutputStream;
99 import java.util.ArrayList;
100 import java.util.Arrays;
101 import java.util.Collection;
102 import java.util.Collections;
103 import java.util.List;
104 import java.util.regex.Matcher;
105 import java.util.regex.Pattern;
107 public class PrintActivity extends Activity implements RemotePrintDocument.UpdateResultCallbacks,
108 PrintErrorFragment.OnActionListener, PageAdapter.ContentCallbacks,
109 OptionsStateChangeListener, OptionsStateController {
110 private static final String LOG_TAG = "PrintActivity";
112 private static final boolean DEBUG = false;
114 public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
116 private static final String FRAGMENT_TAG = "FRAGMENT_TAG";
118 private static final int ORIENTATION_PORTRAIT = 0;
119 private static final int ORIENTATION_LANDSCAPE = 1;
121 private static final int ACTIVITY_REQUEST_CREATE_FILE = 1;
122 private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2;
123 private static final int ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS = 3;
125 private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
127 private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
128 private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1;
130 private static final int STATE_INITIALIZING = 0;
131 private static final int STATE_CONFIGURING = 1;
132 private static final int STATE_PRINT_CONFIRMED = 2;
133 private static final int STATE_PRINT_CANCELED = 3;
134 private static final int STATE_UPDATE_FAILED = 4;
135 private static final int STATE_CREATE_FILE_FAILED = 5;
136 private static final int STATE_PRINTER_UNAVAILABLE = 6;
137 private static final int STATE_UPDATE_SLOW = 7;
138 private static final int STATE_PRINT_COMPLETED = 8;
140 private static final int UI_STATE_PREVIEW = 0;
141 private static final int UI_STATE_ERROR = 1;
142 private static final int UI_STATE_PROGRESS = 2;
144 private static final int MIN_COPIES = 1;
145 private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES);
147 private static final Pattern PATTERN_DIGITS = Pattern.compile("[\\d]+");
149 private static final Pattern PATTERN_ESCAPE_SPECIAL_CHARS = Pattern.compile(
150 "(?=[]\\[+&|!(){}^\"~*?:\\\\])");
152 private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
153 "[\\s]*[0-9]+[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
154 + "[\\s]*[0-9]+[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
156 public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[]{PageRange.ALL_PAGES};
158 private final PrinterAvailabilityDetector mPrinterAvailabilityDetector =
159 new PrinterAvailabilityDetector();
161 private final SimpleStringSplitter mStringCommaSplitter = new SimpleStringSplitter(',');
163 private final OnFocusChangeListener mSelectAllOnFocusListener = new SelectAllOnFocusListener();
165 private PrintSpoolerProvider mSpoolerProvider;
167 private PrintPreviewController mPrintPreviewController;
169 private PrintJobInfo mPrintJob;
170 private RemotePrintDocument mPrintedDocument;
171 private PrinterRegistry mPrinterRegistry;
173 private EditText mCopiesEditText;
175 private TextView mPageRangeTitle;
176 private EditText mPageRangeEditText;
178 private Spinner mDestinationSpinner;
179 private DestinationAdapter mDestinationSpinnerAdapter;
181 private Spinner mMediaSizeSpinner;
182 private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
184 private Spinner mColorModeSpinner;
185 private ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
187 private Spinner mOrientationSpinner;
188 private ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
190 private Spinner mRangeOptionsSpinner;
192 private PrintContentView mOptionsContent;
194 private View mSummaryContainer;
195 private TextView mSummaryCopies;
196 private TextView mSummaryPaperSize;
198 private Button mMoreOptionsButton;
200 private ImageView mPrintButton;
202 private ProgressMessageController mProgressMessageController;
203 private MutexFileProvider mFileProvider;
205 private MediaSizeComparator mMediaSizeComparator;
207 private PrinterInfo mCurrentPrinter;
209 private PageRange[] mSelectedPages;
211 private String mCallingPackageName;
213 private int mCurrentPageCount;
215 private int mState = STATE_INITIALIZING;
217 private int mUiState = UI_STATE_PREVIEW;
220 public void onCreate(Bundle savedInstanceState) {
221 super.onCreate(savedInstanceState);
223 Bundle extras = getIntent().getExtras();
225 mPrintJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB);
226 if (mPrintJob == null) {
227 throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_JOB
228 + " cannot be null");
230 mPrintJob.setAttributes(new PrintAttributes.Builder().build());
232 final IBinder adapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
233 if (adapter == null) {
234 throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER
235 + " cannot be null");
238 mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
240 // This will take just a few milliseconds, so just wait to
241 // bind to the local service before showing the UI.
242 mSpoolerProvider = new PrintSpoolerProvider(this,
246 onConnectedToPrintSpooler(adapter);
251 private void onConnectedToPrintSpooler(final IBinder documentAdapter) {
252 // Now that we are bound to the print spooler service,
253 // create the printer registry and wait for it to get
254 // the first batch of results which will be delivered
255 // after reading historical data. This should be pretty
256 // fast, so just wait before showing the UI.
257 mPrinterRegistry = new PrinterRegistry(PrintActivity.this,
261 onPrinterRegistryReady(documentAdapter);
266 private void onPrinterRegistryReady(IBinder documentAdapter) {
267 // Now that we are bound to the local print spooler service
268 // and the printer registry loaded the historical printers
269 // we can show the UI without flickering.
270 setTitle(R.string.print_dialog);
271 setContentView(R.layout.print_activity);
274 mFileProvider = new MutexFileProvider(
275 PrintSpoolerService.generateFileForPrintJob(
276 PrintActivity.this, mPrintJob.getId()));
277 } catch (IOException ioe) {
278 // At this point we cannot recover, so just take it down.
279 throw new IllegalStateException("Cannot create print job file", ioe);
282 mPrintPreviewController = new PrintPreviewController(PrintActivity.this,
284 mPrintedDocument = new RemotePrintDocument(PrintActivity.this,
285 IPrintDocumentAdapter.Stub.asInterface(documentAdapter),
286 mFileProvider, new RemotePrintDocument.RemoteAdapterDeathObserver() {
288 public void onDied() {
289 // If we are finishing or we are in a state that we do not need any
290 // data from the printing app, then no need to finish.
291 if (isFinishing() || (isFinalState(mState) && !mPrintedDocument.isUpdating())) {
294 if (mPrintedDocument.isUpdating()) {
295 mPrintedDocument.cancel();
297 setState(STATE_PRINT_CANCELED);
300 }, PrintActivity.this);
301 mProgressMessageController = new ProgressMessageController(
303 mMediaSizeComparator = new MediaSizeComparator(PrintActivity.this);
304 mDestinationSpinnerAdapter = new DestinationAdapter();
309 // Now show the updated UI to avoid flicker.
310 mOptionsContent.setVisibility(View.VISIBLE);
311 mSelectedPages = computeSelectedPages();
312 mPrintedDocument.start();
314 ensurePreviewUiShown();
316 setState(STATE_CONFIGURING);
320 public void onResume() {
322 if (mState != STATE_INITIALIZING && mCurrentPrinter != null) {
323 mPrinterRegistry.setTrackedPrinter(mCurrentPrinter.getId());
328 public void onPause() {
329 PrintSpoolerService spooler = mSpoolerProvider.getSpooler();
331 if (mState == STATE_INITIALIZING) {
333 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
340 spooler.updatePrintJobUserConfigurableOptionsNoPersistence(mPrintJob);
343 case STATE_PRINT_CONFIRMED: {
344 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_QUEUED, null);
347 case STATE_PRINT_COMPLETED: {
348 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_COMPLETED, null);
351 case STATE_CREATE_FILE_FAILED: {
352 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_FAILED,
353 getString(R.string.print_write_error_message));
357 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
362 mPrinterAvailabilityDetector.cancel();
363 mPrinterRegistry.setTrackedPrinter(null);
369 public boolean onKeyDown(int keyCode, KeyEvent event) {
370 if (keyCode == KeyEvent.KEYCODE_BACK) {
371 event.startTracking();
374 return super.onKeyDown(keyCode, event);
378 public boolean onKeyUp(int keyCode, KeyEvent event) {
379 if (mState == STATE_INITIALIZING) {
384 if (mState == STATE_PRINT_CANCELED || mState == STATE_PRINT_CONFIRMED
385 || mState == STATE_PRINT_COMPLETED) {
389 if (keyCode == KeyEvent.KEYCODE_BACK
390 && event.isTracking() && !event.isCanceled()) {
391 if (mPrintPreviewController != null && mPrintPreviewController.isOptionsOpened()
393 mPrintPreviewController.closeOptions();
399 return super.onKeyUp(keyCode, event);
403 public void onRequestContentUpdate() {
404 if (canUpdateDocument()) {
405 updateDocument(false);
410 public void onMalformedPdfFile() {
411 onPrintDocumentError("Cannot print a malformed PDF file");
415 public void onSecurePdfFile() {
416 onPrintDocumentError("Cannot print a password protected PDF file");
419 private void onPrintDocumentError(String message) {
420 mProgressMessageController.cancel();
421 ensureErrorUiShown(null, PrintErrorFragment.ACTION_RETRY);
423 setState(STATE_UPDATE_FAILED);
427 mPrintedDocument.kill(message);
431 public void onActionPerformed() {
432 if (mState == STATE_UPDATE_FAILED
433 && canUpdateDocument() && updateDocument(true)) {
434 ensurePreviewUiShown();
435 setState(STATE_CONFIGURING);
440 public void onUpdateCanceled() {
442 Log.i(LOG_TAG, "onUpdateCanceled()");
445 mProgressMessageController.cancel();
446 ensurePreviewUiShown();
449 case STATE_PRINT_CONFIRMED: {
450 requestCreatePdfFileOrFinish();
453 case STATE_PRINT_CANCELED: {
460 public void onUpdateCompleted(RemotePrintDocumentInfo document) {
462 Log.i(LOG_TAG, "onUpdateCompleted()");
465 mProgressMessageController.cancel();
466 ensurePreviewUiShown();
468 // Update the print job with the info for the written document. The page
469 // count we get from the remote document is the pages in the document from
470 // the app perspective but the print job should contain the page count from
471 // print service perspective which is the pages in the written PDF not the
472 // pages in the printed document.
473 PrintDocumentInfo info = document.info;
475 final int pageCount = PageRangeUtils.getNormalizedPageCount(document.writtenPages,
476 getAdjustedPageCount(info));
477 PrintDocumentInfo adjustedInfo = new PrintDocumentInfo.Builder(info.getName())
478 .setContentType(info.getContentType())
479 .setPageCount(pageCount)
481 mPrintJob.setDocumentInfo(adjustedInfo);
482 mPrintJob.setPages(document.printedPages);
486 case STATE_PRINT_CONFIRMED: {
487 requestCreatePdfFileOrFinish();
490 case STATE_PRINT_CANCELED: {
495 updatePrintPreviewController(document.changed);
497 setState(STATE_CONFIGURING);
504 public void onUpdateFailed(CharSequence error) {
506 Log.i(LOG_TAG, "onUpdateFailed()");
509 mProgressMessageController.cancel();
510 ensureErrorUiShown(error, PrintErrorFragment.ACTION_RETRY);
512 setState(STATE_UPDATE_FAILED);
518 public void onOptionsOpened() {
519 updateSelectedPagesFromPreview();
523 public void onOptionsClosed() {
524 PageRange[] selectedPages = computeSelectedPages();
525 if (!Arrays.equals(mSelectedPages, selectedPages)) {
526 mSelectedPages = selectedPages;
529 updatePrintPreviewController(false);
532 // Make sure the IME is not on the way of preview as
533 // the user may have used it to type copies or range.
534 InputMethodManager imm = (InputMethodManager) getSystemService(
535 Context.INPUT_METHOD_SERVICE);
536 imm.hideSoftInputFromWindow(mDestinationSpinner.getWindowToken(), 0);
539 private void updatePrintPreviewController(boolean contentUpdated) {
540 // If we have not heard from the application, do nothing.
541 RemotePrintDocumentInfo documentInfo = mPrintedDocument.getDocumentInfo();
542 if (!documentInfo.laidout) {
546 // Update the preview controller.
547 mPrintPreviewController.onContentUpdated(contentUpdated,
548 getAdjustedPageCount(documentInfo.info),
549 mPrintedDocument.getDocumentInfo().writtenPages,
550 mSelectedPages, mPrintJob.getAttributes().getMediaSize(),
551 mPrintJob.getAttributes().getMinMargins());
556 public boolean canOpenOptions() {
561 public boolean canCloseOptions() {
566 public void onConfigurationChanged(Configuration newConfig) {
567 super.onConfigurationChanged(newConfig);
568 if (mPrintPreviewController != null) {
569 mPrintPreviewController.onOrientationChanged();
574 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
575 switch (requestCode) {
576 case ACTIVITY_REQUEST_CREATE_FILE: {
577 onStartCreateDocumentActivityResult(resultCode, data);
580 case ACTIVITY_REQUEST_SELECT_PRINTER: {
581 onSelectPrinterActivityResult(resultCode, data);
584 case ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS: {
585 onAdvancedPrintOptionsActivityResult(resultCode, data);
590 private void startCreateDocumentActivity() {
594 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
598 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
599 intent.setType("application/pdf");
600 intent.putExtra(Intent.EXTRA_TITLE, info.getName());
601 intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
602 startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
605 private void onStartCreateDocumentActivityResult(int resultCode, Intent data) {
606 if (resultCode == RESULT_OK && data != null) {
607 setState(STATE_PRINT_COMPLETED);
609 final Uri uri = data.getData();
610 // Calling finish here does not invoke lifecycle callbacks but we
611 // update the print job in onPause if finishing, hence post a message.
612 mDestinationSpinner.post(new Runnable() {
615 transformDocumentAndFinish(uri);
618 } else if (resultCode == RESULT_CANCELED) {
619 mState = STATE_CONFIGURING;
622 setState(STATE_CREATE_FILE_FAILED);
624 // Calling finish here does not invoke lifecycle callbacks but we
625 // update the print job in onPause if finishing, hence post a message.
626 mDestinationSpinner.post(new Runnable() {
635 private void startSelectPrinterActivity() {
636 Intent intent = new Intent(this, SelectPrinterActivity.class);
637 startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER);
640 private void onSelectPrinterActivityResult(int resultCode, Intent data) {
641 if (resultCode == RESULT_OK && data != null) {
642 PrinterId printerId = data.getParcelableExtra(INTENT_EXTRA_PRINTER_ID);
643 if (printerId != null) {
644 mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerId);
645 final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
646 if (index != AdapterView.INVALID_POSITION) {
647 mDestinationSpinner.setSelection(index);
653 PrinterId printerId = mCurrentPrinter.getId();
654 final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
655 mDestinationSpinner.setSelection(index);
658 private void startAdvancedPrintOptionsActivity(PrinterInfo printer) {
659 ComponentName serviceName = printer.getId().getServiceName();
661 String activityName = PrintOptionUtils.getAdvancedOptionsActivityName(this, serviceName);
662 if (TextUtils.isEmpty(activityName)) {
666 Intent intent = new Intent(Intent.ACTION_MAIN);
667 intent.setComponent(new ComponentName(serviceName.getPackageName(), activityName));
669 List<ResolveInfo> resolvedActivities = getPackageManager()
670 .queryIntentActivities(intent, 0);
671 if (resolvedActivities.isEmpty()) {
675 // The activity is a component name, therefore it is one or none.
676 if (resolvedActivities.get(0).activityInfo.exported) {
677 intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, mPrintJob);
678 intent.putExtra(PrintService.EXTRA_PRINTER_INFO, printer);
680 // This is external activity and may not be there.
682 startActivityForResult(intent, ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS);
683 } catch (ActivityNotFoundException anfe) {
684 Log.e(LOG_TAG, "Error starting activity for intent: " + intent, anfe);
689 private void onAdvancedPrintOptionsActivityResult(int resultCode, Intent data) {
690 if (resultCode != RESULT_OK || data == null) {
694 PrintJobInfo printJobInfo = data.getParcelableExtra(PrintService.EXTRA_PRINT_JOB_INFO);
696 if (printJobInfo == null) {
700 // Take the advanced options without interpretation.
701 mPrintJob.setAdvancedOptions(printJobInfo.getAdvancedOptions());
703 // Take copies without interpretation as the advanced print dialog
704 // cannot create a print job info with invalid copies.
705 mCopiesEditText.setText(String.valueOf(printJobInfo.getCopies()));
706 mPrintJob.setCopies(printJobInfo.getCopies());
708 PrintAttributes currAttributes = mPrintJob.getAttributes();
709 PrintAttributes newAttributes = printJobInfo.getAttributes();
711 if (newAttributes != null) {
712 // Take the media size only if the current printer supports is.
713 MediaSize oldMediaSize = currAttributes.getMediaSize();
714 MediaSize newMediaSize = newAttributes.getMediaSize();
715 if (!oldMediaSize.equals(newMediaSize)) {
716 final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount();
717 MediaSize newMediaSizePortrait = newAttributes.getMediaSize().asPortrait();
718 for (int i = 0; i < mediaSizeCount; i++) {
719 MediaSize supportedSizePortrait = mMediaSizeSpinnerAdapter.getItem(i)
721 if (supportedSizePortrait.equals(newMediaSizePortrait)) {
722 currAttributes.setMediaSize(newMediaSize);
723 mMediaSizeSpinner.setSelection(i);
724 if (currAttributes.getMediaSize().isPortrait()) {
725 if (mOrientationSpinner.getSelectedItemPosition() != 0) {
726 mOrientationSpinner.setSelection(0);
729 if (mOrientationSpinner.getSelectedItemPosition() != 1) {
730 mOrientationSpinner.setSelection(1);
738 // Take the resolution only if the current printer supports is.
739 Resolution oldResolution = currAttributes.getResolution();
740 Resolution newResolution = newAttributes.getResolution();
741 if (!oldResolution.equals(newResolution)) {
742 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
743 if (capabilities != null) {
744 List<Resolution> resolutions = capabilities.getResolutions();
745 final int resolutionCount = resolutions.size();
746 for (int i = 0; i < resolutionCount; i++) {
747 Resolution resolution = resolutions.get(i);
748 if (resolution.equals(newResolution)) {
749 currAttributes.setResolution(resolution);
756 // Take the color mode only if the current printer supports it.
757 final int currColorMode = currAttributes.getColorMode();
758 final int newColorMode = newAttributes.getColorMode();
759 if (currColorMode != newColorMode) {
760 final int colorModeCount = mColorModeSpinner.getCount();
761 for (int i = 0; i < colorModeCount; i++) {
762 final int supportedColorMode = mColorModeSpinnerAdapter.getItem(i).value;
763 if (supportedColorMode == newColorMode) {
764 currAttributes.setColorMode(newColorMode);
765 mColorModeSpinner.setSelection(i);
772 // Handle selected page changes making sure they are in the doc.
773 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
774 final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0;
775 PageRange[] pageRanges = printJobInfo.getPages();
776 if (pageRanges != null && pageCount > 0) {
777 pageRanges = PageRangeUtils.normalize(pageRanges);
779 List<PageRange> validatedList = new ArrayList<>();
780 final int rangeCount = pageRanges.length;
781 for (int i = 0; i < rangeCount; i++) {
782 PageRange pageRange = pageRanges[i];
783 if (pageRange.getEnd() >= pageCount) {
784 final int rangeStart = pageRange.getStart();
785 final int rangeEnd = pageCount - 1;
786 if (rangeStart <= rangeEnd) {
787 pageRange = new PageRange(rangeStart, rangeEnd);
788 validatedList.add(pageRange);
792 validatedList.add(pageRange);
795 if (!validatedList.isEmpty()) {
796 PageRange[] validatedArray = new PageRange[validatedList.size()];
797 validatedList.toArray(validatedArray);
798 updateSelectedPages(validatedArray, pageCount);
802 // Update the content if needed.
803 if (canUpdateDocument()) {
804 updateDocument(false);
808 private void setState(int state) {
809 if (isFinalState(mState)) {
810 if (isFinalState(state)) {
818 private static boolean isFinalState(int state) {
819 return state == STATE_PRINT_CONFIRMED
820 || state == STATE_PRINT_CANCELED
821 || state == STATE_PRINT_COMPLETED;
824 private void updateSelectedPagesFromPreview() {
825 PageRange[] selectedPages = mPrintPreviewController.getSelectedPages();
826 if (!Arrays.equals(mSelectedPages, selectedPages)) {
827 updateSelectedPages(selectedPages,
828 getAdjustedPageCount(mPrintedDocument.getDocumentInfo().info));
832 private void updateSelectedPages(PageRange[] selectedPages, int pageInDocumentCount) {
833 if (selectedPages == null || selectedPages.length <= 0) {
837 selectedPages = PageRangeUtils.normalize(selectedPages);
839 // Handle the case where all pages are specified explicitly
840 // instead of the *all pages* constant.
841 if (PageRangeUtils.isAllPages(selectedPages, pageInDocumentCount)) {
842 selectedPages = new PageRange[] {PageRange.ALL_PAGES};
845 if (Arrays.equals(mSelectedPages, selectedPages)) {
849 mSelectedPages = selectedPages;
850 mPrintJob.setPages(selectedPages);
852 if (Arrays.equals(selectedPages, ALL_PAGES_ARRAY)) {
853 if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
854 mRangeOptionsSpinner.setSelection(0);
855 mPageRangeEditText.setText("");
857 } else if (selectedPages[0].getStart() >= 0
858 && selectedPages[selectedPages.length - 1].getEnd() < pageInDocumentCount) {
859 if (mRangeOptionsSpinner.getSelectedItemPosition() != 1) {
860 mRangeOptionsSpinner.setSelection(1);
863 StringBuilder builder = new StringBuilder();
864 final int pageRangeCount = selectedPages.length;
865 for (int i = 0; i < pageRangeCount; i++) {
866 if (builder.length() > 0) {
870 final int shownStartPage;
871 final int shownEndPage;
872 PageRange pageRange = selectedPages[i];
873 if (pageRange.equals(PageRange.ALL_PAGES)) {
875 shownEndPage = pageInDocumentCount;
877 shownStartPage = pageRange.getStart() + 1;
878 shownEndPage = pageRange.getEnd() + 1;
881 builder.append(shownStartPage);
883 if (shownStartPage != shownEndPage) {
885 builder.append(shownEndPage);
889 mPageRangeEditText.setText(builder.toString());
893 private void ensureProgressUiShown() {
897 if (mUiState != UI_STATE_PROGRESS) {
898 mUiState = UI_STATE_PROGRESS;
899 mPrintPreviewController.setUiShown(false);
900 Fragment fragment = PrintProgressFragment.newInstance();
901 showFragment(fragment);
905 private void ensurePreviewUiShown() {
909 if (mUiState != UI_STATE_PREVIEW) {
910 mUiState = UI_STATE_PREVIEW;
911 mPrintPreviewController.setUiShown(true);
916 private void ensureErrorUiShown(CharSequence message, int action) {
920 if (mUiState != UI_STATE_ERROR) {
921 mUiState = UI_STATE_ERROR;
922 mPrintPreviewController.setUiShown(false);
923 Fragment fragment = PrintErrorFragment.newInstance(message, action);
924 showFragment(fragment);
928 private void showFragment(Fragment newFragment) {
929 FragmentTransaction transaction = getFragmentManager().beginTransaction();
930 Fragment oldFragment = getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
931 if (oldFragment != null) {
932 transaction.remove(oldFragment);
934 if (newFragment != null) {
935 transaction.add(R.id.embedded_content_container, newFragment, FRAGMENT_TAG);
937 transaction.commit();
938 getFragmentManager().executePendingTransactions();
941 private void requestCreatePdfFileOrFinish() {
942 if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) {
943 startCreateDocumentActivity();
945 transformDocumentAndFinish(null);
949 private void updatePrintAttributesFromCapabilities(PrinterCapabilitiesInfo capabilities) {
950 PrintAttributes defaults = capabilities.getDefaults();
952 // Sort the media sizes based on the current locale.
953 List<MediaSize> sortedMediaSizes = new ArrayList<>(capabilities.getMediaSizes());
954 Collections.sort(sortedMediaSizes, mMediaSizeComparator);
956 PrintAttributes attributes = mPrintJob.getAttributes();
959 MediaSize currMediaSize = attributes.getMediaSize();
960 if (currMediaSize == null) {
961 attributes.setMediaSize(defaults.getMediaSize());
963 boolean foundCurrentMediaSize = false;
964 // Try to find the current media size in the capabilities as
965 // it may be in a different orientation.
966 MediaSize currMediaSizePortrait = currMediaSize.asPortrait();
967 final int mediaSizeCount = sortedMediaSizes.size();
968 for (int i = 0; i < mediaSizeCount; i++) {
969 MediaSize mediaSize = sortedMediaSizes.get(i);
970 if (currMediaSizePortrait.equals(mediaSize.asPortrait())) {
971 attributes.setMediaSize(currMediaSize);
972 foundCurrentMediaSize = true;
976 // If we did not find the current media size fall back to default.
977 if (!foundCurrentMediaSize) {
978 attributes.setMediaSize(defaults.getMediaSize());
983 final int colorMode = attributes.getColorMode();
984 if ((capabilities.getColorModes() & colorMode) == 0) {
985 attributes.setColorMode(defaults.getColorMode());
989 Resolution resolution = attributes.getResolution();
990 if (resolution == null || !capabilities.getResolutions().contains(resolution)) {
991 attributes.setResolution(defaults.getResolution());
995 attributes.setMinMargins(defaults.getMinMargins());
998 private boolean updateDocument(boolean clearLastError) {
999 if (!clearLastError && mPrintedDocument.hasUpdateError()) {
1003 if (clearLastError && mPrintedDocument.hasUpdateError()) {
1004 mPrintedDocument.clearUpdateError();
1007 final boolean preview = mState != STATE_PRINT_CONFIRMED;
1008 final PageRange[] pages;
1010 pages = mPrintPreviewController.getRequestedPages();
1012 pages = mPrintPreviewController.getSelectedPages();
1015 final boolean willUpdate = mPrintedDocument.update(mPrintJob.getAttributes(),
1018 if (willUpdate && !mPrintedDocument.hasLaidOutPages()) {
1019 // When the update is done we update the print preview.
1020 mProgressMessageController.post();
1022 } else if (!willUpdate) {
1024 updatePrintPreviewController(false);
1030 private void addCurrentPrinterToHistory() {
1031 if (mCurrentPrinter != null) {
1032 PrinterId fakePdfPrinterId = mDestinationSpinnerAdapter.getPdfPrinter().getId();
1033 if (!mCurrentPrinter.getId().equals(fakePdfPrinterId)) {
1034 mPrinterRegistry.addHistoricalPrinter(mCurrentPrinter);
1039 private void cancelPrint() {
1040 setState(STATE_PRINT_CANCELED);
1042 if (mPrintedDocument.isUpdating()) {
1043 mPrintedDocument.cancel();
1048 private void confirmPrint() {
1049 setState(STATE_PRINT_CONFIRMED);
1052 addCurrentPrinterToHistory();
1054 PageRange[] selectedPages = computeSelectedPages();
1055 if (!Arrays.equals(mSelectedPages, selectedPages)) {
1056 mSelectedPages = selectedPages;
1058 updatePrintPreviewController(false);
1061 updateSelectedPagesFromPreview();
1062 mPrintPreviewController.closeOptions();
1064 if (canUpdateDocument()) {
1065 updateDocument(false);
1068 if (!mPrintedDocument.isUpdating()) {
1069 requestCreatePdfFileOrFinish();
1073 private void bindUi() {
1075 mSummaryContainer = findViewById(R.id.summary_content);
1076 mSummaryCopies = (TextView) findViewById(R.id.copies_count_summary);
1077 mSummaryPaperSize = (TextView) findViewById(R.id.paper_size_summary);
1079 // Options container
1080 mOptionsContent = (PrintContentView) findViewById(R.id.options_content);
1081 mOptionsContent.setOptionsStateChangeListener(this);
1082 mOptionsContent.setOpenOptionsController(this);
1084 OnItemSelectedListener itemSelectedListener = new MyOnItemSelectedListener();
1085 OnClickListener clickListener = new MyClickListener();
1088 mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
1089 mCopiesEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
1090 mCopiesEditText.setText(MIN_COPIES_STRING);
1091 mCopiesEditText.setSelection(mCopiesEditText.getText().length());
1092 mCopiesEditText.addTextChangedListener(new EditTextWatcher());
1095 mDestinationSpinnerAdapter.registerDataSetObserver(new PrintersObserver());
1096 mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
1097 mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
1098 mDestinationSpinner.setOnItemSelectedListener(itemSelectedListener);
1101 mMediaSizeSpinnerAdapter = new ArrayAdapter<>(
1102 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
1103 mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
1104 mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
1105 mMediaSizeSpinner.setOnItemSelectedListener(itemSelectedListener);
1108 mColorModeSpinnerAdapter = new ArrayAdapter<>(
1109 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
1110 mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
1111 mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
1112 mColorModeSpinner.setOnItemSelectedListener(itemSelectedListener);
1115 mOrientationSpinnerAdapter = new ArrayAdapter<>(
1116 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
1117 String[] orientationLabels = getResources().getStringArray(
1118 R.array.orientation_labels);
1119 mOrientationSpinnerAdapter.add(new SpinnerItem<>(
1120 ORIENTATION_PORTRAIT, orientationLabels[0]));
1121 mOrientationSpinnerAdapter.add(new SpinnerItem<>(
1122 ORIENTATION_LANDSCAPE, orientationLabels[1]));
1123 mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
1124 mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
1125 mOrientationSpinner.setOnItemSelectedListener(itemSelectedListener);
1128 ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter = new ArrayAdapter<>(
1129 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
1130 mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
1131 mRangeOptionsSpinner.setAdapter(rangeOptionsSpinnerAdapter);
1132 mRangeOptionsSpinner.setOnItemSelectedListener(itemSelectedListener);
1133 updatePageRangeOptions(PrintDocumentInfo.PAGE_COUNT_UNKNOWN);
1136 mPageRangeTitle = (TextView) findViewById(R.id.page_range_title);
1137 mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
1138 mPageRangeEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
1139 mPageRangeEditText.addTextChangedListener(new RangeTextWatcher());
1141 // Advanced options button.
1142 mMoreOptionsButton = (Button) findViewById(R.id.more_options_button);
1143 mMoreOptionsButton.setOnClickListener(clickListener);
1146 mPrintButton = (ImageView) findViewById(R.id.print_button);
1147 mPrintButton.setOnClickListener(clickListener);
1150 private final class MyClickListener implements OnClickListener {
1152 public void onClick(View view) {
1153 if (view == mPrintButton) {
1154 if (mCurrentPrinter != null) {
1159 } else if (view == mMoreOptionsButton) {
1160 if (mCurrentPrinter != null) {
1161 startAdvancedPrintOptionsActivity(mCurrentPrinter);
1167 private static boolean canPrint(PrinterInfo printer) {
1168 return printer.getCapabilities() != null
1169 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
1172 void updateOptionsUi() {
1173 // Always update the summary.
1176 if (mState == STATE_PRINT_CONFIRMED
1177 || mState == STATE_PRINT_COMPLETED
1178 || mState == STATE_PRINT_CANCELED
1179 || mState == STATE_UPDATE_FAILED
1180 || mState == STATE_CREATE_FILE_FAILED
1181 || mState == STATE_PRINTER_UNAVAILABLE
1182 || mState == STATE_UPDATE_SLOW) {
1183 if (mState != STATE_PRINTER_UNAVAILABLE) {
1184 mDestinationSpinner.setEnabled(false);
1186 mCopiesEditText.setEnabled(false);
1187 mCopiesEditText.setFocusable(false);
1188 mMediaSizeSpinner.setEnabled(false);
1189 mColorModeSpinner.setEnabled(false);
1190 mOrientationSpinner.setEnabled(false);
1191 mRangeOptionsSpinner.setEnabled(false);
1192 mPageRangeEditText.setEnabled(false);
1193 mPrintButton.setVisibility(View.GONE);
1194 mMoreOptionsButton.setEnabled(false);
1198 // If no current printer, or it has no capabilities, or it is not
1199 // available, we disable all print options except the destination.
1200 if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) {
1201 mCopiesEditText.setEnabled(false);
1202 mCopiesEditText.setFocusable(false);
1203 mMediaSizeSpinner.setEnabled(false);
1204 mColorModeSpinner.setEnabled(false);
1205 mOrientationSpinner.setEnabled(false);
1206 mRangeOptionsSpinner.setEnabled(false);
1207 mPageRangeEditText.setEnabled(false);
1208 mPrintButton.setVisibility(View.GONE);
1209 mMoreOptionsButton.setEnabled(false);
1213 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
1214 PrintAttributes defaultAttributes = capabilities.getDefaults();
1217 mDestinationSpinner.setEnabled(true);
1220 mMediaSizeSpinner.setEnabled(true);
1222 List<MediaSize> mediaSizes = new ArrayList<>(capabilities.getMediaSizes());
1223 // Sort the media sizes based on the current locale.
1224 Collections.sort(mediaSizes, mMediaSizeComparator);
1226 PrintAttributes attributes = mPrintJob.getAttributes();
1228 // If the media sizes changed, we update the adapter and the spinner.
1229 boolean mediaSizesChanged = false;
1230 final int mediaSizeCount = mediaSizes.size();
1231 if (mediaSizeCount != mMediaSizeSpinnerAdapter.getCount()) {
1232 mediaSizesChanged = true;
1234 for (int i = 0; i < mediaSizeCount; i++) {
1235 if (!mediaSizes.get(i).equals(mMediaSizeSpinnerAdapter.getItem(i).value)) {
1236 mediaSizesChanged = true;
1241 if (mediaSizesChanged) {
1242 // Remember the old media size to try selecting it again.
1243 int oldMediaSizeNewIndex = AdapterView.INVALID_POSITION;
1244 MediaSize oldMediaSize = attributes.getMediaSize();
1246 // Rebuild the adapter data.
1247 mMediaSizeSpinnerAdapter.clear();
1248 for (int i = 0; i < mediaSizeCount; i++) {
1249 MediaSize mediaSize = mediaSizes.get(i);
1250 if (oldMediaSize != null
1251 && mediaSize.asPortrait().equals(oldMediaSize.asPortrait())) {
1252 // Update the index of the old selection.
1253 oldMediaSizeNewIndex = i;
1255 mMediaSizeSpinnerAdapter.add(new SpinnerItem<>(
1256 mediaSize, mediaSize.getLabel(getPackageManager())));
1259 if (oldMediaSizeNewIndex != AdapterView.INVALID_POSITION) {
1260 // Select the old media size - nothing really changed.
1261 if (mMediaSizeSpinner.getSelectedItemPosition() != oldMediaSizeNewIndex) {
1262 mMediaSizeSpinner.setSelection(oldMediaSizeNewIndex);
1265 // Select the first or the default.
1266 final int mediaSizeIndex = Math.max(mediaSizes.indexOf(
1267 defaultAttributes.getMediaSize()), 0);
1268 if (mMediaSizeSpinner.getSelectedItemPosition() != mediaSizeIndex) {
1269 mMediaSizeSpinner.setSelection(mediaSizeIndex);
1271 // Respect the orientation of the old selection.
1272 if (oldMediaSize != null) {
1273 if (oldMediaSize.isPortrait()) {
1274 attributes.setMediaSize(mMediaSizeSpinnerAdapter
1275 .getItem(mediaSizeIndex).value.asPortrait());
1277 attributes.setMediaSize(mMediaSizeSpinnerAdapter
1278 .getItem(mediaSizeIndex).value.asLandscape());
1285 mColorModeSpinner.setEnabled(true);
1286 final int colorModes = capabilities.getColorModes();
1288 // If the color modes changed, we update the adapter and the spinner.
1289 boolean colorModesChanged = false;
1290 if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) {
1291 colorModesChanged = true;
1293 int remainingColorModes = colorModes;
1294 int adapterIndex = 0;
1295 while (remainingColorModes != 0) {
1296 final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
1297 final int colorMode = 1 << colorBitOffset;
1298 remainingColorModes &= ~colorMode;
1299 if (colorMode != mColorModeSpinnerAdapter.getItem(adapterIndex).value) {
1300 colorModesChanged = true;
1306 if (colorModesChanged) {
1307 // Remember the old color mode to try selecting it again.
1308 int oldColorModeNewIndex = AdapterView.INVALID_POSITION;
1309 final int oldColorMode = attributes.getColorMode();
1311 // Rebuild the adapter data.
1312 mColorModeSpinnerAdapter.clear();
1313 String[] colorModeLabels = getResources().getStringArray(R.array.color_mode_labels);
1314 int remainingColorModes = colorModes;
1315 while (remainingColorModes != 0) {
1316 final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
1317 final int colorMode = 1 << colorBitOffset;
1318 if (colorMode == oldColorMode) {
1319 // Update the index of the old selection.
1320 oldColorModeNewIndex = colorBitOffset;
1322 remainingColorModes &= ~colorMode;
1323 mColorModeSpinnerAdapter.add(new SpinnerItem<>(colorMode,
1324 colorModeLabels[colorBitOffset]));
1326 if (oldColorModeNewIndex != AdapterView.INVALID_POSITION) {
1327 // Select the old color mode - nothing really changed.
1328 if (mColorModeSpinner.getSelectedItemPosition() != oldColorModeNewIndex) {
1329 mColorModeSpinner.setSelection(oldColorModeNewIndex);
1332 // Select the default.
1333 final int selectedColorMode = colorModes & defaultAttributes.getColorMode();
1334 final int itemCount = mColorModeSpinnerAdapter.getCount();
1335 for (int i = 0; i < itemCount; i++) {
1336 SpinnerItem<Integer> item = mColorModeSpinnerAdapter.getItem(i);
1337 if (selectedColorMode == item.value) {
1338 if (mColorModeSpinner.getSelectedItemPosition() != i) {
1339 mColorModeSpinner.setSelection(i);
1341 attributes.setColorMode(selectedColorMode);
1348 mOrientationSpinner.setEnabled(true);
1349 MediaSize mediaSize = attributes.getMediaSize();
1350 if (mediaSize != null) {
1351 if (mediaSize.isPortrait()
1352 && mOrientationSpinner.getSelectedItemPosition() != 0) {
1353 mOrientationSpinner.setSelection(0);
1354 } else if (!mediaSize.isPortrait()
1355 && mOrientationSpinner.getSelectedItemPosition() != 1) {
1356 mOrientationSpinner.setSelection(1);
1361 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
1362 final int pageCount = getAdjustedPageCount(info);
1363 if (info != null && pageCount > 0) {
1364 if (pageCount == 1) {
1365 mRangeOptionsSpinner.setEnabled(false);
1367 mRangeOptionsSpinner.setEnabled(true);
1368 if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
1369 if (!mPageRangeEditText.isEnabled()) {
1370 mPageRangeEditText.setEnabled(true);
1371 mPageRangeEditText.setVisibility(View.VISIBLE);
1372 mPageRangeTitle.setVisibility(View.VISIBLE);
1373 mPageRangeEditText.requestFocus();
1374 InputMethodManager imm = (InputMethodManager)
1375 getSystemService(Context.INPUT_METHOD_SERVICE);
1376 imm.showSoftInput(mPageRangeEditText, 0);
1379 mPageRangeEditText.setEnabled(false);
1380 mPageRangeEditText.setVisibility(View.INVISIBLE);
1381 mPageRangeTitle.setVisibility(View.INVISIBLE);
1385 if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
1386 mRangeOptionsSpinner.setSelection(0);
1387 mPageRangeEditText.setText("");
1389 mRangeOptionsSpinner.setEnabled(false);
1390 mPageRangeEditText.setEnabled(false);
1391 mPageRangeEditText.setVisibility(View.INVISIBLE);
1392 mPageRangeTitle.setVisibility(View.INVISIBLE);
1395 final int newPageCount = getAdjustedPageCount(info);
1396 if (newPageCount != mCurrentPageCount) {
1397 mCurrentPageCount = newPageCount;
1398 updatePageRangeOptions(newPageCount);
1401 // Advanced print options
1402 ComponentName serviceName = mCurrentPrinter.getId().getServiceName();
1403 if (!TextUtils.isEmpty(PrintOptionUtils.getAdvancedOptionsActivityName(
1404 this, serviceName))) {
1405 mMoreOptionsButton.setVisibility(View.VISIBLE);
1406 mMoreOptionsButton.setEnabled(true);
1408 mMoreOptionsButton.setVisibility(View.GONE);
1409 mMoreOptionsButton.setEnabled(false);
1413 if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
1414 mPrintButton.setImageResource(com.android.internal.R.drawable.ic_print);
1415 mPrintButton.setContentDescription(getString(R.string.print_button));
1417 mPrintButton.setImageResource(R.drawable.ic_menu_savetopdf);
1418 mPrintButton.setContentDescription(getString(R.string.savetopdf_button));
1420 if (!mPrintedDocument.getDocumentInfo().laidout
1421 ||(mRangeOptionsSpinner.getSelectedItemPosition() == 1
1422 && (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors()))
1423 || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
1424 && (mPrintedDocument.getDocumentInfo() == null || hasErrors()))) {
1425 mPrintButton.setVisibility(View.GONE);
1427 mPrintButton.setVisibility(View.VISIBLE);
1431 if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
1432 mCopiesEditText.setEnabled(true);
1433 mCopiesEditText.setFocusableInTouchMode(true);
1435 CharSequence text = mCopiesEditText.getText();
1436 if (TextUtils.isEmpty(text) || !MIN_COPIES_STRING.equals(text.toString())) {
1437 mCopiesEditText.setText(MIN_COPIES_STRING);
1439 mCopiesEditText.setEnabled(false);
1440 mCopiesEditText.setFocusable(false);
1442 if (mCopiesEditText.getError() == null
1443 && TextUtils.isEmpty(mCopiesEditText.getText())) {
1444 mCopiesEditText.setText(MIN_COPIES_STRING);
1445 mCopiesEditText.requestFocus();
1449 private void updateSummary() {
1450 CharSequence copiesText = null;
1451 CharSequence mediaSizeText = null;
1453 if (!TextUtils.isEmpty(mCopiesEditText.getText())) {
1454 copiesText = mCopiesEditText.getText();
1455 mSummaryCopies.setText(copiesText);
1458 final int selectedMediaIndex = mMediaSizeSpinner.getSelectedItemPosition();
1459 if (selectedMediaIndex >= 0) {
1460 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(selectedMediaIndex);
1461 mediaSizeText = mediaItem.label;
1462 mSummaryPaperSize.setText(mediaSizeText);
1465 if (!TextUtils.isEmpty(copiesText) && !TextUtils.isEmpty(mediaSizeText)) {
1466 String summaryText = getString(R.string.summary_template, copiesText, mediaSizeText);
1467 mSummaryContainer.setContentDescription(summaryText);
1471 private void updatePageRangeOptions(int pageCount) {
1472 ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter =
1473 (ArrayAdapter) mRangeOptionsSpinner.getAdapter();
1474 rangeOptionsSpinnerAdapter.clear();
1476 final int[] rangeOptionsValues = getResources().getIntArray(
1477 R.array.page_options_values);
1479 String pageCountLabel = (pageCount > 0) ? String.valueOf(pageCount) : "";
1480 String[] rangeOptionsLabels = new String[] {
1481 getString(R.string.template_all_pages, pageCountLabel),
1482 getString(R.string.template_page_range, pageCountLabel)
1485 final int rangeOptionsCount = rangeOptionsLabels.length;
1486 for (int i = 0; i < rangeOptionsCount; i++) {
1487 rangeOptionsSpinnerAdapter.add(new SpinnerItem<>(
1488 rangeOptionsValues[i], rangeOptionsLabels[i]));
1492 private PageRange[] computeSelectedPages() {
1497 if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
1498 List<PageRange> pageRanges = new ArrayList<>();
1499 mStringCommaSplitter.setString(mPageRangeEditText.getText().toString());
1501 while (mStringCommaSplitter.hasNext()) {
1502 String range = mStringCommaSplitter.next().trim();
1503 if (TextUtils.isEmpty(range)) {
1506 final int dashIndex = range.indexOf('-');
1507 final int fromIndex;
1510 if (dashIndex > 0) {
1511 fromIndex = Integer.parseInt(range.substring(0, dashIndex).trim()) - 1;
1512 // It is possible that the dash is at the end since the input
1513 // verification can has to allow the user to keep entering if
1514 // this would lead to a valid input. So we handle this.
1515 if (dashIndex < range.length() - 1) {
1516 String fromString = range.substring(dashIndex + 1, range.length()).trim();
1517 toIndex = Integer.parseInt(fromString) - 1;
1519 toIndex = fromIndex;
1522 fromIndex = toIndex = Integer.parseInt(range) - 1;
1525 PageRange pageRange = new PageRange(Math.min(fromIndex, toIndex),
1526 Math.max(fromIndex, toIndex));
1527 pageRanges.add(pageRange);
1530 PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
1531 pageRanges.toArray(pageRangesArray);
1533 return PageRangeUtils.normalize(pageRangesArray);
1536 return ALL_PAGES_ARRAY;
1539 private int getAdjustedPageCount(PrintDocumentInfo info) {
1541 final int pageCount = info.getPageCount();
1542 if (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
1546 // If the app does not tell us how many pages are in the
1547 // doc we ask for all pages and use the document page count.
1548 return mPrintPreviewController.getFilePageCount();
1551 private boolean hasErrors() {
1552 return (mCopiesEditText.getError() != null)
1553 || (mPageRangeEditText.getVisibility() == View.VISIBLE
1554 && mPageRangeEditText.getError() != null);
1557 public void onPrinterAvailable(PrinterInfo printer) {
1558 if (mCurrentPrinter.equals(printer)) {
1559 setState(STATE_CONFIGURING);
1560 if (canUpdateDocument()) {
1561 updateDocument(false);
1563 ensurePreviewUiShown();
1568 public void onPrinterUnavailable(PrinterInfo printer) {
1569 if (mCurrentPrinter.getId().equals(printer.getId())) {
1570 setState(STATE_PRINTER_UNAVAILABLE);
1571 if (mPrintedDocument.isUpdating()) {
1572 mPrintedDocument.cancel();
1574 ensureErrorUiShown(getString(R.string.print_error_printer_unavailable),
1575 PrintErrorFragment.ACTION_NONE);
1580 private boolean canUpdateDocument() {
1581 if (mPrintedDocument.isDestroyed()) {
1589 PrintAttributes attributes = mPrintJob.getAttributes();
1591 final int colorMode = attributes.getColorMode();
1592 if (colorMode != PrintAttributes.COLOR_MODE_COLOR
1593 && colorMode != PrintAttributes.COLOR_MODE_MONOCHROME) {
1596 if (attributes.getMediaSize() == null) {
1599 if (attributes.getMinMargins() == null) {
1602 if (attributes.getResolution() == null) {
1606 if (mCurrentPrinter == null) {
1609 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
1610 if (capabilities == null) {
1613 if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
1620 private void transformDocumentAndFinish(final Uri writeToUri) {
1621 // If saving to PDF, apply the attibutes as we are acting as a print service.
1622 PrintAttributes attributes = mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter
1623 ? mPrintJob.getAttributes() : null;
1624 new DocumentTransformer(this, mPrintJob, mFileProvider, attributes, new Runnable() {
1627 if (writeToUri != null) {
1628 mPrintedDocument.writeContent(getContentResolver(), writeToUri);
1635 private void doFinish() {
1636 if (mState != STATE_INITIALIZING) {
1637 mProgressMessageController.cancel();
1638 mPrinterRegistry.setTrackedPrinter(null);
1639 mSpoolerProvider.destroy();
1640 mPrintedDocument.finish();
1641 mPrintedDocument.destroy();
1642 mPrintPreviewController.destroy(new Runnable() {
1653 private final class SpinnerItem<T> {
1655 final CharSequence label;
1657 public SpinnerItem(T value, CharSequence label) {
1662 public String toString() {
1663 return label.toString();
1667 private final class PrinterAvailabilityDetector implements Runnable {
1668 private static final long UNAVAILABLE_TIMEOUT_MILLIS = 10000; // 10sec
1670 private boolean mPosted;
1672 private boolean mPrinterUnavailable;
1674 private PrinterInfo mPrinter;
1676 public void updatePrinter(PrinterInfo printer) {
1677 if (printer.equals(mDestinationSpinnerAdapter.getPdfPrinter())) {
1681 final boolean available = printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
1682 && printer.getCapabilities() != null;
1683 final boolean notifyIfAvailable;
1685 if (mPrinter == null || !mPrinter.getId().equals(printer.getId())) {
1686 notifyIfAvailable = true;
1688 mPrinterUnavailable = false;
1689 mPrinter = new PrinterInfo.Builder(printer).build();
1692 (mPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
1693 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE)
1694 || (mPrinter.getCapabilities() == null
1695 && printer.getCapabilities() != null);
1696 mPrinter.copyFrom(printer);
1701 mPrinterUnavailable = false;
1702 if (notifyIfAvailable) {
1703 onPrinterAvailable(mPrinter);
1706 if (!mPrinterUnavailable) {
1712 public void cancel() {
1714 mPrinterUnavailable = false;
1717 private void postIfNeeded() {
1720 mDestinationSpinner.postDelayed(this, UNAVAILABLE_TIMEOUT_MILLIS);
1724 private void unpostIfNeeded() {
1727 mDestinationSpinner.removeCallbacks(this);
1734 mPrinterUnavailable = true;
1735 onPrinterUnavailable(mPrinter);
1739 private static final class PrinterHolder {
1740 PrinterInfo printer;
1743 public PrinterHolder(PrinterInfo printer) {
1744 this.printer = printer;
1748 private final class DestinationAdapter extends BaseAdapter
1749 implements PrinterRegistry.OnPrintersChangeListener {
1750 private final List<PrinterHolder> mPrinterHolders = new ArrayList<>();
1752 private final PrinterHolder mFakePdfPrinterHolder;
1754 private boolean mHistoricalPrintersLoaded;
1756 public DestinationAdapter() {
1757 mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
1758 if (mHistoricalPrintersLoaded) {
1759 addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters());
1761 mPrinterRegistry.setOnPrintersChangeListener(this);
1762 mFakePdfPrinterHolder = new PrinterHolder(createFakePdfPrinter());
1765 public PrinterInfo getPdfPrinter() {
1766 return mFakePdfPrinterHolder.printer;
1769 public int getPrinterIndex(PrinterId printerId) {
1770 for (int i = 0; i < getCount(); i++) {
1771 PrinterHolder printerHolder = (PrinterHolder) getItem(i);
1772 if (printerHolder != null && !printerHolder.removed
1773 && printerHolder.printer.getId().equals(printerId)) {
1777 return AdapterView.INVALID_POSITION;
1780 public void ensurePrinterInVisibleAdapterPosition(PrinterId printerId) {
1781 final int printerCount = mPrinterHolders.size();
1782 for (int i = 0; i < printerCount; i++) {
1783 PrinterHolder printerHolder = mPrinterHolders.get(i);
1784 if (printerHolder.printer.getId().equals(printerId)) {
1785 // If already in the list - do nothing.
1786 if (i < getCount() - 2) {
1789 // Else replace the last one (two items are not printers).
1790 final int lastPrinterIndex = getCount() - 3;
1791 mPrinterHolders.set(i, mPrinterHolders.get(lastPrinterIndex));
1792 mPrinterHolders.set(lastPrinterIndex, printerHolder);
1793 notifyDataSetChanged();
1800 public int getCount() {
1801 if (mHistoricalPrintersLoaded) {
1802 return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
1808 public boolean isEnabled(int position) {
1809 Object item = getItem(position);
1810 if (item instanceof PrinterHolder) {
1811 PrinterHolder printerHolder = (PrinterHolder) item;
1812 return !printerHolder.removed
1813 && printerHolder.printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
1819 public Object getItem(int position) {
1820 if (mPrinterHolders.isEmpty()) {
1821 if (position == 0) {
1822 return mFakePdfPrinterHolder;
1826 return mPrinterHolders.get(position);
1828 if (position == 1) {
1829 return mFakePdfPrinterHolder;
1831 if (position < getCount() - 1) {
1832 return mPrinterHolders.get(position - 1);
1839 public long getItemId(int position) {
1840 if (mPrinterHolders.isEmpty()) {
1841 if (position == 0) {
1842 return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
1843 } else if (position == 1) {
1844 return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
1847 if (position == 1) {
1848 return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
1850 if (position == getCount() - 1) {
1851 return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
1858 public View getDropDownView(int position, View convertView, ViewGroup parent) {
1859 View view = getView(position, convertView, parent);
1860 view.setEnabled(isEnabled(position));
1865 public View getView(int position, View convertView, ViewGroup parent) {
1866 if (convertView == null) {
1867 convertView = getLayoutInflater().inflate(
1868 R.layout.printer_dropdown_item, parent, false);
1871 CharSequence title = null;
1872 CharSequence subtitle = null;
1873 Drawable icon = null;
1875 if (mPrinterHolders.isEmpty()) {
1876 if (position == 0 && getPdfPrinter() != null) {
1877 PrinterHolder printerHolder = (PrinterHolder) getItem(position);
1878 title = printerHolder.printer.getName();
1879 icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf);
1880 } else if (position == 1) {
1881 title = getString(R.string.all_printers);
1884 if (position == 1 && getPdfPrinter() != null) {
1885 PrinterHolder printerHolder = (PrinterHolder) getItem(position);
1886 title = printerHolder.printer.getName();
1887 icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf);
1888 } else if (position == getCount() - 1) {
1889 title = getString(R.string.all_printers);
1891 PrinterHolder printerHolder = (PrinterHolder) getItem(position);
1892 title = printerHolder.printer.getName();
1894 PackageInfo packageInfo = getPackageManager().getPackageInfo(
1895 printerHolder.printer.getId().getServiceName().getPackageName(), 0);
1896 subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
1897 icon = packageInfo.applicationInfo.loadIcon(getPackageManager());
1898 } catch (NameNotFoundException nnfe) {
1904 TextView titleView = (TextView) convertView.findViewById(R.id.title);
1905 titleView.setText(title);
1907 TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
1908 if (!TextUtils.isEmpty(subtitle)) {
1909 subtitleView.setText(subtitle);
1910 subtitleView.setVisibility(View.VISIBLE);
1912 subtitleView.setText(null);
1913 subtitleView.setVisibility(View.GONE);
1916 ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
1918 iconView.setImageDrawable(icon);
1919 iconView.setVisibility(View.VISIBLE);
1921 iconView.setVisibility(View.INVISIBLE);
1928 public void onPrintersChanged(List<PrinterInfo> printers) {
1929 // We rearrange the printers if the user selects a printer
1930 // not shown in the initial short list. Therefore, we have
1931 // to keep the printer order.
1933 // Check if historical printers are loaded as this adapter is open
1934 // for busyness only if they are. This member is updated here and
1935 // when the adapter is created because the historical printers may
1936 // be loaded before or after the adapter is created.
1937 mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
1939 // No old printers - do not bother keeping their position.
1940 if (mPrinterHolders.isEmpty()) {
1941 addPrinters(mPrinterHolders, printers);
1942 notifyDataSetChanged();
1946 // Add the new printers to a map.
1947 ArrayMap<PrinterId, PrinterInfo> newPrintersMap = new ArrayMap<>();
1948 final int printerCount = printers.size();
1949 for (int i = 0; i < printerCount; i++) {
1950 PrinterInfo printer = printers.get(i);
1951 newPrintersMap.put(printer.getId(), printer);
1954 List<PrinterHolder> newPrinterHolders = new ArrayList<>();
1956 // Update printers we already have which are either updated or removed.
1957 // We do not remove printers if the currently selected printer is removed
1958 // to prevent the user printing to a wrong printer.
1959 final int oldPrinterCount = mPrinterHolders.size();
1960 for (int i = 0; i < oldPrinterCount; i++) {
1961 PrinterHolder printerHolder = mPrinterHolders.get(i);
1962 PrinterId oldPrinterId = printerHolder.printer.getId();
1963 PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId);
1964 if (updatedPrinter != null) {
1965 printerHolder.printer = updatedPrinter;
1967 printerHolder.removed = true;
1969 newPrinterHolders.add(printerHolder);
1972 // Add the rest of the new printers, i.e. what is left.
1973 addPrinters(newPrinterHolders, newPrintersMap.values());
1975 mPrinterHolders.clear();
1976 mPrinterHolders.addAll(newPrinterHolders);
1978 notifyDataSetChanged();
1982 public void onPrintersInvalid() {
1983 mPrinterHolders.clear();
1984 notifyDataSetInvalidated();
1987 public PrinterHolder getPrinterHolder(PrinterId printerId) {
1988 final int itemCount = getCount();
1989 for (int i = 0; i < itemCount; i++) {
1990 Object item = getItem(i);
1991 if (item instanceof PrinterHolder) {
1992 PrinterHolder printerHolder = (PrinterHolder) item;
1993 if (printerId.equals(printerHolder.printer.getId())) {
1994 return printerHolder;
2001 public void pruneRemovedPrinters() {
2002 final int holderCounts = mPrinterHolders.size();
2003 for (int i = holderCounts - 1; i >= 0; i--) {
2004 PrinterHolder printerHolder = mPrinterHolders.get(i);
2005 if (printerHolder.removed) {
2006 mPrinterHolders.remove(i);
2011 private void addPrinters(List<PrinterHolder> list, Collection<PrinterInfo> printers) {
2012 for (PrinterInfo printer : printers) {
2013 PrinterHolder printerHolder = new PrinterHolder(printer);
2014 list.add(printerHolder);
2018 private PrinterInfo createFakePdfPrinter() {
2019 MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintActivity.this);
2021 PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
2023 PrinterCapabilitiesInfo.Builder builder =
2024 new PrinterCapabilitiesInfo.Builder(printerId);
2026 String[] mediaSizeIds = getResources().getStringArray(R.array.pdf_printer_media_sizes);
2027 final int mediaSizeIdCount = mediaSizeIds.length;
2028 for (int i = 0; i < mediaSizeIdCount; i++) {
2029 String id = mediaSizeIds[i];
2030 MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id);
2031 builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize));
2034 builder.addResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300),
2036 builder.setColorModes(PrintAttributes.COLOR_MODE_COLOR
2037 | PrintAttributes.COLOR_MODE_MONOCHROME, PrintAttributes.COLOR_MODE_COLOR);
2039 return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf),
2040 PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build();
2044 private final class PrintersObserver extends DataSetObserver {
2046 public void onChanged() {
2047 PrinterInfo oldPrinterState = mCurrentPrinter;
2048 if (oldPrinterState == null) {
2052 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
2053 oldPrinterState.getId());
2054 if (printerHolder == null) {
2057 PrinterInfo newPrinterState = printerHolder.printer;
2059 if (!printerHolder.removed) {
2060 mDestinationSpinnerAdapter.pruneRemovedPrinters();
2062 onPrinterUnavailable(newPrinterState);
2065 if (oldPrinterState.equals(newPrinterState)) {
2069 PrinterCapabilitiesInfo oldCapab = oldPrinterState.getCapabilities();
2070 PrinterCapabilitiesInfo newCapab = newPrinterState.getCapabilities();
2072 final boolean hasCapab = newCapab != null;
2073 final boolean gotCapab = oldCapab == null && newCapab != null;
2074 final boolean lostCapab = oldCapab != null && newCapab == null;
2075 final boolean capabChanged = capabilitiesChanged(oldCapab, newCapab);
2077 final int oldStatus = oldPrinterState.getStatus();
2078 final int newStatus = newPrinterState.getStatus();
2080 final boolean isActive = newStatus != PrinterInfo.STATUS_UNAVAILABLE;
2081 final boolean becameActive = (oldStatus == PrinterInfo.STATUS_UNAVAILABLE
2082 && oldStatus != newStatus);
2083 final boolean becameInactive = (newStatus == PrinterInfo.STATUS_UNAVAILABLE
2084 && oldStatus != newStatus);
2086 mPrinterAvailabilityDetector.updatePrinter(newPrinterState);
2088 oldPrinterState.copyFrom(newPrinterState);
2090 if ((isActive && gotCapab) || (becameActive && hasCapab)) {
2091 if (hasCapab && capabChanged) {
2092 updatePrintAttributesFromCapabilities(newCapab);
2093 updatePrintPreviewController(false);
2095 onPrinterAvailable(newPrinterState);
2096 } else if ((becameInactive && hasCapab) || (isActive && lostCapab)) {
2097 onPrinterUnavailable(newPrinterState);
2100 final boolean updateNeeded = ((capabChanged && hasCapab && isActive)
2101 || (becameActive && hasCapab) || (isActive && gotCapab));
2103 if (updateNeeded && canUpdateDocument()) {
2104 updateDocument(false);
2110 private boolean capabilitiesChanged(PrinterCapabilitiesInfo oldCapabilities,
2111 PrinterCapabilitiesInfo newCapabilities) {
2112 if (oldCapabilities == null) {
2113 if (newCapabilities != null) {
2116 } else if (!oldCapabilities.equals(newCapabilities)) {
2123 private final class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
2125 public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
2126 if (spinner == mDestinationSpinner) {
2127 if (position == AdapterView.INVALID_POSITION) {
2131 if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) {
2132 startSelectPrinterActivity();
2136 PrinterHolder currentItem = (PrinterHolder) mDestinationSpinner.getSelectedItem();
2137 PrinterInfo currentPrinter = (currentItem != null) ? currentItem.printer : null;
2139 // Why on earth item selected is called if no selection changed.
2140 if (mCurrentPrinter == currentPrinter) {
2144 mCurrentPrinter = currentPrinter;
2146 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
2147 currentPrinter.getId());
2148 if (!printerHolder.removed) {
2149 setState(STATE_CONFIGURING);
2150 mDestinationSpinnerAdapter.pruneRemovedPrinters();
2151 ensurePreviewUiShown();
2154 mPrintJob.setPrinterId(currentPrinter.getId());
2155 mPrintJob.setPrinterName(currentPrinter.getName());
2157 mPrinterRegistry.setTrackedPrinter(currentPrinter.getId());
2159 PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
2160 if (capabilities != null) {
2161 updatePrintAttributesFromCapabilities(capabilities);
2164 mPrinterAvailabilityDetector.updatePrinter(currentPrinter);
2165 } else if (spinner == mMediaSizeSpinner) {
2166 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
2167 PrintAttributes attributes = mPrintJob.getAttributes();
2168 if (mOrientationSpinner.getSelectedItemPosition() == 0) {
2169 attributes.setMediaSize(mediaItem.value.asPortrait());
2171 attributes.setMediaSize(mediaItem.value.asLandscape());
2173 } else if (spinner == mColorModeSpinner) {
2174 SpinnerItem<Integer> colorModeItem = mColorModeSpinnerAdapter.getItem(position);
2175 mPrintJob.getAttributes().setColorMode(colorModeItem.value);
2176 } else if (spinner == mOrientationSpinner) {
2177 SpinnerItem<Integer> orientationItem = mOrientationSpinnerAdapter.getItem(position);
2178 PrintAttributes attributes = mPrintJob.getAttributes();
2179 if (mMediaSizeSpinner.getSelectedItem() != null) {
2180 if (orientationItem.value == ORIENTATION_PORTRAIT) {
2181 attributes.copyFrom(attributes.asPortrait());
2183 attributes.copyFrom(attributes.asLandscape());
2186 } else if (spinner == mRangeOptionsSpinner) {
2187 if (mRangeOptionsSpinner.getSelectedItemPosition() == 0) {
2188 mPageRangeEditText.setText("");
2189 } else if (TextUtils.isEmpty(mPageRangeEditText.getText())) {
2190 mPageRangeEditText.setError("");
2194 if (canUpdateDocument()) {
2195 updateDocument(false);
2202 public void onNothingSelected(AdapterView<?> parent) {
2207 private final class SelectAllOnFocusListener implements OnFocusChangeListener {
2209 public void onFocusChange(View view, boolean hasFocus) {
2210 EditText editText = (EditText) view;
2211 if (!TextUtils.isEmpty(editText.getText())) {
2212 editText.setSelection(editText.getText().length());
2217 private final class RangeTextWatcher implements TextWatcher {
2219 public void onTextChanged(CharSequence s, int start, int before, int count) {
2224 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2229 public void afterTextChanged(Editable editable) {
2230 final boolean hadErrors = hasErrors();
2232 String text = editable.toString();
2234 if (TextUtils.isEmpty(text)) {
2235 mPageRangeEditText.setError("");
2240 String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
2241 if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
2242 mPageRangeEditText.setError("");
2247 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
2248 final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0;
2251 Matcher matcher = PATTERN_DIGITS.matcher(text);
2252 while (matcher.find()) {
2253 String numericString = text.substring(matcher.start(), matcher.end()).trim();
2254 if (TextUtils.isEmpty(numericString)) {
2257 final int pageIndex = Integer.parseInt(numericString);
2258 if (pageIndex < 1 || pageIndex > pageCount) {
2259 mPageRangeEditText.setError("");
2265 // We intentionally do not catch the case of the from page being
2266 // greater than the to page. When computing the requested pages
2267 // we just swap them if necessary.
2269 mPageRangeEditText.setError(null);
2270 mPrintButton.setEnabled(true);
2273 if (hadErrors && !hasErrors()) {
2279 private final class EditTextWatcher implements TextWatcher {
2281 public void onTextChanged(CharSequence s, int start, int before, int count) {
2286 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2291 public void afterTextChanged(Editable editable) {
2292 final boolean hadErrors = hasErrors();
2294 if (editable.length() == 0) {
2295 mCopiesEditText.setError("");
2302 copies = Integer.parseInt(editable.toString());
2303 } catch (NumberFormatException nfe) {
2307 if (copies < MIN_COPIES) {
2308 mCopiesEditText.setError("");
2313 mPrintJob.setCopies(copies);
2315 mCopiesEditText.setError(null);
2319 if (hadErrors && canUpdateDocument()) {
2320 updateDocument(false);
2325 private final class ProgressMessageController implements Runnable {
2326 private static final long PROGRESS_TIMEOUT_MILLIS = 1000;
2328 private final Handler mHandler;
2330 private boolean mPosted;
2332 public ProgressMessageController(Context context) {
2333 mHandler = new Handler(context.getMainLooper(), null, false);
2336 public void post() {
2341 mHandler.postDelayed(this, PROGRESS_TIMEOUT_MILLIS);
2344 public void cancel() {
2349 mHandler.removeCallbacks(this);
2355 setState(STATE_UPDATE_SLOW);
2356 ensureProgressUiShown();
2361 private static final class DocumentTransformer implements ServiceConnection {
2362 private static final String TEMP_FILE_PREFIX = "print_job";
2363 private static final String TEMP_FILE_EXTENSION = ".pdf";
2365 private final Context mContext;
2367 private final MutexFileProvider mFileProvider;
2369 private final PrintJobInfo mPrintJob;
2371 private final PageRange[] mPagesToShred;
2373 private final PrintAttributes mAttributesToApply;
2375 private final Runnable mCallback;
2377 public DocumentTransformer(Context context, PrintJobInfo printJob,
2378 MutexFileProvider fileProvider, PrintAttributes attributes,
2379 Runnable callback) {
2381 mPrintJob = printJob;
2382 mFileProvider = fileProvider;
2383 mCallback = callback;
2384 mPagesToShred = computePagesToShred(mPrintJob);
2385 mAttributesToApply = attributes;
2388 public void transform() {
2389 // If we have only the pages we want, done.
2390 if (mPagesToShred.length <= 0 && mAttributesToApply == null) {
2395 // Bind to the manipulation service and the work
2396 // will be performed upon connection to the service.
2397 Intent intent = new Intent(PdfManipulationService.ACTION_GET_EDITOR);
2398 intent.setClass(mContext, PdfManipulationService.class);
2399 mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
2403 public void onServiceConnected(ComponentName name, IBinder service) {
2404 final IPdfEditor editor = IPdfEditor.Stub.asInterface(service);
2405 new AsyncTask<Void, Void, Void>() {
2407 protected Void doInBackground(Void... params) {
2408 // It's OK to access the data members as they are
2409 // final and this code is the last one to touch
2410 // them as shredding is the very last step, so the
2411 // UI is not interactive at this point.
2412 doTransform(editor);
2418 protected void onPostExecute(Void aVoid) {
2419 mContext.unbindService(DocumentTransformer.this);
2422 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
2426 public void onServiceDisconnected(ComponentName name) {
2430 private void doTransform(IPdfEditor editor) {
2431 File tempFile = null;
2432 ParcelFileDescriptor src = null;
2433 ParcelFileDescriptor dst = null;
2434 InputStream in = null;
2435 OutputStream out = null;
2437 File jobFile = mFileProvider.acquireFile(null);
2438 src = ParcelFileDescriptor.open(jobFile, ParcelFileDescriptor.MODE_READ_WRITE);
2440 // Open the document.
2441 editor.openDocument(src);
2443 // We passed the fd over IPC, close this one.
2447 editor.removePages(mPagesToShred);
2449 // Apply print attributes if needed.
2450 if (mAttributesToApply != null) {
2451 editor.applyPrintAttributes(mAttributesToApply);
2454 // Write the modified PDF to a temp file.
2455 tempFile = File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_EXTENSION,
2456 mContext.getCacheDir());
2457 dst = ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_WRITE);
2461 // Close the document.
2462 editor.closeDocument();
2464 // Copy the temp file over the print job file.
2466 in = new FileInputStream(tempFile);
2467 out = new FileOutputStream(jobFile);
2468 Streams.copy(in, out);
2469 } catch (IOException|RemoteException e) {
2470 Log.e(LOG_TAG, "Error dropping pages", e);
2472 IoUtils.closeQuietly(src);
2473 IoUtils.closeQuietly(dst);
2474 IoUtils.closeQuietly(in);
2475 IoUtils.closeQuietly(out);
2476 if (tempFile != null) {
2479 mFileProvider.releaseFile();
2483 private void updatePrintJob() {
2484 // Update the print job pages.
2485 final int newPageCount = PageRangeUtils.getNormalizedPageCount(
2486 mPrintJob.getPages(), 0);
2487 mPrintJob.setPages(new PageRange[]{PageRange.ALL_PAGES});
2489 // Update the print job document info.
2490 PrintDocumentInfo oldDocInfo = mPrintJob.getDocumentInfo();
2491 PrintDocumentInfo newDocInfo = new PrintDocumentInfo
2492 .Builder(oldDocInfo.getName())
2493 .setContentType(oldDocInfo.getContentType())
2494 .setPageCount(newPageCount)
2496 mPrintJob.setDocumentInfo(newDocInfo);
2499 private static PageRange[] computePagesToShred(PrintJobInfo printJob) {
2500 List<PageRange> rangesToShred = new ArrayList<>();
2501 PageRange previousRange = null;
2503 final int pageCount = printJob.getDocumentInfo().getPageCount();
2505 PageRange[] printedPages = printJob.getPages();
2506 final int rangeCount = printedPages.length;
2507 for (int i = 0; i < rangeCount; i++) {
2508 PageRange range = PageRangeUtils.asAbsoluteRange(printedPages[i], pageCount);
2510 if (previousRange == null) {
2511 final int startPageIdx = 0;
2512 final int endPageIdx = range.getStart() - 1;
2513 if (startPageIdx <= endPageIdx) {
2514 PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
2515 rangesToShred.add(removedRange);
2518 final int startPageIdx = previousRange.getEnd() + 1;
2519 final int endPageIdx = range.getStart() - 1;
2520 if (startPageIdx <= endPageIdx) {
2521 PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
2522 rangesToShred.add(removedRange);
2526 if (i == rangeCount - 1) {
2527 final int startPageIdx = range.getEnd() + 1;
2528 final int endPageIdx = printJob.getDocumentInfo().getPageCount() - 1;
2529 if (startPageIdx <= endPageIdx) {
2530 PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
2531 rangesToShred.add(removedRange);
2535 previousRange = range;
2538 PageRange[] result = new PageRange[rangesToShred.size()];
2539 rangesToShred.toArray(result);