blob: c0faed8afbf65038b20201895157d6a1672cb930 [file] [log] [blame]
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -07001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.printspooler;
18
19import android.app.Activity;
20import android.content.Intent;
21import android.net.Uri;
22import android.os.AsyncTask;
23import android.os.Binder;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.IBinder;
Svetoslav Ganova0027152013-06-25 14:59:53 -070027import android.os.IBinder.DeathRecipient;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070028import android.os.Looper;
29import android.os.Message;
30import android.os.RemoteException;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070031import android.os.UserHandle;
Svetoslav Ganova0027152013-06-25 14:59:53 -070032import android.print.IPrintDocumentAdapter;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070033import android.print.IPrinterDiscoveryObserver;
34import android.print.PageRange;
35import android.print.PrintAttributes;
36import android.print.PrintAttributes.MediaSize;
37import android.print.PrintAttributes.Resolution;
38import android.print.PrintAttributes.Tray;
Svetoslav Ganova0027152013-06-25 14:59:53 -070039import android.print.PrintDocumentAdapter.LayoutResultCallback;
40import android.print.PrintDocumentAdapter.WriteResultCallback;
41import android.print.PrintDocumentInfo;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070042import android.print.PrintJobInfo;
43import android.print.PrinterId;
44import android.print.PrinterInfo;
45import android.text.Editable;
46import android.text.InputFilter;
47import android.text.Spanned;
48import android.text.TextUtils;
49import android.text.TextWatcher;
50import android.util.Log;
Svetoslav Ganova0027152013-06-25 14:59:53 -070051import android.view.Choreographer;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070052import android.view.Menu;
53import android.view.MenuItem;
54import android.view.View;
55import android.view.WindowManager;
56import android.widget.AdapterView;
57import android.widget.AdapterView.OnItemSelectedListener;
58import android.widget.ArrayAdapter;
59import android.widget.EditText;
60import android.widget.Spinner;
61
62import java.io.File;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070063import java.util.ArrayList;
64import java.util.List;
65
66/**
67 * Activity for configuring a print job.
68 */
69public class PrintJobConfigActivity extends Activity {
70
71 private static final boolean DEBUG = false;
72
73 private static final String LOG_TAG = PrintJobConfigActivity.class.getSimpleName();
74
75 public static final String EXTRA_PRINTABLE = "printable";
76 public static final String EXTRA_APP_ID = "appId";
77 public static final String EXTRA_ATTRIBUTES = "attributes";
78 public static final String EXTRA_PRINT_JOB_ID = "printJobId";
79
80 private static final int MIN_COPIES = 1;
81
Svetoslav Ganova0027152013-06-25 14:59:53 -070082 private final PrintSpooler mPrintSpooler = PrintSpooler.getInstance(this);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070083
84 private IPrinterDiscoveryObserver mPrinterDiscoveryObserver;
85
86 private int mAppId;
87 private int mPrintJobId;
88
89 private PrintAttributes mPrintAttributes;
90
Svetoslav Ganova0027152013-06-25 14:59:53 -070091 private RemotePrintDocumentAdapter mRemotePrintAdapter;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070092
93 // UI elements
94
95 private EditText mCopiesEditText;
96
97 private Spinner mDestinationSpinner;
98 public ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter;
99
100 private Spinner mMediaSizeSpinner;
101 public ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
102
103 private Spinner mResolutionSpinner;
104 public ArrayAdapter<SpinnerItem<Resolution>> mResolutionSpinnerAdapter;
105
106 private Spinner mInputTraySpinner;
107 public ArrayAdapter<SpinnerItem<Tray>> mInputTraySpinnerAdapter;
108
109 private Spinner mOutputTraySpinner;
110 public ArrayAdapter<SpinnerItem<Tray>> mOutputTraySpinnerAdapter;
111
112 private Spinner mDuplexModeSpinner;
113 public ArrayAdapter<SpinnerItem<Integer>> mDuplexModeSpinnerAdapter;
114
115 private Spinner mColorModeSpinner;
116 public ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
117
118 private Spinner mFittingModeSpinner;
119 public ArrayAdapter<SpinnerItem<Integer>> mFittingModeSpinnerAdapter;
120
121 private Spinner mOrientationSpinner;
122 public ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
123
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700124 private boolean mPrintConfirmed;
125
Svetoslav Ganova0027152013-06-25 14:59:53 -0700126 private boolean mStarted;
127
128 private IBinder mIPrintDocumentAdapter;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700129
130 // TODO: Implement store/restore state.
131
132 private final OnItemSelectedListener mOnItemSelectedListener =
133 new AdapterView.OnItemSelectedListener() {
134 @Override
135 public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
136 if (spinner == mDestinationSpinner) {
137 updateUi();
138 notifyPrintableStartIfNeeded();
139 } else if (spinner == mMediaSizeSpinner) {
140 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
141 mPrintAttributes.setMediaSize(mediaItem.value);
142 updatePrintableContentIfNeeded();
143 } else if (spinner == mResolutionSpinner) {
144 SpinnerItem<Resolution> resolutionItem =
145 mResolutionSpinnerAdapter.getItem(position);
146 mPrintAttributes.setResolution(resolutionItem.value);
147 updatePrintableContentIfNeeded();
148 } else if (spinner == mInputTraySpinner) {
149 SpinnerItem<Tray> inputTrayItem =
150 mInputTraySpinnerAdapter.getItem(position);
151 mPrintAttributes.setInputTray(inputTrayItem.value);
152 } else if (spinner == mOutputTraySpinner) {
153 SpinnerItem<Tray> outputTrayItem =
154 mOutputTraySpinnerAdapter.getItem(position);
155 mPrintAttributes.setOutputTray(outputTrayItem.value);
156 } else if (spinner == mDuplexModeSpinner) {
157 SpinnerItem<Integer> duplexModeItem =
158 mDuplexModeSpinnerAdapter.getItem(position);
159 mPrintAttributes.setDuplexMode(duplexModeItem.value);
160 } else if (spinner == mColorModeSpinner) {
161 SpinnerItem<Integer> colorModeItem =
162 mColorModeSpinnerAdapter.getItem(position);
163 mPrintAttributes.setColorMode(colorModeItem.value);
164 } else if (spinner == mFittingModeSpinner) {
165 SpinnerItem<Integer> fittingModeItem =
166 mFittingModeSpinnerAdapter.getItem(position);
167 mPrintAttributes.setFittingMode(fittingModeItem.value);
168 } else if (spinner == mOrientationSpinner) {
169 SpinnerItem<Integer> orientationItem =
170 mOrientationSpinnerAdapter.getItem(position);
171 mPrintAttributes.setOrientation(orientationItem.value);
172 }
173 }
174
175 @Override
176 public void onNothingSelected(AdapterView<?> parent) {
177 /* do nothing*/
178 }
179 };
180
181 private final TextWatcher mTextWatcher = new TextWatcher() {
182 @Override
183 public void onTextChanged(CharSequence s, int start, int before, int count) {
184 final int copies = Integer.parseInt(mCopiesEditText.getText().toString());
185 mPrintAttributes.setCopies(copies);
186 }
187
188 @Override
189 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
190 /* do nothing */
191 }
192
193 @Override
194 public void afterTextChanged(Editable s) {
195 /* do nothing */
196 }
197 };
198
199 private final InputFilter mInputFilter = new InputFilter() {
200 @Override
201 public CharSequence filter(CharSequence source, int start, int end,
202 Spanned dest, int dstart, int dend) {
203 StringBuffer text = new StringBuffer(dest.toString());
204 text.replace(dstart, dend, source.subSequence(start, end).toString());
205 if (TextUtils.isEmpty(text)) {
206 return dest;
207 }
208 final int copies = Integer.parseInt(text.toString());
209 if (copies < MIN_COPIES) {
210 return dest;
211 }
212 return null;
213 }
214 };
215
216 private final DeathRecipient mDeathRecipient = new DeathRecipient() {
217 @Override
218 public void binderDied() {
219 finish();
220 }
221 };
222
223 @Override
224 protected void onCreate(Bundle bundle) {
225 super.onCreate(bundle);
226 setContentView(R.layout.print_job_config_activity);
227
228 getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
229 | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
230
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700231 Bundle extras = getIntent().getExtras();
232
233 mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1);
234 if (mPrintJobId < 0) {
235 throw new IllegalArgumentException("Invalid print job id: " + mPrintJobId);
236 }
237
238 mAppId = extras.getInt(EXTRA_APP_ID, -1);
239 if (mAppId < 0) {
240 throw new IllegalArgumentException("Invalid app id: " + mAppId);
241 }
242
243 mPrintAttributes = getIntent().getParcelableExtra(EXTRA_ATTRIBUTES);
244 if (mPrintAttributes == null) {
245 mPrintAttributes = new PrintAttributes.Builder().create();
246 }
247
Svetoslav Ganova0027152013-06-25 14:59:53 -0700248 mIPrintDocumentAdapter = extras.getBinder(EXTRA_PRINTABLE);
249 if (mIPrintDocumentAdapter == null) {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700250 throw new IllegalArgumentException("Printable cannot be null");
251 }
Svetoslav Ganova0027152013-06-25 14:59:53 -0700252 mRemotePrintAdapter = new RemotePrintDocumentAdapter(
253 IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700254 mPrintSpooler.generateFileForPrintJob(mPrintJobId));
255
256 try {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700257 mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700258 } catch (RemoteException re) {
259 finish();
260 }
261
262 mPrinterDiscoveryObserver = new PrintDiscoveryObserver(getMainLooper());
263
264 bindUi();
265 }
266
267 @Override
268 protected void onDestroy() {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700269 mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700270 super.onDestroy();
271 }
272
273 private void bindUi() {
274 // Copies
275 mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
276 mCopiesEditText.setText(String.valueOf(MIN_COPIES));
277 mCopiesEditText.addTextChangedListener(mTextWatcher);
278 mCopiesEditText.setFilters(new InputFilter[] {mInputFilter});
279
280 // Destination.
281 mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
282 mDestinationSpinnerAdapter = new ArrayAdapter<SpinnerItem<PrinterInfo>>(this,
283 android.R.layout.simple_spinner_dropdown_item);
284 mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
285 mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
286
287 // Media size.
288 mMediaSizeSpinner = (Spinner) findViewById(R.id.media_size_spinner);
289 mMediaSizeSpinnerAdapter = new ArrayAdapter<SpinnerItem<MediaSize>>(this,
290 android.R.layout.simple_spinner_dropdown_item);
291 mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
292 mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
293
294 // Resolution.
295 mResolutionSpinner = (Spinner) findViewById(R.id.resolution_spinner);
296 mResolutionSpinnerAdapter = new ArrayAdapter<SpinnerItem<Resolution>>(this,
297 android.R.layout.simple_spinner_dropdown_item);
298 mResolutionSpinner.setAdapter(mResolutionSpinnerAdapter);
299 mResolutionSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
300
301 // Input tray.
302 mInputTraySpinner = (Spinner) findViewById(R.id.input_tray_spinner);
303 mInputTraySpinnerAdapter = new ArrayAdapter<SpinnerItem<Tray>>(this,
304 android.R.layout.simple_spinner_dropdown_item);
305 mInputTraySpinner.setAdapter(mInputTraySpinnerAdapter);
306
307 // Output tray.
308 mOutputTraySpinner = (Spinner) findViewById(R.id.output_tray_spinner);
309 mOutputTraySpinnerAdapter = new ArrayAdapter<SpinnerItem<Tray>>(this,
310 android.R.layout.simple_spinner_dropdown_item);
311 mOutputTraySpinner.setAdapter(mOutputTraySpinnerAdapter);
312 mOutputTraySpinner.setOnItemSelectedListener(mOnItemSelectedListener);
313
314 // Duplex mode.
315 mDuplexModeSpinner = (Spinner) findViewById(R.id.duplex_mode_spinner);
316 mDuplexModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
317 android.R.layout.simple_spinner_dropdown_item);
318 mDuplexModeSpinner.setAdapter(mDuplexModeSpinnerAdapter);
319 mDuplexModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
320
321 // Color mode.
322 mColorModeSpinner = (Spinner) findViewById(R.id.color_mode_spinner);
323 mColorModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
324 android.R.layout.simple_spinner_dropdown_item);
325 mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
326 mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
327
328 // Color mode.
329 mFittingModeSpinner = (Spinner) findViewById(R.id.fitting_mode_spinner);
330 mFittingModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
331 android.R.layout.simple_spinner_dropdown_item);
332 mFittingModeSpinner.setAdapter(mFittingModeSpinnerAdapter);
333 mFittingModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
334
335 // Orientation
336 mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
337 mOrientationSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(this,
338 android.R.layout.simple_spinner_dropdown_item);
339 mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
340 mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
341 }
342
343 private void updateUi() {
344 final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
345 PrinterInfo printer = mDestinationSpinnerAdapter.getItem(selectedIndex).value;
346 printer.getDefaults(mPrintAttributes);
347
348 // Copies.
349 mCopiesEditText.setText(String.valueOf(
350 Math.max(mPrintAttributes.getCopies(), MIN_COPIES)));
351
352 // Media size.
353 mMediaSizeSpinnerAdapter.clear();
354 List<MediaSize> mediaSizes = printer.getMediaSizes();
355 final int mediaSizeCount = mediaSizes.size();
356 for (int i = 0; i < mediaSizeCount; i++) {
357 MediaSize mediaSize = mediaSizes.get(i);
358 mMediaSizeSpinnerAdapter.add(new SpinnerItem<MediaSize>(
Svetoslav17b7f6e2013-06-24 18:29:33 -0700359 mediaSize, mediaSize.getLabel()));
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700360 }
361 final int selectedMediaSizeIndex = mediaSizes.indexOf(
362 mPrintAttributes.getMediaSize());
363 mMediaSizeSpinner.setOnItemSelectedListener(null);
364 mMediaSizeSpinner.setSelection(selectedMediaSizeIndex);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700365
366 // Resolution.
367 mResolutionSpinnerAdapter.clear();
368 List<Resolution> resolutions = printer.getResolutions();
369 final int resolutionCount = resolutions.size();
370 for (int i = 0; i < resolutionCount; i++) {
371 Resolution resolution = resolutions.get(i);
372 mResolutionSpinnerAdapter.add(new SpinnerItem<Resolution>(
373 resolution, resolution.getLabel(getPackageManager())));
374 }
375 final int selectedResolutionIndex = resolutions.indexOf(
376 mPrintAttributes.getResolution());
377 mResolutionSpinner.setOnItemSelectedListener(null);
378 mResolutionSpinner.setSelection(selectedResolutionIndex);
Svetoslav Ganova0027152013-06-25 14:59:53 -0700379
380 // AdapterView has the weird behavior to notify the selection listener for a
381 // selection event that occurred *before* the listener was registered because
382 // it does the real selection change on the next layout pass. To avoid this
383 // behavior we re-attach the listener in the next traversal window - fun!
384 Choreographer.getInstance().postCallback(
385 Choreographer.CALLBACK_TRAVERSAL, new Runnable() {
386 @Override
387 public void run() {
388 mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
389 mResolutionSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
390 }
391 }, null);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700392
393 // Input tray.
394 mInputTraySpinnerAdapter.clear();
395 List<Tray> inputTrays = printer.getInputTrays();
Svetoslav17b7f6e2013-06-24 18:29:33 -0700396 if (inputTrays != null) {
397 final int inputTrayCount = inputTrays.size();
398 for (int i = 0; i < inputTrayCount; i++) {
399 Tray inputTray = inputTrays.get(i);
400 mInputTraySpinnerAdapter.add(new SpinnerItem<Tray>(
401 inputTray, inputTray.getLabel(getPackageManager())));
402 }
403 final int selectedInputTrayIndex = inputTrays.indexOf(
404 mPrintAttributes.getInputTray());
405 mInputTraySpinner.setSelection(selectedInputTrayIndex);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700406 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700407
408 // Output tray.
409 mOutputTraySpinnerAdapter.clear();
410 List<Tray> outputTrays = printer.getOutputTrays();
Svetoslav17b7f6e2013-06-24 18:29:33 -0700411 if (outputTrays != null) {
412 final int outputTrayCount = outputTrays.size();
413 for (int i = 0; i < outputTrayCount; i++) {
414 Tray outputTray = outputTrays.get(i);
415 mOutputTraySpinnerAdapter.add(new SpinnerItem<Tray>(
416 outputTray, outputTray.getLabel(getPackageManager())));
417 }
418 final int selectedOutputTrayIndex = outputTrays.indexOf(
419 mPrintAttributes.getOutputTray());
420 mOutputTraySpinner.setSelection(selectedOutputTrayIndex);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700421 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700422
423 // Duplex mode.
424 final int duplexModes = printer.getDuplexModes();
425 mDuplexModeSpinnerAdapter.clear();
426 String[] duplexModeLabels = getResources().getStringArray(
427 R.array.duplex_mode_labels);
428 int remainingDuplexModes = duplexModes;
429 while (remainingDuplexModes != 0) {
430 final int duplexBitOffset = Integer.numberOfTrailingZeros(remainingDuplexModes);
431 final int duplexMode = 1 << duplexBitOffset;
432 remainingDuplexModes &= ~duplexMode;
433 mDuplexModeSpinnerAdapter.add(new SpinnerItem<Integer>(duplexMode,
434 duplexModeLabels[duplexBitOffset]));
435 }
436 final int selectedDuplexModeIndex = Integer.numberOfTrailingZeros(
437 (duplexModes & mPrintAttributes.getDuplexMode()));
438 mDuplexModeSpinner.setSelection(selectedDuplexModeIndex);
439
440 // Color mode.
441 final int colorModes = printer.getColorModes();
442 mColorModeSpinnerAdapter.clear();
443 String[] colorModeLabels = getResources().getStringArray(
444 R.array.color_mode_labels);
445 int remainingColorModes = colorModes;
446 while (remainingColorModes != 0) {
447 final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
448 final int colorMode = 1 << colorBitOffset;
449 remainingColorModes &= ~colorMode;
450 mColorModeSpinnerAdapter.add(new SpinnerItem<Integer>(colorMode,
451 colorModeLabels[colorBitOffset]));
452 }
453 final int selectedColorModeIndex = Integer.numberOfTrailingZeros(
454 (colorModes & mPrintAttributes.getColorMode()));
455 mColorModeSpinner.setSelection(selectedColorModeIndex);
456
457 // Fitting mode.
458 final int fittingModes = printer.getFittingModes();
459 mFittingModeSpinnerAdapter.clear();
460 String[] fittingModeLabels = getResources().getStringArray(
461 R.array.fitting_mode_labels);
462 int remainingFittingModes = fittingModes;
463 while (remainingFittingModes != 0) {
464 final int fittingBitOffset = Integer.numberOfTrailingZeros(remainingFittingModes);
465 final int fittingMode = 1 << fittingBitOffset;
466 remainingFittingModes &= ~fittingMode;
467 mFittingModeSpinnerAdapter.add(new SpinnerItem<Integer>(fittingMode,
468 fittingModeLabels[fittingBitOffset]));
469 }
470 final int selectedFittingModeIndex = Integer.numberOfTrailingZeros(
471 (fittingModes & mPrintAttributes.getFittingMode()));
472 mFittingModeSpinner.setSelection(selectedFittingModeIndex);
473
474 // Orientation.
475 final int orientations = printer.getOrientations();
476 mOrientationSpinnerAdapter.clear();
477 String[] orientationLabels = getResources().getStringArray(
478 R.array.orientation_labels);
479 int remainingOrientations = orientations;
480 while (remainingOrientations != 0) {
481 final int orientationBitOffset = Integer.numberOfTrailingZeros(remainingOrientations);
482 final int orientation = 1 << orientationBitOffset;
483 remainingOrientations &= ~orientation;
484 mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>(orientation,
485 orientationLabels[orientationBitOffset]));
486 }
487 final int selectedOrientationIndex = Integer.numberOfTrailingZeros(
488 (orientations & mPrintAttributes.getOrientation()));
489 mOrientationSpinner.setSelection(selectedOrientationIndex);
490 }
491
492 @Override
493 protected void onResume() {
494 super.onResume();
Svetoslav Ganova0027152013-06-25 14:59:53 -0700495 mPrintSpooler.startPrinterDiscovery(mPrinterDiscoveryObserver);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700496 notifyPrintableStartIfNeeded();
497 }
498
499 @Override
500 protected void onPause() {
501 super.onPause();
Svetoslav Ganova0027152013-06-25 14:59:53 -0700502 mPrintSpooler.stopPrinterDiscovery();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700503 notifyPrintableFinishIfNeeded();
504 }
505
506 @Override
507 public boolean onCreateOptionsMenu(Menu menu) {
508 getMenuInflater().inflate(R.menu.print_job_config_activity, menu);
509 return true;
510 }
511
512 @Override
513 public boolean onOptionsItemSelected(MenuItem item) {
514 if (item.getItemId() == R.id.print_button) {
515 mPrintConfirmed = true;
516 finish();
517 }
518 return super.onOptionsItemSelected(item);
519 }
520
521 private void notifyPrintableStartIfNeeded() {
522 if (mDestinationSpinner.getSelectedItemPosition() < 0
Svetoslav Ganova0027152013-06-25 14:59:53 -0700523 || mStarted) {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700524 return;
525 }
Svetoslav Ganova0027152013-06-25 14:59:53 -0700526 mStarted = true;
527 mRemotePrintAdapter.start();
528 updatePrintableContentIfNeeded();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700529 }
530
531 private void updatePrintableContentIfNeeded() {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700532 if (!mStarted) {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700533 return;
534 }
535
Svetoslav Ganova0027152013-06-25 14:59:53 -0700536 // TODO: Implement old attributes tracking
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700537 mPrintSpooler.setPrintJobAttributes(mPrintJobId, mPrintAttributes);
538
Svetoslav62836082013-07-17 14:52:35 -0700539 // TODO: Implement setting the print preview attribute
Svetoslav Ganova0027152013-06-25 14:59:53 -0700540 mRemotePrintAdapter.layout(new PrintAttributes.Builder().create(),
541 mPrintAttributes, new LayoutResultCallback() {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700542 @Override
Svetoslav Ganova0027152013-06-25 14:59:53 -0700543 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
544 // TODO: Handle the case of unchanged content
545 mPrintSpooler.setPrintJobPrintDocumentInfo(mPrintJobId, info);
546
547 // TODO: Implement page selector.
548 final List<PageRange> pages = new ArrayList<PageRange>();
549 pages.add(PageRange.ALL_PAGES);
550
551 mRemotePrintAdapter.write(pages, new WriteResultCallback() {
552 @Override
553 public void onWriteFinished(List<PageRange> pages) {
554 updatePrintPreview(mRemotePrintAdapter.getFile());
555 }
556
557 @Override
558 public void onWriteFailed(CharSequence error) {
559 Log.e(LOG_TAG, "Error write layout: " + error);
560 finishActivity(Activity.RESULT_CANCELED);
561 }
562 });
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700563 }
564
565 @Override
Svetoslav Ganova0027152013-06-25 14:59:53 -0700566 public void onLayoutFailed(CharSequence error) {
567 Log.e(LOG_TAG, "Error during layout: " + error);
568 finishActivity(Activity.RESULT_CANCELED);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700569 }
Svetoslav62836082013-07-17 14:52:35 -0700570 }, new Bundle());
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700571 }
572
573 private void notifyPrintableFinishIfNeeded() {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700574 if (!mStarted) {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700575 return;
576 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700577
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700578 if (!mPrintConfirmed) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700579 mRemotePrintAdapter.cancel();
580 }
581 mRemotePrintAdapter.finish();
582
583 // If canceled or no printer, nothing to do.
584 final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
585 if (!mPrintConfirmed || selectedIndex < 0) {
586 // Update the print job's status.
587 mPrintSpooler.setPrintJobState(mPrintJobId,
588 PrintJobInfo.STATE_CANCELED);
589 return;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700590 }
591
Svetoslav Ganova0027152013-06-25 14:59:53 -0700592 // Update the print job's printer.
593 SpinnerItem<PrinterInfo> printerItem =
594 mDestinationSpinnerAdapter.getItem(selectedIndex);
595 PrinterId printerId = printerItem.value.getId();
596 mPrintSpooler.setPrintJobPrinterId(mPrintJobId, printerId);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700597
Svetoslav Ganova0027152013-06-25 14:59:53 -0700598 // Update the print job's status.
599 mPrintSpooler.setPrintJobState(mPrintJobId,
600 PrintJobInfo.STATE_QUEUED);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700601
602 if (DEBUG) {
603 if (mPrintConfirmed) {
604 File file = mRemotePrintAdapter.getFile();
605 if (file.exists()) {
606 new ViewSpooledFileAsyncTask(file).executeOnExecutor(
607 AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
608 }
609 }
610 }
611 }
612
613 private void updatePrintPreview(File file) {
614 // TODO: Implement
615 }
616
617 private void addPrinters(List<PrinterInfo> addedPrinters) {
618 final int addedPrinterCount = addedPrinters.size();
619 for (int i = 0; i < addedPrinterCount; i++) {
620 PrinterInfo addedPrinter = addedPrinters.get(i);
621 boolean duplicate = false;
622 final int existingPrinterCount = mDestinationSpinnerAdapter.getCount();
623 for (int j = 0; j < existingPrinterCount; j++) {
624 PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value;
625 if (addedPrinter.getId().equals(existingPrinter.getId())) {
626 duplicate = true;
627 break;
628 }
629 }
630 if (!duplicate) {
631 mDestinationSpinnerAdapter.add(new SpinnerItem<PrinterInfo>(
632 addedPrinter, addedPrinter.getLabel()));
633 } else {
634 Log.w(LOG_TAG, "Skipping a duplicate printer: " + addedPrinter);
635 }
636 }
637 }
638
639 private void removePrinters(List<PrinterId> pritnerIds) {
640 final int printerIdCount = pritnerIds.size();
641 for (int i = 0; i < printerIdCount; i++) {
642 PrinterId removedPrinterId = pritnerIds.get(i);
643 boolean removed = false;
644 final int existingPrinterCount = mDestinationSpinnerAdapter.getCount();
645 for (int j = 0; j < existingPrinterCount; j++) {
646 PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value;
647 if (removedPrinterId.equals(existingPrinter.getId())) {
648 mDestinationSpinnerAdapter.remove(mDestinationSpinnerAdapter.getItem(j));
649 removed = true;
650 break;
651 }
652 }
653 if (!removed) {
654 Log.w(LOG_TAG, "Ignoring not added printer with id: " + removedPrinterId);
655 }
656 }
657 }
658
Svetoslav Ganova0027152013-06-25 14:59:53 -0700659 // Caution: Use this only for debugging
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700660 private final class ViewSpooledFileAsyncTask extends AsyncTask<Void, Void, Void> {
661
662 private final File mFile;
663
664 public ViewSpooledFileAsyncTask(File file) {
665 mFile = file;
666 }
667
668 @Override
669 protected Void doInBackground(Void... params) {
670 mFile.setExecutable(true, false);
671 mFile.setWritable(true, false);
672 mFile.setReadable(true, false);
673
674 final long identity = Binder.clearCallingIdentity();
675 Intent intent = new Intent(Intent.ACTION_VIEW);
676 intent.setDataAndType(Uri.fromFile(mFile), "application/pdf");
677 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
678 startActivityAsUser(intent, null, UserHandle.CURRENT);
679 Binder.restoreCallingIdentity(identity);
680 return null;
681 }
682 }
683
684 private final class PrintDiscoveryObserver extends IPrinterDiscoveryObserver.Stub {
685 private static final int MESSAGE_ADD_DICOVERED_PRINTERS = 1;
686 private static final int MESSAGE_REMOVE_DICOVERED_PRINTERS = 2;
687
688 private final Handler mHandler;
689
690 @SuppressWarnings("unchecked")
691 public PrintDiscoveryObserver(Looper looper) {
692 mHandler = new Handler(looper, null, true) {
693 @Override
694 public void handleMessage(Message message) {
695 switch (message.what) {
696 case MESSAGE_ADD_DICOVERED_PRINTERS: {
697 List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
698 addPrinters(printers);
699 // Just added the first printer, so select it and start printing.
700 if (mDestinationSpinnerAdapter.getCount() == 1) {
701 mDestinationSpinner.setSelection(0);
702 }
703 } break;
704 case MESSAGE_REMOVE_DICOVERED_PRINTERS: {
705 List<PrinterId> printerIds = (List<PrinterId>) message.obj;
706 removePrinters(printerIds);
707 // TODO: Handle removing the last printer.
708 } break;
709 }
710 }
711 };
712 }
713
714 @Override
715 public void addDiscoveredPrinters(List<PrinterInfo> printers) {
716 mHandler.obtainMessage(MESSAGE_ADD_DICOVERED_PRINTERS, printers).sendToTarget();
717 }
718
719 @Override
720 public void removeDiscoveredPrinters(List<PrinterId> printers) {
721 mHandler.obtainMessage(MESSAGE_REMOVE_DICOVERED_PRINTERS, printers).sendToTarget();
722 }
723 }
724
725 private final class SpinnerItem<T> {
726 final T value;
727 CharSequence label;
728
729 public SpinnerItem(T value, CharSequence label) {
730 this.value = value;
731 this.label = label;
732 }
733
734 public String toString() {
735 return label.toString();
736 }
737 }
738}