| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.printspooler; |
| |
| import android.app.Activity; |
| import android.app.Dialog; |
| import android.app.LoaderManager; |
| import android.content.ActivityNotFoundException; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.Loader; |
| import android.content.ServiceConnection; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.pm.ResolveInfo; |
| import android.content.pm.ServiceInfo; |
| import android.database.DataSetObserver; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.AsyncTask; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.IBinder.DeathRecipient; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.print.ILayoutResultCallback; |
| import android.print.IPrintDocumentAdapter; |
| import android.print.IPrintDocumentAdapterObserver; |
| import android.print.IWriteResultCallback; |
| import android.print.PageRange; |
| import android.print.PrintAttributes; |
| import android.print.PrintAttributes.Margins; |
| import android.print.PrintAttributes.MediaSize; |
| import android.print.PrintAttributes.Resolution; |
| import android.print.PrintDocumentAdapter; |
| import android.print.PrintDocumentInfo; |
| import android.print.PrintJobId; |
| import android.print.PrintJobInfo; |
| import android.print.PrintManager; |
| import android.print.PrinterCapabilitiesInfo; |
| import android.print.PrinterId; |
| import android.print.PrinterInfo; |
| import android.printservice.PrintService; |
| import android.printservice.PrintServiceInfo; |
| import android.provider.DocumentsContract; |
| import android.text.Editable; |
| import android.text.TextUtils; |
| import android.text.TextUtils.SimpleStringSplitter; |
| import android.text.TextWatcher; |
| import android.util.ArrayMap; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.Gravity; |
| import android.view.KeyEvent; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.View.MeasureSpec; |
| import android.view.View.OnAttachStateChangeListener; |
| import android.view.View.OnClickListener; |
| import android.view.View.OnFocusChangeListener; |
| import android.view.ViewConfiguration; |
| import android.view.ViewGroup; |
| import android.view.ViewGroup.LayoutParams; |
| import android.view.ViewPropertyAnimator; |
| import android.view.inputmethod.InputMethodManager; |
| import android.widget.AdapterView; |
| import android.widget.AdapterView.OnItemSelectedListener; |
| import android.widget.ArrayAdapter; |
| import android.widget.BaseAdapter; |
| import android.widget.Button; |
| import android.widget.EditText; |
| import android.widget.FrameLayout; |
| import android.widget.ImageView; |
| import android.widget.Spinner; |
| import android.widget.TextView; |
| |
| import com.android.printspooler.MediaSizeUtils.MediaSizeComparator; |
| |
| import libcore.io.IoUtils; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.lang.ref.WeakReference; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Activity for configuring a print job. |
| */ |
| public class PrintJobConfigActivity extends Activity { |
| |
| private static final String LOG_TAG = "PrintJobConfigActivity"; |
| |
| private static final boolean DEBUG = false; |
| |
| public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID"; |
| |
| private static final int LOADER_ID_PRINTERS_LOADER = 1; |
| |
| private static final int ORIENTATION_PORTRAIT = 0; |
| private static final int ORIENTATION_LANDSCAPE = 1; |
| |
| private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9; |
| |
| private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE; |
| private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1; |
| |
| private static final int ACTIVITY_REQUEST_CREATE_FILE = 1; |
| private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2; |
| private static final int ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS = 3; |
| |
| private static final int CONTROLLER_STATE_FINISHED = 1; |
| private static final int CONTROLLER_STATE_FAILED = 2; |
| private static final int CONTROLLER_STATE_CANCELLED = 3; |
| private static final int CONTROLLER_STATE_INITIALIZED = 4; |
| private static final int CONTROLLER_STATE_STARTED = 5; |
| private static final int CONTROLLER_STATE_LAYOUT_STARTED = 6; |
| private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 7; |
| private static final int CONTROLLER_STATE_WRITE_STARTED = 8; |
| private static final int CONTROLLER_STATE_WRITE_COMPLETED = 9; |
| |
| private static final int EDITOR_STATE_INITIALIZED = 1; |
| private static final int EDITOR_STATE_CONFIRMED_PRINT = 2; |
| private static final int EDITOR_STATE_CANCELLED = 3; |
| |
| private static final int MIN_COPIES = 1; |
| private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES); |
| |
| private static final Pattern PATTERN_DIGITS = Pattern.compile("[\\d]+"); |
| |
| private static final Pattern PATTERN_ESCAPE_SPECIAL_CHARS = Pattern.compile( |
| "(?=[]\\[+&|!(){}^\"~*?:\\\\])"); |
| |
| private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile( |
| "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*?(([,])" |
| + "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+"); |
| |
| public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {PageRange.ALL_PAGES}; |
| |
| private final PrintAttributes mOldPrintAttributes = new PrintAttributes.Builder().build(); |
| private final PrintAttributes mCurrPrintAttributes = new PrintAttributes.Builder().build(); |
| |
| private final DeathRecipient mDeathRecipient = new DeathRecipient() { |
| @Override |
| public void binderDied() { |
| finish(); |
| } |
| }; |
| |
| private Editor mEditor; |
| private Document mDocument; |
| private PrintController mController; |
| |
| private PrintJobId mPrintJobId; |
| |
| private IBinder mIPrintDocumentAdapter; |
| |
| private Dialog mGeneratingPrintJobDialog; |
| |
| private PrintSpoolerProvider mSpoolerProvider; |
| |
| private String mCallingPackageName; |
| |
| @Override |
| protected void onCreate(Bundle bundle) { |
| super.onCreate(bundle); |
| |
| setTitle(R.string.print_dialog); |
| |
| Bundle extras = getIntent().getExtras(); |
| |
| PrintJobInfo printJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB); |
| if (printJob == null) { |
| throw new IllegalArgumentException("printJob cannot be null"); |
| } |
| |
| mPrintJobId = printJob.getId(); |
| mIPrintDocumentAdapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER); |
| if (mIPrintDocumentAdapter == null) { |
| throw new IllegalArgumentException("PrintDocumentAdapter cannot be null"); |
| } |
| |
| try { |
| IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter) |
| .setObserver(new PrintDocumentAdapterObserver(this)); |
| } catch (RemoteException re) { |
| finish(); |
| return; |
| } |
| |
| PrintAttributes attributes = printJob.getAttributes(); |
| if (attributes != null) { |
| mCurrPrintAttributes.copyFrom(attributes); |
| } |
| |
| mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME); |
| |
| setContentView(R.layout.print_job_config_activity_container); |
| |
| try { |
| mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0); |
| } catch (RemoteException re) { |
| finish(); |
| return; |
| } |
| |
| mDocument = new Document(); |
| mEditor = new Editor(); |
| |
| mSpoolerProvider = new PrintSpoolerProvider(this, |
| new Runnable() { |
| @Override |
| public void run() { |
| // We got the spooler so unleash the UI. |
| mController = new PrintController(new RemotePrintDocumentAdapter( |
| IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter), |
| mSpoolerProvider.getSpooler().generateFileForPrintJob(mPrintJobId))); |
| mController.initialize(); |
| |
| mEditor.initialize(); |
| mEditor.postCreate(); |
| } |
| }); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| if (mSpoolerProvider.getSpooler() != null) { |
| mEditor.refreshCurrentPrinter(); |
| } |
| } |
| |
| @Override |
| public void onPause() { |
| if (isFinishing()) { |
| if (mController != null && mController.hasStarted()) { |
| mController.finish(); |
| } |
| if (mEditor != null && mEditor.isPrintConfirmed() |
| && mController != null && mController.isFinished()) { |
| mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId, |
| PrintJobInfo.STATE_QUEUED, null); |
| } else { |
| mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId, |
| PrintJobInfo.STATE_CANCELED, null); |
| } |
| if (mGeneratingPrintJobDialog != null) { |
| mGeneratingPrintJobDialog.dismiss(); |
| mGeneratingPrintJobDialog = null; |
| } |
| mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0); |
| mSpoolerProvider.destroy(); |
| } |
| super.onPause(); |
| } |
| |
| public boolean onTouchEvent(MotionEvent event) { |
| if (mController != null && mEditor != null && |
| !mEditor.isPrintConfirmed() && mEditor.shouldCloseOnTouch(event)) { |
| if (!mController.isWorking()) { |
| PrintJobConfigActivity.this.finish(); |
| } |
| mEditor.cancel(); |
| return true; |
| } |
| return super.onTouchEvent(event); |
| } |
| |
| public boolean onKeyDown(int keyCode, KeyEvent event) { |
| if (keyCode == KeyEvent.KEYCODE_BACK) { |
| event.startTracking(); |
| } |
| return super.onKeyDown(keyCode, event); |
| } |
| |
| public boolean onKeyUp(int keyCode, KeyEvent event) { |
| if (mController != null && mEditor != null) { |
| if (keyCode == KeyEvent.KEYCODE_BACK) { |
| if (mEditor.isShwoingGeneratingPrintJobUi()) { |
| return true; |
| } |
| if (event.isTracking() && !event.isCanceled()) { |
| if (!mController.isWorking()) { |
| PrintJobConfigActivity.this.finish(); |
| } |
| } |
| mEditor.cancel(); |
| return true; |
| } |
| } |
| return super.onKeyUp(keyCode, event); |
| } |
| |
| private boolean printAttributesChanged() { |
| return !mOldPrintAttributes.equals(mCurrPrintAttributes); |
| } |
| |
| private class PrintController { |
| private final AtomicInteger mRequestCounter = new AtomicInteger(); |
| |
| private final RemotePrintDocumentAdapter mRemotePrintAdapter; |
| |
| private final Bundle mMetadata; |
| |
| private final ControllerHandler mHandler; |
| |
| private final LayoutResultCallback mLayoutResultCallback; |
| |
| private final WriteResultCallback mWriteResultCallback; |
| |
| private int mControllerState = CONTROLLER_STATE_INITIALIZED; |
| |
| private boolean mHasStarted; |
| |
| private PageRange[] mRequestedPages; |
| |
| public PrintController(RemotePrintDocumentAdapter adapter) { |
| mRemotePrintAdapter = adapter; |
| mMetadata = new Bundle(); |
| mHandler = new ControllerHandler(getMainLooper()); |
| mLayoutResultCallback = new LayoutResultCallback(mHandler); |
| mWriteResultCallback = new WriteResultCallback(mHandler); |
| } |
| |
| public void initialize() { |
| mHasStarted = false; |
| mControllerState = CONTROLLER_STATE_INITIALIZED; |
| } |
| |
| public void cancel() { |
| if (isWorking()) { |
| mRemotePrintAdapter.cancel(); |
| } |
| mControllerState = CONTROLLER_STATE_CANCELLED; |
| } |
| |
| public boolean isCancelled() { |
| return (mControllerState == CONTROLLER_STATE_CANCELLED); |
| } |
| |
| public boolean isFinished() { |
| return (mControllerState == CONTROLLER_STATE_FINISHED); |
| } |
| |
| public boolean hasStarted() { |
| return mHasStarted; |
| } |
| |
| public boolean hasPerformedLayout() { |
| return mControllerState >= CONTROLLER_STATE_LAYOUT_COMPLETED; |
| } |
| |
| public boolean isPerformingLayout() { |
| return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED; |
| } |
| |
| public boolean isWorking() { |
| return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED |
| || mControllerState == CONTROLLER_STATE_WRITE_STARTED; |
| } |
| |
| public void start() { |
| mControllerState = CONTROLLER_STATE_STARTED; |
| mHasStarted = true; |
| mRemotePrintAdapter.start(); |
| } |
| |
| public void update() { |
| if (!mController.hasStarted()) { |
| mController.start(); |
| } |
| |
| // If the print attributes are the same and we are performing |
| // a layout, then we have to wait for it to completed which will |
| // trigger writing of the necessary pages. |
| final boolean printAttributesChanged = printAttributesChanged(); |
| if (!printAttributesChanged && isPerformingLayout()) { |
| return; |
| } |
| |
| // If print is confirmed we always do a layout since the previous |
| // ones were for preview and this one is for printing. |
| if (!printAttributesChanged && !mEditor.isPrintConfirmed()) { |
| if (mDocument.info == null) { |
| // We are waiting for the result of a layout, so do nothing. |
| return; |
| } |
| // If the attributes didn't change and we have done a layout, then |
| // we do not do a layout but may have to ask the app to write some |
| // pages. Hence, pretend layout completed and nothing changed, so |
| // we handle writing as usual. |
| handleOnLayoutFinished(mDocument.info, false, mRequestCounter.get()); |
| } else { |
| mSpoolerProvider.getSpooler().setPrintJobAttributesNoPersistence( |
| mPrintJobId, mCurrPrintAttributes); |
| |
| mMetadata.putBoolean(PrintDocumentAdapter.EXTRA_PRINT_PREVIEW, |
| !mEditor.isPrintConfirmed()); |
| |
| mControllerState = CONTROLLER_STATE_LAYOUT_STARTED; |
| |
| mRemotePrintAdapter.layout(mOldPrintAttributes, mCurrPrintAttributes, |
| mLayoutResultCallback, mMetadata, mRequestCounter.incrementAndGet()); |
| |
| mOldPrintAttributes.copyFrom(mCurrPrintAttributes); |
| } |
| } |
| |
| public void finish() { |
| mControllerState = CONTROLLER_STATE_FINISHED; |
| mRemotePrintAdapter.finish(); |
| } |
| |
| private void handleOnLayoutFinished(PrintDocumentInfo info, |
| boolean layoutChanged, int sequence) { |
| if (mRequestCounter.get() != sequence) { |
| return; |
| } |
| |
| if (isCancelled()) { |
| mEditor.updateUi(); |
| if (mEditor.isDone()) { |
| PrintJobConfigActivity.this.finish(); |
| } |
| return; |
| } |
| |
| final int oldControllerState = mControllerState; |
| |
| if (mControllerState == CONTROLLER_STATE_LAYOUT_STARTED) { |
| mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED; |
| } |
| |
| // For layout purposes we care only whether the type or the page |
| // count changed. We still do not have the size since we did not |
| // call write. We use "layoutChanged" set by the application to |
| // know whether something else changed about the document. |
| final boolean infoChanged = !equalsIgnoreSize(info, mDocument.info); |
| // If the info changed, we update the document and the print job. |
| if (infoChanged) { |
| mDocument.info = info; |
| // Set the info. |
| mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence( |
| mPrintJobId, info); |
| } |
| |
| // If the document info or the layout changed, then |
| // drop the pages since we have to fetch them again. |
| if (infoChanged || layoutChanged) { |
| mDocument.pages = null; |
| mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence( |
| mPrintJobId, null); |
| } |
| |
| PageRange[] oldRequestedPages = mRequestedPages; |
| |
| // No pages means that the user selected an invalid range while we |
| // were doing a layout or the layout returned a document info for |
| // which the selected range is invalid. In such a case we do not |
| // write anything and wait for the user to fix the range which will |
| // trigger an update. |
| mRequestedPages = mEditor.getRequestedPages(); |
| if (mRequestedPages == null || mRequestedPages.length == 0) { |
| mEditor.updateUi(); |
| if (mEditor.isDone()) { |
| PrintJobConfigActivity.this.finish(); |
| } |
| return; |
| } else { |
| // If print is not confirmed we just ask for the first of the |
| // selected pages to emulate a behavior that shows preview |
| // increasing the chances that apps will implement the APIs |
| // correctly. |
| if (!mEditor.isPrintConfirmed()) { |
| if (ALL_PAGES_ARRAY.equals(mRequestedPages)) { |
| mRequestedPages = new PageRange[] {new PageRange(0, 0)}; |
| } else { |
| final int firstPage = mRequestedPages[0].getStart(); |
| mRequestedPages = new PageRange[] {new PageRange(firstPage, firstPage)}; |
| } |
| } |
| } |
| |
| // If the info and the layout did not change... |
| if (!infoChanged && !layoutChanged |
| // and we have the requested pages ... |
| && (PageRangeUtils.contains(mDocument.pages, mRequestedPages)) |
| // ...or the requested pages are being written... |
| || (oldControllerState == CONTROLLER_STATE_WRITE_STARTED |
| && oldRequestedPages != null |
| && PageRangeUtils.contains(oldRequestedPages, mRequestedPages))) { |
| // Nothing interesting changed and we have all requested pages. |
| // Then update the print jobs's pages as we will not do a write |
| // and we usually update the pages in the write complete callback. |
| if (mDocument.pages != null) { |
| // Update the print job's pages given we have them. |
| updatePrintJobPages(mDocument.pages, mRequestedPages); |
| } |
| mEditor.updateUi(); |
| if (mEditor.isDone()) { |
| requestCreatePdfFileOrFinish(); |
| } |
| return; |
| } |
| |
| mEditor.updateUi(); |
| |
| // Request a write of the pages of interest. |
| mControllerState = CONTROLLER_STATE_WRITE_STARTED; |
| mRemotePrintAdapter.write(mRequestedPages, mWriteResultCallback, |
| mRequestCounter.incrementAndGet()); |
| } |
| |
| private void handleOnLayoutFailed(final CharSequence error, int sequence) { |
| if (mRequestCounter.get() != sequence) { |
| return; |
| } |
| mControllerState = CONTROLLER_STATE_FAILED; |
| mEditor.showUi(Editor.UI_ERROR, new Runnable() { |
| @Override |
| public void run() { |
| if (!TextUtils.isEmpty(error)) { |
| TextView messageView = (TextView) findViewById(R.id.message); |
| messageView.setText(error); |
| } |
| } |
| }); |
| } |
| |
| private void handleOnWriteFinished(PageRange[] pages, int sequence) { |
| if (mRequestCounter.get() != sequence) { |
| return; |
| } |
| |
| if (isCancelled()) { |
| if (mEditor.isDone()) { |
| PrintJobConfigActivity.this.finish(); |
| } |
| return; |
| } |
| |
| mControllerState = CONTROLLER_STATE_WRITE_COMPLETED; |
| |
| // Update the document size. |
| File file = mSpoolerProvider.getSpooler() |
| .generateFileForPrintJob(mPrintJobId); |
| mDocument.info.setDataSize(file.length()); |
| |
| // Update the print job with the updated info. |
| mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence( |
| mPrintJobId, mDocument.info); |
| |
| // Update which pages we have fetched. |
| mDocument.pages = PageRangeUtils.normalize(pages); |
| |
| if (DEBUG) { |
| Log.i(LOG_TAG, "Requested: " + Arrays.toString(mRequestedPages) |
| + " and got: " + Arrays.toString(mDocument.pages)); |
| } |
| |
| updatePrintJobPages(mDocument.pages, mRequestedPages); |
| |
| if (mEditor.isDone()) { |
| requestCreatePdfFileOrFinish(); |
| } |
| } |
| |
| private void updatePrintJobPages(PageRange[] writtenPages, PageRange[] requestedPages) { |
| // Adjust the print job pages based on what was requested and written. |
| // The cases are ordered in the most expected to the least expected. |
| if (Arrays.equals(writtenPages, requestedPages)) { |
| // We got a document with exactly the pages we wanted. Hence, |
| // the printer has to print all pages in the data. |
| mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId, |
| ALL_PAGES_ARRAY); |
| } else if (Arrays.equals(writtenPages, ALL_PAGES_ARRAY)) { |
| // We requested specific pages but got all of them. Hence, |
| // the printer has to print only the requested pages. |
| mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId, |
| requestedPages); |
| } else if (PageRangeUtils.contains(writtenPages, requestedPages)) { |
| // We requested specific pages and got more but not all pages. |
| // Hence, we have to offset appropriately the printed pages to |
| // be based off the start of the written ones instead of zero. |
| // The written pages are always non-null and not empty. |
| final int offset = -writtenPages[0].getStart(); |
| PageRange[] offsetPages = Arrays.copyOf(requestedPages, requestedPages.length); |
| PageRangeUtils.offset(offsetPages, offset); |
| mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId, |
| offsetPages); |
| } else if (Arrays.equals(requestedPages, ALL_PAGES_ARRAY) |
| && writtenPages.length == 1 && writtenPages[0].getStart() == 0 |
| && writtenPages[0].getEnd() == mDocument.info.getPageCount() - 1) { |
| // We requested all pages via the special constant and got all |
| // of them as an explicit enumeration. Hence, the printer has |
| // to print only the requested pages. |
| mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId, |
| writtenPages); |
| } else { |
| // We did not get the pages we requested, then the application |
| // misbehaves, so we fail quickly. |
| mControllerState = CONTROLLER_STATE_FAILED; |
| Log.e(LOG_TAG, "Received invalid pages from the app: requested=" |
| + Arrays.toString(requestedPages) + " written=" |
| + Arrays.toString(writtenPages)); |
| mEditor.showUi(Editor.UI_ERROR, null); |
| } |
| } |
| |
| private void requestCreatePdfFileOrFinish() { |
| if (mEditor.isPrintingToPdf()) { |
| Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); |
| intent.setType("application/pdf"); |
| intent.putExtra(Intent.EXTRA_TITLE, mDocument.info.getName()); |
| intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName); |
| startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE); |
| } else { |
| PrintJobConfigActivity.this.finish(); |
| } |
| } |
| |
| private void handleOnWriteFailed(final CharSequence error, int sequence) { |
| if (mRequestCounter.get() != sequence) { |
| return; |
| } |
| mControllerState = CONTROLLER_STATE_FAILED; |
| mEditor.showUi(Editor.UI_ERROR, new Runnable() { |
| @Override |
| public void run() { |
| if (!TextUtils.isEmpty(error)) { |
| TextView messageView = (TextView) findViewById(R.id.message); |
| messageView.setText(error); |
| } |
| } |
| }); |
| } |
| |
| private boolean equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs) { |
| if (lhs == rhs) { |
| return true; |
| } |
| if (lhs == null) { |
| if (rhs != null) { |
| return false; |
| } |
| } else { |
| if (rhs == null) { |
| return false; |
| } |
| if (lhs.getContentType() != rhs.getContentType() |
| || lhs.getPageCount() != rhs.getPageCount()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private final class ControllerHandler extends Handler { |
| public static final int MSG_ON_LAYOUT_FINISHED = 1; |
| public static final int MSG_ON_LAYOUT_FAILED = 2; |
| public static final int MSG_ON_WRITE_FINISHED = 3; |
| public static final int MSG_ON_WRITE_FAILED = 4; |
| |
| public ControllerHandler(Looper looper) { |
| super(looper, null, false); |
| } |
| |
| @Override |
| public void handleMessage(Message message) { |
| switch (message.what) { |
| case MSG_ON_LAYOUT_FINISHED: { |
| PrintDocumentInfo info = (PrintDocumentInfo) message.obj; |
| final boolean changed = (message.arg1 == 1); |
| final int sequence = message.arg2; |
| handleOnLayoutFinished(info, changed, sequence); |
| } break; |
| |
| case MSG_ON_LAYOUT_FAILED: { |
| CharSequence error = (CharSequence) message.obj; |
| final int sequence = message.arg1; |
| handleOnLayoutFailed(error, sequence); |
| } break; |
| |
| case MSG_ON_WRITE_FINISHED: { |
| PageRange[] pages = (PageRange[]) message.obj; |
| final int sequence = message.arg1; |
| handleOnWriteFinished(pages, sequence); |
| } break; |
| |
| case MSG_ON_WRITE_FAILED: { |
| CharSequence error = (CharSequence) message.obj; |
| final int sequence = message.arg1; |
| handleOnWriteFailed(error, sequence); |
| } break; |
| } |
| } |
| } |
| } |
| |
| private static final class LayoutResultCallback extends ILayoutResultCallback.Stub { |
| private final WeakReference<PrintController.ControllerHandler> mWeakHandler; |
| |
| public LayoutResultCallback(PrintController.ControllerHandler handler) { |
| mWeakHandler = new WeakReference<PrintController.ControllerHandler>(handler); |
| } |
| |
| @Override |
| public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) { |
| Handler handler = mWeakHandler.get(); |
| if (handler != null) { |
| handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_LAYOUT_FINISHED, |
| changed ? 1 : 0, sequence, info).sendToTarget(); |
| } |
| } |
| |
| @Override |
| public void onLayoutFailed(CharSequence error, int sequence) { |
| Handler handler = mWeakHandler.get(); |
| if (handler != null) { |
| handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_LAYOUT_FAILED, |
| sequence, 0, error).sendToTarget(); |
| } |
| } |
| } |
| |
| private static final class WriteResultCallback extends IWriteResultCallback.Stub { |
| private final WeakReference<PrintController.ControllerHandler> mWeakHandler; |
| |
| public WriteResultCallback(PrintController.ControllerHandler handler) { |
| mWeakHandler = new WeakReference<PrintController.ControllerHandler>(handler); |
| } |
| |
| @Override |
| public void onWriteFinished(PageRange[] pages, int sequence) { |
| Handler handler = mWeakHandler.get(); |
| if (handler != null) { |
| handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_WRITE_FINISHED, |
| sequence, 0, pages).sendToTarget(); |
| } |
| } |
| |
| @Override |
| public void onWriteFailed(CharSequence error, int sequence) { |
| Handler handler = mWeakHandler.get(); |
| if (handler != null) { |
| handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_WRITE_FAILED, |
| sequence, 0, error).sendToTarget(); |
| } |
| } |
| } |
| |
| @Override |
| protected void onActivityResult(int requestCode, int resultCode, Intent data) { |
| switch (requestCode) { |
| case ACTIVITY_REQUEST_CREATE_FILE: { |
| if (data != null) { |
| Uri uri = data.getData(); |
| writePrintJobDataAndFinish(uri); |
| } else { |
| mEditor.showUi(Editor.UI_EDITING_PRINT_JOB, |
| new Runnable() { |
| @Override |
| public void run() { |
| mEditor.initialize(); |
| mEditor.bindUi(); |
| mEditor.reselectCurrentPrinter(); |
| mEditor.updateUi(); |
| } |
| }); |
| } |
| } break; |
| |
| case ACTIVITY_REQUEST_SELECT_PRINTER: { |
| if (resultCode == RESULT_OK) { |
| PrinterId printerId = (PrinterId) data.getParcelableExtra( |
| INTENT_EXTRA_PRINTER_ID); |
| if (printerId != null) { |
| mEditor.ensurePrinterSelected(printerId); |
| break; |
| } |
| } |
| mEditor.ensureCurrentPrinterSelected(); |
| } break; |
| |
| case ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS: { |
| if (resultCode == RESULT_OK) { |
| PrintJobInfo printJobInfo = (PrintJobInfo) data.getParcelableExtra( |
| PrintService.EXTRA_PRINT_JOB_INFO); |
| if (printJobInfo != null) { |
| mEditor.updateFromAdvancedOptions(printJobInfo); |
| break; |
| } |
| } |
| mEditor.cancel(); |
| PrintJobConfigActivity.this.finish(); |
| } break; |
| } |
| } |
| |
| private void writePrintJobDataAndFinish(final Uri uri) { |
| new AsyncTask<Void, Void, Void>() { |
| @Override |
| protected Void doInBackground(Void... params) { |
| InputStream in = null; |
| OutputStream out = null; |
| try { |
| PrintJobInfo printJob = mSpoolerProvider.getSpooler() |
| .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY); |
| if (printJob == null) { |
| return null; |
| } |
| File file = mSpoolerProvider.getSpooler() |
| .generateFileForPrintJob(mPrintJobId); |
| in = new FileInputStream(file); |
| out = getContentResolver().openOutputStream(uri); |
| final byte[] buffer = new byte[8192]; |
| while (true) { |
| final int readByteCount = in.read(buffer); |
| if (readByteCount < 0) { |
| break; |
| } |
| out.write(buffer, 0, readByteCount); |
| } |
| } catch (FileNotFoundException fnfe) { |
| Log.e(LOG_TAG, "Error writing print job data!", fnfe); |
| } catch (IOException ioe) { |
| Log.e(LOG_TAG, "Error writing print job data!", ioe); |
| } finally { |
| IoUtils.closeQuietly(in); |
| IoUtils.closeQuietly(out); |
| } |
| return null; |
| } |
| |
| @Override |
| public void onPostExecute(Void result) { |
| mEditor.cancel(); |
| PrintJobConfigActivity.this.finish(); |
| } |
| }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); |
| } |
| |
| private final class Editor { |
| private static final int UI_NONE = 0; |
| private static final int UI_EDITING_PRINT_JOB = 1; |
| private static final int UI_GENERATING_PRINT_JOB = 2; |
| private static final int UI_ERROR = 3; |
| |
| private EditText mCopiesEditText; |
| |
| private TextView mRangeOptionsTitle; |
| private TextView mPageRangeTitle; |
| private EditText mPageRangeEditText; |
| |
| private Spinner mDestinationSpinner; |
| private DestinationAdapter mDestinationSpinnerAdapter; |
| |
| private Spinner mMediaSizeSpinner; |
| private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter; |
| |
| private Spinner mColorModeSpinner; |
| private ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter; |
| |
| private Spinner mOrientationSpinner; |
| private ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter; |
| |
| private Spinner mRangeOptionsSpinner; |
| private ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter; |
| |
| private SimpleStringSplitter mStringCommaSplitter = |
| new SimpleStringSplitter(','); |
| |
| private View mContentContainer; |
| |
| private View mAdvancedPrintOptionsContainer; |
| |
| private Button mAdvancedOptionsButton; |
| |
| private Button mPrintButton; |
| |
| private PrinterId mNextPrinterId; |
| |
| private PrinterInfo mCurrentPrinter; |
| |
| private MediaSizeComparator mMediaSizeComparator; |
| |
| private final OnFocusChangeListener mFocusListener = new OnFocusChangeListener() { |
| @Override |
| public void onFocusChange(View view, boolean hasFocus) { |
| EditText editText = (EditText) view; |
| if (!TextUtils.isEmpty(editText.getText())) { |
| editText.setSelection(editText.getText().length()); |
| } |
| } |
| }; |
| |
| private final OnItemSelectedListener mOnItemSelectedListener = |
| new AdapterView.OnItemSelectedListener() { |
| @Override |
| public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) { |
| if (spinner == mDestinationSpinner) { |
| if (mIgnoreNextDestinationChange) { |
| mIgnoreNextDestinationChange = false; |
| return; |
| } |
| |
| if (position == AdapterView.INVALID_POSITION) { |
| updateUi(); |
| return; |
| } |
| |
| if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) { |
| startSelectPrinterActivity(); |
| return; |
| } |
| |
| mCapabilitiesTimeout.remove(); |
| |
| mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter |
| .getItem(position); |
| |
| mSpoolerProvider.getSpooler().setPrintJobPrinterNoPersistence( |
| mPrintJobId, mCurrentPrinter); |
| |
| if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) { |
| mCapabilitiesTimeout.post(); |
| updateUi(); |
| return; |
| } |
| |
| PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities(); |
| if (capabilities == null) { |
| mCapabilitiesTimeout.post(); |
| updateUi(); |
| refreshCurrentPrinter(); |
| } else { |
| updatePrintAttributes(capabilities); |
| updateUi(); |
| mController.update(); |
| refreshCurrentPrinter(); |
| } |
| } else if (spinner == mMediaSizeSpinner) { |
| if (mIgnoreNextMediaSizeChange) { |
| mIgnoreNextMediaSizeChange = false; |
| return; |
| } |
| if (mOldMediaSizeSelectionIndex |
| == mMediaSizeSpinner.getSelectedItemPosition()) { |
| mOldMediaSizeSelectionIndex = AdapterView.INVALID_POSITION; |
| return; |
| } |
| SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position); |
| if (mOrientationSpinner.getSelectedItemPosition() == 0) { |
| mCurrPrintAttributes.setMediaSize(mediaItem.value.asPortrait()); |
| } else { |
| mCurrPrintAttributes.setMediaSize(mediaItem.value.asLandscape()); |
| } |
| if (!hasErrors()) { |
| mController.update(); |
| } |
| } else if (spinner == mColorModeSpinner) { |
| if (mIgnoreNextColorChange) { |
| mIgnoreNextColorChange = false; |
| return; |
| } |
| if (mOldColorModeSelectionIndex |
| == mColorModeSpinner.getSelectedItemPosition()) { |
| mOldColorModeSelectionIndex = AdapterView.INVALID_POSITION; |
| return; |
| } |
| SpinnerItem<Integer> colorModeItem = |
| mColorModeSpinnerAdapter.getItem(position); |
| mCurrPrintAttributes.setColorMode(colorModeItem.value); |
| if (!hasErrors()) { |
| mController.update(); |
| } |
| } else if (spinner == mOrientationSpinner) { |
| if (mIgnoreNextOrientationChange) { |
| mIgnoreNextOrientationChange = false; |
| return; |
| } |
| SpinnerItem<Integer> orientationItem = |
| mOrientationSpinnerAdapter.getItem(position); |
| setCurrentPrintAttributesOrientation(orientationItem.value); |
| if (!hasErrors()) { |
| mController.update(); |
| } |
| } else if (spinner == mRangeOptionsSpinner) { |
| if (mIgnoreNextRangeOptionChange) { |
| mIgnoreNextRangeOptionChange = false; |
| return; |
| } |
| updateUi(); |
| if (!hasErrors()) { |
| mController.update(); |
| } |
| } |
| } |
| |
| @Override |
| public void onNothingSelected(AdapterView<?> parent) { |
| /* do nothing*/ |
| } |
| }; |
| |
| private void setCurrentPrintAttributesOrientation(int orientation) { |
| MediaSize mediaSize = mCurrPrintAttributes.getMediaSize(); |
| if (orientation == ORIENTATION_PORTRAIT) { |
| if (!mediaSize.isPortrait()) { |
| // Rotate the media size. |
| mCurrPrintAttributes.setMediaSize(mediaSize.asPortrait()); |
| |
| // Rotate the resolution. |
| Resolution oldResolution = mCurrPrintAttributes.getResolution(); |
| Resolution newResolution = new Resolution( |
| oldResolution.getId(), |
| oldResolution.getLabel(), |
| oldResolution.getVerticalDpi(), |
| oldResolution.getHorizontalDpi()); |
| mCurrPrintAttributes.setResolution(newResolution); |
| |
| // Rotate the physical margins. |
| Margins oldMinMargins = mCurrPrintAttributes.getMinMargins(); |
| Margins newMinMargins = new Margins( |
| oldMinMargins.getBottomMils(), |
| oldMinMargins.getLeftMils(), |
| oldMinMargins.getTopMils(), |
| oldMinMargins.getRightMils()); |
| mCurrPrintAttributes.setMinMargins(newMinMargins); |
| } |
| } else { |
| if (mediaSize.isPortrait()) { |
| // Rotate the media size. |
| mCurrPrintAttributes.setMediaSize(mediaSize.asLandscape()); |
| |
| // Rotate the resolution. |
| Resolution oldResolution = mCurrPrintAttributes.getResolution(); |
| Resolution newResolution = new Resolution( |
| oldResolution.getId(), |
| oldResolution.getLabel(), |
| oldResolution.getVerticalDpi(), |
| oldResolution.getHorizontalDpi()); |
| mCurrPrintAttributes.setResolution(newResolution); |
| |
| // Rotate the physical margins. |
| Margins oldMinMargins = mCurrPrintAttributes.getMinMargins(); |
| Margins newMargins = new Margins( |
| oldMinMargins.getTopMils(), |
| oldMinMargins.getRightMils(), |
| oldMinMargins.getBottomMils(), |
| oldMinMargins.getLeftMils()); |
| mCurrPrintAttributes.setMinMargins(newMargins); |
| } |
| } |
| } |
| |
| private void updatePrintAttributes(PrinterCapabilitiesInfo capabilities) { |
| PrintAttributes defaults = capabilities.getDefaults(); |
| |
| // Sort the media sizes based on the current locale. |
| List<MediaSize> sortedMediaSizes = new ArrayList<MediaSize>( |
| capabilities.getMediaSizes()); |
| Collections.sort(sortedMediaSizes, mMediaSizeComparator); |
| |
| // Media size. |
| MediaSize currMediaSize = mCurrPrintAttributes.getMediaSize(); |
| if (currMediaSize == null) { |
| mCurrPrintAttributes.setMediaSize(defaults.getMediaSize()); |
| } else { |
| MediaSize currMediaSizePortrait = currMediaSize.asPortrait(); |
| final int mediaSizeCount = sortedMediaSizes.size(); |
| for (int i = 0; i < mediaSizeCount; i++) { |
| MediaSize mediaSize = sortedMediaSizes.get(i); |
| if (currMediaSizePortrait.equals(mediaSize.asPortrait())) { |
| mCurrPrintAttributes.setMediaSize(currMediaSize); |
| break; |
| } |
| } |
| } |
| |
| // Color mode. |
| final int colorMode = mCurrPrintAttributes.getColorMode(); |
| if ((capabilities.getColorModes() & colorMode) == 0) { |
| mCurrPrintAttributes.setColorMode(colorMode); |
| } |
| |
| // Resolution |
| Resolution resolution = mCurrPrintAttributes.getResolution(); |
| if (resolution == null || !capabilities.getResolutions().contains(resolution)) { |
| mCurrPrintAttributes.setResolution(defaults.getResolution()); |
| } |
| |
| // Margins. |
| Margins margins = mCurrPrintAttributes.getMinMargins(); |
| if (margins == null) { |
| mCurrPrintAttributes.setMinMargins(defaults.getMinMargins()); |
| } else { |
| Margins minMargins = capabilities.getMinMargins(); |
| if (margins.getLeftMils() < minMargins.getLeftMils() |
| || margins.getTopMils() < minMargins.getTopMils() |
| || margins.getRightMils() > minMargins.getRightMils() |
| || margins.getBottomMils() > minMargins.getBottomMils()) { |
| mCurrPrintAttributes.setMinMargins(defaults.getMinMargins()); |
| } |
| } |
| } |
| |
| private final TextWatcher mCopiesTextWatcher = new TextWatcher() { |
| @Override |
| public void onTextChanged(CharSequence s, int start, int before, int count) { |
| /* do nothing */ |
| } |
| |
| @Override |
| public void beforeTextChanged(CharSequence s, int start, int count, int after) { |
| /* do nothing */ |
| } |
| |
| @Override |
| public void afterTextChanged(Editable editable) { |
| if (mIgnoreNextCopiesChange) { |
| mIgnoreNextCopiesChange = false; |
| return; |
| } |
| |
| final boolean hadErrors = hasErrors(); |
| |
| if (editable.length() == 0) { |
| mCopiesEditText.setError(""); |
| updateUi(); |
| return; |
| } |
| |
| int copies = 0; |
| try { |
| copies = Integer.parseInt(editable.toString()); |
| } catch (NumberFormatException nfe) { |
| /* ignore */ |
| } |
| |
| if (copies < MIN_COPIES) { |
| mCopiesEditText.setError(""); |
| updateUi(); |
| return; |
| } |
| |
| mCopiesEditText.setError(null); |
| mSpoolerProvider.getSpooler().setPrintJobCopiesNoPersistence( |
| mPrintJobId, copies); |
| updateUi(); |
| |
| if (hadErrors && !hasErrors() && printAttributesChanged()) { |
| mController.update(); |
| } |
| } |
| }; |
| |
| private final TextWatcher mRangeTextWatcher = new TextWatcher() { |
| @Override |
| public void onTextChanged(CharSequence s, int start, int before, int count) { |
| /* do nothing */ |
| } |
| |
| @Override |
| public void beforeTextChanged(CharSequence s, int start, int count, int after) { |
| /* do nothing */ |
| } |
| |
| @Override |
| public void afterTextChanged(Editable editable) { |
| if (mIgnoreNextRangeChange) { |
| mIgnoreNextRangeChange = false; |
| return; |
| } |
| |
| final boolean hadErrors = hasErrors(); |
| |
| String text = editable.toString(); |
| |
| if (TextUtils.isEmpty(text)) { |
| mPageRangeEditText.setError(""); |
| updateUi(); |
| return; |
| } |
| |
| String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////"); |
| if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) { |
| mPageRangeEditText.setError(""); |
| updateUi(); |
| return; |
| } |
| |
| // The range |
| Matcher matcher = PATTERN_DIGITS.matcher(text); |
| while (matcher.find()) { |
| String numericString = text.substring(matcher.start(), matcher.end()).trim(); |
| if (TextUtils.isEmpty(numericString)) { |
| continue; |
| } |
| final int pageIndex = Integer.parseInt(numericString); |
| if (pageIndex < 1 || pageIndex > mDocument.info.getPageCount()) { |
| mPageRangeEditText.setError(""); |
| updateUi(); |
| return; |
| } |
| } |
| |
| // We intentionally do not catch the case of the from page being |
| // greater than the to page. When computing the requested pages |
| // we just swap them if necessary. |
| |
| // Keep the print job up to date with the selected pages if we |
| // know how many pages are there in the document. |
| PageRange[] requestedPages = getRequestedPages(); |
| if (requestedPages != null && requestedPages.length > 0 |
| && requestedPages[requestedPages.length - 1].getEnd() |
| < mDocument.info.getPageCount()) { |
| mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence( |
| mPrintJobId, requestedPages); |
| } |
| |
| mPageRangeEditText.setError(null); |
| mPrintButton.setEnabled(true); |
| updateUi(); |
| |
| if (hadErrors && !hasErrors() && printAttributesChanged()) { |
| updateUi(); |
| } |
| } |
| }; |
| |
| private final WaitForPrinterCapabilitiesTimeout mCapabilitiesTimeout = |
| new WaitForPrinterCapabilitiesTimeout(); |
| |
| private int mEditorState; |
| |
| private boolean mIgnoreNextDestinationChange; |
| private int mOldMediaSizeSelectionIndex; |
| private int mOldColorModeSelectionIndex; |
| private boolean mIgnoreNextOrientationChange; |
| private boolean mIgnoreNextRangeOptionChange; |
| private boolean mIgnoreNextCopiesChange; |
| private boolean mIgnoreNextRangeChange; |
| private boolean mIgnoreNextMediaSizeChange; |
| private boolean mIgnoreNextColorChange; |
| |
| private int mCurrentUi = UI_NONE; |
| |
| private boolean mFavoritePrinterSelected; |
| |
| public Editor() { |
| showUi(UI_EDITING_PRINT_JOB, null); |
| } |
| |
| public void postCreate() { |
| // Destination. |
| mMediaSizeComparator = new MediaSizeComparator(PrintJobConfigActivity.this); |
| mDestinationSpinnerAdapter = new DestinationAdapter(); |
| mDestinationSpinnerAdapter.registerDataSetObserver(new DataSetObserver() { |
| @Override |
| public void onChanged() { |
| // Initially, we have only safe to PDF as a printer but after some |
| // printers are loaded we want to select the user's favorite one |
| // which is the first. |
| if (!mFavoritePrinterSelected && mDestinationSpinnerAdapter.getCount() > 1) { |
| mFavoritePrinterSelected = true; |
| mDestinationSpinner.setSelection(0); |
| // Workaround again the weird spinner behavior to notify for selection |
| // change on the next layout pass as the current printer is used below. |
| mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter.getItem(0); |
| } |
| |
| // If there is a next printer to select and we succeed selecting |
| // it - done. Let the selection handling code make everything right. |
| if (mNextPrinterId != null && selectPrinter(mNextPrinterId)) { |
| mNextPrinterId = null; |
| return; |
| } |
| |
| // If the current printer properties changed, we update the UI. |
| if (mCurrentPrinter != null) { |
| final int printerCount = mDestinationSpinnerAdapter.getCount(); |
| for (int i = 0; i < printerCount; i++) { |
| Object item = mDestinationSpinnerAdapter.getItem(i); |
| // Some items are not printers |
| if (item instanceof PrinterInfo) { |
| PrinterInfo printer = (PrinterInfo) item; |
| if (!printer.getId().equals(mCurrentPrinter.getId())) { |
| continue; |
| } |
| |
| // If nothing changed - done. |
| if (mCurrentPrinter.equals(printer)) { |
| return; |
| } |
| |
| // If the current printer became available and has no |
| // capabilities, we refresh it. |
| if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE |
| && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE |
| && printer.getCapabilities() == null) { |
| if (!mCapabilitiesTimeout.isPosted()) { |
| mCapabilitiesTimeout.post(); |
| } |
| mCurrentPrinter.copyFrom(printer); |
| refreshCurrentPrinter(); |
| return; |
| } |
| |
| // If the current printer became unavailable or its |
| // capabilities go away, we update the UI and add a |
| // timeout to declare the printer as unavailable. |
| if ((mCurrentPrinter.getStatus() != PrinterInfo.STATUS_UNAVAILABLE |
| && printer.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) |
| || (mCurrentPrinter.getCapabilities() != null |
| && printer.getCapabilities() == null)) { |
| if (!mCapabilitiesTimeout.isPosted()) { |
| mCapabilitiesTimeout.post(); |
| } |
| mCurrentPrinter.copyFrom(printer); |
| updateUi(); |
| return; |
| } |
| |
| // We just refreshed the current printer. |
| if (printer.getCapabilities() != null |
| && mCapabilitiesTimeout.isPosted()) { |
| mCapabilitiesTimeout.remove(); |
| updatePrintAttributes(printer.getCapabilities()); |
| updateUi(); |
| mController.update(); |
| } |
| |
| // Update the UI if capabilities changed. |
| boolean capabilitiesChanged = false; |
| |
| if (mCurrentPrinter.getCapabilities() == null) { |
| if (printer.getCapabilities() != null) { |
| updatePrintAttributes(printer.getCapabilities()); |
| capabilitiesChanged = true; |
| } |
| } else if (!mCurrentPrinter.getCapabilities().equals( |
| printer.getCapabilities())) { |
| capabilitiesChanged = true; |
| } |
| |
| // Update the UI if the status changed. |
| final boolean statusChanged = mCurrentPrinter.getStatus() |
| != printer.getStatus(); |
| |
| // Update the printer with the latest info. |
| if (!mCurrentPrinter.equals(printer)) { |
| mCurrentPrinter.copyFrom(printer); |
| } |
| |
| if (capabilitiesChanged || statusChanged) { |
| // If something changed during update... |
| if (updateUi() || !mController.hasPerformedLayout()) { |
| // Update the document. |
| mController.update(); |
| } |
| } |
| |
| break; |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void onInvalidated() { |
| /* do nothing - we always have one fake PDF printer */ |
| } |
| }); |
| |
| // Media size. |
| mMediaSizeSpinnerAdapter = new ArrayAdapter<SpinnerItem<MediaSize>>( |
| PrintJobConfigActivity.this, |
| R.layout.spinner_dropdown_item, R.id.title); |
| |
| // Color mode. |
| mColorModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>( |
| PrintJobConfigActivity.this, |
| R.layout.spinner_dropdown_item, R.id.title); |
| |
| // Orientation |
| mOrientationSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>( |
| PrintJobConfigActivity.this, |
| R.layout.spinner_dropdown_item, R.id.title); |
| String[] orientationLabels = getResources().getStringArray( |
| R.array.orientation_labels); |
| mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>( |
| ORIENTATION_PORTRAIT, orientationLabels[0])); |
| mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>( |
| ORIENTATION_LANDSCAPE, orientationLabels[1])); |
| |
| // Range options |
| mRangeOptionsSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>( |
| PrintJobConfigActivity.this, |
| R.layout.spinner_dropdown_item, R.id.title); |
| final int[] rangeOptionsValues = getResources().getIntArray( |
| R.array.page_options_values); |
| String[] rangeOptionsLabels = getResources().getStringArray( |
| R.array.page_options_labels); |
| final int rangeOptionsCount = rangeOptionsLabels.length; |
| for (int i = 0; i < rangeOptionsCount; i++) { |
| mRangeOptionsSpinnerAdapter.add(new SpinnerItem<Integer>( |
| rangeOptionsValues[i], rangeOptionsLabels[i])); |
| } |
| |
| showUi(UI_EDITING_PRINT_JOB, null); |
| bindUi(); |
| updateUi(); |
| } |
| |
| public void reselectCurrentPrinter() { |
| if (mCurrentPrinter != null) { |
| // TODO: While the data did not change and we set the adapter |
| // to a newly inflated spinner, the latter does not show the |
| // current item unless we poke the adapter. This requires more |
| // investigation. Maybe an optimization in AdapterView does not |
| // call into the adapter if the view is not visible which is the |
| // case when we set the adapter. |
| mDestinationSpinnerAdapter.notifyDataSetChanged(); |
| final int position = mDestinationSpinnerAdapter.getPrinterIndex( |
| mCurrentPrinter.getId()); |
| mDestinationSpinner.setSelection(position); |
| } |
| } |
| |
| public void refreshCurrentPrinter() { |
| PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem(); |
| if (printer != null) { |
| FusedPrintersProvider printersLoader = (FusedPrintersProvider) |
| (Loader<?>) getLoaderManager().getLoader( |
| LOADER_ID_PRINTERS_LOADER); |
| if (printersLoader != null) { |
| printersLoader.setTrackedPrinter(printer.getId()); |
| } |
| } |
| } |
| |
| public void addCurrentPrinterToHistory() { |
| PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem(); |
| PrinterId fakePdfPritnerId = mDestinationSpinnerAdapter.mFakePdfPrinter.getId(); |
| if (printer != null && !printer.getId().equals(fakePdfPritnerId)) { |
| FusedPrintersProvider printersLoader = (FusedPrintersProvider) |
| (Loader<?>) getLoaderManager().getLoader( |
| LOADER_ID_PRINTERS_LOADER); |
| if (printersLoader != null) { |
| printersLoader.addHistoricalPrinter(printer); |
| } |
| } |
| } |
| |
| public void updateFromAdvancedOptions(PrintJobInfo printJobInfo) { |
| boolean updateContent = false; |
| |
| // Copies. |
| mCopiesEditText.setText(String.valueOf(printJobInfo.getCopies())); |
| |
| // Media size and orientation |
| PrintAttributes attributes = printJobInfo.getAttributes(); |
| if (!mCurrPrintAttributes.getMediaSize().equals(attributes.getMediaSize())) { |
| final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount(); |
| for (int i = 0; i < mediaSizeCount; i++) { |
| MediaSize mediaSize = mMediaSizeSpinnerAdapter.getItem(i).value; |
| if (mediaSize.asPortrait().equals(attributes.getMediaSize().asPortrait())) { |
| updateContent = true; |
| mCurrPrintAttributes.setMediaSize(attributes.getMediaSize()); |
| mMediaSizeSpinner.setSelection(i); |
| mIgnoreNextMediaSizeChange = true; |
| if (attributes.getMediaSize().isPortrait()) { |
| mOrientationSpinner.setSelection(0); |
| mIgnoreNextOrientationChange = true; |
| } else { |
| mOrientationSpinner.setSelection(1); |
| mIgnoreNextOrientationChange = true; |
| } |
| break; |
| } |
| } |
| } |
| |
| // Color mode. |
| final int colorMode = attributes.getColorMode(); |
| if (mCurrPrintAttributes.getColorMode() != colorMode) { |
| if (colorMode == PrintAttributes.COLOR_MODE_MONOCHROME) { |
| updateContent = true; |
| mColorModeSpinner.setSelection(0); |
| mIgnoreNextColorChange = true; |
| mCurrPrintAttributes.setColorMode(attributes.getColorMode()); |
| } else if (colorMode == PrintAttributes.COLOR_MODE_COLOR) { |
| updateContent = true; |
| mColorModeSpinner.setSelection(1); |
| mIgnoreNextColorChange = true; |
| mCurrPrintAttributes.setColorMode(attributes.getColorMode()); |
| } |
| } |
| |
| // Range. |
| PageRange[] pageRanges = printJobInfo.getPages(); |
| if (pageRanges != null && pageRanges.length > 0) { |
| pageRanges = PageRangeUtils.normalize(pageRanges); |
| final int pageRangeCount = pageRanges.length; |
| if (pageRangeCount == 1 && pageRanges[0] == PageRange.ALL_PAGES) { |
| mRangeOptionsSpinner.setSelection(0); |
| } else { |
| final int pageCount = mDocument.info.getPageCount(); |
| if (pageRanges[0].getStart() >= 0 |
| && pageRanges[pageRanges.length - 1].getEnd() < pageCount) { |
| mRangeOptionsSpinner.setSelection(1); |
| StringBuilder builder = new StringBuilder(); |
| for (int i = 0; i < pageRangeCount; i++) { |
| if (builder.length() > 0) { |
| builder.append(','); |
| } |
| PageRange pageRange = pageRanges[i]; |
| final int shownStartPage = pageRange.getStart() + 1; |
| final int shownEndPage = pageRange.getEnd() + 1; |
| builder.append(shownStartPage); |
| if (shownStartPage != shownEndPage) { |
| builder.append('-'); |
| builder.append(shownEndPage); |
| } |
| } |
| mPageRangeEditText.setText(builder.toString()); |
| } |
| } |
| } |
| |
| // Update the advanced options. |
| mSpoolerProvider.getSpooler().setPrintJobAdvancedOptionsNoPersistence( |
| mPrintJobId, printJobInfo.getAdvancedOptions()); |
| |
| // Update the content if needed. |
| if (updateContent) { |
| mController.update(); |
| } |
| } |
| |
| public void ensurePrinterSelected(PrinterId printerId) { |
| // If the printer is not present maybe the loader is not |
| // updated yet. In this case make a note and as soon as |
| // the printer appears will will select it. |
| if (!selectPrinter(printerId)) { |
| mNextPrinterId = printerId; |
| } |
| } |
| |
| public boolean selectPrinter(PrinterId printerId) { |
| mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerId); |
| final int position = mDestinationSpinnerAdapter.getPrinterIndex(printerId); |
| if (position != AdapterView.INVALID_POSITION |
| && position != mDestinationSpinner.getSelectedItemPosition()) { |
| Object item = mDestinationSpinnerAdapter.getItem(position); |
| mCurrentPrinter = (PrinterInfo) item; |
| mDestinationSpinner.setSelection(position); |
| return true; |
| } |
| return false; |
| } |
| |
| public void ensureCurrentPrinterSelected() { |
| if (mCurrentPrinter != null) { |
| selectPrinter(mCurrentPrinter.getId()); |
| } |
| } |
| |
| public boolean isPrintingToPdf() { |
| return mDestinationSpinner.getSelectedItem() |
| == mDestinationSpinnerAdapter.mFakePdfPrinter; |
| } |
| |
| public boolean shouldCloseOnTouch(MotionEvent event) { |
| if (event.getAction() != MotionEvent.ACTION_DOWN) { |
| return false; |
| } |
| |
| final int[] locationInWindow = new int[2]; |
| mContentContainer.getLocationInWindow(locationInWindow); |
| |
| final int windowTouchSlop = ViewConfiguration.get(PrintJobConfigActivity.this) |
| .getScaledWindowTouchSlop(); |
| final int eventX = (int) event.getX(); |
| final int eventY = (int) event.getY(); |
| final int lenientWindowLeft = locationInWindow[0] - windowTouchSlop; |
| final int lenientWindowRight = lenientWindowLeft + mContentContainer.getWidth() |
| + windowTouchSlop; |
| final int lenientWindowTop = locationInWindow[1] - windowTouchSlop; |
| final int lenientWindowBottom = lenientWindowTop + mContentContainer.getHeight() |
| + windowTouchSlop; |
| |
| if (eventX < lenientWindowLeft || eventX > lenientWindowRight |
| || eventY < lenientWindowTop || eventY > lenientWindowBottom) { |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean isShwoingGeneratingPrintJobUi() { |
| return (mCurrentUi == UI_GENERATING_PRINT_JOB); |
| } |
| |
| public void showUi(int ui, final Runnable postSwitchCallback) { |
| if (ui == UI_NONE) { |
| throw new IllegalStateException("cannot remove the ui"); |
| } |
| |
| if (mCurrentUi == ui) { |
| return; |
| } |
| |
| final int oldUi = mCurrentUi; |
| mCurrentUi = ui; |
| |
| switch (oldUi) { |
| case UI_NONE: { |
| switch (ui) { |
| case UI_EDITING_PRINT_JOB: { |
| doUiSwitch(R.layout.print_job_config_activity_content_editing); |
| registerPrintButtonClickListener(); |
| if (postSwitchCallback != null) { |
| postSwitchCallback.run(); |
| } |
| } break; |
| |
| case UI_GENERATING_PRINT_JOB: { |
| doUiSwitch(R.layout.print_job_config_activity_content_generating); |
| registerCancelButtonClickListener(); |
| if (postSwitchCallback != null) { |
| postSwitchCallback.run(); |
| } |
| } break; |
| } |
| } break; |
| |
| case UI_EDITING_PRINT_JOB: { |
| switch (ui) { |
| case UI_GENERATING_PRINT_JOB: { |
| animateUiSwitch(R.layout.print_job_config_activity_content_generating, |
| new Runnable() { |
| @Override |
| public void run() { |
| registerCancelButtonClickListener(); |
| if (postSwitchCallback != null) { |
| postSwitchCallback.run(); |
| } |
| } |
| }, |
| new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, |
| ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)); |
| } break; |
| |
| case UI_ERROR: { |
| animateUiSwitch(R.layout.print_job_config_activity_content_error, |
| new Runnable() { |
| @Override |
| public void run() { |
| registerOkButtonClickListener(); |
| if (postSwitchCallback != null) { |
| postSwitchCallback.run(); |
| } |
| } |
| }, |
| new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, |
| ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)); |
| } break; |
| } |
| } break; |
| |
| case UI_GENERATING_PRINT_JOB: { |
| switch (ui) { |
| case UI_EDITING_PRINT_JOB: { |
| animateUiSwitch(R.layout.print_job_config_activity_content_editing, |
| new Runnable() { |
| @Override |
| public void run() { |
| registerPrintButtonClickListener(); |
| if (postSwitchCallback != null) { |
| postSwitchCallback.run(); |
| } |
| } |
| }, |
| new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, |
| ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER)); |
| } break; |
| |
| case UI_ERROR: { |
| animateUiSwitch(R.layout.print_job_config_activity_content_error, |
| new Runnable() { |
| @Override |
| public void run() { |
| registerOkButtonClickListener(); |
| if (postSwitchCallback != null) { |
| postSwitchCallback.run(); |
| } |
| } |
| }, |
| new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, |
| ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)); |
| } break; |
| } |
| } break; |
| |
| case UI_ERROR: { |
| switch (ui) { |
| case UI_EDITING_PRINT_JOB: { |
| animateUiSwitch(R.layout.print_job_config_activity_content_editing, |
| new Runnable() { |
| @Override |
| public void run() { |
| registerPrintButtonClickListener(); |
| if (postSwitchCallback != null) { |
| postSwitchCallback.run(); |
| } |
| } |
| }, |
| new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, |
| ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER)); |
| } break; |
| } |
| } break; |
| } |
| } |
| |
| private void registerAdvancedPrintOptionsButtonClickListener() { |
| Button advancedOptionsButton = (Button) findViewById(R.id.advanced_settings_button); |
| advancedOptionsButton.setOnClickListener(new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| ComponentName serviceName = mCurrentPrinter.getId().getServiceName(); |
| String activityName = getAdvancedOptionsActivityName(serviceName); |
| if (TextUtils.isEmpty(activityName)) { |
| return; |
| } |
| Intent intent = new Intent(Intent.ACTION_MAIN); |
| intent.setComponent(new ComponentName(serviceName.getPackageName(), |
| activityName)); |
| |
| List<ResolveInfo> resolvedActivities = getPackageManager() |
| .queryIntentActivities(intent, 0); |
| if (resolvedActivities.isEmpty()) { |
| return; |
| } |
| // The activity is a component name, therefore it is one or none. |
| if (resolvedActivities.get(0).activityInfo.exported) { |
| PrintJobInfo printJobInfo = mSpoolerProvider.getSpooler().getPrintJobInfo( |
| mPrintJobId, PrintManager.APP_ID_ANY); |
| intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, printJobInfo); |
| // TODO: Make this an API for the next release. |
| intent.putExtra("android.intent.extra.print.EXTRA_PRINTER_INFO", |
| mCurrentPrinter); |
| try { |
| startActivityForResult(intent, |
| ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS); |
| } catch (ActivityNotFoundException anfe) { |
| Log.e(LOG_TAG, "Error starting activity for intent: " + intent, anfe); |
| } |
| } |
| } |
| }); |
| } |
| |
| private void registerPrintButtonClickListener() { |
| Button printButton = (Button) findViewById(R.id.print_button); |
| printButton.setOnClickListener(new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem(); |
| if (printer != null) { |
| mEditor.confirmPrint(); |
| mController.update(); |
| if (!printer.equals(mDestinationSpinnerAdapter.mFakePdfPrinter)) { |
| mEditor.refreshCurrentPrinter(); |
| } |
| } else { |
| mEditor.cancel(); |
| PrintJobConfigActivity.this.finish(); |
| } |
| } |
| }); |
| } |
| |
| private void registerCancelButtonClickListener() { |
| Button cancelButton = (Button) findViewById(R.id.cancel_button); |
| cancelButton.setOnClickListener(new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| if (!mController.isWorking()) { |
| PrintJobConfigActivity.this.finish(); |
| } |
| mEditor.cancel(); |
| } |
| }); |
| } |
| |
| private void registerOkButtonClickListener() { |
| Button okButton = (Button) findViewById(R.id.ok_button); |
| okButton.setOnClickListener(new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| mEditor.showUi(Editor.UI_EDITING_PRINT_JOB, new Runnable() { |
| @Override |
| public void run() { |
| // Start over with a clean slate. |
| mOldPrintAttributes.clear(); |
| mController.initialize(); |
| mEditor.initialize(); |
| mEditor.bindUi(); |
| mEditor.reselectCurrentPrinter(); |
| if (!mController.hasPerformedLayout()) { |
| mController.update(); |
| } |
| } |
| }); |
| } |
| }); |
| } |
| |
| private void doUiSwitch(int showLayoutId) { |
| ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container); |
| contentContainer.removeAllViews(); |
| getLayoutInflater().inflate(showLayoutId, contentContainer, true); |
| } |
| |
| private void animateUiSwitch(int showLayoutId, final Runnable beforeShowNewUiAction, |
| final LayoutParams containerParams) { |
| // Find everything we will shuffle around. |
| final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container); |
| final View hidingView = contentContainer.getChildAt(0); |
| final View showingView = getLayoutInflater().inflate(showLayoutId, |
| null, false); |
| |
| // First animation - fade out the old content. |
| AutoCancellingAnimator.animate(hidingView).alpha(0.0f) |
| .withLayer().withEndAction(new Runnable() { |
| @Override |
| public void run() { |
| hidingView.setVisibility(View.INVISIBLE); |
| |
| // Prepare the new content with correct size and alpha. |
| showingView.setMinimumWidth(contentContainer.getWidth()); |
| showingView.setAlpha(0.0f); |
| |
| // Compute how to much shrink /stretch the content. |
| final int widthSpec = MeasureSpec.makeMeasureSpec( |
| contentContainer.getWidth(), MeasureSpec.UNSPECIFIED); |
| final int heightSpec = MeasureSpec.makeMeasureSpec( |
| contentContainer.getHeight(), MeasureSpec.UNSPECIFIED); |
| showingView.measure(widthSpec, heightSpec); |
| final float scaleY = (float) showingView.getMeasuredHeight() |
| / (float) contentContainer.getHeight(); |
| |
| // Second animation - resize the container. |
| AutoCancellingAnimator.animate(contentContainer).scaleY(scaleY) |
| .withEndAction(new Runnable() { |
| @Override |
| public void run() { |
| // Swap the old and the new content. |
| contentContainer.removeAllViews(); |
| contentContainer.setScaleY(1.0f); |
| contentContainer.addView(showingView); |
| |
| contentContainer.setLayoutParams(containerParams); |
| |
| beforeShowNewUiAction.run(); |
| |
| // Third animation - show the new content. |
| AutoCancellingAnimator.animate(showingView).alpha(1.0f); |
| } |
| }); |
| } |
| }); |
| } |
| |
| public void initialize() { |
| mEditorState = EDITOR_STATE_INITIALIZED; |
| } |
| |
| public boolean isCancelled() { |
| return mEditorState == EDITOR_STATE_CANCELLED; |
| } |
| |
| public void cancel() { |
| mEditorState = EDITOR_STATE_CANCELLED; |
| mController.cancel(); |
| updateUi(); |
| } |
| |
| public boolean isDone() { |
| return isPrintConfirmed() || isCancelled(); |
| } |
| |
| public boolean isPrintConfirmed() { |
| return mEditorState == EDITOR_STATE_CONFIRMED_PRINT; |
| } |
| |
| public void confirmPrint() { |
| addCurrentPrinterToHistory(); |
| mEditorState = EDITOR_STATE_CONFIRMED_PRINT; |
| showUi(UI_GENERATING_PRINT_JOB, null); |
| } |
| |
| public PageRange[] getRequestedPages() { |
| if (hasErrors()) { |
| return null; |
| } |
| if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) { |
| List<PageRange> pageRanges = new ArrayList<PageRange>(); |
| mStringCommaSplitter.setString(mPageRangeEditText.getText().toString()); |
| |
| while (mStringCommaSplitter.hasNext()) { |
| String range = mStringCommaSplitter.next().trim(); |
| if (TextUtils.isEmpty(range)) { |
| continue; |
| } |
| final int dashIndex = range.indexOf('-'); |
| final int fromIndex; |
| final int toIndex; |
| |
| if (dashIndex > 0) { |
| fromIndex = Integer.parseInt(range.substring(0, dashIndex).trim()) - 1; |
| // It is possible that the dash is at the end since the input |
| // verification can has to allow the user to keep entering if |
| // this would lead to a valid input. So we handle this. |
| toIndex = (dashIndex < range.length() - 1) |
| ? Integer.parseInt(range.substring(dashIndex + 1, |
| range.length()).trim()) - 1 : fromIndex; |
| } else { |
| fromIndex = toIndex = Integer.parseInt(range) - 1; |
| } |
| |
| PageRange pageRange = new PageRange(Math.min(fromIndex, toIndex), |
| Math.max(fromIndex, toIndex)); |
| pageRanges.add(pageRange); |
| } |
| |
| PageRange[] pageRangesArray = new PageRange[pageRanges.size()]; |
| pageRanges.toArray(pageRangesArray); |
| |
| return PageRangeUtils.normalize(pageRangesArray); |
| } |
| |
| return ALL_PAGES_ARRAY; |
| } |
| |
| private void bindUi() { |
| if (mCurrentUi != UI_EDITING_PRINT_JOB) { |
| return; |
| } |
| |
| // Content container |
| mContentContainer = findViewById(R.id.content_container); |
| |
| // Copies |
| mCopiesEditText = (EditText) findViewById(R.id.copies_edittext); |
| mCopiesEditText.setOnFocusChangeListener(mFocusListener); |
| mCopiesEditText.setText(MIN_COPIES_STRING); |
| mCopiesEditText.setSelection(mCopiesEditText.getText().length()); |
| mCopiesEditText.addTextChangedListener(mCopiesTextWatcher); |
| if (!TextUtils.equals(mCopiesEditText.getText(), MIN_COPIES_STRING)) { |
| mIgnoreNextCopiesChange = true; |
| } |
| mSpoolerProvider.getSpooler().setPrintJobCopiesNoPersistence( |
| mPrintJobId, MIN_COPIES); |
| |
| // Destination. |
| mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner); |
| mDestinationSpinner.setDropDownWidth(ViewGroup.LayoutParams.MATCH_PARENT); |
| mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter); |
| mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| if (mDestinationSpinnerAdapter.getCount() > 0) { |
| mIgnoreNextDestinationChange = true; |
| } |
| |
| // Media size. |
| mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner); |
| mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter); |
| mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| if (mMediaSizeSpinnerAdapter.getCount() > 0) { |
| mOldMediaSizeSelectionIndex = 0; |
| } |
| |
| // Color mode. |
| mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner); |
| mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter); |
| mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| if (mColorModeSpinnerAdapter.getCount() > 0) { |
| mOldColorModeSelectionIndex = 0; |
| } |
| |
| // Orientation |
| mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner); |
| mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter); |
| mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| if (mOrientationSpinnerAdapter.getCount() > 0) { |
| mIgnoreNextOrientationChange = true; |
| } |
| |
| // Range options |
| mRangeOptionsTitle = (TextView) findViewById(R.id.range_options_title); |
| mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner); |
| mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter); |
| mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| if (mRangeOptionsSpinnerAdapter.getCount() > 0) { |
| mIgnoreNextRangeOptionChange = true; |
| } |
| |
| // Page range |
| mPageRangeTitle = (TextView) findViewById(R.id.page_range_title); |
| mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext); |
| mPageRangeEditText.setOnFocusChangeListener(mFocusListener); |
| mPageRangeEditText.addTextChangedListener(mRangeTextWatcher); |
| |
| // Advanced options button. |
| mAdvancedPrintOptionsContainer = findViewById(R.id.advanced_settings_container); |
| mAdvancedOptionsButton = (Button) findViewById(R.id.advanced_settings_button); |
| registerAdvancedPrintOptionsButtonClickListener(); |
| |
| // Print button |
| mPrintButton = (Button) findViewById(R.id.print_button); |
| registerPrintButtonClickListener(); |
| } |
| |
| public boolean updateUi() { |
| if (mCurrentUi != UI_EDITING_PRINT_JOB) { |
| return false; |
| } |
| if (isPrintConfirmed() || isCancelled()) { |
| mDestinationSpinner.setEnabled(false); |
| mCopiesEditText.setEnabled(false); |
| mMediaSizeSpinner.setEnabled(false); |
| mColorModeSpinner.setEnabled(false); |
| mOrientationSpinner.setEnabled(false); |
| mRangeOptionsSpinner.setEnabled(false); |
| mPageRangeEditText.setEnabled(false); |
| mPrintButton.setEnabled(false); |
| mAdvancedOptionsButton.setEnabled(false); |
| return false; |
| } |
| |
| // If a printer with capabilities is selected, then we enabled all options. |
| boolean allOptionsEnabled = false; |
| final int selectedIndex = mDestinationSpinner.getSelectedItemPosition(); |
| if (selectedIndex >= 0) { |
| Object item = mDestinationSpinnerAdapter.getItem(selectedIndex); |
| if (item instanceof PrinterInfo) { |
| PrinterInfo printer = (PrinterInfo) item; |
| if (printer.getCapabilities() != null |
| && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) { |
| allOptionsEnabled = true; |
| } |
| } |
| } |
| |
| if (!allOptionsEnabled) { |
| mCopiesEditText.setEnabled(false); |
| mMediaSizeSpinner.setEnabled(false); |
| mColorModeSpinner.setEnabled(false); |
| mOrientationSpinner.setEnabled(false); |
| mRangeOptionsSpinner.setEnabled(false); |
| mPageRangeEditText.setEnabled(false); |
| mPrintButton.setEnabled(false); |
| mAdvancedOptionsButton.setEnabled(false); |
| return false; |
| } else { |
| boolean someAttributeSelectionChanged = false; |
| |
| PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem(); |
| PrinterCapabilitiesInfo capabilities = printer.getCapabilities(); |
| PrintAttributes defaultAttributes = printer.getCapabilities().getDefaults(); |
| |
| // Media size. |
| // Sort the media sizes based on the current locale. |
| List<MediaSize> mediaSizes = new ArrayList<MediaSize>(capabilities.getMediaSizes()); |
| Collections.sort(mediaSizes, mMediaSizeComparator); |
| |
| // If the media sizes changed, we update the adapter and the spinner. |
| boolean mediaSizesChanged = false; |
| final int mediaSizeCount = mediaSizes.size(); |
| if (mediaSizeCount != mMediaSizeSpinnerAdapter.getCount()) { |
| mediaSizesChanged = true; |
| } else { |
| for (int i = 0; i < mediaSizeCount; i++) { |
| if (!mediaSizes.get(i).equals(mMediaSizeSpinnerAdapter.getItem(i).value)) { |
| mediaSizesChanged = true; |
| break; |
| } |
| } |
| } |
| if (mediaSizesChanged) { |
| // Remember the old media size to try selecting it again. |
| int oldMediaSizeNewIndex = AdapterView.INVALID_POSITION; |
| MediaSize oldMediaSize = mCurrPrintAttributes.getMediaSize(); |
| |
| // Rebuild the adapter data. |
| mMediaSizeSpinnerAdapter.clear(); |
| for (int i = 0; i < mediaSizeCount; i++) { |
| MediaSize mediaSize = mediaSizes.get(i); |
| if (mediaSize.asPortrait().equals(oldMediaSize.asPortrait())) { |
| // Update the index of the old selection. |
| oldMediaSizeNewIndex = i; |
| } |
| mMediaSizeSpinnerAdapter.add(new SpinnerItem<MediaSize>( |
| mediaSize, mediaSize.getLabel(getPackageManager()))); |
| } |
| |
| mMediaSizeSpinner.setEnabled(true); |
| |
| if (oldMediaSizeNewIndex != AdapterView.INVALID_POSITION) { |
| // Select the old media size - nothing really changed. |
| setMediaSizeSpinnerSelectionNoCallback(oldMediaSizeNewIndex); |
| } else { |
| // Select the first or the default and mark if selection changed. |
| final int mediaSizeIndex = Math.max(mediaSizes.indexOf( |
| defaultAttributes.getMediaSize()), 0); |
| setMediaSizeSpinnerSelectionNoCallback(mediaSizeIndex); |
| if (oldMediaSize.isPortrait()) { |
| mCurrPrintAttributes.setMediaSize(mMediaSizeSpinnerAdapter |
| .getItem(mediaSizeIndex).value.asPortrait()); |
| } else { |
| mCurrPrintAttributes.setMediaSize(mMediaSizeSpinnerAdapter |
| .getItem(mediaSizeIndex).value.asLandscape()); |
| } |
| someAttributeSelectionChanged = true; |
| } |
| } |
| mMediaSizeSpinner.setEnabled(true); |
| |
| // Color mode. |
| final int colorModes = capabilities.getColorModes(); |
| |
| // If the color modes changed, we update the adapter and the spinner. |
| boolean colorModesChanged = false; |
| if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) { |
| colorModesChanged = true; |
| } else { |
| int remainingColorModes = colorModes; |
| int adapterIndex = 0; |
| while (remainingColorModes != 0) { |
| final int colorBitOffset = Integer.numberOfTrailingZeros( |
| remainingColorModes); |
| final int colorMode = 1 << colorBitOffset; |
| remainingColorModes &= ~colorMode; |
| if (colorMode != mColorModeSpinnerAdapter.getItem(adapterIndex).value) { |
| colorModesChanged = true; |
| break; |
| } |
| adapterIndex++; |
| } |
| } |
| if (colorModesChanged) { |
| // Remember the old color mode to try selecting it again. |
| int oldColorModeNewIndex = AdapterView.INVALID_POSITION; |
| final int oldColorMode = mCurrPrintAttributes.getColorMode(); |
| |
| // Rebuild the adapter data. |
| mColorModeSpinnerAdapter.clear(); |
| String[] colorModeLabels = getResources().getStringArray( |
| R.array.color_mode_labels); |
| int remainingColorModes = colorModes; |
| while (remainingColorModes != 0) { |
| final int colorBitOffset = Integer.numberOfTrailingZeros( |
| remainingColorModes); |
| final int colorMode = 1 << colorBitOffset; |
| if (colorMode == oldColorMode) { |
| // Update the index of the old selection. |
| oldColorModeNewIndex = colorBitOffset; |
| } |
| remainingColorModes &= ~colorMode; |
| mColorModeSpinnerAdapter.add(new SpinnerItem<Integer>(colorMode, |
| colorModeLabels[colorBitOffset])); |
| } |
| mColorModeSpinner.setEnabled(true); |
| if (oldColorModeNewIndex != AdapterView.INVALID_POSITION) { |
| // Select the old color mode - nothing really changed. |
| setColorModeSpinnerSelectionNoCallback(oldColorModeNewIndex); |
| } else { |
| final int selectedColorMode = colorModes & defaultAttributes.getColorMode(); |
| final int itemCount = mColorModeSpinnerAdapter.getCount(); |
| for (int i = 0; i < itemCount; i++) { |
| SpinnerItem<Integer> item = mColorModeSpinnerAdapter.getItem(i); |
| if (selectedColorMode == item.value) { |
| setColorModeSpinnerSelectionNoCallback(i); |
| mCurrPrintAttributes.setColorMode(selectedColorMode); |
| someAttributeSelectionChanged = true; |
| } |
| } |
| } |
| } |
| mColorModeSpinner.setEnabled(true); |
| |
| // Orientation |
| MediaSize mediaSize = mCurrPrintAttributes.getMediaSize(); |
| if (mediaSize.isPortrait() |
| && mOrientationSpinner.getSelectedItemPosition() != 0) { |
| mIgnoreNextOrientationChange = true; |
| mOrientationSpinner.setSelection(0); |
| } else if (!mediaSize.isPortrait() |
| && mOrientationSpinner.getSelectedItemPosition() != 1) { |
| mIgnoreNextOrientationChange = true; |
| mOrientationSpinner.setSelection(1); |
| } |
| mOrientationSpinner.setEnabled(true); |
| |
| // Range options |
| PrintDocumentInfo info = mDocument.info; |
| if (info != null && info.getPageCount() > 0) { |
| if (info.getPageCount() == 1) { |
| mRangeOptionsSpinner.setEnabled(false); |
| } else { |
| mRangeOptionsSpinner.setEnabled(true); |
| if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) { |
| if (!mPageRangeEditText.isEnabled()) { |
| mPageRangeEditText.setEnabled(true); |
| mPageRangeEditText.setVisibility(View.VISIBLE); |
| mPageRangeTitle.setVisibility(View.VISIBLE); |
| mPageRangeEditText.requestFocus(); |
| InputMethodManager imm = (InputMethodManager) |
| getSystemService(INPUT_METHOD_SERVICE); |
| imm.showSoftInput(mPageRangeEditText, 0); |
| } |
| } else { |
| mPageRangeEditText.setEnabled(false); |
| mPageRangeEditText.setVisibility(View.INVISIBLE); |
| mPageRangeTitle.setVisibility(View.INVISIBLE); |
| } |
| } |
| final int pageCount = mDocument.info.getPageCount(); |
| String title = (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) |
| ? getString(R.string.label_pages, String.valueOf(pageCount)) |
| : getString(R.string.page_count_unknown); |
| mRangeOptionsTitle.setText(title); |
| } else { |
| if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) { |
| mIgnoreNextRangeOptionChange = true; |
| mRangeOptionsSpinner.setSelection(0); |
| } |
| mRangeOptionsSpinner.setEnabled(false); |
| mRangeOptionsTitle.setText(getString(R.string.page_count_unknown)); |
| mPageRangeEditText.setEnabled(false); |
| mPageRangeEditText.setVisibility(View.INVISIBLE); |
| mPageRangeTitle.setVisibility(View.INVISIBLE); |
| } |
| |
| // Advanced print options |
| ComponentName serviceName = mCurrentPrinter.getId().getServiceName(); |
| if (!TextUtils.isEmpty(getAdvancedOptionsActivityName(serviceName))) { |
| mAdvancedPrintOptionsContainer.setVisibility(View.VISIBLE); |
| mAdvancedOptionsButton.setEnabled(true); |
| } else { |
| mAdvancedPrintOptionsContainer.setVisibility(View.GONE); |
| mAdvancedOptionsButton.setEnabled(false); |
| } |
| |
| // Print |
| if (mDestinationSpinner.getSelectedItemId() |
| != DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF) { |
| String newText = getString(R.string.print_button); |
| if (!TextUtils.equals(newText, mPrintButton.getText())) { |
| mPrintButton.setText(R.string.print_button); |
| } |
| } else { |
| String newText = getString(R.string.save_button); |
| if (!TextUtils.equals(newText, mPrintButton.getText())) { |
| mPrintButton.setText(R.string.save_button); |
| } |
| } |
| if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1 |
| && (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors())) |
| || (mRangeOptionsSpinner.getSelectedItemPosition() == 0 |
| && (!mController.hasPerformedLayout() || hasErrors()))) { |
| mPrintButton.setEnabled(false); |
| } else { |
| mPrintButton.setEnabled(true); |
| } |
| |
| // Copies |
| if (mDestinationSpinner.getSelectedItemId() |
| != DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF) { |
| mCopiesEditText.setEnabled(true); |
| } else { |
| mCopiesEditText.setEnabled(false); |
| } |
| if (mCopiesEditText.getError() == null |
| && TextUtils.isEmpty(mCopiesEditText.getText())) { |
| mIgnoreNextCopiesChange = true; |
| mCopiesEditText.setText(String.valueOf(MIN_COPIES)); |
| mCopiesEditText.requestFocus(); |
| } |
| |
| return someAttributeSelectionChanged; |
| } |
| } |
| |
| private String getAdvancedOptionsActivityName(ComponentName serviceName) { |
| PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); |
| List<PrintServiceInfo> printServices = printManager.getEnabledPrintServices(); |
| final int printServiceCount = printServices.size(); |
| for (int i = 0; i < printServiceCount; i ++) { |
| PrintServiceInfo printServiceInfo = printServices.get(i); |
| ServiceInfo serviceInfo = printServiceInfo.getResolveInfo().serviceInfo; |
| if (serviceInfo.name.equals(serviceName.getClassName()) |
| && serviceInfo.packageName.equals(serviceName.getPackageName())) { |
| return printServiceInfo.getAdvancedOptionsActivityName(); |
| } |
| } |
| return null; |
| } |
| |
| private void setMediaSizeSpinnerSelectionNoCallback(int position) { |
| if (mMediaSizeSpinner.getSelectedItemPosition() != position) { |
| mOldMediaSizeSelectionIndex = position; |
| mMediaSizeSpinner.setSelection(position); |
| } |
| } |
| |
| private void setColorModeSpinnerSelectionNoCallback(int position) { |
| if (mColorModeSpinner.getSelectedItemPosition() != position) { |
| mOldColorModeSelectionIndex = position; |
| mColorModeSpinner.setSelection(position); |
| } |
| } |
| |
| private void startSelectPrinterActivity() { |
| Intent intent = new Intent(PrintJobConfigActivity.this, |
| SelectPrinterActivity.class); |
| startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER); |
| } |
| |
| private boolean hasErrors() { |
| if (mCopiesEditText.getError() != null) { |
| return true; |
| } |
| return mPageRangeEditText.getVisibility() == View.VISIBLE |
| && mPageRangeEditText.getError() != null; |
| } |
| |
| private final class SpinnerItem<T> { |
| final T value; |
| CharSequence label; |
| |
| public SpinnerItem(T value, CharSequence label) { |
| this.value = value; |
| this.label = label; |
| } |
| |
| public String toString() { |
| return label.toString(); |
| } |
| } |
| |
| private final class WaitForPrinterCapabilitiesTimeout implements Runnable { |
| private static final long GET_CAPABILITIES_TIMEOUT_MILLIS = 10000; // 10sec |
| |
| private boolean mIsPosted; |
| |
| public void post() { |
| if (!mIsPosted) { |
| mDestinationSpinner.postDelayed(this, |
| GET_CAPABILITIES_TIMEOUT_MILLIS); |
| mIsPosted = true; |
| } |
| } |
| |
| public void remove() { |
| if (mIsPosted) { |
| mIsPosted = false; |
| mDestinationSpinner.removeCallbacks(this); |
| } |
| } |
| |
| public boolean isPosted() { |
| return mIsPosted; |
| } |
| |
| @Override |
| public void run() { |
| mIsPosted = false; |
| if (mDestinationSpinner.getSelectedItemPosition() >= 0) { |
| View itemView = mDestinationSpinner.getSelectedView(); |
| TextView titleView = (TextView) itemView.findViewById(R.id.subtitle); |
| try { |
| PackageInfo packageInfo = getPackageManager().getPackageInfo( |
| mCurrentPrinter.getId().getServiceName().getPackageName(), 0); |
| CharSequence service = packageInfo.applicationInfo.loadLabel( |
| getPackageManager()); |
| String subtitle = getString(R.string.printer_unavailable, service.toString()); |
| titleView.setText(subtitle); |
| } catch (NameNotFoundException nnfe) { |
| /* ignore */ |
| } |
| } |
| } |
| } |
| |
| private final class DestinationAdapter extends BaseAdapter |
| implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>{ |
| private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>(); |
| |
| private PrinterInfo mFakePdfPrinter; |
| |
| public DestinationAdapter() { |
| getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this); |
| } |
| |
| public int getPrinterIndex(PrinterId printerId) { |
| for (int i = 0; i < getCount(); i++) { |
| PrinterInfo printer = (PrinterInfo) getItem(i); |
| if (printer != null && printer.getId().equals(printerId)) { |
| return i; |
| } |
| } |
| return AdapterView.INVALID_POSITION; |
| } |
| |
| public void ensurePrinterInVisibleAdapterPosition(PrinterId printerId) { |
| final int printerCount = mPrinters.size(); |
| for (int i = 0; i < printerCount; i++) { |
| PrinterInfo printer = (PrinterInfo) mPrinters.get(i); |
| if (printer.getId().equals(printerId)) { |
| // If already in the list - do nothing. |
| if (i < getCount() - 2) { |
| return; |
| } |
| // Else replace the last one (two items are not printers). |
| final int lastPrinterIndex = getCount() - 3; |
| mPrinters.set(i, mPrinters.get(lastPrinterIndex)); |
| mPrinters.set(lastPrinterIndex, printer); |
| notifyDataSetChanged(); |
| return; |
| } |
| } |
| } |
| |
| @Override |
| public int getCount() { |
| if (mFakePdfPrinter == null) { |
| return 0; |
| } |
| return Math.min(mPrinters.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT); |
| } |
| |
| @Override |
| public boolean isEnabled(int position) { |
| Object item = getItem(position); |
| if (item instanceof PrinterInfo) { |
| PrinterInfo printer = (PrinterInfo) item; |
| return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; |
| } |
| return true; |
| } |
| |
| @Override |
| public Object getItem(int position) { |
| if (mPrinters.isEmpty()) { |
| if (position == 0 && mFakePdfPrinter != null) { |
| return mFakePdfPrinter; |
| } |
| } else { |
| if (position < 1) { |
| return mPrinters.get(position); |
| } |
| if (position == 1 && mFakePdfPrinter != null) { |
| return mFakePdfPrinter; |
| } |
| if (position < getCount() - 1) { |
| return mPrinters.get(position - 1); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public long getItemId(int position) { |
| if (mPrinters.isEmpty()) { |
| if (mFakePdfPrinter != null) { |
| if (position == 0) { |
| return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF; |
| } else if (position == 1) { |
| return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS; |
| } |
| } |
| } else { |
| if (position == 1 && mFakePdfPrinter != null) { |
| return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF; |
| } |
| if (position == getCount() - 1) { |
| return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS; |
| } |
| } |
| return position; |
| } |
| |
| @Override |
| public View getDropDownView(int position, View convertView, |
| ViewGroup parent) { |
| View view = getView(position, convertView, parent); |
| view.setEnabled(isEnabled(position)); |
| return view; |
| } |
| |
| @Override |
| public View getView(int position, View convertView, ViewGroup parent) { |
| if (convertView == null) { |
| convertView = getLayoutInflater().inflate( |
| R.layout.printer_dropdown_item, parent, false); |
| } |
| |
| CharSequence title = null; |
| CharSequence subtitle = null; |
| Drawable icon = null; |
| |
| if (mPrinters.isEmpty()) { |
| if (position == 0 && mFakePdfPrinter != null) { |
| PrinterInfo printer = (PrinterInfo) getItem(position); |
| title = printer.getName(); |
| } else if (position == 1) { |
| title = getString(R.string.all_printers); |
| } |
| } else { |
| if (position == 1 && mFakePdfPrinter != null) { |
| PrinterInfo printer = (PrinterInfo) getItem(position); |
| title = printer.getName(); |
| } else if (position == getCount() - 1) { |
| title = getString(R.string.all_printers); |
| } else { |
| PrinterInfo printer = (PrinterInfo) getItem(position); |
| title = printer.getName(); |
| try { |
| PackageInfo packageInfo = getPackageManager().getPackageInfo( |
| printer.getId().getServiceName().getPackageName(), 0); |
| subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager()); |
| icon = packageInfo.applicationInfo.loadIcon(getPackageManager()); |
| } catch (NameNotFoundException nnfe) { |
| /* ignore */ |
| } |
| } |
| } |
| |
| TextView titleView = (TextView) convertView.findViewById(R.id.title); |
| titleView.setText(title); |
| |
| TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle); |
| if (!TextUtils.isEmpty(subtitle)) { |
| subtitleView.setText(subtitle); |
| subtitleView.setVisibility(View.VISIBLE); |
| } else { |
| subtitleView.setText(null); |
| subtitleView.setVisibility(View.GONE); |
| } |
| |
| ImageView iconView = (ImageView) convertView.findViewById(R.id.icon); |
| if (icon != null) { |
| iconView.setImageDrawable(icon); |
| iconView.setVisibility(View.VISIBLE); |
| } else { |
| iconView.setVisibility(View.INVISIBLE); |
| } |
| |
| return convertView; |
| } |
| |
| @Override |
| public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) { |
| if (id == LOADER_ID_PRINTERS_LOADER) { |
| return new FusedPrintersProvider(PrintJobConfigActivity.this); |
| } |
| return null; |
| } |
| |
| @Override |
| public void onLoadFinished(Loader<List<PrinterInfo>> loader, |
| List<PrinterInfo> printers) { |
| // If this is the first load, create the fake PDF printer. |
| // We do this to avoid flicker where the PDF printer is the |
| // only one and as soon as the loader loads the favorites |
| // it gets switched. Not a great user experience. |
| if (mFakePdfPrinter == null) { |
| mCurrentPrinter = mFakePdfPrinter = createFakePdfPrinter(); |
| updatePrintAttributes(mCurrentPrinter.getCapabilities()); |
| updateUi(); |
| } |
| |
| // We rearrange the printers if the user selects a printer |
| // not shown in the initial short list. Therefore, we have |
| // to keep the printer order. |
| |
| // No old printers - do not bother keeping their position. |
| if (mPrinters.isEmpty()) { |
| mPrinters.addAll(printers); |
| mEditor.ensureCurrentPrinterSelected(); |
| notifyDataSetChanged(); |
| return; |
| } |
| |
| // Add the new printers to a map. |
| ArrayMap<PrinterId, PrinterInfo> newPrintersMap = |
| new ArrayMap<PrinterId, PrinterInfo>(); |
| final int printerCount = printers.size(); |
| for (int i = 0; i < printerCount; i++) { |
| PrinterInfo printer = printers.get(i); |
| newPrintersMap.put(printer.getId(), printer); |
| } |
| |
| List<PrinterInfo> newPrinters = new ArrayList<PrinterInfo>(); |
| |
| // Update printers we already have. |
| final int oldPrinterCount = mPrinters.size(); |
| for (int i = 0; i < oldPrinterCount; i++) { |
| PrinterId oldPrinterId = mPrinters.get(i).getId(); |
| PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId); |
| if (updatedPrinter != null) { |
| newPrinters.add(updatedPrinter); |
| } |
| } |
| |
| // Add the rest of the new printers, i.e. what is left. |
| newPrinters.addAll(newPrintersMap.values()); |
| |
| mPrinters.clear(); |
| mPrinters.addAll(newPrinters); |
| |
| mEditor.ensureCurrentPrinterSelected(); |
| notifyDataSetChanged(); |
| } |
| |
| @Override |
| public void onLoaderReset(Loader<List<PrinterInfo>> loader) { |
| mPrinters.clear(); |
| notifyDataSetInvalidated(); |
| } |
| |
| |
| private PrinterInfo createFakePdfPrinter() { |
| MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintJobConfigActivity.this); |
| |
| PrinterId printerId = new PrinterId(getComponentName(), "PDF printer"); |
| |
| PrinterCapabilitiesInfo.Builder builder = |
| new PrinterCapabilitiesInfo.Builder(printerId); |
| |
| String[] mediaSizeIds = getResources().getStringArray( |
| R.array.pdf_printer_media_sizes); |
| final int mediaSizeIdCount = mediaSizeIds.length; |
| for (int i = 0; i < mediaSizeIdCount; i++) { |
| String id = mediaSizeIds[i]; |
| MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id); |
| builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize)); |
| } |
| |
| builder.addResolution(new Resolution("PDF resolution", "PDF resolution", |
| 300, 300), true); |
| builder.setColorModes(PrintAttributes.COLOR_MODE_COLOR |
| | PrintAttributes.COLOR_MODE_MONOCHROME, |
| PrintAttributes.COLOR_MODE_COLOR); |
| |
| return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf), |
| PrinterInfo.STATUS_IDLE) |
| .setCapabilities(builder.build()) |
| .build(); |
| } |
| } |
| } |
| |
| /** |
| * An instance of this class class is intended to be the first focusable |
| * in a layout to which the system automatically gives focus. It performs |
| * some voodoo to avoid the first tap on it to start an edit mode, rather |
| * to bring up the IME, i.e. to get the behavior as if the view was not |
| * focused. |
| */ |
| public static final class CustomEditText extends EditText { |
| private boolean mClickedBeforeFocus; |
| private CharSequence mError; |
| |
| public CustomEditText(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| } |
| |
| @Override |
| public boolean performClick() { |
| super.performClick(); |
| if (isFocused() && !mClickedBeforeFocus) { |
| clearFocus(); |
| requestFocus(); |
| } |
| mClickedBeforeFocus = true; |
| return true; |
| } |
| |
| @Override |
| public CharSequence getError() { |
| return mError; |
| } |
| |
| @Override |
| public void setError(CharSequence error, Drawable icon) { |
| setCompoundDrawables(null, null, icon, null); |
| mError = error; |
| } |
| |
| protected void onFocusChanged(boolean gainFocus, int direction, |
| Rect previouslyFocusedRect) { |
| if (!gainFocus) { |
| mClickedBeforeFocus = false; |
| } |
| super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); |
| } |
| } |
| |
| private static final class Document { |
| public PrintDocumentInfo info; |
| public PageRange[] pages; |
| } |
| |
| private static final class PageRangeUtils { |
| |
| private static final Comparator<PageRange> sComparator = new Comparator<PageRange>() { |
| @Override |
| public int compare(PageRange lhs, PageRange rhs) { |
| return lhs.getStart() - rhs.getStart(); |
| } |
| }; |
| |
| private PageRangeUtils() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public static boolean contains(PageRange[] ourRanges, PageRange[] otherRanges) { |
| if (ourRanges == null || otherRanges == null) { |
| return false; |
| } |
| |
| if (ourRanges.length == 1 |
| && PageRange.ALL_PAGES.equals(ourRanges[0])) { |
| return true; |
| } |
| |
| ourRanges = normalize(ourRanges); |
| otherRanges = normalize(otherRanges); |
| |
| // Note that the code below relies on the ranges being normalized |
| // which is they contain monotonically increasing non-intersecting |
| // subranges whose start is less that or equal to the end. |
| int otherRangeIdx = 0; |
| final int ourRangeCount = ourRanges.length; |
| final int otherRangeCount = otherRanges.length; |
| for (int ourRangeIdx = 0; ourRangeIdx < ourRangeCount; ourRangeIdx++) { |
| PageRange ourRange = ourRanges[ourRangeIdx]; |
| for (; otherRangeIdx < otherRangeCount; otherRangeIdx++) { |
| PageRange otherRange = otherRanges[otherRangeIdx]; |
| if (otherRange.getStart() > ourRange.getEnd()) { |
| break; |
| } |
| if (otherRange.getStart() < ourRange.getStart() |
| || otherRange.getEnd() > ourRange.getEnd()) { |
| return false; |
| } |
| } |
| } |
| if (otherRangeIdx < otherRangeCount) { |
| return false; |
| } |
| return true; |
| } |
| |
| public static PageRange[] normalize(PageRange[] pageRanges) { |
| if (pageRanges == null) { |
| return null; |
| } |
| final int oldRangeCount = pageRanges.length; |
| if (oldRangeCount <= 1) { |
| return pageRanges; |
| } |
| Arrays.sort(pageRanges, sComparator); |
| int newRangeCount = 1; |
| for (int i = 0; i < oldRangeCount - 1; i++) { |
| newRangeCount++; |
| PageRange currentRange = pageRanges[i]; |
| PageRange nextRange = pageRanges[i + 1]; |
| if (currentRange.getEnd() + 1 >= nextRange.getStart()) { |
| newRangeCount--; |
| pageRanges[i] = null; |
| pageRanges[i + 1] = new PageRange(currentRange.getStart(), |
| Math.max(currentRange.getEnd(), nextRange.getEnd())); |
| } |
| } |
| if (newRangeCount == oldRangeCount) { |
| return pageRanges; |
| } |
| return Arrays.copyOfRange(pageRanges, oldRangeCount - newRangeCount, |
| oldRangeCount); |
| } |
| |
| public static void offset(PageRange[] pageRanges, int offset) { |
| if (offset == 0) { |
| return; |
| } |
| final int pageRangeCount = pageRanges.length; |
| for (int i = 0; i < pageRangeCount; i++) { |
| final int start = pageRanges[i].getStart() + offset; |
| final int end = pageRanges[i].getEnd() + offset; |
| pageRanges[i] = new PageRange(start, end); |
| } |
| } |
| } |
| |
| private static final class AutoCancellingAnimator |
| implements OnAttachStateChangeListener, Runnable { |
| |
| private ViewPropertyAnimator mAnimator; |
| |
| private boolean mCancelled; |
| private Runnable mEndCallback; |
| |
| public static AutoCancellingAnimator animate(View view) { |
| ViewPropertyAnimator animator = view.animate(); |
| AutoCancellingAnimator cancellingWrapper = |
| new AutoCancellingAnimator(animator); |
| view.addOnAttachStateChangeListener(cancellingWrapper); |
| return cancellingWrapper; |
| } |
| |
| private AutoCancellingAnimator(ViewPropertyAnimator animator) { |
| mAnimator = animator; |
| } |
| |
| public AutoCancellingAnimator alpha(float alpha) { |
| mAnimator = mAnimator.alpha(alpha); |
| return this; |
| } |
| |
| public void cancel() { |
| mAnimator.cancel(); |
| } |
| |
| public AutoCancellingAnimator withLayer() { |
| mAnimator = mAnimator.withLayer(); |
| return this; |
| } |
| |
| public AutoCancellingAnimator withEndAction(Runnable callback) { |
| mEndCallback = callback; |
| mAnimator = mAnimator.withEndAction(this); |
| return this; |
| } |
| |
| public AutoCancellingAnimator scaleY(float scale) { |
| mAnimator = mAnimator.scaleY(scale); |
| return this; |
| } |
| |
| @Override |
| public void onViewAttachedToWindow(View v) { |
| /* do nothing */ |
| } |
| |
| @Override |
| public void onViewDetachedFromWindow(View v) { |
| cancel(); |
| } |
| |
| @Override |
| public void run() { |
| if (!mCancelled) { |
| mEndCallback.run(); |
| } |
| } |
| } |
| |
| private static final class PrintSpoolerProvider implements ServiceConnection { |
| private final Context mContext; |
| private final Runnable mCallback; |
| |
| private PrintSpoolerService mSpooler; |
| |
| public PrintSpoolerProvider(Context context, Runnable callback) { |
| mContext = context; |
| mCallback = callback; |
| Intent intent = new Intent(mContext, PrintSpoolerService.class); |
| mContext.bindService(intent, this, 0); |
| } |
| |
| public PrintSpoolerService getSpooler() { |
| return mSpooler; |
| } |
| |
| public void destroy() { |
| if (mSpooler != null) { |
| mContext.unbindService(this); |
| } |
| } |
| |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| mSpooler = ((PrintSpoolerService.PrintSpooler) service).getService(); |
| if (mSpooler != null) { |
| mCallback.run(); |
| } |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| /* do noting - we are in the same process */ |
| } |
| } |
| |
| private static final class PrintDocumentAdapterObserver |
| extends IPrintDocumentAdapterObserver.Stub { |
| private final WeakReference<PrintJobConfigActivity> mWeakActvity; |
| |
| public PrintDocumentAdapterObserver(PrintJobConfigActivity activity) { |
| mWeakActvity = new WeakReference<PrintJobConfigActivity>(activity); |
| } |
| |
| @Override |
| public void onDestroy() { |
| final PrintJobConfigActivity activity = mWeakActvity.get(); |
| if (activity != null) { |
| activity.mController.mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| if (activity.mController != null) { |
| activity.mController.cancel(); |
| } |
| if (activity.mEditor != null) { |
| activity.mEditor.cancel(); |
| } |
| activity.finish(); |
| } |
| }); |
| } |
| } |
| } |
| } |