Initially we show the print dialog and when the user presses print we show a generating dialog with an indefinite spinner and a cancel button. The transition between the two UIs which are really different layouts show in the print activity is animated. In the middle of the animation from print to generating UI there was a jump of the content and an undesired window animation kicking in. This is a side effect of changing the activity to floating so now changing the container size was causing window resize and hence animation. Fun! bug:10983508 Change-Id: I7d88e073c55863b945cdb50822401592f32d44c3
2488 lines
104 KiB
Java
2488 lines
104 KiB
Java
/*
|
|
* 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.Context;
|
|
import android.content.Intent;
|
|
import android.content.Loader;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.PackageManager.NameNotFoundException;
|
|
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.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.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.ViewGroup.LayoutParams;
|
|
import android.view.ViewConfiguration;
|
|
import android.view.ViewGroup;
|
|
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 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;
|
|
|
|
import libcore.io.IoUtils;
|
|
|
|
/**
|
|
* 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 EXTRA_PRINT_DOCUMENT_ADAPTER = "printDocumentAdapter";
|
|
public static final String EXTRA_PRINT_JOB = "printJob";
|
|
|
|
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 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;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle bundle) {
|
|
super.onCreate(bundle);
|
|
|
|
setTitle(R.string.print_dialog);
|
|
|
|
Bundle extras = getIntent().getExtras();
|
|
|
|
PrintJobInfo printJob = extras.getParcelable(EXTRA_PRINT_JOB);
|
|
if (printJob == null) {
|
|
throw new IllegalArgumentException("printJob cannot be null");
|
|
}
|
|
|
|
mPrintJobId = printJob.getId();
|
|
mIPrintDocumentAdapter = extras.getBinder(EXTRA_PRINT_DOCUMENT_ADAPTER);
|
|
if (mIPrintDocumentAdapter == null) {
|
|
throw new IllegalArgumentException("PrintDocumentAdapter cannot be null");
|
|
}
|
|
|
|
PrintAttributes attributes = printJob.getAttributes();
|
|
if (attributes != null) {
|
|
mCurrPrintAttributes.copyFrom(attributes);
|
|
}
|
|
|
|
setContentView(R.layout.print_job_config_activity_container);
|
|
|
|
mDocument = new Document();
|
|
mController = new PrintController(new RemotePrintDocumentAdapter(
|
|
IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
|
|
PrintSpoolerService.peekInstance().generateFileForPrintJob(mPrintJobId)));
|
|
mEditor = new Editor();
|
|
|
|
try {
|
|
mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
|
|
} catch (RemoteException re) {
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
mController.initialize();
|
|
mEditor.initialize();
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
mEditor.refreshCurrentPrinter();
|
|
}
|
|
|
|
@Override
|
|
protected void onDestroy() {
|
|
// We can safely do the work in here since at this point
|
|
// the system is bound to our (spooler) process which
|
|
// guarantees that this process will not be killed.
|
|
if (mController.hasStarted()) {
|
|
mController.finish();
|
|
}
|
|
if (mEditor.isPrintConfirmed() && mController.isFinished()) {
|
|
PrintSpoolerService.peekInstance().setPrintJobState(mPrintJobId,
|
|
PrintJobInfo.STATE_QUEUED, null);
|
|
} else {
|
|
PrintSpoolerService.peekInstance().setPrintJobState(mPrintJobId,
|
|
PrintJobInfo.STATE_CANCELED, null);
|
|
}
|
|
mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
|
|
if (mGeneratingPrintJobDialog != null) {
|
|
mGeneratingPrintJobDialog.dismiss();
|
|
mGeneratingPrintJobDialog = null;
|
|
}
|
|
super.onDestroy();
|
|
}
|
|
|
|
public boolean onTouchEvent(MotionEvent event) {
|
|
if (!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 (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() {
|
|
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 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 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 {
|
|
PrintSpoolerService.peekInstance().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;
|
|
}
|
|
|
|
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.
|
|
PrintSpoolerService.peekInstance().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;
|
|
PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(
|
|
mPrintJobId, 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 || 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 and we already have
|
|
// the requested pages, then nothing else to do.
|
|
if (!infoChanged && !layoutChanged
|
|
&& PageRangeUtils.contains(mDocument.pages, 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.
|
|
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(CharSequence error, int sequence) {
|
|
if (mRequestCounter.get() != sequence) {
|
|
return;
|
|
}
|
|
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, 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 = PrintSpoolerService.peekInstance()
|
|
.generateFileForPrintJob(mPrintJobId);
|
|
mDocument.info.setDataSize(file.length());
|
|
|
|
// Update the print job with the updated info.
|
|
PrintSpoolerService.peekInstance().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.
|
|
PrintSpoolerService.peekInstance().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.
|
|
PrintSpoolerService.peekInstance().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);
|
|
PrintSpoolerService.peekInstance().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.
|
|
PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
|
|
writtenPages);
|
|
} 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");
|
|
mEditor.cancel();
|
|
PrintJobConfigActivity.this.finish();
|
|
}
|
|
}
|
|
|
|
private void requestCreatePdfFileOrFinish() {
|
|
if (mEditor.isPrintingToPdf()) {
|
|
PrintJobInfo printJob = PrintSpoolerService.peekInstance()
|
|
.getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
|
|
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
|
intent.setType("application/pdf");
|
|
intent.putExtra(Intent.EXTRA_TITLE, printJob.getLabel());
|
|
startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
|
|
} else {
|
|
PrintJobConfigActivity.this.finish();
|
|
}
|
|
}
|
|
|
|
private void handleOnWriteFailed(CharSequence error, int sequence) {
|
|
if (mRequestCounter.get() != sequence) {
|
|
return;
|
|
}
|
|
mControllerState = CONTROLLER_STATE_FAILED;
|
|
Log.e(LOG_TAG, "Error during write: " + error);
|
|
PrintJobConfigActivity.this.finish();
|
|
}
|
|
|
|
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.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;
|
|
}
|
|
}
|
|
|
|
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 = PrintSpoolerService.peekInstance()
|
|
.getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
|
|
if (printJob == null) {
|
|
return null;
|
|
}
|
|
File file = PrintSpoolerService.peekInstance()
|
|
.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 EditText mCopiesEditText;
|
|
|
|
private TextView mRangeOptionsTitle;
|
|
private TextView mPageRangeTitle;
|
|
private EditText mPageRangeEditText;
|
|
|
|
private Spinner mDestinationSpinner;
|
|
private final DestinationAdapter mDestinationSpinnerAdapter;
|
|
|
|
private Spinner mMediaSizeSpinner;
|
|
private final ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
|
|
|
|
private Spinner mColorModeSpinner;
|
|
private final ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
|
|
|
|
private Spinner mOrientationSpinner;
|
|
private final ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
|
|
|
|
private Spinner mRangeOptionsSpinner;
|
|
private final ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
|
|
|
|
private final SimpleStringSplitter mStringCommaSplitter =
|
|
new SimpleStringSplitter(',');
|
|
|
|
private View mContentContainer;
|
|
|
|
private Button mPrintButton;
|
|
|
|
private PrinterId mNextPrinterId;
|
|
|
|
private PrinterInfo mCurrentPrinter;
|
|
|
|
private final MediaSizeComparator mMediaSizeComparator;
|
|
|
|
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 (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) {
|
|
startSelectPrinterActivity();
|
|
return;
|
|
}
|
|
|
|
mCapabilitiesTimeout.remove();
|
|
|
|
mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter
|
|
.getItem(position);
|
|
|
|
PrintSpoolerService.peekInstance().setPrintJobPrinterNoPersistence(
|
|
mPrintJobId, mCurrentPrinter);
|
|
|
|
if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
|
|
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 (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 (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);
|
|
PrintSpoolerService.peekInstance().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.
|
|
|
|
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 int mCurrentUi = UI_NONE;
|
|
|
|
private boolean mFavoritePrinterSelected;
|
|
|
|
public Editor() {
|
|
// 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() > 2) {
|
|
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 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
|
|
&& !mCapabilitiesTimeout.isPosted()) {
|
|
mCapabilitiesTimeout.post();
|
|
refreshCurrentPrinter();
|
|
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) {
|
|
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 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 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;
|
|
}
|
|
|
|
switch (mCurrentUi) {
|
|
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;
|
|
}
|
|
} 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;
|
|
}
|
|
} break;
|
|
}
|
|
|
|
mCurrentUi = ui;
|
|
}
|
|
|
|
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 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 postAnimateCommand,
|
|
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).withLayer()
|
|
.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);
|
|
|
|
// Third animation - show the new content.
|
|
AutoCancellingAnimator.animate(showingView).withLayer().alpha(1.0f)
|
|
.withEndAction(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
postAnimateCommand.run();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
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.setText(MIN_COPIES_STRING);
|
|
mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
|
|
if (!TextUtils.equals(mCopiesEditText.getText(), MIN_COPIES_STRING)) {
|
|
mIgnoreNextCopiesChange = true;
|
|
}
|
|
PrintSpoolerService.peekInstance().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 && mController.hasStarted()) {
|
|
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.addTextChangedListener(mRangeTextWatcher);
|
|
|
|
// 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);
|
|
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);
|
|
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 selectedColorModeIndex = Integer.numberOfTrailingZeros(
|
|
(colorModes & defaultAttributes.getColorMode()));
|
|
setColorModeSpinnerSelectionNoCallback(selectedColorModeIndex);
|
|
mCurrPrintAttributes.setColorMode(mColorModeSpinnerAdapter
|
|
.getItem(selectedColorModeIndex).value);
|
|
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);
|
|
}
|
|
|
|
// Print/Print preview
|
|
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 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.title);
|
|
String title = getString(R.string.printer_unavailable,
|
|
mCurrentPrinter.getName());
|
|
titleView.setText(title);
|
|
}
|
|
}
|
|
}
|
|
|
|
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() {
|
|
final int additionalItemCount = (mFakePdfPrinter != null) ? 2 : 1;
|
|
return Math.min(mPrinters.size() + additionalItemCount,
|
|
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 (position == 0 && mFakePdfPrinter != null) {
|
|
return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
|
|
}
|
|
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);
|
|
}
|
|
|
|
convertView.getLayoutParams().width = mDestinationSpinner.getWidth();
|
|
|
|
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.GONE);
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|