summaryrefslogtreecommitdiffstats
path: root/packages/PrintSpooler/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/PrintSpooler/src')
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java794
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java734
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java187
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java219
4 files changed, 1934 insertions, 0 deletions
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
new file mode 100644
index 0000000..ae2fe5c
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -0,0 +1,794 @@
+/*
+ * 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.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.IBinder.DeathRecipient;
+import android.print.IPrintAdapter;
+import android.print.IPrintManager;
+import android.print.IPrinterDiscoveryObserver;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
+import android.print.PrintAttributes.Tray;
+import android.print.PrintJobInfo;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.Spinner;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Activity for configuring a print job.
+ */
+public class PrintJobConfigActivity extends Activity {
+
+ private static final boolean DEBUG = false;
+
+ private static final String LOG_TAG = PrintJobConfigActivity.class.getSimpleName();
+
+ public static final String EXTRA_PRINTABLE = "printable";
+ public static final String EXTRA_APP_ID = "appId";
+ public static final String EXTRA_ATTRIBUTES = "attributes";
+ public static final String EXTRA_PRINT_JOB_ID = "printJobId";
+
+ private static final int MIN_COPIES = 1;
+
+ private final List<QueuedAsyncTask<?>> mTaskQueue = new ArrayList<QueuedAsyncTask<?>>();
+
+ private IPrintManager mPrintManager;
+
+ private IPrinterDiscoveryObserver mPrinterDiscoveryObserver;
+
+ private int mAppId;
+ private int mPrintJobId;
+
+ private PrintAttributes mPrintAttributes;
+
+ private final PrintSpooler mPrintSpooler = PrintSpooler.getInstance(this);
+
+ private RemotePrintAdapter mRemotePrintAdapter;
+
+ // UI elements
+
+ private EditText mCopiesEditText;
+
+ private Spinner mDestinationSpinner;
+ public ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter;
+
+ private Spinner mMediaSizeSpinner;
+ public ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
+
+ private Spinner mResolutionSpinner;
+ public ArrayAdapter<SpinnerItem<Resolution>> mResolutionSpinnerAdapter;
+
+ private Spinner mInputTraySpinner;
+ public ArrayAdapter<SpinnerItem<Tray>> mInputTraySpinnerAdapter;
+
+ private Spinner mOutputTraySpinner;
+ public ArrayAdapter<SpinnerItem<Tray>> mOutputTraySpinnerAdapter;
+
+ private Spinner mDuplexModeSpinner;
+ public ArrayAdapter<SpinnerItem<Integer>> mDuplexModeSpinnerAdapter;
+
+ private Spinner mColorModeSpinner;
+ public ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
+
+ private Spinner mFittingModeSpinner;
+ public ArrayAdapter<SpinnerItem<Integer>> mFittingModeSpinnerAdapter;
+
+ private Spinner mOrientationSpinner;
+ public ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
+
+ private boolean mPrintStarted;
+
+ private boolean mPrintConfirmed;
+
+ private IBinder mPrinable;
+
+ // TODO: Implement store/restore state.
+
+ private final OnItemSelectedListener mOnItemSelectedListener =
+ new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
+ if (spinner == mDestinationSpinner) {
+ updateUi();
+ notifyPrintableStartIfNeeded();
+ } else if (spinner == mMediaSizeSpinner) {
+ SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
+ mPrintAttributes.setMediaSize(mediaItem.value);
+ updatePrintableContentIfNeeded();
+ } else if (spinner == mResolutionSpinner) {
+ SpinnerItem<Resolution> resolutionItem =
+ mResolutionSpinnerAdapter.getItem(position);
+ mPrintAttributes.setResolution(resolutionItem.value);
+ updatePrintableContentIfNeeded();
+ } else if (spinner == mInputTraySpinner) {
+ SpinnerItem<Tray> inputTrayItem =
+ mInputTraySpinnerAdapter.getItem(position);
+ mPrintAttributes.setInputTray(inputTrayItem.value);
+ } else if (spinner == mOutputTraySpinner) {
+ SpinnerItem<Tray> outputTrayItem =
+ mOutputTraySpinnerAdapter.getItem(position);
+ mPrintAttributes.setOutputTray(outputTrayItem.value);
+ } else if (spinner == mDuplexModeSpinner) {
+ SpinnerItem<Integer> duplexModeItem =
+ mDuplexModeSpinnerAdapter.getItem(position);
+ mPrintAttributes.setDuplexMode(duplexModeItem.value);
+ } else if (spinner == mColorModeSpinner) {
+ SpinnerItem<Integer> colorModeItem =
+ mColorModeSpinnerAdapter.getItem(position);
+ mPrintAttributes.setColorMode(colorModeItem.value);
+ } else if (spinner == mFittingModeSpinner) {
+ SpinnerItem<Integer> fittingModeItem =
+ mFittingModeSpinnerAdapter.getItem(position);
+ mPrintAttributes.setFittingMode(fittingModeItem.value);
+ } else if (spinner == mOrientationSpinner) {
+ SpinnerItem<Integer> orientationItem =
+ mOrientationSpinnerAdapter.getItem(position);
+ mPrintAttributes.setOrientation(orientationItem.value);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ /* do nothing*/
+ }
+ };
+
+ private final TextWatcher mTextWatcher = new TextWatcher() {
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ final int copies = Integer.parseInt(mCopiesEditText.getText().toString());
+ mPrintAttributes.setCopies(copies);
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ /* do nothing */
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ /* do nothing */
+ }
+ };
+
+ private final InputFilter mInputFilter = new InputFilter() {
+ @Override
+ public CharSequence filter(CharSequence source, int start, int end,
+ Spanned dest, int dstart, int dend) {
+ StringBuffer text = new StringBuffer(dest.toString());
+ text.replace(dstart, dend, source.subSequence(start, end).toString());
+ if (TextUtils.isEmpty(text)) {
+ return dest;
+ }
+ final int copies = Integer.parseInt(text.toString());
+ if (copies < MIN_COPIES) {
+ return dest;
+ }
+ return null;
+ }
+ };
+
+ private final DeathRecipient mDeathRecipient = new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ finish();
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.print_job_config_activity);
+
+ getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
+ | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+
+ mPrintManager = (IPrintManager) IPrintManager.Stub.asInterface(
+ ServiceManager.getService(PRINT_SERVICE));
+
+ Bundle extras = getIntent().getExtras();
+
+ mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1);
+ if (mPrintJobId < 0) {
+ throw new IllegalArgumentException("Invalid print job id: " + mPrintJobId);
+ }
+
+ mAppId = extras.getInt(EXTRA_APP_ID, -1);
+ if (mAppId < 0) {
+ throw new IllegalArgumentException("Invalid app id: " + mAppId);
+ }
+
+ mPrintAttributes = getIntent().getParcelableExtra(EXTRA_ATTRIBUTES);
+ if (mPrintAttributes == null) {
+ mPrintAttributes = new PrintAttributes.Builder().create();
+ }
+
+ mPrinable = extras.getBinder(EXTRA_PRINTABLE);
+ if (mPrinable == null) {
+ throw new IllegalArgumentException("Printable cannot be null");
+ }
+ mRemotePrintAdapter = new RemotePrintAdapter(IPrintAdapter.Stub.asInterface(mPrinable),
+ mPrintSpooler.generateFileForPrintJob(mPrintJobId));
+
+ try {
+ mPrinable.linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException re) {
+ finish();
+ }
+
+ mPrinterDiscoveryObserver = new PrintDiscoveryObserver(getMainLooper());
+
+ bindUi();
+ }
+
+ @Override
+ protected void onDestroy() {
+ mPrinable.unlinkToDeath(mDeathRecipient, 0);
+ super.onDestroy();
+ }
+
+ private void bindUi() {
+ // Copies
+ mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
+ mCopiesEditText.setText(String.valueOf(MIN_COPIES));
+ mCopiesEditText.addTextChangedListener(mTextWatcher);
+ mCopiesEditText.setFilters(new InputFilter[] {mInputFilter});
+
+ // Destination.
+ mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
+ mDestinationSpinnerAdapter = new ArrayAdapter<SpinnerItem<PrinterInfo>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
+ mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Media size.
+ mMediaSizeSpinner = (Spinner) findViewById(R.id.media_size_spinner);
+ mMediaSizeSpinnerAdapter = new ArrayAdapter<SpinnerItem<MediaSize>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
+ mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Resolution.
+ mResolutionSpinner = (Spinner) findViewById(R.id.resolution_spinner);
+ mResolutionSpinnerAdapter = new ArrayAdapter<SpinnerItem<Resolution>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mResolutionSpinner.setAdapter(mResolutionSpinnerAdapter);
+ mResolutionSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Input tray.
+ mInputTraySpinner = (Spinner) findViewById(R.id.input_tray_spinner);
+ mInputTraySpinnerAdapter = new ArrayAdapter<SpinnerItem<Tray>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mInputTraySpinner.setAdapter(mInputTraySpinnerAdapter);
+
+ // Output tray.
+ mOutputTraySpinner = (Spinner) findViewById(R.id.output_tray_spinner);
+ mOutputTraySpinnerAdapter = new ArrayAdapter<SpinnerItem<Tray>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mOutputTraySpinner.setAdapter(mOutputTraySpinnerAdapter);
+ mOutputTraySpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Duplex mode.
+ mDuplexModeSpinner = (Spinner) findViewById(R.id.duplex_mode_spinner);
+ mDuplexModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mDuplexModeSpinner.setAdapter(mDuplexModeSpinnerAdapter);
+ mDuplexModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Color mode.
+ mColorModeSpinner = (Spinner) findViewById(R.id.color_mode_spinner);
+ mColorModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
+ mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Color mode.
+ mFittingModeSpinner = (Spinner) findViewById(R.id.fitting_mode_spinner);
+ mFittingModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mFittingModeSpinner.setAdapter(mFittingModeSpinnerAdapter);
+ mFittingModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Orientation
+ mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
+ mOrientationSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
+ android.R.layout.simple_spinner_dropdown_item);
+ mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
+ mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ }
+
+ private void updateUi() {
+ final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
+ PrinterInfo printer = mDestinationSpinnerAdapter.getItem(selectedIndex).value;
+ printer.getDefaults(mPrintAttributes);
+
+ // Copies.
+ mCopiesEditText.setText(String.valueOf(
+ Math.max(mPrintAttributes.getCopies(), MIN_COPIES)));
+
+ // Media size.
+ mMediaSizeSpinnerAdapter.clear();
+ List<MediaSize> mediaSizes = printer.getMediaSizes();
+ final int mediaSizeCount = mediaSizes.size();
+ for (int i = 0; i < mediaSizeCount; i++) {
+ MediaSize mediaSize = mediaSizes.get(i);
+ mMediaSizeSpinnerAdapter.add(new SpinnerItem<MediaSize>(
+ mediaSize, mediaSize.getLabel(getPackageManager())));
+ }
+ final int selectedMediaSizeIndex = mediaSizes.indexOf(
+ mPrintAttributes.getMediaSize());
+ mMediaSizeSpinner.setOnItemSelectedListener(null);
+ mMediaSizeSpinner.setSelection(selectedMediaSizeIndex);
+ mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Resolution.
+ mResolutionSpinnerAdapter.clear();
+ List<Resolution> resolutions = printer.getResolutions();
+ final int resolutionCount = resolutions.size();
+ for (int i = 0; i < resolutionCount; i++) {
+ Resolution resolution = resolutions.get(i);
+ mResolutionSpinnerAdapter.add(new SpinnerItem<Resolution>(
+ resolution, resolution.getLabel(getPackageManager())));
+ }
+ final int selectedResolutionIndex = resolutions.indexOf(
+ mPrintAttributes.getResolution());
+ mResolutionSpinner.setOnItemSelectedListener(null);
+ mResolutionSpinner.setSelection(selectedResolutionIndex);
+ mResolutionSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+
+ // Input tray.
+ mInputTraySpinnerAdapter.clear();
+ List<Tray> inputTrays = printer.getInputTrays();
+ final int inputTrayCount = inputTrays.size();
+ for (int i = 0; i < inputTrayCount; i++) {
+ Tray inputTray = inputTrays.get(i);
+ mInputTraySpinnerAdapter.add(new SpinnerItem<Tray>(
+ inputTray, inputTray.getLabel(getPackageManager())));
+ }
+ final int selectedInputTrayIndex = inputTrays.indexOf(
+ mPrintAttributes.getInputTray());
+ mInputTraySpinner.setSelection(selectedInputTrayIndex);
+
+ // Output tray.
+ mOutputTraySpinnerAdapter.clear();
+ List<Tray> outputTrays = printer.getOutputTrays();
+ final int outputTrayCount = outputTrays.size();
+ for (int i = 0; i < outputTrayCount; i++) {
+ Tray outputTray = outputTrays.get(i);
+ mOutputTraySpinnerAdapter.add(new SpinnerItem<Tray>(
+ outputTray, outputTray.getLabel(getPackageManager())));
+ }
+ final int selectedOutputTrayIndex = outputTrays.indexOf(
+ mPrintAttributes.getOutputTray());
+ mOutputTraySpinner.setSelection(selectedOutputTrayIndex);
+
+ // Duplex mode.
+ final int duplexModes = printer.getDuplexModes();
+ mDuplexModeSpinnerAdapter.clear();
+ String[] duplexModeLabels = getResources().getStringArray(
+ R.array.duplex_mode_labels);
+ int remainingDuplexModes = duplexModes;
+ while (remainingDuplexModes != 0) {
+ final int duplexBitOffset = Integer.numberOfTrailingZeros(remainingDuplexModes);
+ final int duplexMode = 1 << duplexBitOffset;
+ remainingDuplexModes &= ~duplexMode;
+ mDuplexModeSpinnerAdapter.add(new SpinnerItem<Integer>(duplexMode,
+ duplexModeLabels[duplexBitOffset]));
+ }
+ final int selectedDuplexModeIndex = Integer.numberOfTrailingZeros(
+ (duplexModes & mPrintAttributes.getDuplexMode()));
+ mDuplexModeSpinner.setSelection(selectedDuplexModeIndex);
+
+ // Color mode.
+ final int colorModes = printer.getColorModes();
+ 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]));
+ }
+ final int selectedColorModeIndex = Integer.numberOfTrailingZeros(
+ (colorModes & mPrintAttributes.getColorMode()));
+ mColorModeSpinner.setSelection(selectedColorModeIndex);
+
+ // Fitting mode.
+ final int fittingModes = printer.getFittingModes();
+ mFittingModeSpinnerAdapter.clear();
+ String[] fittingModeLabels = getResources().getStringArray(
+ R.array.fitting_mode_labels);
+ int remainingFittingModes = fittingModes;
+ while (remainingFittingModes != 0) {
+ final int fittingBitOffset = Integer.numberOfTrailingZeros(remainingFittingModes);
+ final int fittingMode = 1 << fittingBitOffset;
+ remainingFittingModes &= ~fittingMode;
+ mFittingModeSpinnerAdapter.add(new SpinnerItem<Integer>(fittingMode,
+ fittingModeLabels[fittingBitOffset]));
+ }
+ final int selectedFittingModeIndex = Integer.numberOfTrailingZeros(
+ (fittingModes & mPrintAttributes.getFittingMode()));
+ mFittingModeSpinner.setSelection(selectedFittingModeIndex);
+
+ // Orientation.
+ final int orientations = printer.getOrientations();
+ 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]));
+ }
+ final int selectedOrientationIndex = Integer.numberOfTrailingZeros(
+ (orientations & mPrintAttributes.getOrientation()));
+ mOrientationSpinner.setSelection(selectedOrientationIndex);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ try {
+ mPrintManager.startDiscoverPrinters(mPrinterDiscoveryObserver);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error starting printer discovery!", re);
+ }
+ notifyPrintableStartIfNeeded();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ try {
+ mPrintManager.stopDiscoverPrinters();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error starting printer discovery!", re);
+ }
+ notifyPrintableFinishIfNeeded();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.print_job_config_activity, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.print_button) {
+ mPrintConfirmed = true;
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void notifyPrintableStartIfNeeded() {
+ if (mDestinationSpinner.getSelectedItemPosition() < 0
+ || mPrintStarted) {
+ return;
+ }
+ mPrintStarted = true;
+ new QueuedAsyncTask<Void>(mTaskQueue) {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ mRemotePrintAdapter.start();
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error reading printed data!", ioe);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ updatePrintableContentIfNeeded();
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ private void updatePrintableContentIfNeeded() {
+ if (!mPrintStarted) {
+ return;
+ }
+
+ mPrintSpooler.setPrintJobAttributes(mPrintJobId, mPrintAttributes);
+
+ // TODO: Implement page selector.
+ final List<PageRange> pages = new ArrayList<PageRange>();
+ pages.add(PageRange.ALL_PAGES);
+
+ new QueuedAsyncTask<File>(mTaskQueue) {
+ @Override
+ protected File doInBackground(Void... params) {
+ try {
+ mRemotePrintAdapter.printAttributesChanged(mPrintAttributes);
+ mRemotePrintAdapter.cancelPrint();
+ mRemotePrintAdapter.print(pages);
+ return mRemotePrintAdapter.getFile();
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error reading printed data!", ioe);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(File file) {
+ super.onPostExecute(file);
+ updatePrintPreview(file);
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ private void notifyPrintableFinishIfNeeded() {
+ if (!mPrintStarted) {
+ return;
+ }
+ mPrintStarted = false;
+
+ // Cancel all pending async tasks if the activity was canceled.
+ if (!mPrintConfirmed) {
+ final int taskCount = mTaskQueue.size();
+ for (int i = taskCount - 1; i >= 0; i--) {
+ mTaskQueue.remove(i).cancel();
+ }
+ }
+
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ // Notify the app that printing completed.
+ try {
+ mRemotePrintAdapter.finish();
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error reading printed data!", ioe);
+ }
+
+ // If canceled, nothing to do.
+ if (!mPrintConfirmed) {
+ mPrintSpooler.setPrintJobState(mPrintJobId,
+ PrintJobInfo.STATE_CANCELED);
+ return null;
+ }
+
+ // No printer, nothing to do.
+ final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
+ if (selectedIndex < 0) {
+ // Update the print job's status.
+ mPrintSpooler.setPrintJobState(mPrintJobId,
+ PrintJobInfo.STATE_CANCELED);
+ return null;
+ }
+
+ // Update the print job's printer.
+ SpinnerItem<PrinterInfo> printerItem =
+ mDestinationSpinnerAdapter.getItem(selectedIndex);
+ PrinterId printerId = printerItem.value.getId();
+ mPrintSpooler.setPrintJobPrinterId(mPrintJobId, printerId);
+
+ // Update the print job's status.
+ mPrintSpooler.setPrintJobState(mPrintJobId,
+ PrintJobInfo.STATE_QUEUED);
+ return null;
+ }
+
+ // Important: If we are canceling, then we do not wait for the write
+ // to complete since the result will be discarded anyway, we simply
+ // execute the finish immediately which will interrupt the write.
+ }.executeOnExecutor(mPrintConfirmed ? AsyncTask.SERIAL_EXECUTOR
+ : AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+
+ if (DEBUG) {
+ if (mPrintConfirmed) {
+ File file = mRemotePrintAdapter.getFile();
+ if (file.exists()) {
+ new ViewSpooledFileAsyncTask(file).executeOnExecutor(
+ AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+ }
+ }
+ }
+
+ private void updatePrintPreview(File file) {
+ // TODO: Implement
+ }
+
+ private 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);
+ }
+ }
+ }
+
+ private 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);
+ }
+ }
+ }
+
+ private abstract class QueuedAsyncTask<T> extends AsyncTask<Void, Void, T> {
+
+ private final List<QueuedAsyncTask<?>> mPendingOrRunningTasks;
+
+ public QueuedAsyncTask(List<QueuedAsyncTask<?>> pendingOrRunningTasks) {
+ mPendingOrRunningTasks = pendingOrRunningTasks;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mPendingOrRunningTasks.add(this);
+ }
+
+ @Override
+ protected void onPostExecute(T result) {
+ mPendingOrRunningTasks.remove(this);
+ }
+
+ public void cancel() {
+ super.cancel(true);
+ mPendingOrRunningTasks.remove(this);
+ }
+ }
+
+ private final class ViewSpooledFileAsyncTask extends AsyncTask<Void, Void, Void> {
+
+ private final File mFile;
+
+ public ViewSpooledFileAsyncTask(File file) {
+ mFile = file;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ mFile.setExecutable(true, false);
+ mFile.setWritable(true, false);
+ mFile.setReadable(true, false);
+
+ final long identity = Binder.clearCallingIdentity();
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(Uri.fromFile(mFile), "application/pdf");
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivityAsUser(intent, null, UserHandle.CURRENT);
+ Binder.restoreCallingIdentity(identity);
+ return null;
+ }
+ }
+
+ private final class PrintDiscoveryObserver extends IPrinterDiscoveryObserver.Stub {
+ private static final int MESSAGE_ADD_DICOVERED_PRINTERS = 1;
+ private static final int MESSAGE_REMOVE_DICOVERED_PRINTERS = 2;
+
+ private final Handler mHandler;
+
+ @SuppressWarnings("unchecked")
+ public PrintDiscoveryObserver(Looper looper) {
+ mHandler = new Handler(looper, null, true) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MESSAGE_ADD_DICOVERED_PRINTERS: {
+ List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
+ addPrinters(printers);
+ // Just added the first printer, so select it and start printing.
+ if (mDestinationSpinnerAdapter.getCount() == 1) {
+ mDestinationSpinner.setSelection(0);
+ }
+ } break;
+ case MESSAGE_REMOVE_DICOVERED_PRINTERS: {
+ List<PrinterId> printerIds = (List<PrinterId>) message.obj;
+ removePrinters(printerIds);
+ // TODO: Handle removing the last printer.
+ } break;
+ }
+ }
+ };
+ }
+
+ @Override
+ public void addDiscoveredPrinters(List<PrinterInfo> printers) {
+ mHandler.obtainMessage(MESSAGE_ADD_DICOVERED_PRINTERS, printers).sendToTarget();
+ }
+
+ @Override
+ public void removeDiscoveredPrinters(List<PrinterId> printers) {
+ mHandler.obtainMessage(MESSAGE_REMOVE_DICOVERED_PRINTERS, printers).sendToTarget();
+ }
+ }
+
+ 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();
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
new file mode 100644
index 0000000..2b27b69
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
@@ -0,0 +1,734 @@
+/*
+ * 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 java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.print.IPrintClient;
+import android.print.IPrintManager;
+import android.print.PrintAttributes;
+import android.print.PrintJobInfo;
+import android.print.PrintManager;
+import android.print.PrinterId;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+public class PrintSpooler {
+
+ private static final String LOG_TAG = PrintSpooler.class.getSimpleName();
+
+ private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
+
+ private static final boolean DEBUG_PERSISTENCE = false;
+
+ private static final boolean PERSISTNECE_MANAGER_ENABLED = false;
+
+ private static final String PRINT_FILE_EXTENSION = "pdf";
+
+ private static int sPrintJobIdCounter;
+
+ private static final Object sLock = new Object();
+
+ private final Object mLock = new Object();
+
+ private static PrintSpooler sInstance;
+
+ private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
+
+ private final PersistenceManager mPersistanceManager;
+
+ private final Context mContext;
+
+ private final IPrintManager mPrintManager;
+
+ public static PrintSpooler getInstance(Context context) {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new PrintSpooler(context);
+ }
+ return sInstance;
+ }
+ }
+
+ private PrintSpooler(Context context) {
+ mContext = context;
+ mPersistanceManager = new PersistenceManager();
+ mPersistanceManager.readStateLocked();
+ mPrintManager = IPrintManager.Stub.asInterface(
+ ServiceManager.getService("print"));
+ }
+
+ public List<PrintJobInfo> getPrintJobs(ComponentName componentName, int state, int appId) {
+ synchronized (mLock) {
+ List<PrintJobInfo> foundPrintJobs = null;
+ final int printJobCount = mPrintJobs.size();
+ for (int i = 0; i < printJobCount; i++) {
+ PrintJobInfo printJob = mPrintJobs.get(i);
+ PrinterId printerId = printJob.getPrinterId();
+ final boolean sameComponent = (componentName == null
+ || (printerId != null
+ && componentName.equals(printerId.getServiceComponentName())));
+ final boolean sameAppId = appId == PrintManager.APP_ID_ANY
+ || printJob.getAppId() == appId;
+ final boolean sameState = state == PrintJobInfo.STATE_ANY
+ || state == printJob.getState();
+ if (sameComponent && sameAppId && sameState) {
+ if (foundPrintJobs == null) {
+ foundPrintJobs = new ArrayList<PrintJobInfo>();
+ }
+ foundPrintJobs.add(printJob);
+ }
+ }
+ return foundPrintJobs;
+ }
+ }
+
+ public PrintJobInfo getPrintJob(int printJobId, int appId) {
+ synchronized (mLock) {
+ final int printJobCount = mPrintJobs.size();
+ for (int i = 0; i < printJobCount; i++) {
+ PrintJobInfo printJob = mPrintJobs.get(i);
+ if (printJob.getId() == printJobId
+ && (appId == PrintManager.APP_ID_ANY || appId == printJob.getAppId())) {
+ return printJob;
+ }
+ }
+ return null;
+ }
+ }
+
+ public boolean cancelPrintJob(int printJobId, int appId) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJob(printJobId, appId);
+ if (printJob != null) {
+ switch (printJob.getState()) {
+ case PrintJobInfo.STATE_CREATED: {
+ removePrintJobLocked(printJob);
+ } return true;
+ case PrintJobInfo.STATE_QUEUED: {
+ removePrintJobLocked(printJob);
+ } return true;
+ default: {
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ public PrintJobInfo createPrintJob(CharSequence label, IPrintClient client,
+ PrintAttributes attributes, int appId) {
+ synchronized (mLock) {
+ final int printJobId = generatePrintJobIdLocked();
+ PrintJobInfo printJob = new PrintJobInfo();
+ printJob.setId(printJobId);
+ printJob.setAppId(appId);
+ printJob.setLabel(label);
+ printJob.setAttributes(attributes);
+
+ addPrintJobLocked(printJob);
+ setPrintJobState(printJobId, PrintJobInfo.STATE_CREATED);
+
+ return printJob;
+ }
+ }
+
+ private int generatePrintJobIdLocked() {
+ int printJobId = sPrintJobIdCounter++;
+ while (isDuplicatePrintJobId(printJobId)) {
+ printJobId = sPrintJobIdCounter++;
+ }
+ return printJobId;
+ }
+
+ private boolean isDuplicatePrintJobId(int printJobId) {
+ final int printJobCount = mPrintJobs.size();
+ for (int j = 0; j < printJobCount; j++) {
+ PrintJobInfo printJob = mPrintJobs.get(j);
+ if (printJob.getId() == printJobId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @SuppressWarnings("resource")
+ public boolean writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
+ synchronized (mLock) {
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ try {
+ PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ File file = generateFileForPrintJob(printJobId);
+ in = new FileInputStream(file);
+ out = new FileOutputStream(fd.getFileDescriptor());
+ final byte[] buffer = new byte[8192];
+ while (true) {
+ final int readByteCount = in.read(buffer);
+ if (readByteCount < 0) {
+ return true;
+ }
+ 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 {
+ closeIfNotNullNoException(in);
+ closeIfNotNullNoException(out);
+ closeIfNotNullNoException(fd);
+ }
+ }
+ return false;
+ }
+
+ private void closeIfNotNullNoException(Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (IOException ioe) {
+ /* ignore */;
+ }
+ }
+ }
+
+ public File generateFileForPrintJob(int printJobId) {
+ return new File(mContext.getFilesDir(), "print_job_"
+ + printJobId + "." + PRINT_FILE_EXTENSION);
+ }
+
+ private void addPrintJobLocked(PrintJobInfo printJob) {
+ mPrintJobs.add(printJob);
+ if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ Slog.i(LOG_TAG, "[ADD] " + printJob);
+ }
+ }
+
+ private void removePrintJobLocked(PrintJobInfo printJob) {
+ if (mPrintJobs.remove(printJob)) {
+ generateFileForPrintJob(printJob.getId()).delete();
+ if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ Slog.i(LOG_TAG, "[REMOVE] " + printJob);
+ }
+ }
+ }
+
+ public boolean setPrintJobState(int printJobId, int state) {
+ boolean success = false;
+ PrintJobInfo queuedPrintJob = null;
+
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null && printJob.getState() < state) {
+ success = true;
+ printJob.setState(state);
+ // TODO: Update notifications.
+ switch (state) {
+ case PrintJobInfo.STATE_COMPLETED:
+ case PrintJobInfo.STATE_CANCELED: {
+ removePrintJobLocked(printJob);
+ } break;
+ case PrintJobInfo.STATE_QUEUED: {
+ queuedPrintJob = new PrintJobInfo(printJob);
+ } break;
+ }
+ if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ Slog.i(LOG_TAG, "[STATUS CHANGED] " + printJob);
+ }
+ mPersistanceManager.writeStateLocked();
+ }
+ }
+
+ if (queuedPrintJob != null) {
+ try {
+ mPrintManager.onPrintJobQueued(queuedPrintJob.getPrinterId(),
+ queuedPrintJob);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+
+ return success;
+ }
+
+ public boolean setPrintJobTag(int printJobId, String tag) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setTag(tag);
+ mPersistanceManager.writeStateLocked();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void setPrintJobAttributes(int printJobId, PrintAttributes attributes) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setAttributes(attributes);
+ mPersistanceManager.writeStateLocked();
+ }
+ }
+ }
+
+ public void setPrintJobPrinterId(int printJobId, PrinterId printerId) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setPrinterId(printerId);
+ mPersistanceManager.writeStateLocked();
+ }
+ }
+ }
+
+ private final class PersistenceManager {
+ private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
+
+ private static final String TAG_SPOOLER = "spooler";
+ private static final String TAG_JOB = "job";
+ private static final String TAG_ID = "id";
+ private static final String TAG_TAG = "tag";
+ private static final String TAG_APP_ID = "app-id";
+ private static final String TAG_STATE = "state";
+ private static final String TAG_ATTRIBUTES = "attributes";
+ private static final String TAG_LABEL = "label";
+ private static final String TAG_PRINTER = "printer";
+
+ private static final String ATTRIBUTE_MEDIA_SIZE = "mediaSize";
+ private static final String ATTRIBUTE_RESOLUTION = "resolution";
+ private static final String ATTRIBUTE_MARGINS = "margins";
+ private static final String ATTRIBUTE_INPUT_TRAY = "inputTray";
+ private static final String ATTRIBUTE_OUTPUT_TRAY = "outputTray";
+ private static final String ATTRIBUTE_DUPLEX_MODE = "duplexMode";
+ private static final String ATTRIBUTE_COLOR_MODE = "colorMode";
+ private static final String ATTRIBUTE_FITTING_MODE = "fittingMode";
+ private static final String ATTRIBUTE_ORIENTATION = "orientation";
+
+ private final AtomicFile mStatePersistFile;
+
+ private boolean mWriteStateScheduled;
+
+ private PersistenceManager() {
+ mStatePersistFile = new AtomicFile(new File(mContext.getFilesDir(),
+ PERSIST_FILE_NAME));
+ }
+
+ public void writeStateLocked() {
+ // TODO: Implement persistence of PrintableInfo
+ if (!PERSISTNECE_MANAGER_ENABLED) {
+ return;
+ }
+ if (mWriteStateScheduled) {
+ return;
+ }
+ mWriteStateScheduled = true;
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ synchronized (mLock) {
+ mWriteStateScheduled = false;
+ doWriteStateLocked();
+ }
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ private void doWriteStateLocked() {
+ FileOutputStream out = null;
+ try {
+ out = mStatePersistFile.startWrite();
+
+ XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(out, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_SPOOLER);
+
+ List<PrintJobInfo> printJobs = mPrintJobs;
+
+ final int printJobCount = printJobs.size();
+ for (int j = 0; j < printJobCount; j++) {
+ PrintJobInfo printJob = printJobs.get(j);
+
+ final int state = printJob.getState();
+ if (state < PrintJobInfo.STATE_QUEUED
+ || state > PrintJobInfo.STATE_FAILED) {
+ continue;
+ }
+
+ serializer.startTag(null, TAG_JOB);
+
+ serializer.startTag(null, TAG_ID);
+ serializer.text(String.valueOf(printJob.getId()));
+ serializer.endTag(null, TAG_ID);
+
+ serializer.startTag(null, TAG_TAG);
+ serializer.text(printJob.getTag());
+ serializer.endTag(null, TAG_TAG);
+
+ serializer.startTag(null, TAG_APP_ID);
+ serializer.text(String.valueOf(printJob.getAppId()));
+ serializer.endTag(null, TAG_APP_ID);
+
+ serializer.startTag(null, TAG_LABEL);
+ serializer.text(printJob.getLabel().toString());
+ serializer.endTag(null, TAG_LABEL);
+
+ serializer.startTag(null, TAG_STATE);
+ serializer.text(String.valueOf(printJob.getState()));
+ serializer.endTag(null, TAG_STATE);
+
+ serializer.startTag(null, TAG_PRINTER);
+ serializer.text(printJob.getPrinterId().flattenToString());
+ serializer.endTag(null, TAG_PRINTER);
+
+ PrintAttributes attributes = printJob.getAttributes();
+ if (attributes != null) {
+ serializer.startTag(null, TAG_ATTRIBUTES);
+
+ //TODO: Implement persistence of the attributes below.
+
+// MediaSize mediaSize = attributes.getMediaSize();
+// if (mediaSize != null) {
+// serializer.attribute(null, ATTRIBUTE_MEDIA_SIZE,
+// mediaSize.flattenToString());
+// }
+//
+// Resolution resolution = attributes.getResolution();
+// if (resolution != null) {
+// serializer.attribute(null, ATTRIBUTE_RESOLUTION,
+// resolution.flattenToString());
+// }
+//
+// Margins margins = attributes.getMargins();
+// if (margins != null) {
+// serializer.attribute(null, ATTRIBUTE_MARGINS,
+// margins.flattenToString());
+// }
+//
+// Tray inputTray = attributes.getInputTray();
+// if (inputTray != null) {
+// serializer.attribute(null, ATTRIBUTE_INPUT_TRAY,
+// inputTray.flattenToString());
+// }
+//
+// Tray outputTray = attributes.getOutputTray();
+// if (outputTray != null) {
+// serializer.attribute(null, ATTRIBUTE_OUTPUT_TRAY,
+// outputTray.flattenToString());
+// }
+
+ final int duplexMode = attributes.getDuplexMode();
+ if (duplexMode > 0) {
+ serializer.attribute(null, ATTRIBUTE_DUPLEX_MODE,
+ String.valueOf(duplexMode));
+ }
+
+ final int colorMode = attributes.getColorMode();
+ if (colorMode > 0) {
+ serializer.attribute(null, ATTRIBUTE_COLOR_MODE,
+ String.valueOf(colorMode));
+ }
+
+ final int fittingMode = attributes.getFittingMode();
+ if (fittingMode > 0) {
+ serializer.attribute(null, ATTRIBUTE_FITTING_MODE,
+ String.valueOf(fittingMode));
+ }
+
+ final int orientation = attributes.getOrientation();
+ if (orientation > 0) {
+ serializer.attribute(null, ATTRIBUTE_ORIENTATION,
+ String.valueOf(orientation));
+ }
+
+ serializer.endTag(null, TAG_ATTRIBUTES);
+ }
+
+ serializer.endTag(null, TAG_JOB);
+
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[PERSISTED] " + printJob);
+ }
+ }
+
+ serializer.endTag(null, TAG_SPOOLER);
+ serializer.endDocument();
+ mStatePersistFile.finishWrite(out);
+ } catch (IOException e) {
+ Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
+ mStatePersistFile.failWrite(out);
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException ioe) {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ public void readStateLocked() {
+ if (!PERSISTNECE_MANAGER_ENABLED) {
+ return;
+ }
+ FileInputStream in = null;
+ try {
+ in = mStatePersistFile.openRead();
+ } catch (FileNotFoundException e) {
+ Log.i(LOG_TAG, "No existing print spooler state.");
+ return;
+ }
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parseState(parser);
+ } catch (IllegalStateException ise) {
+ Slog.w(LOG_TAG, "Failed parsing " + ise);
+ } catch (NullPointerException npe) {
+ Slog.w(LOG_TAG, "Failed parsing " + npe);
+ } catch (NumberFormatException nfe) {
+ Slog.w(LOG_TAG, "Failed parsing " + nfe);
+ } catch (XmlPullParserException xppe) {
+ Slog.w(LOG_TAG, "Failed parsing " + xppe);
+ } catch (IOException ioe) {
+ Slog.w(LOG_TAG, "Failed parsing " + ioe);
+ } catch (IndexOutOfBoundsException iobe) {
+ Slog.w(LOG_TAG, "Failed parsing " + iobe);
+ } finally {
+ try {
+ in.close();
+ } catch (IOException ioe) {
+ /* ignore */
+ }
+ }
+ }
+
+ private void parseState(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
+ parser.next();
+
+ while (parsePrintJob(parser)) {
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
+ }
+
+ private boolean parsePrintJob(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ skipEmptyTextTags(parser);
+ if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
+ return false;
+ }
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_ID);
+ parser.next();
+ final int printJobId = Integer.parseInt(parser.getText());
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_ID);
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_TAG);
+ parser.next();
+ String tag = parser.getText();
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_TAG);
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_APP_ID);
+ parser.next();
+ final int appId = Integer.parseInt(parser.getText());
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_APP_ID);
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_LABEL);
+ parser.next();
+ String label = parser.getText();
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_LABEL);
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_STATE);
+ parser.next();
+ final int state = Integer.parseInt(parser.getText());
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_STATE);
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_PRINTER);
+ parser.next();
+ PrinterId printerId = PrinterId.unflattenFromString(parser.getText());
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_PRINTER);
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES);
+
+ final int attributeCount = parser.getAttributeCount();
+ PrintAttributes attributes = null;
+ if (attributeCount > 0) {
+ PrintAttributes.Builder builder = new PrintAttributes.Builder();
+
+ // TODO: Implement reading of the attributes below.
+
+// String mediaSize = parser.getAttributeValue(null, ATTRIBUTE_MEDIA_SIZE);
+// if (mediaSize != null) {
+// builder.setMediaSize(MediaSize.unflattenFromString(mediaSize));
+// }
+//
+// String resolution = parser.getAttributeValue(null, ATTRIBUTE_RESOLUTION);
+// if (resolution != null) {
+// builder.setMediaSize(Resolution.unflattenFromString(resolution));
+// }
+//
+// String margins = parser.getAttributeValue(null, ATTRIBUTE_MARGINS);
+// if (margins != null) {
+// builder.setMediaSize(Margins.unflattenFromString(margins));
+// }
+//
+// String inputTray = parser.getAttributeValue(null, ATTRIBUTE_INPUT_TRAY);
+// if (inputTray != null) {
+// builder.setMediaSize(Tray.unflattenFromString(inputTray));
+// }
+//
+// String outputTray = parser.getAttributeValue(null, ATTRIBUTE_OUTPUT_TRAY);
+// if (outputTray != null) {
+// builder.setMediaSize(Tray.unflattenFromString(outputTray));
+// }
+//
+// String duplexMode = parser.getAttributeValue(null, ATTRIBUTE_DUPLEX_MODE);
+// if (duplexMode != null) {
+// builder.setDuplexMode(Integer.parseInt(duplexMode));
+// }
+
+ String colorMode = parser.getAttributeValue(null, ATTRIBUTE_COLOR_MODE);
+ if (colorMode != null) {
+ builder.setColorMode(Integer.parseInt(colorMode));
+ }
+
+ String fittingMode = parser.getAttributeValue(null, ATTRIBUTE_COLOR_MODE);
+ if (fittingMode != null) {
+ builder.setFittingMode(Integer.parseInt(fittingMode));
+ }
+ }
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
+ parser.next();
+
+ PrintJobInfo printJob = new PrintJobInfo();
+ printJob.setId(printJobId);
+ printJob.setTag(tag);
+ printJob.setAppId(appId);
+ printJob.setLabel(label);
+ printJob.setState(state);
+ printJob.setAttributes(attributes);
+ printJob.setPrinterId(printerId);
+
+ mPrintJobs.add(printJob);
+
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[RESTORED] " + printJob);
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_JOB);
+
+ return true;
+ }
+
+ private void expect(XmlPullParser parser, int type, String tag)
+ throws IOException, XmlPullParserException {
+ if (!accept(parser, type, tag)) {
+ throw new XmlPullParserException("Exepected event: " + type
+ + " and tag: " + tag + " but got event: " + parser.getEventType()
+ + " and tag:" + parser.getName());
+ }
+ }
+
+ private void skipEmptyTextTags(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ while (accept(parser, XmlPullParser.TEXT, null)
+ && "\n".equals(parser.getText())) {
+ parser.next();
+ }
+ }
+
+ private boolean accept(XmlPullParser parser, int type, String tag)
+ throws IOException, XmlPullParserException {
+ if (parser.getEventType() != type) {
+ return false;
+ }
+ if (tag != null) {
+ if (!tag.equals(parser.getName())) {
+ return false;
+ }
+ } else if (parser.getName() != null) {
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
new file mode 100644
index 0000000..57c4557
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
@@ -0,0 +1,187 @@
+/*
+ * 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 java.util.List;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.print.IPrintAdapter;
+import android.print.IPrintClient;
+import android.print.IPrintSpoolerService;
+import android.print.IPrintSpoolerServiceCallbacks;
+import android.print.PrintAttributes;
+import android.print.PrintJobInfo;
+import android.util.Slog;
+
+import com.android.internal.os.SomeArgs;
+
+/**
+ * Service for exposing some of the {@link PrintSpooler} functionality to
+ * another process.
+ */
+public final class PrintSpoolerService extends Service {
+
+ private static final String LOG_TAG = "PrintSpoolerService";
+
+ private Intent mStartPrintJobConfigActivityIntent;
+
+ private PrintSpooler mSpooler;
+
+ private Handler mHanlder;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mStartPrintJobConfigActivityIntent = new Intent(PrintSpoolerService.this,
+ PrintJobConfigActivity.class);
+ mSpooler = PrintSpooler.getInstance(this);
+ mHanlder = new MyHandler(getMainLooper());
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new IPrintSpoolerService.Stub() {
+ @Override
+ public void getPrintJobs(IPrintSpoolerServiceCallbacks callback,
+ ComponentName componentName, int state, int appId, int sequence)
+ throws RemoteException {
+ List<PrintJobInfo> printJobs = null;
+ try {
+ printJobs = mSpooler.getPrintJobs(componentName, state, appId);
+ } finally {
+ callback.onGetPrintJobsResult(printJobs, sequence);
+ }
+ }
+
+ @Override
+ public void getPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback,
+ int appId, int sequence) throws RemoteException {
+ PrintJobInfo printJob = null;
+ try {
+ printJob = mSpooler.getPrintJob(printJobId, appId);
+ } finally {
+ callback.onGetPrintJobInfoResult(printJob, sequence);
+ }
+ }
+
+ @Override
+ public void cancelPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback,
+ int appId, int sequence) throws RemoteException {
+ boolean success = false;
+ try {
+ success = mSpooler.cancelPrintJob(printJobId, appId);
+ } finally {
+ callback.onCancelPrintJobResult(success, sequence);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void createPrintJob(String printJobName, IPrintClient client,
+ IPrintAdapter printAdapter, PrintAttributes attributes,
+ IPrintSpoolerServiceCallbacks callback, int appId, int sequence)
+ throws RemoteException {
+ PrintJobInfo printJob = null;
+ try {
+ printJob = mSpooler.createPrintJob(printJobName, client,
+ attributes, appId);
+ if (printJob != null) {
+ Intent intent = mStartPrintJobConfigActivityIntent;
+ intent.putExtra(PrintJobConfigActivity.EXTRA_PRINTABLE,
+ printAdapter.asBinder());
+ intent.putExtra(PrintJobConfigActivity.EXTRA_APP_ID, appId);
+ intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_JOB_ID,
+ printJob.getId());
+ intent.putExtra(PrintJobConfigActivity.EXTRA_ATTRIBUTES, attributes);
+
+ IntentSender sender = PendingIntent.getActivity(
+ PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = client;
+ args.arg2 = sender;
+ mHanlder.obtainMessage(0, args).sendToTarget();
+ }
+ } finally {
+ callback.onCreatePrintJobResult(printJob, sequence);
+ }
+ }
+
+ @Override
+ public void setPrintJobState(int printJobId, int state,
+ IPrintSpoolerServiceCallbacks callback, int sequece)
+ throws RemoteException {
+ boolean success = false;
+ try {
+ // TODO: Make sure the clients (print services) can set the state
+ // only to acceptable ones, e.g. not settings STATE_CREATED.
+ success = mSpooler.setPrintJobState(printJobId, state);
+ } finally {
+ callback.onSetPrintJobStateResult(success, sequece);
+ }
+ }
+
+ @Override
+ public void setPrintJobTag(int printJobId, String tag,
+ IPrintSpoolerServiceCallbacks callback, int sequece)
+ throws RemoteException {
+ boolean success = false;
+ try {
+ success = mSpooler.setPrintJobTag(printJobId, tag);
+ } finally {
+ callback.onSetPrintJobTagResult(success, sequece);
+ }
+ }
+
+ @Override
+ public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
+ mSpooler.writePrintJobData(fd, printJobId);
+ }
+ };
+ }
+
+ private static final class MyHandler extends Handler {
+
+ public MyHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ SomeArgs args = (SomeArgs) message.obj;
+ IPrintClient client = (IPrintClient) args.arg1;
+ IntentSender sender = (IntentSender) args.arg2;
+ args.recycle();
+ try {
+ client.startPrintJobConfigActivity(sender);
+ } catch (RemoteException re) {
+ Slog.i(LOG_TAG, "Error starting print job config activity!", re);
+ }
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java
new file mode 100644
index 0000000..7537218
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java
@@ -0,0 +1,219 @@
+/*
+ * 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.os.ICancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.print.IPrintAdapter;
+import android.print.IPrintProgressListener;
+import android.print.PageRange;
+import android.print.PrintAdapterInfo;
+import android.print.PrintAttributes;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * This class represents a remote print adapter instance.
+ */
+final class RemotePrintAdapter {
+ private static final String LOG_TAG = "RemotePrintAdapter";
+
+ private static final boolean DEBUG = true;
+
+ private final Object mLock = new Object();
+
+ private final IPrintAdapter mRemoteInterface;
+
+ private final File mFile;
+
+ private final IPrintProgressListener mIPrintProgressListener;
+
+ private PrintAdapterInfo mInfo;
+
+ private ICancellationSignal mCancellationSignal;
+
+ private Thread mWriteThread;
+
+ public RemotePrintAdapter(IPrintAdapter printAdatper, File file) {
+ mRemoteInterface = printAdatper;
+ mFile = file;
+ mIPrintProgressListener = new IPrintProgressListener.Stub() {
+ @Override
+ public void onWriteStarted(PrintAdapterInfo info,
+ ICancellationSignal cancellationSignal) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "IPrintProgressListener#onWriteStarted()");
+ }
+ synchronized (mLock) {
+ mInfo = info;
+ mCancellationSignal = cancellationSignal;
+ }
+ }
+
+ @Override
+ public void onWriteFinished(List<PageRange> pages) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "IPrintProgressListener#onWriteFinished(" + pages + ")");
+ }
+ synchronized (mLock) {
+ if (isPrintingLocked()) {
+ mWriteThread.interrupt();
+ mCancellationSignal = null;
+ }
+ }
+ }
+
+ @Override
+ public void onWriteFailed(CharSequence error) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "IPrintProgressListener#onWriteFailed(" + error + ")");
+ }
+ synchronized (mLock) {
+ if (isPrintingLocked()) {
+ mWriteThread.interrupt();
+ mCancellationSignal = null;
+ }
+ }
+ }
+ };
+ }
+
+ public File getFile() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "getFile()");
+ }
+ return mFile;
+ }
+
+ public void start() throws IOException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "start()");
+ }
+ try {
+ mRemoteInterface.start();
+ } catch (RemoteException re) {
+ throw new IOException("Error reading file", re);
+ }
+ }
+
+ public void printAttributesChanged(PrintAttributes attributes) throws IOException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "printAttributesChanged(" + attributes +")");
+ }
+ try {
+ mRemoteInterface.printAttributesChanged(attributes);
+ } catch (RemoteException re) {
+ throw new IOException("Error reading file", re);
+ }
+ }
+
+ public void print(List<PageRange> pages) throws IOException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "print(" + pages +")");
+ }
+ InputStream in = null;
+ OutputStream out = null;
+ ParcelFileDescriptor source = null;
+ ParcelFileDescriptor sink = null;
+ synchronized (mLock) {
+ mWriteThread = Thread.currentThread();
+ }
+ try {
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ source = pipe[0];
+ sink = pipe[1];
+
+ in = new FileInputStream(source.getFileDescriptor());
+ out = new FileOutputStream(mFile);
+
+ // Async call to initiate the other process writing the data.
+ mRemoteInterface.print(pages, sink, mIPrintProgressListener);
+
+ // Close the source. It is now held by the client.
+ sink.close();
+ sink = null;
+
+ final byte[] buffer = new byte[8192];
+ while (true) {
+ if (Thread.currentThread().isInterrupted()) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ final int readByteCount = in.read(buffer);
+ if (readByteCount < 0) {
+ break;
+ }
+ out.write(buffer, 0, readByteCount);
+ }
+ } catch (RemoteException re) {
+ throw new IOException("Error reading file", re);
+ } catch (IOException ioe) {
+ throw new IOException("Error reading file", ioe);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ IoUtils.closeQuietly(sink);
+ IoUtils.closeQuietly(source);
+ }
+ }
+
+ public void cancelPrint() throws IOException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "cancelPrint()");
+ }
+ synchronized (mLock) {
+ if (isPrintingLocked()) {
+ try {
+ mCancellationSignal.cancel();
+ } catch (RemoteException re) {
+ throw new IOException("Error cancelling print", re);
+ }
+ }
+ }
+ }
+
+ public void finish() throws IOException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "finish()");
+ }
+ try {
+ mRemoteInterface.finish();
+ } catch (RemoteException re) {
+ throw new IOException("Error reading file", re);
+ }
+ }
+
+ public PrintAdapterInfo getInfo() {
+ synchronized (mLock) {
+ return mInfo;
+ }
+ }
+
+ private boolean isPrintingLocked() {
+ return mCancellationSignal != null;
+ }
+}