| /* |
| * 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.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.os.Build; |
| 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.IPrinterDiscoveryObserver; |
| import android.print.IWriteResultCallback; |
| import android.print.PageRange; |
| import android.print.PrintAttributes; |
| import android.print.PrintAttributes.MediaSize; |
| import android.print.PrintDocumentAdapter; |
| import android.print.PrintDocumentInfo; |
| import android.print.PrintJobInfo; |
| import android.print.PrinterId; |
| import android.print.PrinterInfo; |
| import android.text.Editable; |
| import android.text.TextUtils; |
| import android.text.TextUtils.SimpleStringSplitter; |
| import android.text.TextWatcher; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.Choreographer; |
| import android.view.KeyEvent; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.view.ViewGroup; |
| import android.view.WindowManager; |
| import android.view.inputmethod.InputMethodManager; |
| import android.widget.AdapterView; |
| import android.widget.AdapterView.OnItemSelectedListener; |
| import android.widget.ArrayAdapter; |
| import android.widget.Button; |
| import android.widget.EditText; |
| import android.widget.Spinner; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| 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 = true && Build.IS_DEBUGGABLE; |
| |
| private static final boolean LIVE_PREVIEW_SUPPORTED = false; |
| |
| public static final String EXTRA_PRINT_DOCUMENT_ADAPTER = "printDocumentAdapter"; |
| public static final String EXTRA_PRINT_ATTRIBUTES = "printAttributes"; |
| public static final String EXTRA_PRINT_JOB_ID = "printJobId"; |
| |
| private static final int CONTROLLER_STATE_INITIALIZED = 1; |
| private static final int CONTROLLER_STATE_STARTED = 2; |
| private static final int CONTROLLER_STATE_LAYOUT_STARTED = 3; |
| private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 4; |
| private static final int CONTROLLER_STATE_WRITE_STARTED = 5; |
| private static final int CONTROLLER_STATE_WRITE_COMPLETED = 6; |
| private static final int CONTROLLER_STATE_FINISHED = 7; |
| private static final int CONTROLLER_STATE_FAILED = 8; |
| private static final int CONTROLLER_STATE_CANCELLED = 9; |
| |
| private static final int EDITOR_STATE_INITIALIZED = 1; |
| private static final int EDITOR_STATE_CONFIRMED_PRINT = 2; |
| private static final int EDITOR_STATE_CONFIRMED_PREVIEW = 3; |
| private static final int EDITOR_STATE_CANCELLED = 4; |
| |
| private static final int MIN_COPIES = 1; |
| |
| 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( |
| "([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().create(); |
| private final PrintAttributes mCurrPrintAttributes = new PrintAttributes.Builder().create(); |
| private final PrintAttributes mTempPrintAttributes = new PrintAttributes.Builder().create(); |
| |
| private final DeathRecipient mDeathRecipient = new DeathRecipient() { |
| @Override |
| public void binderDied() { |
| finish(); |
| } |
| }; |
| |
| private PrintSpooler mSpooler; |
| private Editor mEditor; |
| private Document mDocument; |
| private PrintController mController; |
| private PrinterDiscoveryObserver mPrinterDiscoveryObserver; |
| |
| private int mPrintJobId; |
| |
| private IBinder mIPrintDocumentAdapter; |
| |
| @Override |
| protected void onCreate(Bundle bundle) { |
| super.onCreate(bundle); |
| setContentView(R.layout.print_job_config_activity); |
| |
| getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); |
| |
| Bundle extras = getIntent().getExtras(); |
| |
| mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1); |
| if (mPrintJobId < 0) { |
| throw new IllegalArgumentException("Invalid print job id: " + mPrintJobId); |
| } |
| |
| mIPrintDocumentAdapter = extras.getBinder(EXTRA_PRINT_DOCUMENT_ADAPTER); |
| if (mIPrintDocumentAdapter == null) { |
| throw new IllegalArgumentException("PrintDocumentAdapter cannot be null"); |
| } |
| |
| PrintAttributes attributes = getIntent().getParcelableExtra(EXTRA_PRINT_ATTRIBUTES); |
| if (attributes != null) { |
| mCurrPrintAttributes.copyFrom(attributes); |
| } |
| |
| mSpooler = PrintSpooler.getInstance(this); |
| mEditor = new Editor(); |
| mDocument = new Document(); |
| mController = new PrintController(new RemotePrintDocumentAdapter( |
| IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter), |
| mSpooler.generateFileForPrintJob(mPrintJobId))); |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| try { |
| mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0); |
| } catch (RemoteException re) { |
| finish(); |
| return; |
| } |
| mController.initialize(); |
| mEditor.initialize(); |
| mPrinterDiscoveryObserver = new PrinterDiscoveryObserver(mEditor, getMainLooper()); |
| mSpooler.startPrinterDiscovery(mPrinterDiscoveryObserver); |
| } |
| |
| @Override |
| protected void onPause() { |
| mSpooler.stopPrinterDiscovery(); |
| mPrinterDiscoveryObserver.destroy(); |
| mPrinterDiscoveryObserver = null; |
| if (mController.isCancelled() || mController.isFailed()) { |
| mSpooler.setPrintJobState(mPrintJobId, |
| PrintJobInfo.STATE_CANCELED); |
| } else if (mController.hasStarted()) { |
| mController.finish(); |
| if (mEditor.isPrintConfirmed()) { |
| if (mController.isFinished()) { |
| mSpooler.setPrintJobState(mPrintJobId, |
| PrintJobInfo.STATE_QUEUED); |
| } else { |
| mSpooler.setPrintJobState(mPrintJobId, |
| PrintJobInfo.STATE_CANCELED); |
| } |
| } |
| } |
| mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0); |
| super.onPause(); |
| } |
| |
| public boolean onTouchEvent(MotionEvent event) { |
| if (!mEditor.isPrintConfirmed() && !mEditor.isPreviewConfirmed() |
| && getWindow().shouldCloseOnTouch(this, 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 (keyCode == KeyEvent.KEYCODE_BACK && 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 Handler mHandler; |
| |
| private int mControllerState = CONTROLLER_STATE_INITIALIZED; |
| |
| private PageRange[] mRequestedPages; |
| |
| private Bundle mMetadata = new Bundle(); |
| |
| private final ILayoutResultCallback mILayoutResultCallback = |
| new ILayoutResultCallback.Stub() { |
| @Override |
| public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) { |
| if (mRequestCounter.get() == sequence) { |
| mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FINISHED, changed ? 1 : 0, |
| 0, info).sendToTarget(); |
| } |
| } |
| |
| @Override |
| public void onLayoutFailed(CharSequence error, int sequence) { |
| if (mRequestCounter.get() == sequence) { |
| mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FAILED, error).sendToTarget(); |
| } |
| } |
| }; |
| |
| private IWriteResultCallback mIWriteResultCallback = new IWriteResultCallback.Stub() { |
| @Override |
| public void onWriteFinished(PageRange[] pages, int sequence) { |
| if (mRequestCounter.get() == sequence) { |
| mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FINISHED, pages).sendToTarget(); |
| } |
| } |
| |
| @Override |
| public void onWriteFailed(CharSequence error, int sequence) { |
| if (mRequestCounter.get() == sequence) { |
| mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FAILED, error).sendToTarget(); |
| } |
| } |
| }; |
| |
| public PrintController(RemotePrintDocumentAdapter adapter) { |
| mRemotePrintAdapter = adapter; |
| mHandler = new MyHandler(Looper.getMainLooper()); |
| } |
| |
| public void initialize() { |
| mControllerState = CONTROLLER_STATE_INITIALIZED; |
| } |
| |
| public void cancel() { |
| mControllerState = CONTROLLER_STATE_CANCELLED; |
| } |
| |
| public boolean isCancelled() { |
| return (mControllerState == CONTROLLER_STATE_CANCELLED); |
| } |
| |
| public boolean isFinished() { |
| return (mControllerState == CONTROLLER_STATE_FINISHED); |
| } |
| |
| public boolean isFailed() { |
| return (mControllerState == CONTROLLER_STATE_FAILED); |
| } |
| |
| public boolean hasStarted() { |
| return mControllerState >= CONTROLLER_STATE_STARTED; |
| } |
| |
| public boolean hasPerformedLayout() { |
| return mControllerState >= CONTROLLER_STATE_LAYOUT_COMPLETED; |
| } |
| |
| public boolean isWorking() { |
| return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED |
| || mControllerState == CONTROLLER_STATE_WRITE_STARTED; |
| } |
| |
| public void start() { |
| mControllerState = CONTROLLER_STATE_STARTED; |
| mRemotePrintAdapter.start(); |
| } |
| |
| public void update() { |
| if (!printAttributesChanged()) { |
| // If the attributes changes, 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); |
| } else { |
| mSpooler.setPrintJobAttributesNoPersistence(mPrintJobId, mCurrPrintAttributes); |
| |
| mMetadata.putBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW, |
| !mEditor.isPrintConfirmed()); |
| |
| mControllerState = CONTROLLER_STATE_LAYOUT_STARTED; |
| |
| mRemotePrintAdapter.layout(mOldPrintAttributes, mCurrPrintAttributes, |
| mILayoutResultCallback, mMetadata, mRequestCounter.incrementAndGet()); |
| |
| mOldPrintAttributes.copyFrom(mCurrPrintAttributes); |
| } |
| } |
| |
| public void finish() { |
| mControllerState = CONTROLLER_STATE_FINISHED; |
| mRemotePrintAdapter.finish(); |
| } |
| |
| private void handleOnLayoutFinished(PrintDocumentInfo info, boolean layoutChanged) { |
| if (isCancelled()) { |
| if (mEditor.isDone()) { |
| PrintJobConfigActivity.this.finish(); |
| } |
| return; |
| } |
| |
| mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED; |
| |
| // If the info changed, we update the document and the print job, |
| // and update the UI since the the page range selection may have |
| // become invalid. |
| final boolean infoChanged = !info.equals(mDocument.info); |
| if (infoChanged) { |
| mDocument.info = info; |
| mSpooler.setPrintJobPrintDocumentInfoNoPersistence(mPrintJobId, info); |
| mEditor.updateUi(); |
| } |
| |
| // 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; |
| } |
| |
| // 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) { |
| if (mEditor.isDone()) { |
| PrintJobConfigActivity.this.finish(); |
| } |
| return; |
| } |
| |
| // If the info and the layout did not change and we already have |
| // the requested pages, then nothing else to do. |
| if (!infoChanged && !layoutChanged |
| && PageRangeUtils.contains(mDocument.pages, mRequestedPages)) { |
| if (mEditor.isDone()) { |
| PrintJobConfigActivity.this.finish(); |
| } |
| return; |
| } |
| |
| // If we do not support live preview and the current layout is |
| // not for preview purposes, i.e. the user did not poke the |
| // preview button, then just skip the write. |
| if (!LIVE_PREVIEW_SUPPORTED && !mEditor.isPreviewConfirmed() |
| && mMetadata.getBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW)) { |
| if (mEditor.isDone()) { |
| PrintJobConfigActivity.this.finish(); |
| } |
| return; |
| } |
| |
| // Request a write of the pages of interest. |
| mControllerState = CONTROLLER_STATE_WRITE_STARTED; |
| mRemotePrintAdapter.write(mRequestedPages, mIWriteResultCallback, |
| mRequestCounter.incrementAndGet()); |
| } |
| |
| private void handleOnLayoutFailed(CharSequence error) { |
| mControllerState = CONTROLLER_STATE_FAILED; |
| // TODO: We need some UI for announcing an error. |
| Log.e(LOG_TAG, "Error during layout: " + error); |
| PrintJobConfigActivity.this.finish(); |
| } |
| |
| private void handleOnWriteFinished(PageRange[] pages) { |
| if (isCancelled()) { |
| if (mEditor.isDone()) { |
| PrintJobConfigActivity.this.finish(); |
| } |
| return; |
| } |
| |
| mControllerState = CONTROLLER_STATE_WRITE_COMPLETED; |
| |
| // 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)); |
| } |
| |
| // 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(mDocument.pages, mRequestedPages)) { |
| // We got a document with exactly the pages we wanted. Hence, |
| // the printer has to print all pages in the data. |
| mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, ALL_PAGES_ARRAY); |
| } else if (Arrays.equals(mDocument.pages, ALL_PAGES_ARRAY)) { |
| // We requested specific pages but got all of them. Hence, |
| // the printer has to print only the requested pages. |
| mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mRequestedPages); |
| } else if (PageRangeUtils.contains(mDocument.pages, mRequestedPages)) { |
| // We requested specific pages and got more but not all pages. |
| // Hence, we have to offset appropriately the printed pages to |
| // excle the pages we did not request. Note that pages is |
| // guaranteed to be not null and not empty. |
| final int offset = mDocument.pages[0].getStart() - pages[0].getStart(); |
| PageRange[] offsetPages = Arrays.copyOf(mDocument.pages, mDocument.pages.length); |
| PageRangeUtils.offsetStart(offsetPages, offset); |
| mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, offsetPages); |
| } else if (Arrays.equals(mRequestedPages, ALL_PAGES_ARRAY) |
| && mDocument.pages.length == 1 && mDocument.pages[0].getStart() == 0 |
| && mDocument.pages[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. |
| mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mDocument.pages); |
| } else { |
| // We did not get the pages we requested, then the application |
| // misbehaves, so we fail quickly. |
| // TODO: We need some UI for announcing an error. |
| mControllerState = CONTROLLER_STATE_FAILED; |
| Log.e(LOG_TAG, "Received invalid pages from the app"); |
| PrintJobConfigActivity.this.finish(); |
| } |
| |
| if (mEditor.isDone()) { |
| PrintJobConfigActivity.this.finish(); |
| } |
| } |
| |
| private void handleOnWriteFailed(CharSequence error) { |
| mControllerState = CONTROLLER_STATE_FAILED; |
| Log.e(LOG_TAG, "Error during write: " + error); |
| PrintJobConfigActivity.this.finish(); |
| } |
| |
| private final class MyHandler 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 MyHandler(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); |
| mController.handleOnLayoutFinished(info, changed); |
| } break; |
| |
| case MSG_ON_LAYOUT_FAILED: { |
| CharSequence error = (CharSequence) message.obj; |
| mController.handleOnLayoutFailed(error); |
| } break; |
| |
| case MSG_ON_WRITE_FINISHED: { |
| PageRange[] pages = (PageRange[]) message.obj; |
| mController.handleOnWriteFinished(pages); |
| } break; |
| |
| case MSG_ON_WRITE_FAILED: { |
| CharSequence error = (CharSequence) message.obj; |
| mController.handleOnWriteFailed(error); |
| } break; |
| } |
| } |
| } |
| } |
| |
| private final class Editor { |
| private final EditText mCopiesEditText; |
| |
| private final TextView mRangeTitle; |
| private final EditText mRangeEditText; |
| |
| private final Spinner mDestinationSpinner; |
| private final ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter; |
| |
| private final Spinner mMediaSizeSpinner; |
| private final ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter; |
| |
| private final Spinner mColorModeSpinner; |
| private final ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter; |
| |
| private final Spinner mOrientationSpinner; |
| private final ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter; |
| |
| private final Spinner mRangeOptionsSpinner; |
| private final ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter; |
| |
| private final SimpleStringSplitter mStringCommaSplitter = |
| new SimpleStringSplitter(','); |
| |
| private final Button mPrintPreviewButton; |
| |
| private final Button mPrintButton; |
| |
| private final OnItemSelectedListener mOnItemSelectedListener = |
| new AdapterView.OnItemSelectedListener() { |
| @Override |
| public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) { |
| if (spinner == mDestinationSpinner) { |
| mCurrPrintAttributes.clear(); |
| SpinnerItem<PrinterInfo> dstItem = mDestinationSpinnerAdapter.getItem(position); |
| if (dstItem != null) { |
| PrinterInfo printer = dstItem.value; |
| mSpooler.setPrintJobPrinterIdNoPersistence(mPrintJobId, printer.getId()); |
| printer.getDefaults(mCurrPrintAttributes); |
| if (!printer.hasAllRequiredAttributes()) { |
| List<PrinterId> printerIds = new ArrayList<PrinterId>(); |
| printerIds.add(printer.getId()); |
| mSpooler.onReqeustUpdatePrinters(printerIds); |
| //TODO: We need a timeout for the update. |
| } else { |
| if (!mController.hasStarted()) { |
| mController.start(); |
| } |
| if (!hasErrors()) { |
| mController.update(); |
| } |
| } |
| } |
| updateUi(); |
| } else if (spinner == mMediaSizeSpinner) { |
| SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position); |
| mCurrPrintAttributes.setMediaSize(mediaItem.value); |
| if (!hasErrors()) { |
| mController.update(); |
| } |
| } else if (spinner == mColorModeSpinner) { |
| SpinnerItem<Integer> colorModeItem = |
| mColorModeSpinnerAdapter.getItem(position); |
| mCurrPrintAttributes.setColorMode(colorModeItem.value); |
| if (!hasErrors()) { |
| mController.update(); |
| } |
| } else if (spinner == mOrientationSpinner) { |
| SpinnerItem<Integer> orientationItem = |
| mOrientationSpinnerAdapter.getItem(position); |
| mCurrPrintAttributes.setOrientation(orientationItem.value); |
| if (!hasErrors()) { |
| mController.update(); |
| } |
| } else if (spinner == mRangeOptionsSpinner) { |
| updateUi(); |
| if (!hasErrors()) { |
| mController.update(); |
| } |
| } |
| } |
| |
| @Override |
| public void onNothingSelected(AdapterView<?> parent) { |
| /* do nothing*/ |
| } |
| }; |
| |
| 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) { |
| final boolean hadErrors = hasErrors(); |
| |
| if (editable.length() == 0) { |
| mCopiesEditText.setError(""); |
| updateUi(); |
| return; |
| } |
| |
| final int copies = Integer.parseInt(editable.toString()); |
| if (copies < MIN_COPIES) { |
| mCopiesEditText.setError(""); |
| updateUi(); |
| return; |
| } |
| |
| mCopiesEditText.setError(null); |
| mSpooler.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) { |
| final boolean hadErrors = hasErrors(); |
| |
| String text = editable.toString(); |
| |
| if (TextUtils.isEmpty(text)) { |
| mRangeEditText.setError(""); |
| updateUi(); |
| return; |
| } |
| |
| String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////"); |
| if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) { |
| mRangeEditText.setError(""); |
| updateUi(); |
| return; |
| } |
| |
| Matcher matcher = PATTERN_DIGITS.matcher(text); |
| while (matcher.find()) { |
| String numericString = text.substring(matcher.start(), matcher.end()); |
| final int pageIndex = Integer.parseInt(numericString); |
| if (pageIndex < 1 || pageIndex > mDocument.info.getPageCount()) { |
| mRangeEditText.setError(""); |
| updateUi(); |
| return; |
| } |
| } |
| |
| //TODO: Catch the error if start is less grater than the end. |
| |
| mRangeEditText.setError(null); |
| mPrintButton.setEnabled(true); |
| updateUi(); |
| |
| if (hadErrors && !hasErrors() && printAttributesChanged()) { |
| updateUi(); |
| } |
| } |
| }; |
| |
| private int mEditorState; |
| |
| public Editor() { |
| // Copies |
| mCopiesEditText = (EditText) findViewById(R.id.copies_edittext); |
| mCopiesEditText.addTextChangedListener(mCopiesTextWatcher); |
| mCopiesEditText.selectAll(); |
| |
| // Destination. |
| mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner); |
| mDestinationSpinnerAdapter = new DestinationAdapter(); |
| mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter); |
| mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| |
| // Media size. |
| mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner); |
| mMediaSizeSpinnerAdapter = new ArrayAdapter<SpinnerItem<MediaSize>>( |
| PrintJobConfigActivity.this, |
| R.layout.spinner_dropdown_item, R.id.title); |
| mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter); |
| mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| |
| // Color mode. |
| mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner); |
| mColorModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>( |
| PrintJobConfigActivity.this, |
| R.layout.spinner_dropdown_item, R.id.title); |
| mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter); |
| mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| |
| // Orientation |
| mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner); |
| mOrientationSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>( |
| PrintJobConfigActivity.this, |
| R.layout.spinner_dropdown_item, R.id.title); |
| mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter); |
| mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| |
| // Range |
| mRangeTitle = (TextView) findViewById(R.id.page_range_title); |
| mRangeEditText = (EditText) findViewById(R.id.page_range_edittext); |
| mRangeEditText.addTextChangedListener(mRangeTextWatcher); |
| |
| // Range options |
| mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner); |
| 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])); |
| } |
| mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter); |
| mRangeOptionsSpinner.setSelection(0); |
| // Here is some voodoo to circumvent the weird behavior of AdapterView |
| // in which a selection listener may get a callback for an event that |
| // happened before the listener was registered. The reason for that is |
| // that the selection change is handled on the next layout pass. |
| Choreographer.getInstance().postCallbackDelayed(Choreographer.CALLBACK_TRAVERSAL, |
| new Runnable() { |
| @Override |
| public void run() { |
| mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| } |
| }, null, Choreographer.getFrameDelay() * 2); |
| |
| mPrintPreviewButton = (Button) findViewById(R.id.print_preview_button); |
| mPrintPreviewButton.setOnClickListener(new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| mEditor.confirmPreview(); |
| // TODO: Implement me |
| Toast.makeText(PrintJobConfigActivity.this, |
| "Stop poking me! Not implemented yet :)", |
| Toast.LENGTH_LONG).show(); |
| } |
| }); |
| |
| mPrintButton = (Button) findViewById(R.id.print_button); |
| mPrintButton.setOnClickListener(new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| mEditor.confirmPrint(); |
| updateUi(); |
| mController.update(); |
| } |
| }); |
| } |
| |
| public void initialize() { |
| mEditorState = EDITOR_STATE_INITIALIZED; |
| mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION); |
| } |
| |
| public boolean isCancelled() { |
| return mEditorState == EDITOR_STATE_CANCELLED; |
| } |
| |
| public void cancel() { |
| mEditorState = EDITOR_STATE_CANCELLED; |
| mController.cancel(); |
| updateUi(); |
| } |
| |
| public boolean isDone() { |
| return isPrintConfirmed() || isPreviewConfirmed() || isCancelled(); |
| } |
| |
| public boolean isPrintConfirmed() { |
| return mEditorState == EDITOR_STATE_CONFIRMED_PRINT; |
| } |
| |
| public void confirmPrint() { |
| mEditorState = EDITOR_STATE_CONFIRMED_PRINT; |
| } |
| |
| public boolean isPreviewConfirmed() { |
| return mEditorState == EDITOR_STATE_CONFIRMED_PRINT; |
| } |
| |
| public void confirmPreview() { |
| mEditorState = EDITOR_STATE_CONFIRMED_PREVIEW; |
| } |
| |
| public PageRange[] getRequestedPages() { |
| if (hasErrors()) { |
| return null; |
| } |
| if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) { |
| List<PageRange> pageRanges = new ArrayList<PageRange>(); |
| mStringCommaSplitter.setString(mRangeEditText.getText().toString()); |
| |
| while (mStringCommaSplitter.hasNext()) { |
| String range = mStringCommaSplitter.next().trim(); |
| final int dashIndex = range.indexOf('-'); |
| final int fromIndex; |
| final int toIndex; |
| |
| if (dashIndex > 0) { |
| fromIndex = Integer.parseInt(range.substring(0, dashIndex)) - 1; |
| toIndex = Integer.parseInt(range.substring( |
| dashIndex + 1, range.length())) - 1; |
| } else { |
| fromIndex = toIndex = Integer.parseInt(range); |
| } |
| |
| PageRange pageRange = new PageRange(fromIndex, toIndex); |
| pageRanges.add(pageRange); |
| } |
| |
| PageRange[] pageRangesArray = new PageRange[pageRanges.size()]; |
| pageRanges.toArray(pageRangesArray); |
| |
| return PageRangeUtils.normalize(pageRangesArray); |
| } |
| |
| return ALL_PAGES_ARRAY; |
| } |
| |
| public void updateUi() { |
| if (isPrintConfirmed() || isPreviewConfirmed() || isCancelled()) { |
| mDestinationSpinner.setEnabled(false); |
| mCopiesEditText.setEnabled(false); |
| mMediaSizeSpinner.setEnabled(false); |
| mColorModeSpinner.setEnabled(false); |
| mOrientationSpinner.setEnabled(false); |
| mRangeOptionsSpinner.setEnabled(false); |
| mRangeEditText.setEnabled(false); |
| mPrintPreviewButton.setEnabled(false); |
| mPrintButton.setEnabled(false); |
| return; |
| } |
| |
| final int selectedIndex = mDestinationSpinner.getSelectedItemPosition(); |
| |
| if (selectedIndex < 0 || !mDestinationSpinnerAdapter.getItem( |
| selectedIndex).value.hasAllRequiredAttributes()) { |
| |
| // Destination |
| mDestinationSpinner.setEnabled(false); |
| |
| mCopiesEditText.removeTextChangedListener(mCopiesTextWatcher); |
| mCopiesEditText.setText(String.valueOf(MIN_COPIES)); |
| mCopiesEditText.addTextChangedListener(mCopiesTextWatcher); |
| mCopiesEditText.setEnabled(false); |
| |
| // Media size |
| mMediaSizeSpinner.setOnItemSelectedListener(null); |
| mMediaSizeSpinner.setSelection(AdapterView.INVALID_POSITION); |
| mMediaSizeSpinner.setEnabled(false); |
| |
| // Color mode |
| mColorModeSpinner.setOnItemSelectedListener(null); |
| mColorModeSpinner.setSelection(AdapterView.INVALID_POSITION); |
| mColorModeSpinner.setEnabled(false); |
| |
| // Orientation |
| mOrientationSpinner.setOnItemSelectedListener(null); |
| mOrientationSpinner.setSelection(AdapterView.INVALID_POSITION); |
| mOrientationSpinner.setEnabled(false); |
| |
| // Range |
| mRangeOptionsSpinner.setOnItemSelectedListener(null); |
| mRangeOptionsSpinner.setSelection(0); |
| mRangeOptionsSpinner.setEnabled(false); |
| mRangeTitle.setText(getString(R.string.label_pages, |
| getString(R.string.page_count_unknown))); |
| mRangeEditText.removeTextChangedListener(mRangeTextWatcher); |
| mRangeEditText.setText(""); |
| mRangeEditText.addTextChangedListener(mRangeTextWatcher); |
| mRangeEditText.setEnabled(false); |
| mRangeEditText.setVisibility(View.INVISIBLE); |
| |
| // Print preview |
| mPrintPreviewButton.setEnabled(false); |
| mPrintPreviewButton.setText(getString(R.string.print_preview)); |
| |
| // Print |
| mPrintButton.setEnabled(false); |
| } else { |
| PrintAttributes defaultAttributes = mTempPrintAttributes; |
| PrinterInfo printer = mDestinationSpinnerAdapter.getItem(selectedIndex).value; |
| printer.getDefaults(defaultAttributes); |
| |
| // Destination |
| mDestinationSpinner.setEnabled(true); |
| |
| // Copies |
| mCopiesEditText.setEnabled(true); |
| |
| // Media size. |
| List<MediaSize> mediaSizes = printer.getMediaSizes(); |
| 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) { |
| mMediaSizeSpinnerAdapter.clear(); |
| for (int i = 0; i < mediaSizeCount; i++) { |
| MediaSize mediaSize = mediaSizes.get(i); |
| mMediaSizeSpinnerAdapter.add(new SpinnerItem<MediaSize>( |
| mediaSize, mediaSize.getLabel())); |
| } |
| if (mediaSizeCount > 0) { |
| mMediaSizeSpinner.setEnabled(true); |
| final int selectedMediaSizeIndex = Math.max(mediaSizes.indexOf( |
| defaultAttributes.getMediaSize()), 0); |
| mMediaSizeSpinner.setOnItemSelectedListener(null); |
| mMediaSizeSpinner.setSelection(selectedMediaSizeIndex); |
| } |
| } |
| |
| // Color mode. |
| final int colorModes = printer.getColorModes(); |
| boolean colorModesChanged = false; |
| if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) { |
| colorModesChanged = true; |
| } else { |
| int remainingColorModes = colorModes; |
| while (remainingColorModes != 0) { |
| final int colorBitOffset = Integer.numberOfTrailingZeros( |
| remainingColorModes); |
| final int colorMode = 1 << colorBitOffset; |
| remainingColorModes &= ~colorMode; |
| if (colorMode != mColorModeSpinnerAdapter.getItem(colorBitOffset).value) { |
| colorModesChanged = true; |
| break; |
| } |
| } |
| } |
| if (colorModesChanged) { |
| 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; |
| remainingColorModes &= ~colorMode; |
| mColorModeSpinnerAdapter.add(new SpinnerItem<Integer>(colorMode, |
| colorModeLabels[colorBitOffset])); |
| } |
| if (colorModes > 0) { |
| mColorModeSpinner.setEnabled(true); |
| final int selectedColorModeIndex = Integer.numberOfTrailingZeros( |
| (colorModes & defaultAttributes.getColorMode())); |
| mColorModeSpinner.setOnItemSelectedListener(null); |
| mColorModeSpinner.setSelection(selectedColorModeIndex); |
| } |
| } |
| |
| // Orientation. |
| final int orientations = printer.getOrientations(); |
| boolean orientationsChanged = false; |
| if (Integer.bitCount(orientations) != mOrientationSpinnerAdapter.getCount()) { |
| orientationsChanged = true; |
| } else { |
| int remainingOrientations = orientations; |
| while (remainingOrientations != 0) { |
| final int orientationBitOffset = Integer.numberOfTrailingZeros( |
| remainingOrientations); |
| final int orientation = 1 << orientationBitOffset; |
| remainingOrientations &= ~orientation; |
| if (orientation != mOrientationSpinnerAdapter.getItem( |
| orientationBitOffset).value) { |
| orientationsChanged = true; |
| break; |
| } |
| } |
| } |
| if (orientationsChanged) { |
| mOrientationSpinnerAdapter.clear(); |
| String[] orientationLabels = getResources().getStringArray( |
| R.array.orientation_labels); |
| int remainingOrientations = orientations; |
| while (remainingOrientations != 0) { |
| final int orientationBitOffset = Integer.numberOfTrailingZeros( |
| remainingOrientations); |
| final int orientation = 1 << orientationBitOffset; |
| remainingOrientations &= ~orientation; |
| mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>(orientation, |
| orientationLabels[orientationBitOffset])); |
| } |
| if (orientations > 0) { |
| mOrientationSpinner.setEnabled(true); |
| final int selectedOrientationIndex = Integer.numberOfTrailingZeros( |
| (orientations & defaultAttributes.getOrientation())); |
| mOrientationSpinner.setOnItemSelectedListener(null); |
| mOrientationSpinner.setSelection(selectedOrientationIndex); |
| } |
| } |
| |
| // Range options |
| PrintDocumentInfo info = mDocument.info; |
| if (info != null && (info.getPageCount() > 1 |
| || info.getPageCount() == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)) { |
| mRangeOptionsSpinner.setEnabled(true); |
| if (mRangeOptionsSpinner.getSelectedItemPosition() > 0 |
| && !mRangeEditText.isEnabled()) { |
| mRangeEditText.setEnabled(true); |
| mRangeEditText.setVisibility(View.VISIBLE); |
| mRangeEditText.requestFocus(); |
| InputMethodManager imm = (InputMethodManager) |
| getSystemService(INPUT_METHOD_SERVICE); |
| imm.showSoftInput(mRangeEditText, 0); |
| } |
| final int pageCount = mDocument.info.getPageCount(); |
| mRangeTitle.setText(getString(R.string.label_pages, |
| (pageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) |
| ? getString(R.string.page_count_unknown) |
| : String.valueOf(pageCount))); |
| } else { |
| mRangeOptionsSpinner.setOnItemSelectedListener(null); |
| mRangeOptionsSpinner.setSelection(0); |
| mRangeOptionsSpinner.setEnabled(false); |
| mRangeTitle.setText(getString(R.string.label_pages, |
| getString(R.string.page_count_unknown))); |
| mRangeEditText.setEnabled(false); |
| mRangeEditText.setVisibility(View.INVISIBLE); |
| } |
| |
| // Print/Print preview |
| if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1 |
| && (TextUtils.isEmpty(mRangeEditText.getText()) || hasErrors())) |
| || (mRangeOptionsSpinner.getSelectedItemPosition() == 0 |
| && (!mController.hasPerformedLayout() || hasErrors()))) { |
| mPrintPreviewButton.setEnabled(false); |
| mPrintButton.setEnabled(false); |
| } else { |
| mPrintPreviewButton.setEnabled(true); |
| if (hasPdfViewer()) { |
| mPrintPreviewButton.setText(getString(R.string.print_preview)); |
| } else { |
| mPrintPreviewButton.setText(getString(R.string.install_for_print_preview)); |
| } |
| mPrintButton.setEnabled(true); |
| } |
| |
| // Copies |
| if (mCopiesEditText.getError() == null |
| && TextUtils.isEmpty(mCopiesEditText.getText())) { |
| mCopiesEditText.setText(String.valueOf(MIN_COPIES)); |
| mCopiesEditText.selectAll(); |
| mCopiesEditText.requestFocus(); |
| } |
| } |
| |
| // Here is some voodoo to circumvent the weird behavior of AdapterView |
| // in which a selection listener may get a callback for an event that |
| // happened before the listener was registered. The reason for that is |
| // that the selection change is handled on the next layout pass. |
| Choreographer.getInstance().postCallbackDelayed(Choreographer.CALLBACK_TRAVERSAL, |
| new Runnable() { |
| @Override |
| public void run() { |
| mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener); |
| } |
| }, null, Choreographer.getFrameDelay() * 2); |
| } |
| |
| public void addPrinters(List<PrinterInfo> addedPrinters) { |
| final int addedPrinterCount = addedPrinters.size(); |
| for (int i = 0; i < addedPrinterCount; i++) { |
| PrinterInfo addedPrinter = addedPrinters.get(i); |
| boolean duplicate = false; |
| final int existingPrinterCount = mDestinationSpinnerAdapter.getCount(); |
| for (int j = 0; j < existingPrinterCount; j++) { |
| PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value; |
| if (addedPrinter.getId().equals(existingPrinter.getId())) { |
| duplicate = true; |
| break; |
| } |
| } |
| if (!duplicate) { |
| mDestinationSpinnerAdapter.add(new SpinnerItem<PrinterInfo>( |
| addedPrinter, addedPrinter.getLabel())); |
| } else { |
| Log.w(LOG_TAG, "Skipping a duplicate printer: " + addedPrinter); |
| } |
| } |
| |
| if (mDestinationSpinner.getSelectedItemPosition() == AdapterView.INVALID_POSITION |
| && mDestinationSpinnerAdapter.getCount() > 0) { |
| mDestinationSpinner.setSelection(0); |
| } |
| } |
| |
| public void removePrinters(List<PrinterId> pritnerIds) { |
| final int printerIdCount = pritnerIds.size(); |
| for (int i = 0; i < printerIdCount; i++) { |
| PrinterId removedPrinterId = pritnerIds.get(i); |
| boolean removed = false; |
| final int existingPrinterCount = mDestinationSpinnerAdapter.getCount(); |
| for (int j = 0; j < existingPrinterCount; j++) { |
| PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value; |
| if (removedPrinterId.equals(existingPrinter.getId())) { |
| mDestinationSpinnerAdapter.remove(mDestinationSpinnerAdapter.getItem(j)); |
| removed = true; |
| break; |
| } |
| } |
| if (!removed) { |
| Log.w(LOG_TAG, "Ignoring not added printer with id: " + removedPrinterId); |
| } |
| } |
| |
| if (mDestinationSpinner.getSelectedItemPosition() != AdapterView.INVALID_POSITION |
| && mDestinationSpinnerAdapter.getCount() == 0) { |
| mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| public void updatePrinters(List<PrinterInfo> pritners) { |
| SpinnerItem<PrinterInfo> selectedItem = |
| (SpinnerItem<PrinterInfo>) mDestinationSpinner.getSelectedItem(); |
| PrinterId selectedPrinterId = (selectedItem != null) |
| ? selectedItem.value.getId() : null; |
| |
| boolean updated = false; |
| |
| final int printerCount = pritners.size(); |
| for (int i = 0; i < printerCount; i++) { |
| PrinterInfo updatedPrinter = pritners.get(i); |
| final int existingPrinterCount = mDestinationSpinnerAdapter.getCount(); |
| for (int j = 0; j < existingPrinterCount; j++) { |
| PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value; |
| if (updatedPrinter.getId().equals(existingPrinter.getId())) { |
| existingPrinter.copyFrom(updatedPrinter); |
| updated = true; |
| if (selectedPrinterId != null |
| && selectedPrinterId.equals(updatedPrinter.getId())) { |
| // The selected printer was updated. We simulate a fake |
| // selection to reuse the normal printer change handling. |
| mOnItemSelectedListener.onItemSelected(mDestinationSpinner, |
| mDestinationSpinner.getSelectedView(), |
| mDestinationSpinner.getSelectedItemPosition(), |
| mDestinationSpinner.getSelectedItemId()); |
| // TODO: This will reset the UI to the defaults for the |
| // printer. We may need to revisit this. |
| |
| } |
| break; |
| } |
| } |
| } |
| if (updated) { |
| mDestinationSpinnerAdapter.notifyDataSetChanged(); |
| } |
| } |
| |
| private boolean hasErrors() { |
| return mRangeEditText.getError() != null |
| || mCopiesEditText.getError() != null; |
| } |
| |
| private boolean hasPdfViewer() { |
| Intent intent = new Intent(Intent.ACTION_VIEW); |
| intent.setType("application/pdf"); |
| return !getPackageManager().queryIntentActivities(intent, |
| PackageManager.MATCH_DEFAULT_ONLY).isEmpty(); |
| } |
| |
| 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 DestinationAdapter extends ArrayAdapter<SpinnerItem<PrinterInfo>> { |
| |
| public DestinationAdapter() { |
| super( PrintJobConfigActivity.this, R.layout.spinner_dropdown_item); |
| } |
| |
| @Override |
| public View getDropDownView(int position, View convertView, |
| ViewGroup parent) { |
| return getView(position, convertView, parent); |
| } |
| |
| @Override |
| public View getView(int position, View convertView, ViewGroup parent) { |
| if (convertView == null) { |
| convertView = getLayoutInflater().inflate( |
| R.layout.spinner_dropdown_item, parent, false); |
| } |
| |
| PrinterInfo printerInfo = getItem(position).value; |
| TextView title = (TextView) convertView.findViewById(R.id.title); |
| title.setText(printerInfo.getLabel()); |
| |
| try { |
| TextView subtitle = (TextView) |
| convertView.findViewById(R.id.subtitle); |
| PackageManager pm = getPackageManager(); |
| PackageInfo packageInfo = pm.getPackageInfo( |
| printerInfo.getId().getService().getPackageName(), 0); |
| subtitle.setText(packageInfo.applicationInfo.loadLabel(pm)); |
| subtitle.setVisibility(View.VISIBLE); |
| } catch (NameNotFoundException nnfe) { |
| /* ignore */ |
| } |
| |
| return convertView; |
| } |
| } |
| } |
| |
| private static final class PrinterDiscoveryObserver extends IPrinterDiscoveryObserver.Stub { |
| private static final int MSG_ON_PRINTERS_ADDED = 1; |
| private static final int MSG_ON_PRINTERS_REMOVED = 2; |
| private static final int MSG_ON_PRINTERS_UPDATED = 3; |
| |
| private Handler mHandler; |
| private Editor mEditor; |
| |
| @SuppressWarnings("unchecked") |
| public PrinterDiscoveryObserver(Editor editor, Looper looper) { |
| mEditor = editor; |
| mHandler = new Handler(looper, null, true) { |
| @Override |
| public void handleMessage(Message message) { |
| switch (message.what) { |
| case MSG_ON_PRINTERS_ADDED: { |
| List<PrinterInfo> printers = (List<PrinterInfo>) message.obj; |
| mEditor.addPrinters(printers); |
| } break; |
| |
| case MSG_ON_PRINTERS_REMOVED: { |
| List<PrinterId> printerIds = (List<PrinterId>) message.obj; |
| mEditor.removePrinters(printerIds); |
| } break; |
| |
| case MSG_ON_PRINTERS_UPDATED: { |
| List<PrinterInfo> printers = (List<PrinterInfo>) message.obj; |
| mEditor.updatePrinters(printers); |
| } break; |
| } |
| } |
| }; |
| } |
| |
| @Override |
| public void onPrintersAdded(List<PrinterInfo> printers) { |
| synchronized (this) { |
| if (mHandler != null) { |
| mHandler.obtainMessage(MSG_ON_PRINTERS_ADDED, printers) |
| .sendToTarget(); |
| } |
| } |
| } |
| |
| @Override |
| public void onPrintersRemoved(List<PrinterId> printers) { |
| synchronized (this) { |
| if (mHandler != null) { |
| mHandler.obtainMessage(MSG_ON_PRINTERS_REMOVED, printers) |
| .sendToTarget(); |
| } |
| } |
| } |
| |
| @Override |
| public void onPrintersUpdated(List<PrinterInfo> printers) { |
| synchronized (this) { |
| if (mHandler != null) { |
| mHandler.obtainMessage(MSG_ON_PRINTERS_UPDATED, printers) |
| .sendToTarget(); |
| } |
| } |
| } |
| |
| public void destroy() { |
| synchronized (this) { |
| mHandler = null; |
| mEditor = null; |
| } |
| } |
| } |
| |
| /** |
| * 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[] ourPageRanges, PageRange[] otherPageRanges) { |
| if (ourPageRanges == null || otherPageRanges == null) { |
| return false; |
| } |
| |
| otherPageRanges = normalize(otherPageRanges); |
| |
| int otherPageIdx = 0; |
| final int myPageCount = ourPageRanges.length; |
| final int otherPageCount = otherPageRanges.length; |
| for (int i= 0; i < myPageCount; i++) { |
| PageRange myPage = ourPageRanges[i]; |
| for (; otherPageIdx < otherPageCount; otherPageIdx++) { |
| PageRange otherPage = otherPageRanges[otherPageIdx]; |
| if (otherPage.getStart() > myPage.getStart()) { |
| break; |
| } |
| if ((otherPage.getStart() < myPage.getStart() |
| && otherPage.getEnd() > myPage.getStart()) |
| || (otherPage.getEnd() > myPage.getEnd() |
| && otherPage.getStart() < myPage.getEnd()) |
| || (otherPage.getEnd() < myPage.getStart())) { |
| return false; |
| } |
| } |
| } |
| if (otherPageIdx < otherPageCount) { |
| return false; |
| } |
| return true; |
| } |
| |
| public static PageRange[] normalize(PageRange[] pageRanges) { |
| if (pageRanges == null) { |
| return null; |
| } |
| final int oldPageCount = pageRanges.length; |
| if (oldPageCount <= 1) { |
| return pageRanges; |
| } |
| Arrays.sort(pageRanges, sComparator); |
| int newRangeCount = 0; |
| for (int i = 0; i < oldPageCount - 1; i++) { |
| newRangeCount++; |
| PageRange currentRange = pageRanges[i]; |
| PageRange nextRange = pageRanges[i + 1]; |
| if (currentRange.getEnd() >= nextRange.getStart()) { |
| newRangeCount--; |
| pageRanges[i] = null; |
| pageRanges[i + 1] = new PageRange(currentRange.getStart(), |
| nextRange.getEnd()); |
| } |
| } |
| if (newRangeCount == oldPageCount) { |
| return pageRanges; |
| } |
| return Arrays.copyOfRange(pageRanges, oldPageCount - newRangeCount, |
| oldPageCount - 1); |
| } |
| |
| public static void offsetStart(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); |
| } |
| } |
| } |
| } |