blob: 4ba04e53db2aea9758b4e79e0eabc19dc7a1f47c [file] [log] [blame]
Svetoslava798c0a2014-05-15 10:47:19 -07001/*
2 * Copyright (C) 2014 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.ui;
18
19import android.app.Activity;
20import android.app.Fragment;
21import android.app.FragmentTransaction;
22import android.content.ActivityNotFoundException;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
Svetoslav62ce3322014-09-04 21:17:17 -070026import android.content.ServiceConnection;
Svetoslava798c0a2014-05-15 10:47:19 -070027import android.content.pm.PackageInfo;
28import android.content.pm.PackageManager.NameNotFoundException;
29import android.content.pm.ResolveInfo;
Svet Ganov525a66b2014-06-14 22:29:00 -070030import android.content.res.Configuration;
Svetoslava798c0a2014-05-15 10:47:19 -070031import android.database.DataSetObserver;
32import android.graphics.drawable.Drawable;
33import android.net.Uri;
Svetoslav62ce3322014-09-04 21:17:17 -070034import android.os.AsyncTask;
Svetoslava798c0a2014-05-15 10:47:19 -070035import android.os.Bundle;
36import android.os.Handler;
37import android.os.IBinder;
Svetoslav62ce3322014-09-04 21:17:17 -070038import android.os.ParcelFileDescriptor;
39import android.os.RemoteException;
Svetoslava798c0a2014-05-15 10:47:19 -070040import android.print.IPrintDocumentAdapter;
41import android.print.PageRange;
42import android.print.PrintAttributes;
43import android.print.PrintAttributes.MediaSize;
44import android.print.PrintAttributes.Resolution;
45import android.print.PrintDocumentInfo;
46import android.print.PrintJobInfo;
47import android.print.PrintManager;
48import android.print.PrinterCapabilitiesInfo;
49import android.print.PrinterId;
50import android.print.PrinterInfo;
51import android.printservice.PrintService;
52import android.provider.DocumentsContract;
53import android.text.Editable;
54import android.text.TextUtils;
55import android.text.TextUtils.SimpleStringSplitter;
56import android.text.TextWatcher;
57import android.util.ArrayMap;
58import android.util.Log;
59import android.view.KeyEvent;
60import android.view.View;
61import android.view.View.OnClickListener;
62import android.view.View.OnFocusChangeListener;
63import android.view.ViewGroup;
64import android.view.inputmethod.InputMethodManager;
65import android.widget.AdapterView;
66import android.widget.AdapterView.OnItemSelectedListener;
67import android.widget.ArrayAdapter;
68import android.widget.BaseAdapter;
69import android.widget.Button;
70import android.widget.EditText;
71import android.widget.ImageView;
72import android.widget.Spinner;
73import android.widget.TextView;
74
75import com.android.printspooler.R;
Svet Ganov525a66b2014-06-14 22:29:00 -070076import com.android.printspooler.model.MutexFileProvider;
Svetoslava798c0a2014-05-15 10:47:19 -070077import com.android.printspooler.model.PrintSpoolerProvider;
78import com.android.printspooler.model.PrintSpoolerService;
79import com.android.printspooler.model.RemotePrintDocument;
Svet Ganov525a66b2014-06-14 22:29:00 -070080import com.android.printspooler.model.RemotePrintDocument.RemotePrintDocumentInfo;
Svetoslav62ce3322014-09-04 21:17:17 -070081import com.android.printspooler.renderer.IPdfEditor;
82import com.android.printspooler.renderer.PdfManipulationService;
Svetoslava798c0a2014-05-15 10:47:19 -070083import com.android.printspooler.util.MediaSizeUtils;
84import com.android.printspooler.util.MediaSizeUtils.MediaSizeComparator;
85import com.android.printspooler.util.PageRangeUtils;
86import com.android.printspooler.util.PrintOptionUtils;
Svet Ganov525a66b2014-06-14 22:29:00 -070087import com.android.printspooler.widget.PrintContentView;
88import com.android.printspooler.widget.PrintContentView.OptionsStateChangeListener;
89import com.android.printspooler.widget.PrintContentView.OptionsStateController;
Svetoslav62ce3322014-09-04 21:17:17 -070090import libcore.io.IoUtils;
91import libcore.io.Streams;
Svetoslava798c0a2014-05-15 10:47:19 -070092
Svetoslav62ce3322014-09-04 21:17:17 -070093import java.io.File;
94import java.io.FileInputStream;
95import java.io.FileOutputStream;
Svet Ganov525a66b2014-06-14 22:29:00 -070096import java.io.IOException;
Svetoslav62ce3322014-09-04 21:17:17 -070097import java.io.InputStream;
98import java.io.OutputStream;
Svet Ganov525a66b2014-06-14 22:29:00 -070099import java.util.ArrayList;
100import java.util.Arrays;
101import java.util.Collection;
102import java.util.Collections;
103import java.util.List;
Svetoslava798c0a2014-05-15 10:47:19 -0700104import java.util.regex.Matcher;
105import java.util.regex.Pattern;
106
107public class PrintActivity extends Activity implements RemotePrintDocument.UpdateResultCallbacks,
Svetoslav5ef522b2014-07-23 20:15:09 -0700108 PrintErrorFragment.OnActionListener, PageAdapter.ContentCallbacks,
Svet Ganov525a66b2014-06-14 22:29:00 -0700109 OptionsStateChangeListener, OptionsStateController {
Svetoslava798c0a2014-05-15 10:47:19 -0700110 private static final String LOG_TAG = "PrintActivity";
111
Svetoslavf8ffa562014-07-23 18:22:03 -0700112 private static final boolean DEBUG = false;
Svet Ganov525a66b2014-06-14 22:29:00 -0700113
Svetoslava798c0a2014-05-15 10:47:19 -0700114 public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
115
Svet Ganov525a66b2014-06-14 22:29:00 -0700116 private static final String FRAGMENT_TAG = "FRAGMENT_TAG";
117
Svetoslava798c0a2014-05-15 10:47:19 -0700118 private static final int ORIENTATION_PORTRAIT = 0;
119 private static final int ORIENTATION_LANDSCAPE = 1;
120
121 private static final int ACTIVITY_REQUEST_CREATE_FILE = 1;
122 private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2;
123 private static final int ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS = 3;
124
125 private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
126
127 private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
128 private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1;
129
Svetoslav6552bf32014-09-03 21:15:55 -0700130 private static final int STATE_INITIALIZING = 0;
131 private static final int STATE_CONFIGURING = 1;
132 private static final int STATE_PRINT_CONFIRMED = 2;
133 private static final int STATE_PRINT_CANCELED = 3;
134 private static final int STATE_UPDATE_FAILED = 4;
135 private static final int STATE_CREATE_FILE_FAILED = 5;
136 private static final int STATE_PRINTER_UNAVAILABLE = 6;
137 private static final int STATE_UPDATE_SLOW = 7;
138 private static final int STATE_PRINT_COMPLETED = 8;
Svetoslava798c0a2014-05-15 10:47:19 -0700139
140 private static final int UI_STATE_PREVIEW = 0;
141 private static final int UI_STATE_ERROR = 1;
142 private static final int UI_STATE_PROGRESS = 2;
143
144 private static final int MIN_COPIES = 1;
145 private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES);
146
147 private static final Pattern PATTERN_DIGITS = Pattern.compile("[\\d]+");
148
149 private static final Pattern PATTERN_ESCAPE_SPECIAL_CHARS = Pattern.compile(
150 "(?=[]\\[+&|!(){}^\"~*?:\\\\])");
151
152 private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
Svet Ganov525a66b2014-06-14 22:29:00 -0700153 "[\\s]*[0-9]+[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
154 + "[\\s]*[0-9]+[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
Svetoslava798c0a2014-05-15 10:47:19 -0700155
Svet Ganov525a66b2014-06-14 22:29:00 -0700156 public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[]{PageRange.ALL_PAGES};
Svetoslava798c0a2014-05-15 10:47:19 -0700157
158 private final PrinterAvailabilityDetector mPrinterAvailabilityDetector =
159 new PrinterAvailabilityDetector();
160
161 private final SimpleStringSplitter mStringCommaSplitter = new SimpleStringSplitter(',');
162
163 private final OnFocusChangeListener mSelectAllOnFocusListener = new SelectAllOnFocusListener();
164
165 private PrintSpoolerProvider mSpoolerProvider;
166
Svet Ganov525a66b2014-06-14 22:29:00 -0700167 private PrintPreviewController mPrintPreviewController;
168
Svetoslava798c0a2014-05-15 10:47:19 -0700169 private PrintJobInfo mPrintJob;
170 private RemotePrintDocument mPrintedDocument;
171 private PrinterRegistry mPrinterRegistry;
172
173 private EditText mCopiesEditText;
174
Svetoslava798c0a2014-05-15 10:47:19 -0700175 private TextView mPageRangeTitle;
176 private EditText mPageRangeEditText;
177
178 private Spinner mDestinationSpinner;
179 private DestinationAdapter mDestinationSpinnerAdapter;
180
181 private Spinner mMediaSizeSpinner;
182 private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
183
184 private Spinner mColorModeSpinner;
185 private ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
186
Svetoslav948c9a62015-02-02 19:47:04 -0800187 private Spinner mDuplexModeSpinner;
188 private ArrayAdapter<SpinnerItem<Integer>> mDuplexModeSpinnerAdapter;
189
Svetoslava798c0a2014-05-15 10:47:19 -0700190 private Spinner mOrientationSpinner;
191 private ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
192
193 private Spinner mRangeOptionsSpinner;
194
Svet Ganov525a66b2014-06-14 22:29:00 -0700195 private PrintContentView mOptionsContent;
Svetoslava798c0a2014-05-15 10:47:19 -0700196
Svetoslave652b022014-09-09 22:11:10 -0700197 private View mSummaryContainer;
Svetoslava798c0a2014-05-15 10:47:19 -0700198 private TextView mSummaryCopies;
199 private TextView mSummaryPaperSize;
200
Svetoslava798c0a2014-05-15 10:47:19 -0700201 private Button mMoreOptionsButton;
202
203 private ImageView mPrintButton;
204
205 private ProgressMessageController mProgressMessageController;
Svetoslav62ce3322014-09-04 21:17:17 -0700206 private MutexFileProvider mFileProvider;
Svetoslava798c0a2014-05-15 10:47:19 -0700207
208 private MediaSizeComparator mMediaSizeComparator;
209
Svet Ganov48fec5c2014-07-14 00:14:07 -0700210 private PrinterInfo mCurrentPrinter;
Svetoslava798c0a2014-05-15 10:47:19 -0700211
Svet Ganov525a66b2014-06-14 22:29:00 -0700212 private PageRange[] mSelectedPages;
213
Svetoslava798c0a2014-05-15 10:47:19 -0700214 private String mCallingPackageName;
215
Svetoslav73764e32014-07-15 15:56:46 -0700216 private int mCurrentPageCount;
217
Svetoslav6552bf32014-09-03 21:15:55 -0700218 private int mState = STATE_INITIALIZING;
Svetoslava798c0a2014-05-15 10:47:19 -0700219
220 private int mUiState = UI_STATE_PREVIEW;
221
222 @Override
223 public void onCreate(Bundle savedInstanceState) {
224 super.onCreate(savedInstanceState);
225
226 Bundle extras = getIntent().getExtras();
227
228 mPrintJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB);
229 if (mPrintJob == null) {
230 throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_JOB
231 + " cannot be null");
232 }
233 mPrintJob.setAttributes(new PrintAttributes.Builder().build());
234
235 final IBinder adapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
236 if (adapter == null) {
237 throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER
238 + " cannot be null");
239 }
240
241 mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
242
243 // This will take just a few milliseconds, so just wait to
244 // bind to the local service before showing the UI.
245 mSpoolerProvider = new PrintSpoolerProvider(this,
246 new Runnable() {
247 @Override
248 public void run() {
Svet Ganov525a66b2014-06-14 22:29:00 -0700249 onConnectedToPrintSpooler(adapter);
Svetoslava798c0a2014-05-15 10:47:19 -0700250 }
251 });
252 }
253
Svet Ganov525a66b2014-06-14 22:29:00 -0700254 private void onConnectedToPrintSpooler(final IBinder documentAdapter) {
255 // Now that we are bound to the print spooler service,
256 // create the printer registry and wait for it to get
257 // the first batch of results which will be delivered
258 // after reading historical data. This should be pretty
259 // fast, so just wait before showing the UI.
260 mPrinterRegistry = new PrinterRegistry(PrintActivity.this,
261 new Runnable() {
262 @Override
263 public void run() {
264 onPrinterRegistryReady(documentAdapter);
265 }
266 });
267 }
268
269 private void onPrinterRegistryReady(IBinder documentAdapter) {
270 // Now that we are bound to the local print spooler service
271 // and the printer registry loaded the historical printers
272 // we can show the UI without flickering.
273 setTitle(R.string.print_dialog);
274 setContentView(R.layout.print_activity);
275
Svet Ganov525a66b2014-06-14 22:29:00 -0700276 try {
Svetoslav62ce3322014-09-04 21:17:17 -0700277 mFileProvider = new MutexFileProvider(
Svet Ganov525a66b2014-06-14 22:29:00 -0700278 PrintSpoolerService.generateFileForPrintJob(
279 PrintActivity.this, mPrintJob.getId()));
280 } catch (IOException ioe) {
281 // At this point we cannot recover, so just take it down.
282 throw new IllegalStateException("Cannot create print job file", ioe);
283 }
284
285 mPrintPreviewController = new PrintPreviewController(PrintActivity.this,
Svetoslav62ce3322014-09-04 21:17:17 -0700286 mFileProvider);
Svet Ganov525a66b2014-06-14 22:29:00 -0700287 mPrintedDocument = new RemotePrintDocument(PrintActivity.this,
288 IPrintDocumentAdapter.Stub.asInterface(documentAdapter),
Svetoslave17123d2014-09-11 12:39:05 -0700289 mFileProvider, new RemotePrintDocument.RemoteAdapterDeathObserver() {
Svet Ganov525a66b2014-06-14 22:29:00 -0700290 @Override
Svetoslave17123d2014-09-11 12:39:05 -0700291 public void onDied() {
Svetoslav05e041b2014-10-14 14:14:49 -0700292 // If we are finishing or we are in a state that we do not need any
293 // data from the printing app, then no need to finish.
294 if (isFinishing() || (isFinalState(mState) && !mPrintedDocument.isUpdating())) {
Svetoslave17123d2014-09-11 12:39:05 -0700295 return;
296 }
Svet Ganovf6cd14d2014-11-20 07:43:30 -0800297 if (mPrintedDocument.isUpdating()) {
298 mPrintedDocument.cancel();
299 }
Svetoslav62ce3322014-09-04 21:17:17 -0700300 setState(STATE_PRINT_CANCELED);
Svetoslave17123d2014-09-11 12:39:05 -0700301 doFinish();
Svet Ganov525a66b2014-06-14 22:29:00 -0700302 }
303 }, PrintActivity.this);
Svet Ganov525a66b2014-06-14 22:29:00 -0700304 mProgressMessageController = new ProgressMessageController(
305 PrintActivity.this);
Svet Ganov525a66b2014-06-14 22:29:00 -0700306 mMediaSizeComparator = new MediaSizeComparator(PrintActivity.this);
Svet Ganov525a66b2014-06-14 22:29:00 -0700307 mDestinationSpinnerAdapter = new DestinationAdapter();
308
309 bindUi();
Svet Ganov525a66b2014-06-14 22:29:00 -0700310 updateOptionsUi();
311
312 // Now show the updated UI to avoid flicker.
313 mOptionsContent.setVisibility(View.VISIBLE);
Svet Ganov525a66b2014-06-14 22:29:00 -0700314 mSelectedPages = computeSelectedPages();
Svet Ganov525a66b2014-06-14 22:29:00 -0700315 mPrintedDocument.start();
316
317 ensurePreviewUiShown();
Svetoslav6552bf32014-09-03 21:15:55 -0700318
319 setState(STATE_CONFIGURING);
Svet Ganov525a66b2014-06-14 22:29:00 -0700320 }
321
Svetoslava798c0a2014-05-15 10:47:19 -0700322 @Override
Svetoslavd724a402014-09-16 11:53:15 -0700323 public void onResume() {
324 super.onResume();
325 if (mState != STATE_INITIALIZING && mCurrentPrinter != null) {
326 mPrinterRegistry.setTrackedPrinter(mCurrentPrinter.getId());
327 }
328 }
329
330 @Override
Svetoslava798c0a2014-05-15 10:47:19 -0700331 public void onPause() {
Svetoslav3ef8e202014-09-10 14:35:58 -0700332 PrintSpoolerService spooler = mSpoolerProvider.getSpooler();
333
Svetoslav6552bf32014-09-03 21:15:55 -0700334 if (mState == STATE_INITIALIZING) {
Svetoslav3ef8e202014-09-10 14:35:58 -0700335 if (isFinishing()) {
336 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
337 }
Svetoslav6552bf32014-09-03 21:15:55 -0700338 super.onPause();
339 return;
340 }
341
Svetoslava798c0a2014-05-15 10:47:19 -0700342 if (isFinishing()) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700343 spooler.updatePrintJobUserConfigurableOptionsNoPersistence(mPrintJob);
344
345 switch (mState) {
346 case STATE_PRINT_CONFIRMED: {
347 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_QUEUED, null);
348 } break;
349
Svetoslavb59555c2014-07-24 10:13:00 -0700350 case STATE_PRINT_COMPLETED: {
351 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_COMPLETED, null);
352 } break;
353
Svet Ganov525a66b2014-06-14 22:29:00 -0700354 case STATE_CREATE_FILE_FAILED: {
355 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_FAILED,
356 getString(R.string.print_write_error_message));
357 } break;
358
359 default: {
360 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
361 } break;
Svetoslava798c0a2014-05-15 10:47:19 -0700362 }
Svetoslava798c0a2014-05-15 10:47:19 -0700363 }
364
365 mPrinterAvailabilityDetector.cancel();
Svetoslavd724a402014-09-16 11:53:15 -0700366 mPrinterRegistry.setTrackedPrinter(null);
Svetoslava798c0a2014-05-15 10:47:19 -0700367
368 super.onPause();
369 }
370
371 @Override
372 public boolean onKeyDown(int keyCode, KeyEvent event) {
373 if (keyCode == KeyEvent.KEYCODE_BACK) {
374 event.startTracking();
375 return true;
376 }
377 return super.onKeyDown(keyCode, event);
378 }
379
380 @Override
381 public boolean onKeyUp(int keyCode, KeyEvent event) {
Svetoslav6552bf32014-09-03 21:15:55 -0700382 if (mState == STATE_INITIALIZING) {
Svetoslave17123d2014-09-11 12:39:05 -0700383 doFinish();
384 return true;
385 }
386
Svet Ganovfce84f02014-10-31 16:56:52 -0700387 if (mState == STATE_PRINT_CANCELED || mState == STATE_PRINT_CONFIRMED
Svetoslave17123d2014-09-11 12:39:05 -0700388 || mState == STATE_PRINT_COMPLETED) {
Svetoslav3ef8e202014-09-10 14:35:58 -0700389 return true;
Svetoslav6552bf32014-09-03 21:15:55 -0700390 }
391
Svetoslava798c0a2014-05-15 10:47:19 -0700392 if (keyCode == KeyEvent.KEYCODE_BACK
393 && event.isTracking() && !event.isCanceled()) {
Svetoslav6552bf32014-09-03 21:15:55 -0700394 if (mPrintPreviewController != null && mPrintPreviewController.isOptionsOpened()
Svetoslav15cbc8a2014-07-11 09:45:07 -0700395 && !hasErrors()) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700396 mPrintPreviewController.closeOptions();
397 } else {
398 cancelPrint();
399 }
Svetoslava798c0a2014-05-15 10:47:19 -0700400 return true;
401 }
402 return super.onKeyUp(keyCode, event);
403 }
404
405 @Override
Svetoslav5ef522b2014-07-23 20:15:09 -0700406 public void onRequestContentUpdate() {
Svet Ganov525a66b2014-06-14 22:29:00 -0700407 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -0700408 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -0700409 }
410 }
411
412 @Override
Svetoslav5ef522b2014-07-23 20:15:09 -0700413 public void onMalformedPdfFile() {
Svet Ganovfce84f02014-10-31 16:56:52 -0700414 onPrintDocumentError("Cannot print a malformed PDF file");
415 }
416
417 @Override
418 public void onSecurePdfFile() {
419 onPrintDocumentError("Cannot print a password protected PDF file");
420 }
421
422 private void onPrintDocumentError(String message) {
Svetoslav5ef522b2014-07-23 20:15:09 -0700423 mProgressMessageController.cancel();
424 ensureErrorUiShown(null, PrintErrorFragment.ACTION_RETRY);
425
426 setState(STATE_UPDATE_FAILED);
427
428 updateOptionsUi();
Svet Ganovfce84f02014-10-31 16:56:52 -0700429
430 mPrintedDocument.kill(message);
Svetoslav5ef522b2014-07-23 20:15:09 -0700431 }
432
433 @Override
Svet Ganov525a66b2014-06-14 22:29:00 -0700434 public void onActionPerformed() {
Svetoslav5ef522b2014-07-23 20:15:09 -0700435 if (mState == STATE_UPDATE_FAILED
Svetoslav62ce3322014-09-04 21:17:17 -0700436 && canUpdateDocument() && updateDocument(true)) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700437 ensurePreviewUiShown();
438 setState(STATE_CONFIGURING);
439 updateOptionsUi();
Svetoslava798c0a2014-05-15 10:47:19 -0700440 }
441 }
442
443 public void onUpdateCanceled() {
Svet Ganov525a66b2014-06-14 22:29:00 -0700444 if (DEBUG) {
445 Log.i(LOG_TAG, "onUpdateCanceled()");
446 }
447
Svetoslava798c0a2014-05-15 10:47:19 -0700448 mProgressMessageController.cancel();
449 ensurePreviewUiShown();
Svet Ganov525a66b2014-06-14 22:29:00 -0700450
451 switch (mState) {
452 case STATE_PRINT_CONFIRMED: {
453 requestCreatePdfFileOrFinish();
454 } break;
455
456 case STATE_PRINT_CANCELED: {
Svetoslave17123d2014-09-11 12:39:05 -0700457 doFinish();
Svet Ganov525a66b2014-06-14 22:29:00 -0700458 } break;
459 }
Svetoslava798c0a2014-05-15 10:47:19 -0700460 }
461
462 @Override
Svet Ganov525a66b2014-06-14 22:29:00 -0700463 public void onUpdateCompleted(RemotePrintDocumentInfo document) {
464 if (DEBUG) {
465 Log.i(LOG_TAG, "onUpdateCompleted()");
466 }
467
Svetoslava798c0a2014-05-15 10:47:19 -0700468 mProgressMessageController.cancel();
469 ensurePreviewUiShown();
470
471 // Update the print job with the info for the written document. The page
472 // count we get from the remote document is the pages in the document from
473 // the app perspective but the print job should contain the page count from
474 // print service perspective which is the pages in the written PDF not the
475 // pages in the printed document.
476 PrintDocumentInfo info = document.info;
Svet Ganov525a66b2014-06-14 22:29:00 -0700477 if (info != null) {
478 final int pageCount = PageRangeUtils.getNormalizedPageCount(document.writtenPages,
479 getAdjustedPageCount(info));
480 PrintDocumentInfo adjustedInfo = new PrintDocumentInfo.Builder(info.getName())
481 .setContentType(info.getContentType())
482 .setPageCount(pageCount)
483 .build();
484 mPrintJob.setDocumentInfo(adjustedInfo);
485 mPrintJob.setPages(document.printedPages);
Svetoslava798c0a2014-05-15 10:47:19 -0700486 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700487
488 switch (mState) {
489 case STATE_PRINT_CONFIRMED: {
490 requestCreatePdfFileOrFinish();
491 } break;
492
Svet Ganoveaaf0512014-11-26 04:09:27 -0800493 case STATE_PRINT_CANCELED: {
494 updateOptionsUi();
495 } break;
496
Svet Ganov525a66b2014-06-14 22:29:00 -0700497 default: {
498 updatePrintPreviewController(document.changed);
499
500 setState(STATE_CONFIGURING);
501 updateOptionsUi();
502 } break;
503 }
Svetoslava798c0a2014-05-15 10:47:19 -0700504 }
505
506 @Override
507 public void onUpdateFailed(CharSequence error) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700508 if (DEBUG) {
509 Log.i(LOG_TAG, "onUpdateFailed()");
510 }
511
512 mProgressMessageController.cancel();
Svetoslava798c0a2014-05-15 10:47:19 -0700513 ensureErrorUiShown(error, PrintErrorFragment.ACTION_RETRY);
Svet Ganov525a66b2014-06-14 22:29:00 -0700514
515 setState(STATE_UPDATE_FAILED);
516
Svetoslava798c0a2014-05-15 10:47:19 -0700517 updateOptionsUi();
518 }
519
520 @Override
Svet Ganov525a66b2014-06-14 22:29:00 -0700521 public void onOptionsOpened() {
522 updateSelectedPagesFromPreview();
523 }
524
525 @Override
526 public void onOptionsClosed() {
527 PageRange[] selectedPages = computeSelectedPages();
528 if (!Arrays.equals(mSelectedPages, selectedPages)) {
529 mSelectedPages = selectedPages;
530
531 // Update preview.
532 updatePrintPreviewController(false);
533 }
534
535 // Make sure the IME is not on the way of preview as
536 // the user may have used it to type copies or range.
537 InputMethodManager imm = (InputMethodManager) getSystemService(
538 Context.INPUT_METHOD_SERVICE);
539 imm.hideSoftInputFromWindow(mDestinationSpinner.getWindowToken(), 0);
540 }
541
542 private void updatePrintPreviewController(boolean contentUpdated) {
543 // If we have not heard from the application, do nothing.
544 RemotePrintDocumentInfo documentInfo = mPrintedDocument.getDocumentInfo();
545 if (!documentInfo.laidout) {
546 return;
547 }
548
549 // Update the preview controller.
550 mPrintPreviewController.onContentUpdated(contentUpdated,
551 getAdjustedPageCount(documentInfo.info),
552 mPrintedDocument.getDocumentInfo().writtenPages,
553 mSelectedPages, mPrintJob.getAttributes().getMediaSize(),
554 mPrintJob.getAttributes().getMinMargins());
555 }
556
557
558 @Override
559 public boolean canOpenOptions() {
560 return true;
561 }
562
563 @Override
564 public boolean canCloseOptions() {
565 return !hasErrors();
566 }
567
568 @Override
569 public void onConfigurationChanged(Configuration newConfig) {
570 super.onConfigurationChanged(newConfig);
Svet Ganovf6cd14d2014-11-20 07:43:30 -0800571 if (mPrintPreviewController != null) {
572 mPrintPreviewController.onOrientationChanged();
573 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700574 }
575
576 @Override
Svetoslava798c0a2014-05-15 10:47:19 -0700577 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
578 switch (requestCode) {
579 case ACTIVITY_REQUEST_CREATE_FILE: {
580 onStartCreateDocumentActivityResult(resultCode, data);
Svetoslav5ef522b2014-07-23 20:15:09 -0700581 } break;
Svetoslava798c0a2014-05-15 10:47:19 -0700582
583 case ACTIVITY_REQUEST_SELECT_PRINTER: {
584 onSelectPrinterActivityResult(resultCode, data);
Svetoslav5ef522b2014-07-23 20:15:09 -0700585 } break;
Svetoslava798c0a2014-05-15 10:47:19 -0700586
587 case ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS: {
588 onAdvancedPrintOptionsActivityResult(resultCode, data);
Svetoslav5ef522b2014-07-23 20:15:09 -0700589 } break;
Svetoslava798c0a2014-05-15 10:47:19 -0700590 }
591 }
592
593 private void startCreateDocumentActivity() {
Svetoslave1dcb392014-09-26 19:49:14 -0700594 if (!isResumed()) {
595 return;
596 }
Svetoslava798c0a2014-05-15 10:47:19 -0700597 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
598 if (info == null) {
599 return;
600 }
601 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
602 intent.setType("application/pdf");
603 intent.putExtra(Intent.EXTRA_TITLE, info.getName());
604 intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
605 startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
606 }
607
608 private void onStartCreateDocumentActivityResult(int resultCode, Intent data) {
609 if (resultCode == RESULT_OK && data != null) {
Svetoslavb59555c2014-07-24 10:13:00 -0700610 setState(STATE_PRINT_COMPLETED);
611 updateOptionsUi();
Svetoslav62ce3322014-09-04 21:17:17 -0700612 final Uri uri = data.getData();
Svet Ganov525a66b2014-06-14 22:29:00 -0700613 // Calling finish here does not invoke lifecycle callbacks but we
614 // update the print job in onPause if finishing, hence post a message.
615 mDestinationSpinner.post(new Runnable() {
616 @Override
617 public void run() {
Svetoslavbec22be2014-09-25 13:03:20 -0700618 transformDocumentAndFinish(uri);
Svet Ganov525a66b2014-06-14 22:29:00 -0700619 }
620 });
Svetoslava798c0a2014-05-15 10:47:19 -0700621 } else if (resultCode == RESULT_CANCELED) {
Svetoslavb75632c2014-09-17 18:38:27 -0700622 mState = STATE_CONFIGURING;
Svetoslava798c0a2014-05-15 10:47:19 -0700623 updateOptionsUi();
624 } else {
Svet Ganov525a66b2014-06-14 22:29:00 -0700625 setState(STATE_CREATE_FILE_FAILED);
Svetoslava798c0a2014-05-15 10:47:19 -0700626 updateOptionsUi();
Svet Ganov525a66b2014-06-14 22:29:00 -0700627 // Calling finish here does not invoke lifecycle callbacks but we
628 // update the print job in onPause if finishing, hence post a message.
629 mDestinationSpinner.post(new Runnable() {
630 @Override
631 public void run() {
Svetoslave17123d2014-09-11 12:39:05 -0700632 doFinish();
Svet Ganov525a66b2014-06-14 22:29:00 -0700633 }
634 });
Svetoslava798c0a2014-05-15 10:47:19 -0700635 }
636 }
637
638 private void startSelectPrinterActivity() {
639 Intent intent = new Intent(this, SelectPrinterActivity.class);
640 startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER);
641 }
642
643 private void onSelectPrinterActivityResult(int resultCode, Intent data) {
644 if (resultCode == RESULT_OK && data != null) {
645 PrinterId printerId = data.getParcelableExtra(INTENT_EXTRA_PRINTER_ID);
646 if (printerId != null) {
647 mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerId);
648 final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
649 if (index != AdapterView.INVALID_POSITION) {
650 mDestinationSpinner.setSelection(index);
651 return;
652 }
653 }
654 }
655
Svet Ganov48fec5c2014-07-14 00:14:07 -0700656 PrinterId printerId = mCurrentPrinter.getId();
Svetoslava798c0a2014-05-15 10:47:19 -0700657 final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
658 mDestinationSpinner.setSelection(index);
659 }
660
661 private void startAdvancedPrintOptionsActivity(PrinterInfo printer) {
662 ComponentName serviceName = printer.getId().getServiceName();
663
664 String activityName = PrintOptionUtils.getAdvancedOptionsActivityName(this, serviceName);
665 if (TextUtils.isEmpty(activityName)) {
666 return;
667 }
668
669 Intent intent = new Intent(Intent.ACTION_MAIN);
670 intent.setComponent(new ComponentName(serviceName.getPackageName(), activityName));
671
672 List<ResolveInfo> resolvedActivities = getPackageManager()
673 .queryIntentActivities(intent, 0);
674 if (resolvedActivities.isEmpty()) {
675 return;
676 }
677
678 // The activity is a component name, therefore it is one or none.
679 if (resolvedActivities.get(0).activityInfo.exported) {
680 intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, mPrintJob);
681 intent.putExtra(PrintService.EXTRA_PRINTER_INFO, printer);
682
683 // This is external activity and may not be there.
684 try {
685 startActivityForResult(intent, ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS);
686 } catch (ActivityNotFoundException anfe) {
687 Log.e(LOG_TAG, "Error starting activity for intent: " + intent, anfe);
688 }
689 }
690 }
691
692 private void onAdvancedPrintOptionsActivityResult(int resultCode, Intent data) {
693 if (resultCode != RESULT_OK || data == null) {
694 return;
695 }
696
697 PrintJobInfo printJobInfo = data.getParcelableExtra(PrintService.EXTRA_PRINT_JOB_INFO);
698
699 if (printJobInfo == null) {
700 return;
701 }
702
703 // Take the advanced options without interpretation.
704 mPrintJob.setAdvancedOptions(printJobInfo.getAdvancedOptions());
705
706 // Take copies without interpretation as the advanced print dialog
707 // cannot create a print job info with invalid copies.
708 mCopiesEditText.setText(String.valueOf(printJobInfo.getCopies()));
709 mPrintJob.setCopies(printJobInfo.getCopies());
710
711 PrintAttributes currAttributes = mPrintJob.getAttributes();
712 PrintAttributes newAttributes = printJobInfo.getAttributes();
713
Svet Ganov2eb7fad2014-10-01 17:49:16 -0700714 if (newAttributes != null) {
715 // Take the media size only if the current printer supports is.
716 MediaSize oldMediaSize = currAttributes.getMediaSize();
717 MediaSize newMediaSize = newAttributes.getMediaSize();
718 if (!oldMediaSize.equals(newMediaSize)) {
719 final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount();
720 MediaSize newMediaSizePortrait = newAttributes.getMediaSize().asPortrait();
721 for (int i = 0; i < mediaSizeCount; i++) {
722 MediaSize supportedSizePortrait = mMediaSizeSpinnerAdapter.getItem(i)
723 .value.asPortrait();
724 if (supportedSizePortrait.equals(newMediaSizePortrait)) {
725 currAttributes.setMediaSize(newMediaSize);
726 mMediaSizeSpinner.setSelection(i);
727 if (currAttributes.getMediaSize().isPortrait()) {
728 if (mOrientationSpinner.getSelectedItemPosition() != 0) {
729 mOrientationSpinner.setSelection(0);
730 }
731 } else {
732 if (mOrientationSpinner.getSelectedItemPosition() != 1) {
733 mOrientationSpinner.setSelection(1);
734 }
Svetoslava798c0a2014-05-15 10:47:19 -0700735 }
Svet Ganov2eb7fad2014-10-01 17:49:16 -0700736 break;
Svetoslava798c0a2014-05-15 10:47:19 -0700737 }
Svetoslava798c0a2014-05-15 10:47:19 -0700738 }
739 }
Svetoslava798c0a2014-05-15 10:47:19 -0700740
Svet Ganov2eb7fad2014-10-01 17:49:16 -0700741 // Take the resolution only if the current printer supports is.
742 Resolution oldResolution = currAttributes.getResolution();
743 Resolution newResolution = newAttributes.getResolution();
744 if (!oldResolution.equals(newResolution)) {
745 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
746 if (capabilities != null) {
747 List<Resolution> resolutions = capabilities.getResolutions();
748 final int resolutionCount = resolutions.size();
749 for (int i = 0; i < resolutionCount; i++) {
750 Resolution resolution = resolutions.get(i);
751 if (resolution.equals(newResolution)) {
752 currAttributes.setResolution(resolution);
753 break;
754 }
755 }
756 }
757 }
758
759 // Take the color mode only if the current printer supports it.
760 final int currColorMode = currAttributes.getColorMode();
761 final int newColorMode = newAttributes.getColorMode();
762 if (currColorMode != newColorMode) {
763 final int colorModeCount = mColorModeSpinner.getCount();
764 for (int i = 0; i < colorModeCount; i++) {
765 final int supportedColorMode = mColorModeSpinnerAdapter.getItem(i).value;
766 if (supportedColorMode == newColorMode) {
767 currAttributes.setColorMode(newColorMode);
768 mColorModeSpinner.setSelection(i);
769 break;
770 }
Svetoslava798c0a2014-05-15 10:47:19 -0700771 }
772 }
Svetoslav948c9a62015-02-02 19:47:04 -0800773
774 // Take the duplex mode only if the current printer supports it.
775 final int currDuplexMode = currAttributes.getDuplexMode();
776 final int newDuplexMode = newAttributes.getDuplexMode();
777 if (currDuplexMode != newDuplexMode) {
778 final int duplexModeCount = mDuplexModeSpinner.getCount();
779 for (int i = 0; i < duplexModeCount; i++) {
780 final int supportedDuplexMode = mDuplexModeSpinnerAdapter.getItem(i).value;
781 if (supportedDuplexMode == newDuplexMode) {
782 currAttributes.setDuplexMode(newDuplexMode);
783 mDuplexModeSpinner.setSelection(i);
784 break;
785 }
786 }
787 }
Svetoslava798c0a2014-05-15 10:47:19 -0700788 }
789
Svetoslav528424c2014-09-26 19:11:29 -0700790 // Handle selected page changes making sure they are in the doc.
Svet Ganov525a66b2014-06-14 22:29:00 -0700791 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
792 final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0;
Svetoslava798c0a2014-05-15 10:47:19 -0700793 PageRange[] pageRanges = printJobInfo.getPages();
Svetoslav528424c2014-09-26 19:11:29 -0700794 if (pageRanges != null && pageCount > 0) {
795 pageRanges = PageRangeUtils.normalize(pageRanges);
796
797 List<PageRange> validatedList = new ArrayList<>();
798 final int rangeCount = pageRanges.length;
799 for (int i = 0; i < rangeCount; i++) {
800 PageRange pageRange = pageRanges[i];
801 if (pageRange.getEnd() >= pageCount) {
802 final int rangeStart = pageRange.getStart();
803 final int rangeEnd = pageCount - 1;
804 if (rangeStart <= rangeEnd) {
805 pageRange = new PageRange(rangeStart, rangeEnd);
806 validatedList.add(pageRange);
807 }
808 break;
809 }
810 validatedList.add(pageRange);
811 }
812
813 if (!validatedList.isEmpty()) {
814 PageRange[] validatedArray = new PageRange[validatedList.size()];
815 validatedList.toArray(validatedArray);
816 updateSelectedPages(validatedArray, pageCount);
817 }
818 }
Svetoslava798c0a2014-05-15 10:47:19 -0700819
820 // Update the content if needed.
821 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -0700822 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -0700823 }
824 }
825
Svet Ganov525a66b2014-06-14 22:29:00 -0700826 private void setState(int state) {
827 if (isFinalState(mState)) {
828 if (isFinalState(state)) {
829 mState = state;
830 }
831 } else {
832 mState = state;
833 }
834 }
835
836 private static boolean isFinalState(int state) {
837 return state == STATE_PRINT_CONFIRMED
Svetoslavb59555c2014-07-24 10:13:00 -0700838 || state == STATE_PRINT_CANCELED
839 || state == STATE_PRINT_COMPLETED;
Svet Ganov525a66b2014-06-14 22:29:00 -0700840 }
841
842 private void updateSelectedPagesFromPreview() {
843 PageRange[] selectedPages = mPrintPreviewController.getSelectedPages();
844 if (!Arrays.equals(mSelectedPages, selectedPages)) {
845 updateSelectedPages(selectedPages,
846 getAdjustedPageCount(mPrintedDocument.getDocumentInfo().info));
847 }
848 }
849
850 private void updateSelectedPages(PageRange[] selectedPages, int pageInDocumentCount) {
851 if (selectedPages == null || selectedPages.length <= 0) {
852 return;
853 }
854
855 selectedPages = PageRangeUtils.normalize(selectedPages);
856
857 // Handle the case where all pages are specified explicitly
858 // instead of the *all pages* constant.
859 if (PageRangeUtils.isAllPages(selectedPages, pageInDocumentCount)) {
860 selectedPages = new PageRange[] {PageRange.ALL_PAGES};
861 }
862
863 if (Arrays.equals(mSelectedPages, selectedPages)) {
864 return;
865 }
866
867 mSelectedPages = selectedPages;
868 mPrintJob.setPages(selectedPages);
869
870 if (Arrays.equals(selectedPages, ALL_PAGES_ARRAY)) {
871 if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
872 mRangeOptionsSpinner.setSelection(0);
873 mPageRangeEditText.setText("");
874 }
875 } else if (selectedPages[0].getStart() >= 0
876 && selectedPages[selectedPages.length - 1].getEnd() < pageInDocumentCount) {
877 if (mRangeOptionsSpinner.getSelectedItemPosition() != 1) {
878 mRangeOptionsSpinner.setSelection(1);
879 }
880
881 StringBuilder builder = new StringBuilder();
882 final int pageRangeCount = selectedPages.length;
883 for (int i = 0; i < pageRangeCount; i++) {
884 if (builder.length() > 0) {
885 builder.append(',');
886 }
887
888 final int shownStartPage;
889 final int shownEndPage;
890 PageRange pageRange = selectedPages[i];
891 if (pageRange.equals(PageRange.ALL_PAGES)) {
892 shownStartPage = 1;
893 shownEndPage = pageInDocumentCount;
894 } else {
895 shownStartPage = pageRange.getStart() + 1;
896 shownEndPage = pageRange.getEnd() + 1;
897 }
898
899 builder.append(shownStartPage);
900
901 if (shownStartPage != shownEndPage) {
902 builder.append('-');
903 builder.append(shownEndPage);
904 }
905 }
906
907 mPageRangeEditText.setText(builder.toString());
908 }
909 }
910
Svetoslava798c0a2014-05-15 10:47:19 -0700911 private void ensureProgressUiShown() {
Svetoslav23d33612014-09-16 10:50:52 -0700912 if (isFinishing()) {
913 return;
914 }
Svetoslava798c0a2014-05-15 10:47:19 -0700915 if (mUiState != UI_STATE_PROGRESS) {
916 mUiState = UI_STATE_PROGRESS;
Svet Ganov525a66b2014-06-14 22:29:00 -0700917 mPrintPreviewController.setUiShown(false);
Svetoslava798c0a2014-05-15 10:47:19 -0700918 Fragment fragment = PrintProgressFragment.newInstance();
919 showFragment(fragment);
920 }
921 }
922
923 private void ensurePreviewUiShown() {
Svetoslav23d33612014-09-16 10:50:52 -0700924 if (isFinishing()) {
925 return;
926 }
Svetoslava798c0a2014-05-15 10:47:19 -0700927 if (mUiState != UI_STATE_PREVIEW) {
928 mUiState = UI_STATE_PREVIEW;
Svet Ganov525a66b2014-06-14 22:29:00 -0700929 mPrintPreviewController.setUiShown(true);
930 showFragment(null);
Svetoslava798c0a2014-05-15 10:47:19 -0700931 }
932 }
933
934 private void ensureErrorUiShown(CharSequence message, int action) {
Svetoslav23d33612014-09-16 10:50:52 -0700935 if (isFinishing()) {
936 return;
937 }
Svetoslava798c0a2014-05-15 10:47:19 -0700938 if (mUiState != UI_STATE_ERROR) {
939 mUiState = UI_STATE_ERROR;
Svet Ganov525a66b2014-06-14 22:29:00 -0700940 mPrintPreviewController.setUiShown(false);
Svetoslava798c0a2014-05-15 10:47:19 -0700941 Fragment fragment = PrintErrorFragment.newInstance(message, action);
942 showFragment(fragment);
943 }
944 }
945
Svet Ganov525a66b2014-06-14 22:29:00 -0700946 private void showFragment(Fragment newFragment) {
Svetoslava798c0a2014-05-15 10:47:19 -0700947 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Svet Ganov525a66b2014-06-14 22:29:00 -0700948 Fragment oldFragment = getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
Svetoslava798c0a2014-05-15 10:47:19 -0700949 if (oldFragment != null) {
950 transaction.remove(oldFragment);
951 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700952 if (newFragment != null) {
953 transaction.add(R.id.embedded_content_container, newFragment, FRAGMENT_TAG);
954 }
Svetoslava798c0a2014-05-15 10:47:19 -0700955 transaction.commit();
Svet Ganov525a66b2014-06-14 22:29:00 -0700956 getFragmentManager().executePendingTransactions();
Svetoslava798c0a2014-05-15 10:47:19 -0700957 }
958
959 private void requestCreatePdfFileOrFinish() {
Svet Ganov48fec5c2014-07-14 00:14:07 -0700960 if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) {
Svetoslava798c0a2014-05-15 10:47:19 -0700961 startCreateDocumentActivity();
962 } else {
Svetoslavbec22be2014-09-25 13:03:20 -0700963 transformDocumentAndFinish(null);
Svetoslava798c0a2014-05-15 10:47:19 -0700964 }
965 }
966
Svetoslava798c0a2014-05-15 10:47:19 -0700967 private void updatePrintAttributesFromCapabilities(PrinterCapabilitiesInfo capabilities) {
968 PrintAttributes defaults = capabilities.getDefaults();
969
970 // Sort the media sizes based on the current locale.
971 List<MediaSize> sortedMediaSizes = new ArrayList<>(capabilities.getMediaSizes());
972 Collections.sort(sortedMediaSizes, mMediaSizeComparator);
973
974 PrintAttributes attributes = mPrintJob.getAttributes();
975
976 // Media size.
977 MediaSize currMediaSize = attributes.getMediaSize();
978 if (currMediaSize == null) {
979 attributes.setMediaSize(defaults.getMediaSize());
980 } else {
981 boolean foundCurrentMediaSize = false;
982 // Try to find the current media size in the capabilities as
983 // it may be in a different orientation.
984 MediaSize currMediaSizePortrait = currMediaSize.asPortrait();
985 final int mediaSizeCount = sortedMediaSizes.size();
986 for (int i = 0; i < mediaSizeCount; i++) {
987 MediaSize mediaSize = sortedMediaSizes.get(i);
988 if (currMediaSizePortrait.equals(mediaSize.asPortrait())) {
989 attributes.setMediaSize(currMediaSize);
990 foundCurrentMediaSize = true;
991 break;
992 }
993 }
994 // If we did not find the current media size fall back to default.
995 if (!foundCurrentMediaSize) {
996 attributes.setMediaSize(defaults.getMediaSize());
997 }
998 }
999
1000 // Color mode.
1001 final int colorMode = attributes.getColorMode();
1002 if ((capabilities.getColorModes() & colorMode) == 0) {
1003 attributes.setColorMode(defaults.getColorMode());
1004 }
1005
Svetoslav948c9a62015-02-02 19:47:04 -08001006 // Duplex mode.
1007 final int duplexMode = attributes.getDuplexMode();
1008 if ((capabilities.getDuplexModes() & duplexMode) == 0) {
1009 attributes.setDuplexMode(defaults.getDuplexMode());
1010 }
1011
Svetoslava798c0a2014-05-15 10:47:19 -07001012 // Resolution
1013 Resolution resolution = attributes.getResolution();
1014 if (resolution == null || !capabilities.getResolutions().contains(resolution)) {
1015 attributes.setResolution(defaults.getResolution());
1016 }
1017
1018 // Margins.
1019 attributes.setMinMargins(defaults.getMinMargins());
1020 }
1021
Svetoslav62ce3322014-09-04 21:17:17 -07001022 private boolean updateDocument(boolean clearLastError) {
Svetoslava798c0a2014-05-15 10:47:19 -07001023 if (!clearLastError && mPrintedDocument.hasUpdateError()) {
1024 return false;
1025 }
1026
1027 if (clearLastError && mPrintedDocument.hasUpdateError()) {
1028 mPrintedDocument.clearUpdateError();
1029 }
1030
Svetoslav62ce3322014-09-04 21:17:17 -07001031 final boolean preview = mState != STATE_PRINT_CONFIRMED;
Svet Ganov525a66b2014-06-14 22:29:00 -07001032 final PageRange[] pages;
1033 if (preview) {
1034 pages = mPrintPreviewController.getRequestedPages();
1035 } else {
1036 pages = mPrintPreviewController.getSelectedPages();
1037 }
Svetoslava798c0a2014-05-15 10:47:19 -07001038
Svet Ganov525a66b2014-06-14 22:29:00 -07001039 final boolean willUpdate = mPrintedDocument.update(mPrintJob.getAttributes(),
1040 pages, preview);
1041
Svetoslav6552bf32014-09-03 21:15:55 -07001042 if (willUpdate && !mPrintedDocument.hasLaidOutPages()) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001043 // When the update is done we update the print preview.
1044 mProgressMessageController.post();
1045 return true;
Svetoslav7fd5ada2014-09-16 14:41:17 -07001046 } else if (!willUpdate) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001047 // Update preview.
1048 updatePrintPreviewController(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001049 }
1050
1051 return false;
1052 }
1053
1054 private void addCurrentPrinterToHistory() {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001055 if (mCurrentPrinter != null) {
Svetoslava798c0a2014-05-15 10:47:19 -07001056 PrinterId fakePdfPrinterId = mDestinationSpinnerAdapter.getPdfPrinter().getId();
Svet Ganov48fec5c2014-07-14 00:14:07 -07001057 if (!mCurrentPrinter.getId().equals(fakePdfPrinterId)) {
1058 mPrinterRegistry.addHistoricalPrinter(mCurrentPrinter);
Svetoslava798c0a2014-05-15 10:47:19 -07001059 }
1060 }
1061 }
1062
Svetoslava798c0a2014-05-15 10:47:19 -07001063 private void cancelPrint() {
Svet Ganov525a66b2014-06-14 22:29:00 -07001064 setState(STATE_PRINT_CANCELED);
Svetoslava798c0a2014-05-15 10:47:19 -07001065 updateOptionsUi();
1066 if (mPrintedDocument.isUpdating()) {
1067 mPrintedDocument.cancel();
1068 }
Svetoslave17123d2014-09-11 12:39:05 -07001069 doFinish();
Svetoslava798c0a2014-05-15 10:47:19 -07001070 }
1071
1072 private void confirmPrint() {
Svet Ganov525a66b2014-06-14 22:29:00 -07001073 setState(STATE_PRINT_CONFIRMED);
1074
Svetoslava798c0a2014-05-15 10:47:19 -07001075 updateOptionsUi();
Svet Ganov525a66b2014-06-14 22:29:00 -07001076 addCurrentPrinterToHistory();
1077
1078 PageRange[] selectedPages = computeSelectedPages();
1079 if (!Arrays.equals(mSelectedPages, selectedPages)) {
1080 mSelectedPages = selectedPages;
1081 // Update preview.
1082 updatePrintPreviewController(false);
1083 }
1084
1085 updateSelectedPagesFromPreview();
1086 mPrintPreviewController.closeOptions();
1087
Svetoslava798c0a2014-05-15 10:47:19 -07001088 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07001089 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001090 }
Svet Ganov525a66b2014-06-14 22:29:00 -07001091
Svetoslava798c0a2014-05-15 10:47:19 -07001092 if (!mPrintedDocument.isUpdating()) {
1093 requestCreatePdfFileOrFinish();
1094 }
1095 }
1096
1097 private void bindUi() {
1098 // Summary
Svetoslave652b022014-09-09 22:11:10 -07001099 mSummaryContainer = findViewById(R.id.summary_content);
Svetoslava798c0a2014-05-15 10:47:19 -07001100 mSummaryCopies = (TextView) findViewById(R.id.copies_count_summary);
1101 mSummaryPaperSize = (TextView) findViewById(R.id.paper_size_summary);
1102
1103 // Options container
Svet Ganov525a66b2014-06-14 22:29:00 -07001104 mOptionsContent = (PrintContentView) findViewById(R.id.options_content);
1105 mOptionsContent.setOptionsStateChangeListener(this);
1106 mOptionsContent.setOpenOptionsController(this);
Svetoslava798c0a2014-05-15 10:47:19 -07001107
1108 OnItemSelectedListener itemSelectedListener = new MyOnItemSelectedListener();
1109 OnClickListener clickListener = new MyClickListener();
1110
1111 // Copies
1112 mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
1113 mCopiesEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
1114 mCopiesEditText.setText(MIN_COPIES_STRING);
1115 mCopiesEditText.setSelection(mCopiesEditText.getText().length());
1116 mCopiesEditText.addTextChangedListener(new EditTextWatcher());
1117
1118 // Destination.
1119 mDestinationSpinnerAdapter.registerDataSetObserver(new PrintersObserver());
1120 mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
1121 mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
1122 mDestinationSpinner.setOnItemSelectedListener(itemSelectedListener);
Svetoslava798c0a2014-05-15 10:47:19 -07001123
1124 // Media size.
1125 mMediaSizeSpinnerAdapter = new ArrayAdapter<>(
Svetoslavc404cac2014-08-27 18:37:16 -07001126 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
Svetoslava798c0a2014-05-15 10:47:19 -07001127 mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
1128 mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
1129 mMediaSizeSpinner.setOnItemSelectedListener(itemSelectedListener);
1130
1131 // Color mode.
1132 mColorModeSpinnerAdapter = new ArrayAdapter<>(
Svetoslavc404cac2014-08-27 18:37:16 -07001133 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
Svetoslava798c0a2014-05-15 10:47:19 -07001134 mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
1135 mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
1136 mColorModeSpinner.setOnItemSelectedListener(itemSelectedListener);
1137
Svetoslav948c9a62015-02-02 19:47:04 -08001138 // Duplex mode.
1139 mDuplexModeSpinnerAdapter = new ArrayAdapter<>(
1140 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
1141 mDuplexModeSpinner = (Spinner) findViewById(R.id.duplex_spinner);
1142 mDuplexModeSpinner.setAdapter(mDuplexModeSpinnerAdapter);
1143 mDuplexModeSpinner.setOnItemSelectedListener(itemSelectedListener);
1144
Svetoslava798c0a2014-05-15 10:47:19 -07001145 // Orientation
1146 mOrientationSpinnerAdapter = new ArrayAdapter<>(
Svetoslavc404cac2014-08-27 18:37:16 -07001147 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
Svetoslava798c0a2014-05-15 10:47:19 -07001148 String[] orientationLabels = getResources().getStringArray(
Svet Ganov525a66b2014-06-14 22:29:00 -07001149 R.array.orientation_labels);
Svetoslava798c0a2014-05-15 10:47:19 -07001150 mOrientationSpinnerAdapter.add(new SpinnerItem<>(
1151 ORIENTATION_PORTRAIT, orientationLabels[0]));
1152 mOrientationSpinnerAdapter.add(new SpinnerItem<>(
1153 ORIENTATION_LANDSCAPE, orientationLabels[1]));
1154 mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
1155 mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
1156 mOrientationSpinner.setOnItemSelectedListener(itemSelectedListener);
1157
1158 // Range options
Svetoslavc404cac2014-08-27 18:37:16 -07001159 ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter = new ArrayAdapter<>(
1160 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
Svetoslava798c0a2014-05-15 10:47:19 -07001161 mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
1162 mRangeOptionsSpinner.setAdapter(rangeOptionsSpinnerAdapter);
1163 mRangeOptionsSpinner.setOnItemSelectedListener(itemSelectedListener);
Svetoslav73764e32014-07-15 15:56:46 -07001164 updatePageRangeOptions(PrintDocumentInfo.PAGE_COUNT_UNKNOWN);
Svetoslava798c0a2014-05-15 10:47:19 -07001165
1166 // Page range
1167 mPageRangeTitle = (TextView) findViewById(R.id.page_range_title);
1168 mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
1169 mPageRangeEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
1170 mPageRangeEditText.addTextChangedListener(new RangeTextWatcher());
1171
1172 // Advanced options button.
Svetoslava798c0a2014-05-15 10:47:19 -07001173 mMoreOptionsButton = (Button) findViewById(R.id.more_options_button);
Svet Ganov525a66b2014-06-14 22:29:00 -07001174 mMoreOptionsButton.setOnClickListener(clickListener);
Svetoslava798c0a2014-05-15 10:47:19 -07001175
1176 // Print button
1177 mPrintButton = (ImageView) findViewById(R.id.print_button);
1178 mPrintButton.setOnClickListener(clickListener);
1179 }
1180
1181 private final class MyClickListener implements OnClickListener {
1182 @Override
1183 public void onClick(View view) {
1184 if (view == mPrintButton) {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001185 if (mCurrentPrinter != null) {
Svetoslava798c0a2014-05-15 10:47:19 -07001186 confirmPrint();
1187 } else {
1188 cancelPrint();
1189 }
1190 } else if (view == mMoreOptionsButton) {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001191 if (mCurrentPrinter != null) {
1192 startAdvancedPrintOptionsActivity(mCurrentPrinter);
Svetoslava798c0a2014-05-15 10:47:19 -07001193 }
1194 }
1195 }
1196 }
1197
1198 private static boolean canPrint(PrinterInfo printer) {
1199 return printer.getCapabilities() != null
1200 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
1201 }
1202
Svet Ganov525a66b2014-06-14 22:29:00 -07001203 void updateOptionsUi() {
Svetoslava798c0a2014-05-15 10:47:19 -07001204 // Always update the summary.
Svetoslave652b022014-09-09 22:11:10 -07001205 updateSummary();
Svetoslava798c0a2014-05-15 10:47:19 -07001206
1207 if (mState == STATE_PRINT_CONFIRMED
Svetoslavb59555c2014-07-24 10:13:00 -07001208 || mState == STATE_PRINT_COMPLETED
Svetoslava798c0a2014-05-15 10:47:19 -07001209 || mState == STATE_PRINT_CANCELED
1210 || mState == STATE_UPDATE_FAILED
1211 || mState == STATE_CREATE_FILE_FAILED
Svet Ganov525a66b2014-06-14 22:29:00 -07001212 || mState == STATE_PRINTER_UNAVAILABLE
1213 || mState == STATE_UPDATE_SLOW) {
Svetoslava798c0a2014-05-15 10:47:19 -07001214 if (mState != STATE_PRINTER_UNAVAILABLE) {
1215 mDestinationSpinner.setEnabled(false);
1216 }
1217 mCopiesEditText.setEnabled(false);
Svetoslavc404cac2014-08-27 18:37:16 -07001218 mCopiesEditText.setFocusable(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001219 mMediaSizeSpinner.setEnabled(false);
1220 mColorModeSpinner.setEnabled(false);
Svetoslav948c9a62015-02-02 19:47:04 -08001221 mDuplexModeSpinner.setEnabled(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001222 mOrientationSpinner.setEnabled(false);
1223 mRangeOptionsSpinner.setEnabled(false);
1224 mPageRangeEditText.setEnabled(false);
Svet Ganov525a66b2014-06-14 22:29:00 -07001225 mPrintButton.setVisibility(View.GONE);
Svetoslava798c0a2014-05-15 10:47:19 -07001226 mMoreOptionsButton.setEnabled(false);
1227 return;
1228 }
1229
1230 // If no current printer, or it has no capabilities, or it is not
1231 // available, we disable all print options except the destination.
Svet Ganov48fec5c2014-07-14 00:14:07 -07001232 if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) {
Svetoslava798c0a2014-05-15 10:47:19 -07001233 mCopiesEditText.setEnabled(false);
Svetoslavc404cac2014-08-27 18:37:16 -07001234 mCopiesEditText.setFocusable(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001235 mMediaSizeSpinner.setEnabled(false);
1236 mColorModeSpinner.setEnabled(false);
Svetoslav948c9a62015-02-02 19:47:04 -08001237 mDuplexModeSpinner.setEnabled(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001238 mOrientationSpinner.setEnabled(false);
1239 mRangeOptionsSpinner.setEnabled(false);
1240 mPageRangeEditText.setEnabled(false);
Svet Ganov525a66b2014-06-14 22:29:00 -07001241 mPrintButton.setVisibility(View.GONE);
Svetoslava798c0a2014-05-15 10:47:19 -07001242 mMoreOptionsButton.setEnabled(false);
1243 return;
1244 }
1245
Svet Ganov48fec5c2014-07-14 00:14:07 -07001246 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
Svetoslava798c0a2014-05-15 10:47:19 -07001247 PrintAttributes defaultAttributes = capabilities.getDefaults();
1248
Svet Ganov525a66b2014-06-14 22:29:00 -07001249 // Destination.
1250 mDestinationSpinner.setEnabled(true);
1251
Svetoslava798c0a2014-05-15 10:47:19 -07001252 // Media size.
1253 mMediaSizeSpinner.setEnabled(true);
1254
1255 List<MediaSize> mediaSizes = new ArrayList<>(capabilities.getMediaSizes());
1256 // Sort the media sizes based on the current locale.
1257 Collections.sort(mediaSizes, mMediaSizeComparator);
1258
1259 PrintAttributes attributes = mPrintJob.getAttributes();
1260
1261 // If the media sizes changed, we update the adapter and the spinner.
1262 boolean mediaSizesChanged = false;
1263 final int mediaSizeCount = mediaSizes.size();
1264 if (mediaSizeCount != mMediaSizeSpinnerAdapter.getCount()) {
1265 mediaSizesChanged = true;
1266 } else {
1267 for (int i = 0; i < mediaSizeCount; i++) {
1268 if (!mediaSizes.get(i).equals(mMediaSizeSpinnerAdapter.getItem(i).value)) {
1269 mediaSizesChanged = true;
1270 break;
1271 }
1272 }
1273 }
1274 if (mediaSizesChanged) {
1275 // Remember the old media size to try selecting it again.
1276 int oldMediaSizeNewIndex = AdapterView.INVALID_POSITION;
1277 MediaSize oldMediaSize = attributes.getMediaSize();
1278
1279 // Rebuild the adapter data.
1280 mMediaSizeSpinnerAdapter.clear();
1281 for (int i = 0; i < mediaSizeCount; i++) {
1282 MediaSize mediaSize = mediaSizes.get(i);
1283 if (oldMediaSize != null
1284 && mediaSize.asPortrait().equals(oldMediaSize.asPortrait())) {
1285 // Update the index of the old selection.
1286 oldMediaSizeNewIndex = i;
1287 }
1288 mMediaSizeSpinnerAdapter.add(new SpinnerItem<>(
1289 mediaSize, mediaSize.getLabel(getPackageManager())));
1290 }
1291
1292 if (oldMediaSizeNewIndex != AdapterView.INVALID_POSITION) {
1293 // Select the old media size - nothing really changed.
1294 if (mMediaSizeSpinner.getSelectedItemPosition() != oldMediaSizeNewIndex) {
1295 mMediaSizeSpinner.setSelection(oldMediaSizeNewIndex);
1296 }
1297 } else {
1298 // Select the first or the default.
1299 final int mediaSizeIndex = Math.max(mediaSizes.indexOf(
1300 defaultAttributes.getMediaSize()), 0);
1301 if (mMediaSizeSpinner.getSelectedItemPosition() != mediaSizeIndex) {
1302 mMediaSizeSpinner.setSelection(mediaSizeIndex);
1303 }
1304 // Respect the orientation of the old selection.
1305 if (oldMediaSize != null) {
1306 if (oldMediaSize.isPortrait()) {
1307 attributes.setMediaSize(mMediaSizeSpinnerAdapter
1308 .getItem(mediaSizeIndex).value.asPortrait());
1309 } else {
1310 attributes.setMediaSize(mMediaSizeSpinnerAdapter
1311 .getItem(mediaSizeIndex).value.asLandscape());
1312 }
1313 }
1314 }
1315 }
1316
1317 // Color mode.
1318 mColorModeSpinner.setEnabled(true);
1319 final int colorModes = capabilities.getColorModes();
1320
1321 // If the color modes changed, we update the adapter and the spinner.
1322 boolean colorModesChanged = false;
1323 if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) {
1324 colorModesChanged = true;
1325 } else {
1326 int remainingColorModes = colorModes;
1327 int adapterIndex = 0;
1328 while (remainingColorModes != 0) {
1329 final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
1330 final int colorMode = 1 << colorBitOffset;
1331 remainingColorModes &= ~colorMode;
1332 if (colorMode != mColorModeSpinnerAdapter.getItem(adapterIndex).value) {
1333 colorModesChanged = true;
1334 break;
1335 }
1336 adapterIndex++;
1337 }
1338 }
1339 if (colorModesChanged) {
1340 // Remember the old color mode to try selecting it again.
1341 int oldColorModeNewIndex = AdapterView.INVALID_POSITION;
1342 final int oldColorMode = attributes.getColorMode();
1343
1344 // Rebuild the adapter data.
1345 mColorModeSpinnerAdapter.clear();
1346 String[] colorModeLabels = getResources().getStringArray(R.array.color_mode_labels);
1347 int remainingColorModes = colorModes;
1348 while (remainingColorModes != 0) {
1349 final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
1350 final int colorMode = 1 << colorBitOffset;
1351 if (colorMode == oldColorMode) {
1352 // Update the index of the old selection.
Svetoslav948c9a62015-02-02 19:47:04 -08001353 oldColorModeNewIndex = mColorModeSpinnerAdapter.getCount();
Svetoslava798c0a2014-05-15 10:47:19 -07001354 }
1355 remainingColorModes &= ~colorMode;
1356 mColorModeSpinnerAdapter.add(new SpinnerItem<>(colorMode,
1357 colorModeLabels[colorBitOffset]));
1358 }
1359 if (oldColorModeNewIndex != AdapterView.INVALID_POSITION) {
1360 // Select the old color mode - nothing really changed.
1361 if (mColorModeSpinner.getSelectedItemPosition() != oldColorModeNewIndex) {
1362 mColorModeSpinner.setSelection(oldColorModeNewIndex);
1363 }
1364 } else {
1365 // Select the default.
1366 final int selectedColorMode = colorModes & defaultAttributes.getColorMode();
1367 final int itemCount = mColorModeSpinnerAdapter.getCount();
1368 for (int i = 0; i < itemCount; i++) {
1369 SpinnerItem<Integer> item = mColorModeSpinnerAdapter.getItem(i);
1370 if (selectedColorMode == item.value) {
1371 if (mColorModeSpinner.getSelectedItemPosition() != i) {
1372 mColorModeSpinner.setSelection(i);
1373 }
1374 attributes.setColorMode(selectedColorMode);
Svetoslav948c9a62015-02-02 19:47:04 -08001375 break;
Svetoslava798c0a2014-05-15 10:47:19 -07001376 }
1377 }
1378 }
1379 }
1380
Svetoslav948c9a62015-02-02 19:47:04 -08001381 // Duplex mode.
1382 mDuplexModeSpinner.setEnabled(true);
1383 final int duplexModes = capabilities.getDuplexModes();
1384
1385 // If the duplex modes changed, we update the adapter and the spinner.
1386 // Note that we use bit count +1 to account for the no duplex option.
1387 boolean duplexModesChanged = false;
1388 if (Integer.bitCount(duplexModes) != mDuplexModeSpinnerAdapter.getCount()) {
1389 duplexModesChanged = true;
1390 } else {
1391 int remainingDuplexModes = duplexModes;
1392 int adapterIndex = 0;
1393 while (remainingDuplexModes != 0) {
1394 final int duplexBitOffset = Integer.numberOfTrailingZeros(remainingDuplexModes);
1395 final int duplexMode = 1 << duplexBitOffset;
1396 remainingDuplexModes &= ~duplexMode;
1397 if (duplexMode != mDuplexModeSpinnerAdapter.getItem(adapterIndex).value) {
1398 duplexModesChanged = true;
1399 break;
1400 }
1401 adapterIndex++;
1402 }
1403 }
1404 if (duplexModesChanged) {
1405 // Remember the old duplex mode to try selecting it again. Also the fallback
1406 // is no duplexing which is always the first item in the dropdown.
1407 int oldDuplexModeNewIndex = AdapterView.INVALID_POSITION;
1408 final int oldDuplexMode = attributes.getDuplexMode();
1409
1410 // Rebuild the adapter data.
1411 mDuplexModeSpinnerAdapter.clear();
1412 String[] duplexModeLabels = getResources().getStringArray(R.array.duplex_mode_labels);
1413 int remainingDuplexModes = duplexModes;
1414 while (remainingDuplexModes != 0) {
1415 final int duplexBitOffset = Integer.numberOfTrailingZeros(remainingDuplexModes);
1416 final int duplexMode = 1 << duplexBitOffset;
1417 if (duplexMode == oldDuplexMode) {
1418 // Update the index of the old selection.
1419 oldDuplexModeNewIndex = mDuplexModeSpinnerAdapter.getCount();
1420 }
1421 remainingDuplexModes &= ~duplexMode;
1422 mDuplexModeSpinnerAdapter.add(new SpinnerItem<>(duplexMode,
1423 duplexModeLabels[duplexBitOffset]));
1424 }
1425
1426 if (oldDuplexModeNewIndex != AdapterView.INVALID_POSITION) {
1427 // Select the old duplex mode - nothing really changed.
1428 if (mDuplexModeSpinner.getSelectedItemPosition() != oldDuplexModeNewIndex) {
1429 mDuplexModeSpinner.setSelection(oldDuplexModeNewIndex);
1430 }
1431 } else {
1432 // Select the default.
1433 final int selectedDuplexMode = defaultAttributes.getDuplexMode();
1434 final int itemCount = mDuplexModeSpinnerAdapter.getCount();
1435 for (int i = 0; i < itemCount; i++) {
1436 SpinnerItem<Integer> item = mDuplexModeSpinnerAdapter.getItem(i);
1437 if (selectedDuplexMode == item.value) {
1438 if (mDuplexModeSpinner.getSelectedItemPosition() != i) {
1439 mDuplexModeSpinner.setSelection(i);
1440 }
1441 attributes.setDuplexMode(selectedDuplexMode);
1442 break;
1443 }
1444 }
1445 }
1446 }
1447
1448 mDuplexModeSpinner.setEnabled(mDuplexModeSpinnerAdapter.getCount() > 1);
1449
Svetoslava798c0a2014-05-15 10:47:19 -07001450 // Orientation
1451 mOrientationSpinner.setEnabled(true);
1452 MediaSize mediaSize = attributes.getMediaSize();
1453 if (mediaSize != null) {
1454 if (mediaSize.isPortrait()
1455 && mOrientationSpinner.getSelectedItemPosition() != 0) {
1456 mOrientationSpinner.setSelection(0);
1457 } else if (!mediaSize.isPortrait()
1458 && mOrientationSpinner.getSelectedItemPosition() != 1) {
1459 mOrientationSpinner.setSelection(1);
1460 }
1461 }
1462
1463 // Range options
1464 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
Svet Ganov525a66b2014-06-14 22:29:00 -07001465 final int pageCount = getAdjustedPageCount(info);
1466 if (info != null && pageCount > 0) {
1467 if (pageCount == 1) {
Svetoslava798c0a2014-05-15 10:47:19 -07001468 mRangeOptionsSpinner.setEnabled(false);
1469 } else {
1470 mRangeOptionsSpinner.setEnabled(true);
1471 if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
1472 if (!mPageRangeEditText.isEnabled()) {
1473 mPageRangeEditText.setEnabled(true);
1474 mPageRangeEditText.setVisibility(View.VISIBLE);
1475 mPageRangeTitle.setVisibility(View.VISIBLE);
1476 mPageRangeEditText.requestFocus();
1477 InputMethodManager imm = (InputMethodManager)
1478 getSystemService(Context.INPUT_METHOD_SERVICE);
1479 imm.showSoftInput(mPageRangeEditText, 0);
1480 }
1481 } else {
1482 mPageRangeEditText.setEnabled(false);
1483 mPageRangeEditText.setVisibility(View.INVISIBLE);
1484 mPageRangeTitle.setVisibility(View.INVISIBLE);
1485 }
1486 }
Svetoslava798c0a2014-05-15 10:47:19 -07001487 } else {
1488 if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
1489 mRangeOptionsSpinner.setSelection(0);
Svet Ganov525a66b2014-06-14 22:29:00 -07001490 mPageRangeEditText.setText("");
Svetoslava798c0a2014-05-15 10:47:19 -07001491 }
1492 mRangeOptionsSpinner.setEnabled(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001493 mPageRangeEditText.setEnabled(false);
1494 mPageRangeEditText.setVisibility(View.INVISIBLE);
1495 mPageRangeTitle.setVisibility(View.INVISIBLE);
1496 }
1497
Svetoslav73764e32014-07-15 15:56:46 -07001498 final int newPageCount = getAdjustedPageCount(info);
1499 if (newPageCount != mCurrentPageCount) {
1500 mCurrentPageCount = newPageCount;
1501 updatePageRangeOptions(newPageCount);
1502 }
1503
Svetoslava798c0a2014-05-15 10:47:19 -07001504 // Advanced print options
Svet Ganov48fec5c2014-07-14 00:14:07 -07001505 ComponentName serviceName = mCurrentPrinter.getId().getServiceName();
Svetoslava798c0a2014-05-15 10:47:19 -07001506 if (!TextUtils.isEmpty(PrintOptionUtils.getAdvancedOptionsActivityName(
1507 this, serviceName))) {
Svetoslav3c238242014-08-19 13:44:29 -07001508 mMoreOptionsButton.setVisibility(View.VISIBLE);
Svetoslava798c0a2014-05-15 10:47:19 -07001509 mMoreOptionsButton.setEnabled(true);
1510 } else {
Svetoslav3c238242014-08-19 13:44:29 -07001511 mMoreOptionsButton.setVisibility(View.GONE);
Svetoslava798c0a2014-05-15 10:47:19 -07001512 mMoreOptionsButton.setEnabled(false);
1513 }
1514
1515 // Print
Svet Ganov48fec5c2014-07-14 00:14:07 -07001516 if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
Svetoslava798c0a2014-05-15 10:47:19 -07001517 mPrintButton.setImageResource(com.android.internal.R.drawable.ic_print);
Svetoslave652b022014-09-09 22:11:10 -07001518 mPrintButton.setContentDescription(getString(R.string.print_button));
Svetoslava798c0a2014-05-15 10:47:19 -07001519 } else {
Svetoslavf8ffa562014-07-23 18:22:03 -07001520 mPrintButton.setImageResource(R.drawable.ic_menu_savetopdf);
Svetoslave652b022014-09-09 22:11:10 -07001521 mPrintButton.setContentDescription(getString(R.string.savetopdf_button));
Svetoslava798c0a2014-05-15 10:47:19 -07001522 }
Svetoslave1dcb392014-09-26 19:49:14 -07001523 if (!mPrintedDocument.getDocumentInfo().laidout
1524 ||(mRangeOptionsSpinner.getSelectedItemPosition() == 1
Svetoslava798c0a2014-05-15 10:47:19 -07001525 && (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors()))
Svet Ganov525a66b2014-06-14 22:29:00 -07001526 || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
Svetoslava798c0a2014-05-15 10:47:19 -07001527 && (mPrintedDocument.getDocumentInfo() == null || hasErrors()))) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001528 mPrintButton.setVisibility(View.GONE);
Svetoslava798c0a2014-05-15 10:47:19 -07001529 } else {
Svet Ganov525a66b2014-06-14 22:29:00 -07001530 mPrintButton.setVisibility(View.VISIBLE);
Svetoslava798c0a2014-05-15 10:47:19 -07001531 }
1532
1533 // Copies
Svet Ganov48fec5c2014-07-14 00:14:07 -07001534 if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
Svetoslava798c0a2014-05-15 10:47:19 -07001535 mCopiesEditText.setEnabled(true);
Svetoslavc404cac2014-08-27 18:37:16 -07001536 mCopiesEditText.setFocusableInTouchMode(true);
Svetoslava798c0a2014-05-15 10:47:19 -07001537 } else {
Svet Ganov45e50e92014-10-23 12:39:08 -07001538 CharSequence text = mCopiesEditText.getText();
1539 if (TextUtils.isEmpty(text) || !MIN_COPIES_STRING.equals(text.toString())) {
1540 mCopiesEditText.setText(MIN_COPIES_STRING);
1541 }
Svetoslava798c0a2014-05-15 10:47:19 -07001542 mCopiesEditText.setEnabled(false);
Svetoslavc404cac2014-08-27 18:37:16 -07001543 mCopiesEditText.setFocusable(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001544 }
1545 if (mCopiesEditText.getError() == null
1546 && TextUtils.isEmpty(mCopiesEditText.getText())) {
Svet Ganov45e50e92014-10-23 12:39:08 -07001547 mCopiesEditText.setText(MIN_COPIES_STRING);
Svetoslava798c0a2014-05-15 10:47:19 -07001548 mCopiesEditText.requestFocus();
1549 }
1550 }
1551
Svetoslave652b022014-09-09 22:11:10 -07001552 private void updateSummary() {
1553 CharSequence copiesText = null;
1554 CharSequence mediaSizeText = null;
1555
1556 if (!TextUtils.isEmpty(mCopiesEditText.getText())) {
1557 copiesText = mCopiesEditText.getText();
1558 mSummaryCopies.setText(copiesText);
1559 }
1560
1561 final int selectedMediaIndex = mMediaSizeSpinner.getSelectedItemPosition();
1562 if (selectedMediaIndex >= 0) {
1563 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(selectedMediaIndex);
1564 mediaSizeText = mediaItem.label;
1565 mSummaryPaperSize.setText(mediaSizeText);
1566 }
1567
1568 if (!TextUtils.isEmpty(copiesText) && !TextUtils.isEmpty(mediaSizeText)) {
1569 String summaryText = getString(R.string.summary_template, copiesText, mediaSizeText);
1570 mSummaryContainer.setContentDescription(summaryText);
1571 }
1572 }
1573
Svetoslav73764e32014-07-15 15:56:46 -07001574 private void updatePageRangeOptions(int pageCount) {
1575 ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter =
1576 (ArrayAdapter) mRangeOptionsSpinner.getAdapter();
1577 rangeOptionsSpinnerAdapter.clear();
1578
1579 final int[] rangeOptionsValues = getResources().getIntArray(
1580 R.array.page_options_values);
1581
1582 String pageCountLabel = (pageCount > 0) ? String.valueOf(pageCount) : "";
1583 String[] rangeOptionsLabels = new String[] {
1584 getString(R.string.template_all_pages, pageCountLabel),
1585 getString(R.string.template_page_range, pageCountLabel)
1586 };
1587
1588 final int rangeOptionsCount = rangeOptionsLabels.length;
1589 for (int i = 0; i < rangeOptionsCount; i++) {
1590 rangeOptionsSpinnerAdapter.add(new SpinnerItem<>(
1591 rangeOptionsValues[i], rangeOptionsLabels[i]));
1592 }
1593 }
1594
Svet Ganov525a66b2014-06-14 22:29:00 -07001595 private PageRange[] computeSelectedPages() {
Svetoslava798c0a2014-05-15 10:47:19 -07001596 if (hasErrors()) {
1597 return null;
1598 }
1599
1600 if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
1601 List<PageRange> pageRanges = new ArrayList<>();
1602 mStringCommaSplitter.setString(mPageRangeEditText.getText().toString());
1603
1604 while (mStringCommaSplitter.hasNext()) {
1605 String range = mStringCommaSplitter.next().trim();
1606 if (TextUtils.isEmpty(range)) {
1607 continue;
1608 }
1609 final int dashIndex = range.indexOf('-');
1610 final int fromIndex;
1611 final int toIndex;
1612
1613 if (dashIndex > 0) {
1614 fromIndex = Integer.parseInt(range.substring(0, dashIndex).trim()) - 1;
1615 // It is possible that the dash is at the end since the input
1616 // verification can has to allow the user to keep entering if
1617 // this would lead to a valid input. So we handle this.
1618 if (dashIndex < range.length() - 1) {
1619 String fromString = range.substring(dashIndex + 1, range.length()).trim();
1620 toIndex = Integer.parseInt(fromString) - 1;
1621 } else {
1622 toIndex = fromIndex;
1623 }
1624 } else {
1625 fromIndex = toIndex = Integer.parseInt(range) - 1;
1626 }
1627
1628 PageRange pageRange = new PageRange(Math.min(fromIndex, toIndex),
1629 Math.max(fromIndex, toIndex));
1630 pageRanges.add(pageRange);
1631 }
1632
1633 PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
1634 pageRanges.toArray(pageRangesArray);
1635
1636 return PageRangeUtils.normalize(pageRangesArray);
1637 }
1638
1639 return ALL_PAGES_ARRAY;
1640 }
1641
Svet Ganov525a66b2014-06-14 22:29:00 -07001642 private int getAdjustedPageCount(PrintDocumentInfo info) {
1643 if (info != null) {
1644 final int pageCount = info.getPageCount();
1645 if (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
1646 return pageCount;
1647 }
1648 }
1649 // If the app does not tell us how many pages are in the
1650 // doc we ask for all pages and use the document page count.
1651 return mPrintPreviewController.getFilePageCount();
1652 }
1653
Svetoslava798c0a2014-05-15 10:47:19 -07001654 private boolean hasErrors() {
1655 return (mCopiesEditText.getError() != null)
1656 || (mPageRangeEditText.getVisibility() == View.VISIBLE
Svet Ganov525a66b2014-06-14 22:29:00 -07001657 && mPageRangeEditText.getError() != null);
Svetoslava798c0a2014-05-15 10:47:19 -07001658 }
1659
1660 public void onPrinterAvailable(PrinterInfo printer) {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001661 if (mCurrentPrinter.equals(printer)) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001662 setState(STATE_CONFIGURING);
Svetoslava798c0a2014-05-15 10:47:19 -07001663 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07001664 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001665 }
1666 ensurePreviewUiShown();
1667 updateOptionsUi();
1668 }
1669 }
1670
1671 public void onPrinterUnavailable(PrinterInfo printer) {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001672 if (mCurrentPrinter.getId().equals(printer.getId())) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001673 setState(STATE_PRINTER_UNAVAILABLE);
Svetoslava798c0a2014-05-15 10:47:19 -07001674 if (mPrintedDocument.isUpdating()) {
1675 mPrintedDocument.cancel();
1676 }
1677 ensureErrorUiShown(getString(R.string.print_error_printer_unavailable),
1678 PrintErrorFragment.ACTION_NONE);
1679 updateOptionsUi();
1680 }
1681 }
1682
Svet Ganov525a66b2014-06-14 22:29:00 -07001683 private boolean canUpdateDocument() {
1684 if (mPrintedDocument.isDestroyed()) {
1685 return false;
1686 }
1687
1688 if (hasErrors()) {
1689 return false;
1690 }
1691
1692 PrintAttributes attributes = mPrintJob.getAttributes();
1693
1694 final int colorMode = attributes.getColorMode();
1695 if (colorMode != PrintAttributes.COLOR_MODE_COLOR
1696 && colorMode != PrintAttributes.COLOR_MODE_MONOCHROME) {
1697 return false;
1698 }
1699 if (attributes.getMediaSize() == null) {
1700 return false;
1701 }
1702 if (attributes.getMinMargins() == null) {
1703 return false;
1704 }
1705 if (attributes.getResolution() == null) {
1706 return false;
1707 }
1708
Svet Ganov48fec5c2014-07-14 00:14:07 -07001709 if (mCurrentPrinter == null) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001710 return false;
1711 }
Svet Ganov48fec5c2014-07-14 00:14:07 -07001712 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
Svet Ganov525a66b2014-06-14 22:29:00 -07001713 if (capabilities == null) {
1714 return false;
1715 }
Svet Ganov48fec5c2014-07-14 00:14:07 -07001716 if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001717 return false;
1718 }
1719
1720 return true;
1721 }
1722
Svetoslavbec22be2014-09-25 13:03:20 -07001723 private void transformDocumentAndFinish(final Uri writeToUri) {
1724 // If saving to PDF, apply the attibutes as we are acting as a print service.
1725 PrintAttributes attributes = mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter
1726 ? mPrintJob.getAttributes() : null;
1727 new DocumentTransformer(this, mPrintJob, mFileProvider, attributes, new Runnable() {
Svetoslav62ce3322014-09-04 21:17:17 -07001728 @Override
1729 public void run() {
1730 if (writeToUri != null) {
1731 mPrintedDocument.writeContent(getContentResolver(), writeToUri);
1732 }
Svetoslave17123d2014-09-11 12:39:05 -07001733 doFinish();
Svetoslav62ce3322014-09-04 21:17:17 -07001734 }
Svetoslavbec22be2014-09-25 13:03:20 -07001735 }).transform();
Svetoslav62ce3322014-09-04 21:17:17 -07001736 }
1737
Svetoslave17123d2014-09-11 12:39:05 -07001738 private void doFinish() {
1739 if (mState != STATE_INITIALIZING) {
1740 mProgressMessageController.cancel();
1741 mPrinterRegistry.setTrackedPrinter(null);
Svetoslave17123d2014-09-11 12:39:05 -07001742 mSpoolerProvider.destroy();
1743 mPrintedDocument.finish();
1744 mPrintedDocument.destroy();
Svet Ganovc80814e2014-11-24 02:01:37 -08001745 mPrintPreviewController.destroy(new Runnable() {
1746 @Override
1747 public void run() {
1748 finish();
1749 }
1750 });
1751 } else {
1752 finish();
Svetoslave17123d2014-09-11 12:39:05 -07001753 }
Svetoslave17123d2014-09-11 12:39:05 -07001754 }
1755
Svetoslava798c0a2014-05-15 10:47:19 -07001756 private final class SpinnerItem<T> {
1757 final T value;
1758 final CharSequence label;
1759
1760 public SpinnerItem(T value, CharSequence label) {
1761 this.value = value;
1762 this.label = label;
1763 }
1764
1765 public String toString() {
1766 return label.toString();
1767 }
1768 }
1769
1770 private final class PrinterAvailabilityDetector implements Runnable {
1771 private static final long UNAVAILABLE_TIMEOUT_MILLIS = 10000; // 10sec
1772
1773 private boolean mPosted;
1774
1775 private boolean mPrinterUnavailable;
1776
1777 private PrinterInfo mPrinter;
1778
1779 public void updatePrinter(PrinterInfo printer) {
1780 if (printer.equals(mDestinationSpinnerAdapter.getPdfPrinter())) {
1781 return;
1782 }
1783
1784 final boolean available = printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
1785 && printer.getCapabilities() != null;
1786 final boolean notifyIfAvailable;
1787
1788 if (mPrinter == null || !mPrinter.getId().equals(printer.getId())) {
1789 notifyIfAvailable = true;
1790 unpostIfNeeded();
1791 mPrinterUnavailable = false;
1792 mPrinter = new PrinterInfo.Builder(printer).build();
1793 } else {
1794 notifyIfAvailable =
Svet Ganov525a66b2014-06-14 22:29:00 -07001795 (mPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
1796 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE)
1797 || (mPrinter.getCapabilities() == null
1798 && printer.getCapabilities() != null);
Svetoslava798c0a2014-05-15 10:47:19 -07001799 mPrinter.copyFrom(printer);
1800 }
1801
1802 if (available) {
1803 unpostIfNeeded();
1804 mPrinterUnavailable = false;
1805 if (notifyIfAvailable) {
1806 onPrinterAvailable(mPrinter);
1807 }
1808 } else {
1809 if (!mPrinterUnavailable) {
1810 postIfNeeded();
1811 }
1812 }
1813 }
1814
1815 public void cancel() {
1816 unpostIfNeeded();
1817 mPrinterUnavailable = false;
1818 }
1819
1820 private void postIfNeeded() {
1821 if (!mPosted) {
1822 mPosted = true;
1823 mDestinationSpinner.postDelayed(this, UNAVAILABLE_TIMEOUT_MILLIS);
1824 }
1825 }
1826
1827 private void unpostIfNeeded() {
1828 if (mPosted) {
1829 mPosted = false;
1830 mDestinationSpinner.removeCallbacks(this);
1831 }
1832 }
1833
1834 @Override
1835 public void run() {
1836 mPosted = false;
1837 mPrinterUnavailable = true;
1838 onPrinterUnavailable(mPrinter);
1839 }
1840 }
1841
1842 private static final class PrinterHolder {
1843 PrinterInfo printer;
1844 boolean removed;
1845
1846 public PrinterHolder(PrinterInfo printer) {
1847 this.printer = printer;
1848 }
1849 }
1850
1851 private final class DestinationAdapter extends BaseAdapter
1852 implements PrinterRegistry.OnPrintersChangeListener {
1853 private final List<PrinterHolder> mPrinterHolders = new ArrayList<>();
1854
1855 private final PrinterHolder mFakePdfPrinterHolder;
1856
Svet Ganov48fec5c2014-07-14 00:14:07 -07001857 private boolean mHistoricalPrintersLoaded;
1858
Svetoslava798c0a2014-05-15 10:47:19 -07001859 public DestinationAdapter() {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001860 mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
1861 if (mHistoricalPrintersLoaded) {
1862 addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters());
1863 }
Svetoslava798c0a2014-05-15 10:47:19 -07001864 mPrinterRegistry.setOnPrintersChangeListener(this);
1865 mFakePdfPrinterHolder = new PrinterHolder(createFakePdfPrinter());
1866 }
1867
1868 public PrinterInfo getPdfPrinter() {
1869 return mFakePdfPrinterHolder.printer;
1870 }
1871
1872 public int getPrinterIndex(PrinterId printerId) {
1873 for (int i = 0; i < getCount(); i++) {
1874 PrinterHolder printerHolder = (PrinterHolder) getItem(i);
1875 if (printerHolder != null && !printerHolder.removed
1876 && printerHolder.printer.getId().equals(printerId)) {
1877 return i;
1878 }
1879 }
1880 return AdapterView.INVALID_POSITION;
1881 }
1882
1883 public void ensurePrinterInVisibleAdapterPosition(PrinterId printerId) {
1884 final int printerCount = mPrinterHolders.size();
1885 for (int i = 0; i < printerCount; i++) {
1886 PrinterHolder printerHolder = mPrinterHolders.get(i);
1887 if (printerHolder.printer.getId().equals(printerId)) {
1888 // If already in the list - do nothing.
1889 if (i < getCount() - 2) {
1890 return;
1891 }
1892 // Else replace the last one (two items are not printers).
1893 final int lastPrinterIndex = getCount() - 3;
1894 mPrinterHolders.set(i, mPrinterHolders.get(lastPrinterIndex));
1895 mPrinterHolders.set(lastPrinterIndex, printerHolder);
1896 notifyDataSetChanged();
1897 return;
1898 }
1899 }
1900 }
1901
1902 @Override
1903 public int getCount() {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001904 if (mHistoricalPrintersLoaded) {
1905 return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
1906 }
1907 return 0;
Svetoslava798c0a2014-05-15 10:47:19 -07001908 }
1909
1910 @Override
1911 public boolean isEnabled(int position) {
1912 Object item = getItem(position);
1913 if (item instanceof PrinterHolder) {
1914 PrinterHolder printerHolder = (PrinterHolder) item;
1915 return !printerHolder.removed
1916 && printerHolder.printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
1917 }
1918 return true;
1919 }
1920
1921 @Override
1922 public Object getItem(int position) {
1923 if (mPrinterHolders.isEmpty()) {
1924 if (position == 0) {
1925 return mFakePdfPrinterHolder;
1926 }
1927 } else {
1928 if (position < 1) {
1929 return mPrinterHolders.get(position);
1930 }
1931 if (position == 1) {
1932 return mFakePdfPrinterHolder;
1933 }
1934 if (position < getCount() - 1) {
1935 return mPrinterHolders.get(position - 1);
1936 }
1937 }
1938 return null;
1939 }
1940
1941 @Override
1942 public long getItemId(int position) {
1943 if (mPrinterHolders.isEmpty()) {
1944 if (position == 0) {
1945 return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
1946 } else if (position == 1) {
1947 return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
1948 }
1949 } else {
1950 if (position == 1) {
1951 return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
1952 }
1953 if (position == getCount() - 1) {
1954 return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
1955 }
1956 }
1957 return position;
1958 }
1959
1960 @Override
1961 public View getDropDownView(int position, View convertView, ViewGroup parent) {
1962 View view = getView(position, convertView, parent);
1963 view.setEnabled(isEnabled(position));
1964 return view;
1965 }
1966
1967 @Override
1968 public View getView(int position, View convertView, ViewGroup parent) {
1969 if (convertView == null) {
1970 convertView = getLayoutInflater().inflate(
1971 R.layout.printer_dropdown_item, parent, false);
1972 }
1973
1974 CharSequence title = null;
1975 CharSequence subtitle = null;
1976 Drawable icon = null;
1977
1978 if (mPrinterHolders.isEmpty()) {
1979 if (position == 0 && getPdfPrinter() != null) {
1980 PrinterHolder printerHolder = (PrinterHolder) getItem(position);
1981 title = printerHolder.printer.getName();
Svetoslav3c238242014-08-19 13:44:29 -07001982 icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf);
Svetoslava798c0a2014-05-15 10:47:19 -07001983 } else if (position == 1) {
1984 title = getString(R.string.all_printers);
1985 }
1986 } else {
1987 if (position == 1 && getPdfPrinter() != null) {
1988 PrinterHolder printerHolder = (PrinterHolder) getItem(position);
1989 title = printerHolder.printer.getName();
Svetoslav3c238242014-08-19 13:44:29 -07001990 icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf);
Svetoslava798c0a2014-05-15 10:47:19 -07001991 } else if (position == getCount() - 1) {
1992 title = getString(R.string.all_printers);
1993 } else {
1994 PrinterHolder printerHolder = (PrinterHolder) getItem(position);
1995 title = printerHolder.printer.getName();
1996 try {
1997 PackageInfo packageInfo = getPackageManager().getPackageInfo(
1998 printerHolder.printer.getId().getServiceName().getPackageName(), 0);
1999 subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
2000 icon = packageInfo.applicationInfo.loadIcon(getPackageManager());
2001 } catch (NameNotFoundException nnfe) {
2002 /* ignore */
2003 }
2004 }
2005 }
2006
2007 TextView titleView = (TextView) convertView.findViewById(R.id.title);
2008 titleView.setText(title);
2009
2010 TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
2011 if (!TextUtils.isEmpty(subtitle)) {
2012 subtitleView.setText(subtitle);
2013 subtitleView.setVisibility(View.VISIBLE);
2014 } else {
2015 subtitleView.setText(null);
2016 subtitleView.setVisibility(View.GONE);
2017 }
2018
2019 ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
2020 if (icon != null) {
2021 iconView.setImageDrawable(icon);
2022 iconView.setVisibility(View.VISIBLE);
2023 } else {
2024 iconView.setVisibility(View.INVISIBLE);
2025 }
2026
2027 return convertView;
2028 }
2029
2030 @Override
2031 public void onPrintersChanged(List<PrinterInfo> printers) {
2032 // We rearrange the printers if the user selects a printer
2033 // not shown in the initial short list. Therefore, we have
2034 // to keep the printer order.
2035
Svet Ganov48fec5c2014-07-14 00:14:07 -07002036 // Check if historical printers are loaded as this adapter is open
2037 // for busyness only if they are. This member is updated here and
2038 // when the adapter is created because the historical printers may
2039 // be loaded before or after the adapter is created.
2040 mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
2041
Svetoslava798c0a2014-05-15 10:47:19 -07002042 // No old printers - do not bother keeping their position.
2043 if (mPrinterHolders.isEmpty()) {
2044 addPrinters(mPrinterHolders, printers);
2045 notifyDataSetChanged();
2046 return;
2047 }
2048
2049 // Add the new printers to a map.
2050 ArrayMap<PrinterId, PrinterInfo> newPrintersMap = new ArrayMap<>();
2051 final int printerCount = printers.size();
2052 for (int i = 0; i < printerCount; i++) {
2053 PrinterInfo printer = printers.get(i);
2054 newPrintersMap.put(printer.getId(), printer);
2055 }
2056
2057 List<PrinterHolder> newPrinterHolders = new ArrayList<>();
2058
2059 // Update printers we already have which are either updated or removed.
2060 // We do not remove printers if the currently selected printer is removed
2061 // to prevent the user printing to a wrong printer.
2062 final int oldPrinterCount = mPrinterHolders.size();
2063 for (int i = 0; i < oldPrinterCount; i++) {
2064 PrinterHolder printerHolder = mPrinterHolders.get(i);
2065 PrinterId oldPrinterId = printerHolder.printer.getId();
2066 PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId);
2067 if (updatedPrinter != null) {
2068 printerHolder.printer = updatedPrinter;
2069 } else {
2070 printerHolder.removed = true;
2071 }
2072 newPrinterHolders.add(printerHolder);
2073 }
2074
2075 // Add the rest of the new printers, i.e. what is left.
2076 addPrinters(newPrinterHolders, newPrintersMap.values());
2077
2078 mPrinterHolders.clear();
2079 mPrinterHolders.addAll(newPrinterHolders);
2080
2081 notifyDataSetChanged();
2082 }
2083
2084 @Override
2085 public void onPrintersInvalid() {
2086 mPrinterHolders.clear();
2087 notifyDataSetInvalidated();
2088 }
2089
2090 public PrinterHolder getPrinterHolder(PrinterId printerId) {
2091 final int itemCount = getCount();
2092 for (int i = 0; i < itemCount; i++) {
2093 Object item = getItem(i);
2094 if (item instanceof PrinterHolder) {
2095 PrinterHolder printerHolder = (PrinterHolder) item;
2096 if (printerId.equals(printerHolder.printer.getId())) {
2097 return printerHolder;
2098 }
2099 }
2100 }
2101 return null;
2102 }
2103
2104 public void pruneRemovedPrinters() {
2105 final int holderCounts = mPrinterHolders.size();
2106 for (int i = holderCounts - 1; i >= 0; i--) {
2107 PrinterHolder printerHolder = mPrinterHolders.get(i);
2108 if (printerHolder.removed) {
2109 mPrinterHolders.remove(i);
2110 }
2111 }
2112 }
2113
2114 private void addPrinters(List<PrinterHolder> list, Collection<PrinterInfo> printers) {
2115 for (PrinterInfo printer : printers) {
2116 PrinterHolder printerHolder = new PrinterHolder(printer);
2117 list.add(printerHolder);
2118 }
2119 }
2120
2121 private PrinterInfo createFakePdfPrinter() {
2122 MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintActivity.this);
2123
2124 PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
2125
2126 PrinterCapabilitiesInfo.Builder builder =
2127 new PrinterCapabilitiesInfo.Builder(printerId);
2128
2129 String[] mediaSizeIds = getResources().getStringArray(R.array.pdf_printer_media_sizes);
2130 final int mediaSizeIdCount = mediaSizeIds.length;
2131 for (int i = 0; i < mediaSizeIdCount; i++) {
2132 String id = mediaSizeIds[i];
2133 MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id);
2134 builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize));
2135 }
2136
2137 builder.addResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300),
2138 true);
2139 builder.setColorModes(PrintAttributes.COLOR_MODE_COLOR
2140 | PrintAttributes.COLOR_MODE_MONOCHROME, PrintAttributes.COLOR_MODE_COLOR);
2141
2142 return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf),
2143 PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build();
2144 }
2145 }
2146
2147 private final class PrintersObserver extends DataSetObserver {
2148 @Override
2149 public void onChanged() {
Svet Ganov48fec5c2014-07-14 00:14:07 -07002150 PrinterInfo oldPrinterState = mCurrentPrinter;
Svetoslava798c0a2014-05-15 10:47:19 -07002151 if (oldPrinterState == null) {
2152 return;
2153 }
2154
2155 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
2156 oldPrinterState.getId());
2157 if (printerHolder == null) {
2158 return;
2159 }
2160 PrinterInfo newPrinterState = printerHolder.printer;
2161
2162 if (!printerHolder.removed) {
2163 mDestinationSpinnerAdapter.pruneRemovedPrinters();
2164 } else {
2165 onPrinterUnavailable(newPrinterState);
2166 }
2167
2168 if (oldPrinterState.equals(newPrinterState)) {
2169 return;
2170 }
2171
2172 PrinterCapabilitiesInfo oldCapab = oldPrinterState.getCapabilities();
2173 PrinterCapabilitiesInfo newCapab = newPrinterState.getCapabilities();
2174
2175 final boolean hasCapab = newCapab != null;
2176 final boolean gotCapab = oldCapab == null && newCapab != null;
2177 final boolean lostCapab = oldCapab != null && newCapab == null;
2178 final boolean capabChanged = capabilitiesChanged(oldCapab, newCapab);
2179
2180 final int oldStatus = oldPrinterState.getStatus();
2181 final int newStatus = newPrinterState.getStatus();
2182
2183 final boolean isActive = newStatus != PrinterInfo.STATUS_UNAVAILABLE;
2184 final boolean becameActive = (oldStatus == PrinterInfo.STATUS_UNAVAILABLE
2185 && oldStatus != newStatus);
2186 final boolean becameInactive = (newStatus == PrinterInfo.STATUS_UNAVAILABLE
2187 && oldStatus != newStatus);
2188
2189 mPrinterAvailabilityDetector.updatePrinter(newPrinterState);
2190
2191 oldPrinterState.copyFrom(newPrinterState);
2192
2193 if ((isActive && gotCapab) || (becameActive && hasCapab)) {
Svet Ganov525a66b2014-06-14 22:29:00 -07002194 if (hasCapab && capabChanged) {
2195 updatePrintAttributesFromCapabilities(newCapab);
Svetoslave0fa06c2014-09-15 18:17:21 -07002196 updatePrintPreviewController(false);
Svet Ganov525a66b2014-06-14 22:29:00 -07002197 }
Svetoslava798c0a2014-05-15 10:47:19 -07002198 onPrinterAvailable(newPrinterState);
Svet Ganov525a66b2014-06-14 22:29:00 -07002199 } else if ((becameInactive && hasCapab) || (isActive && lostCapab)) {
Svetoslava798c0a2014-05-15 10:47:19 -07002200 onPrinterUnavailable(newPrinterState);
2201 }
2202
Svetoslava798c0a2014-05-15 10:47:19 -07002203 final boolean updateNeeded = ((capabChanged && hasCapab && isActive)
2204 || (becameActive && hasCapab) || (isActive && gotCapab));
2205
2206 if (updateNeeded && canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07002207 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07002208 }
2209
2210 updateOptionsUi();
2211 }
2212
2213 private boolean capabilitiesChanged(PrinterCapabilitiesInfo oldCapabilities,
2214 PrinterCapabilitiesInfo newCapabilities) {
2215 if (oldCapabilities == null) {
2216 if (newCapabilities != null) {
2217 return true;
2218 }
2219 } else if (!oldCapabilities.equals(newCapabilities)) {
2220 return true;
2221 }
2222 return false;
2223 }
2224 }
2225
2226 private final class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
2227 @Override
2228 public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
2229 if (spinner == mDestinationSpinner) {
2230 if (position == AdapterView.INVALID_POSITION) {
2231 return;
2232 }
2233
2234 if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) {
2235 startSelectPrinterActivity();
2236 return;
2237 }
2238
Svet Ganov48fec5c2014-07-14 00:14:07 -07002239 PrinterHolder currentItem = (PrinterHolder) mDestinationSpinner.getSelectedItem();
2240 PrinterInfo currentPrinter = (currentItem != null) ? currentItem.printer : null;
Svetoslava798c0a2014-05-15 10:47:19 -07002241
2242 // Why on earth item selected is called if no selection changed.
Svet Ganov48fec5c2014-07-14 00:14:07 -07002243 if (mCurrentPrinter == currentPrinter) {
Svetoslava798c0a2014-05-15 10:47:19 -07002244 return;
2245 }
Svet Ganov525a66b2014-06-14 22:29:00 -07002246
Svet Ganov48fec5c2014-07-14 00:14:07 -07002247 mCurrentPrinter = currentPrinter;
Svetoslava798c0a2014-05-15 10:47:19 -07002248
2249 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
2250 currentPrinter.getId());
2251 if (!printerHolder.removed) {
Svet Ganov525a66b2014-06-14 22:29:00 -07002252 setState(STATE_CONFIGURING);
Svetoslava798c0a2014-05-15 10:47:19 -07002253 mDestinationSpinnerAdapter.pruneRemovedPrinters();
2254 ensurePreviewUiShown();
2255 }
2256
2257 mPrintJob.setPrinterId(currentPrinter.getId());
2258 mPrintJob.setPrinterName(currentPrinter.getName());
2259
2260 mPrinterRegistry.setTrackedPrinter(currentPrinter.getId());
2261
2262 PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
2263 if (capabilities != null) {
Svet Ganov525a66b2014-06-14 22:29:00 -07002264 updatePrintAttributesFromCapabilities(capabilities);
Svetoslava798c0a2014-05-15 10:47:19 -07002265 }
2266
2267 mPrinterAvailabilityDetector.updatePrinter(currentPrinter);
2268 } else if (spinner == mMediaSizeSpinner) {
2269 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
Svet Ganov525a66b2014-06-14 22:29:00 -07002270 PrintAttributes attributes = mPrintJob.getAttributes();
Svetoslava798c0a2014-05-15 10:47:19 -07002271 if (mOrientationSpinner.getSelectedItemPosition() == 0) {
Svet Ganov525a66b2014-06-14 22:29:00 -07002272 attributes.setMediaSize(mediaItem.value.asPortrait());
Svetoslava798c0a2014-05-15 10:47:19 -07002273 } else {
Svet Ganov525a66b2014-06-14 22:29:00 -07002274 attributes.setMediaSize(mediaItem.value.asLandscape());
Svetoslava798c0a2014-05-15 10:47:19 -07002275 }
2276 } else if (spinner == mColorModeSpinner) {
2277 SpinnerItem<Integer> colorModeItem = mColorModeSpinnerAdapter.getItem(position);
2278 mPrintJob.getAttributes().setColorMode(colorModeItem.value);
Svetoslav948c9a62015-02-02 19:47:04 -08002279 } else if (spinner == mDuplexModeSpinner) {
2280 SpinnerItem<Integer> duplexModeItem = mDuplexModeSpinnerAdapter.getItem(position);
2281 mPrintJob.getAttributes().setDuplexMode(duplexModeItem.value);
Svetoslava798c0a2014-05-15 10:47:19 -07002282 } else if (spinner == mOrientationSpinner) {
2283 SpinnerItem<Integer> orientationItem = mOrientationSpinnerAdapter.getItem(position);
2284 PrintAttributes attributes = mPrintJob.getAttributes();
Svetoslave3bbb3d2014-06-12 10:43:20 -07002285 if (mMediaSizeSpinner.getSelectedItem() != null) {
2286 if (orientationItem.value == ORIENTATION_PORTRAIT) {
2287 attributes.copyFrom(attributes.asPortrait());
2288 } else {
2289 attributes.copyFrom(attributes.asLandscape());
2290 }
Svetoslava798c0a2014-05-15 10:47:19 -07002291 }
Svet Ganov525a66b2014-06-14 22:29:00 -07002292 } else if (spinner == mRangeOptionsSpinner) {
2293 if (mRangeOptionsSpinner.getSelectedItemPosition() == 0) {
2294 mPageRangeEditText.setText("");
2295 } else if (TextUtils.isEmpty(mPageRangeEditText.getText())) {
2296 mPageRangeEditText.setError("");
2297 }
Svetoslava798c0a2014-05-15 10:47:19 -07002298 }
2299
2300 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07002301 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07002302 }
2303
2304 updateOptionsUi();
2305 }
2306
2307 @Override
2308 public void onNothingSelected(AdapterView<?> parent) {
2309 /* do nothing*/
2310 }
2311 }
2312
Svetoslava798c0a2014-05-15 10:47:19 -07002313 private final class SelectAllOnFocusListener implements OnFocusChangeListener {
2314 @Override
2315 public void onFocusChange(View view, boolean hasFocus) {
2316 EditText editText = (EditText) view;
2317 if (!TextUtils.isEmpty(editText.getText())) {
2318 editText.setSelection(editText.getText().length());
2319 }
2320 }
2321 }
2322
2323 private final class RangeTextWatcher implements TextWatcher {
2324 @Override
2325 public void onTextChanged(CharSequence s, int start, int before, int count) {
2326 /* do nothing */
2327 }
2328
2329 @Override
2330 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2331 /* do nothing */
2332 }
2333
2334 @Override
2335 public void afterTextChanged(Editable editable) {
2336 final boolean hadErrors = hasErrors();
2337
2338 String text = editable.toString();
2339
2340 if (TextUtils.isEmpty(text)) {
2341 mPageRangeEditText.setError("");
2342 updateOptionsUi();
2343 return;
2344 }
2345
2346 String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
2347 if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
2348 mPageRangeEditText.setError("");
2349 updateOptionsUi();
2350 return;
2351 }
2352
2353 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
Svet Ganov525a66b2014-06-14 22:29:00 -07002354 final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0;
Svetoslava798c0a2014-05-15 10:47:19 -07002355
2356 // The range
2357 Matcher matcher = PATTERN_DIGITS.matcher(text);
2358 while (matcher.find()) {
2359 String numericString = text.substring(matcher.start(), matcher.end()).trim();
2360 if (TextUtils.isEmpty(numericString)) {
2361 continue;
2362 }
2363 final int pageIndex = Integer.parseInt(numericString);
2364 if (pageIndex < 1 || pageIndex > pageCount) {
2365 mPageRangeEditText.setError("");
2366 updateOptionsUi();
2367 return;
2368 }
2369 }
2370
2371 // We intentionally do not catch the case of the from page being
2372 // greater than the to page. When computing the requested pages
2373 // we just swap them if necessary.
2374
Svetoslava798c0a2014-05-15 10:47:19 -07002375 mPageRangeEditText.setError(null);
2376 mPrintButton.setEnabled(true);
2377 updateOptionsUi();
2378
2379 if (hadErrors && !hasErrors()) {
2380 updateOptionsUi();
2381 }
2382 }
2383 }
2384
2385 private final class EditTextWatcher implements TextWatcher {
2386 @Override
2387 public void onTextChanged(CharSequence s, int start, int before, int count) {
2388 /* do nothing */
2389 }
2390
2391 @Override
2392 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2393 /* do nothing */
2394 }
2395
2396 @Override
2397 public void afterTextChanged(Editable editable) {
2398 final boolean hadErrors = hasErrors();
2399
2400 if (editable.length() == 0) {
2401 mCopiesEditText.setError("");
2402 updateOptionsUi();
2403 return;
2404 }
2405
2406 int copies = 0;
2407 try {
2408 copies = Integer.parseInt(editable.toString());
2409 } catch (NumberFormatException nfe) {
2410 /* ignore */
2411 }
2412
2413 if (copies < MIN_COPIES) {
2414 mCopiesEditText.setError("");
2415 updateOptionsUi();
2416 return;
2417 }
2418
2419 mPrintJob.setCopies(copies);
2420
2421 mCopiesEditText.setError(null);
2422
2423 updateOptionsUi();
2424
2425 if (hadErrors && canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07002426 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07002427 }
2428 }
2429 }
2430
2431 private final class ProgressMessageController implements Runnable {
2432 private static final long PROGRESS_TIMEOUT_MILLIS = 1000;
2433
2434 private final Handler mHandler;
2435
2436 private boolean mPosted;
2437
2438 public ProgressMessageController(Context context) {
2439 mHandler = new Handler(context.getMainLooper(), null, false);
2440 }
2441
2442 public void post() {
2443 if (mPosted) {
2444 return;
2445 }
2446 mPosted = true;
2447 mHandler.postDelayed(this, PROGRESS_TIMEOUT_MILLIS);
2448 }
2449
2450 public void cancel() {
2451 if (!mPosted) {
2452 return;
2453 }
2454 mPosted = false;
2455 mHandler.removeCallbacks(this);
2456 }
2457
2458 @Override
2459 public void run() {
Svet Ganov525a66b2014-06-14 22:29:00 -07002460 mPosted = false;
2461 setState(STATE_UPDATE_SLOW);
Svetoslava798c0a2014-05-15 10:47:19 -07002462 ensureProgressUiShown();
Svet Ganov525a66b2014-06-14 22:29:00 -07002463 updateOptionsUi();
Svetoslava798c0a2014-05-15 10:47:19 -07002464 }
2465 }
Svetoslav62ce3322014-09-04 21:17:17 -07002466
Svetoslavbec22be2014-09-25 13:03:20 -07002467 private static final class DocumentTransformer implements ServiceConnection {
Svetoslav62ce3322014-09-04 21:17:17 -07002468 private static final String TEMP_FILE_PREFIX = "print_job";
2469 private static final String TEMP_FILE_EXTENSION = ".pdf";
2470
2471 private final Context mContext;
2472
2473 private final MutexFileProvider mFileProvider;
2474
2475 private final PrintJobInfo mPrintJob;
2476
2477 private final PageRange[] mPagesToShred;
2478
Svetoslavbec22be2014-09-25 13:03:20 -07002479 private final PrintAttributes mAttributesToApply;
2480
Svetoslav62ce3322014-09-04 21:17:17 -07002481 private final Runnable mCallback;
2482
Svetoslavbec22be2014-09-25 13:03:20 -07002483 public DocumentTransformer(Context context, PrintJobInfo printJob,
2484 MutexFileProvider fileProvider, PrintAttributes attributes,
2485 Runnable callback) {
Svetoslav62ce3322014-09-04 21:17:17 -07002486 mContext = context;
2487 mPrintJob = printJob;
2488 mFileProvider = fileProvider;
2489 mCallback = callback;
2490 mPagesToShred = computePagesToShred(mPrintJob);
Svetoslavbec22be2014-09-25 13:03:20 -07002491 mAttributesToApply = attributes;
Svetoslav62ce3322014-09-04 21:17:17 -07002492 }
2493
Svetoslavbec22be2014-09-25 13:03:20 -07002494 public void transform() {
Svetoslav62ce3322014-09-04 21:17:17 -07002495 // If we have only the pages we want, done.
Svetoslavbec22be2014-09-25 13:03:20 -07002496 if (mPagesToShred.length <= 0 && mAttributesToApply == null) {
Svetoslav62ce3322014-09-04 21:17:17 -07002497 mCallback.run();
2498 return;
2499 }
2500
2501 // Bind to the manipulation service and the work
2502 // will be performed upon connection to the service.
2503 Intent intent = new Intent(PdfManipulationService.ACTION_GET_EDITOR);
2504 intent.setClass(mContext, PdfManipulationService.class);
2505 mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
2506 }
2507
2508 @Override
2509 public void onServiceConnected(ComponentName name, IBinder service) {
2510 final IPdfEditor editor = IPdfEditor.Stub.asInterface(service);
2511 new AsyncTask<Void, Void, Void>() {
2512 @Override
2513 protected Void doInBackground(Void... params) {
Svetoslavfb3532ee2014-09-15 18:28:51 -07002514 // It's OK to access the data members as they are
2515 // final and this code is the last one to touch
2516 // them as shredding is the very last step, so the
2517 // UI is not interactive at this point.
Svetoslavbec22be2014-09-25 13:03:20 -07002518 doTransform(editor);
Svetoslavfb3532ee2014-09-15 18:28:51 -07002519 updatePrintJob();
Svetoslav62ce3322014-09-04 21:17:17 -07002520 return null;
2521 }
Svetoslavfb3532ee2014-09-15 18:28:51 -07002522
2523 @Override
2524 protected void onPostExecute(Void aVoid) {
Svetoslavbec22be2014-09-25 13:03:20 -07002525 mContext.unbindService(DocumentTransformer.this);
Svetoslavfb3532ee2014-09-15 18:28:51 -07002526 mCallback.run();
2527 }
Svetoslavb75632c2014-09-17 18:38:27 -07002528 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
Svetoslav62ce3322014-09-04 21:17:17 -07002529 }
2530
2531 @Override
2532 public void onServiceDisconnected(ComponentName name) {
2533 /* do nothing */
2534 }
2535
Svetoslavbec22be2014-09-25 13:03:20 -07002536 private void doTransform(IPdfEditor editor) {
Svetoslav62ce3322014-09-04 21:17:17 -07002537 File tempFile = null;
2538 ParcelFileDescriptor src = null;
2539 ParcelFileDescriptor dst = null;
2540 InputStream in = null;
2541 OutputStream out = null;
2542 try {
2543 File jobFile = mFileProvider.acquireFile(null);
2544 src = ParcelFileDescriptor.open(jobFile, ParcelFileDescriptor.MODE_READ_WRITE);
2545
2546 // Open the document.
2547 editor.openDocument(src);
2548
2549 // We passed the fd over IPC, close this one.
2550 src.close();
2551
2552 // Drop the pages.
2553 editor.removePages(mPagesToShred);
2554
Svetoslavbec22be2014-09-25 13:03:20 -07002555 // Apply print attributes if needed.
2556 if (mAttributesToApply != null) {
2557 editor.applyPrintAttributes(mAttributesToApply);
2558 }
2559
Svetoslav62ce3322014-09-04 21:17:17 -07002560 // Write the modified PDF to a temp file.
2561 tempFile = File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_EXTENSION,
2562 mContext.getCacheDir());
2563 dst = ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_WRITE);
2564 editor.write(dst);
2565 dst.close();
2566
2567 // Close the document.
2568 editor.closeDocument();
2569
2570 // Copy the temp file over the print job file.
2571 jobFile.delete();
2572 in = new FileInputStream(tempFile);
2573 out = new FileOutputStream(jobFile);
2574 Streams.copy(in, out);
2575 } catch (IOException|RemoteException e) {
2576 Log.e(LOG_TAG, "Error dropping pages", e);
2577 } finally {
2578 IoUtils.closeQuietly(src);
2579 IoUtils.closeQuietly(dst);
2580 IoUtils.closeQuietly(in);
2581 IoUtils.closeQuietly(out);
2582 if (tempFile != null) {
2583 tempFile.delete();
2584 }
Svetoslav56683482014-09-23 16:22:42 -07002585 mFileProvider.releaseFile();
Svetoslav62ce3322014-09-04 21:17:17 -07002586 }
2587 }
2588
2589 private void updatePrintJob() {
2590 // Update the print job pages.
2591 final int newPageCount = PageRangeUtils.getNormalizedPageCount(
2592 mPrintJob.getPages(), 0);
2593 mPrintJob.setPages(new PageRange[]{PageRange.ALL_PAGES});
2594
2595 // Update the print job document info.
2596 PrintDocumentInfo oldDocInfo = mPrintJob.getDocumentInfo();
2597 PrintDocumentInfo newDocInfo = new PrintDocumentInfo
2598 .Builder(oldDocInfo.getName())
2599 .setContentType(oldDocInfo.getContentType())
2600 .setPageCount(newPageCount)
2601 .build();
2602 mPrintJob.setDocumentInfo(newDocInfo);
2603 }
2604
2605 private static PageRange[] computePagesToShred(PrintJobInfo printJob) {
2606 List<PageRange> rangesToShred = new ArrayList<>();
2607 PageRange previousRange = null;
2608
2609 final int pageCount = printJob.getDocumentInfo().getPageCount();
2610
2611 PageRange[] printedPages = printJob.getPages();
2612 final int rangeCount = printedPages.length;
2613 for (int i = 0; i < rangeCount; i++) {
2614 PageRange range = PageRangeUtils.asAbsoluteRange(printedPages[i], pageCount);
2615
2616 if (previousRange == null) {
2617 final int startPageIdx = 0;
2618 final int endPageIdx = range.getStart() - 1;
2619 if (startPageIdx <= endPageIdx) {
2620 PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
2621 rangesToShred.add(removedRange);
2622 }
2623 } else {
2624 final int startPageIdx = previousRange.getEnd() + 1;
2625 final int endPageIdx = range.getStart() - 1;
2626 if (startPageIdx <= endPageIdx) {
2627 PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
2628 rangesToShred.add(removedRange);
2629 }
2630 }
2631
2632 if (i == rangeCount - 1) {
2633 final int startPageIdx = range.getEnd() + 1;
2634 final int endPageIdx = printJob.getDocumentInfo().getPageCount() - 1;
2635 if (startPageIdx <= endPageIdx) {
2636 PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
2637 rangesToShred.add(removedRange);
2638 }
2639 }
2640
2641 previousRange = range;
2642 }
2643
2644 PageRange[] result = new PageRange[rangesToShred.size()];
2645 rangesToShred.toArray(result);
2646 return result;
2647 }
2648 }
Svet Ganov561b8932014-09-02 21:51:45 +00002649}