blob: a24d6643f8ae92ca049be6ee75638ff1d0518d95 [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;
Philip P. Moltmann853a6f52015-11-03 10:38:56 -080020import android.app.AlertDialog;
21import android.app.Dialog;
22import android.app.DialogFragment;
Svetoslava798c0a2014-05-15 10:47:19 -070023import android.app.Fragment;
24import android.app.FragmentTransaction;
Philip P. Moltmann66c96592016-02-24 11:32:43 -080025import android.app.LoaderManager;
Svetoslava798c0a2014-05-15 10:47:19 -070026import android.content.ActivityNotFoundException;
27import android.content.ComponentName;
28import android.content.Context;
Philip P. Moltmann853a6f52015-11-03 10:38:56 -080029import android.content.DialogInterface;
Svetoslava798c0a2014-05-15 10:47:19 -070030import android.content.Intent;
Philip P. Moltmann66c96592016-02-24 11:32:43 -080031import android.content.Loader;
Svetoslav62ce3322014-09-04 21:17:17 -070032import android.content.ServiceConnection;
Philip P. Moltmann853a6f52015-11-03 10:38:56 -080033import android.content.SharedPreferences;
34import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
Philip P. Moltmann853a6f52015-11-03 10:38:56 -080035import android.content.pm.PackageManager;
Svetoslava798c0a2014-05-15 10:47:19 -070036import android.content.pm.PackageManager.NameNotFoundException;
37import android.content.pm.ResolveInfo;
Svet Ganov525a66b2014-06-14 22:29:00 -070038import android.content.res.Configuration;
Svetoslava798c0a2014-05-15 10:47:19 -070039import android.database.DataSetObserver;
40import android.graphics.drawable.Drawable;
41import android.net.Uri;
Svetoslav62ce3322014-09-04 21:17:17 -070042import android.os.AsyncTask;
Svetoslava798c0a2014-05-15 10:47:19 -070043import android.os.Bundle;
44import android.os.Handler;
45import android.os.IBinder;
Svetoslav62ce3322014-09-04 21:17:17 -070046import android.os.ParcelFileDescriptor;
47import android.os.RemoteException;
Svetoslava798c0a2014-05-15 10:47:19 -070048import android.print.IPrintDocumentAdapter;
49import android.print.PageRange;
50import android.print.PrintAttributes;
51import android.print.PrintAttributes.MediaSize;
52import android.print.PrintAttributes.Resolution;
53import android.print.PrintDocumentInfo;
54import android.print.PrintJobInfo;
55import android.print.PrintManager;
Philip P. Moltmann66c96592016-02-24 11:32:43 -080056import android.print.PrintServicesLoader;
Svetoslava798c0a2014-05-15 10:47:19 -070057import android.print.PrinterCapabilitiesInfo;
58import android.print.PrinterId;
59import android.print.PrinterInfo;
60import android.printservice.PrintService;
Philip P. Moltmann66c96592016-02-24 11:32:43 -080061import android.printservice.PrintServiceInfo;
Svetoslava798c0a2014-05-15 10:47:19 -070062import android.provider.DocumentsContract;
63import android.text.Editable;
64import android.text.TextUtils;
65import android.text.TextUtils.SimpleStringSplitter;
66import android.text.TextWatcher;
67import android.util.ArrayMap;
Philip P. Moltmann4959caf2016-01-21 14:30:56 -080068import android.util.ArraySet;
Svetoslava798c0a2014-05-15 10:47:19 -070069import android.util.Log;
Philip P. Moltmann443075a2016-01-26 13:04:21 -080070import android.util.TypedValue;
Svetoslava798c0a2014-05-15 10:47:19 -070071import android.view.KeyEvent;
Philip P. Moltmann5e548962015-11-13 15:33:40 -080072import android.view.MotionEvent;
Svetoslava798c0a2014-05-15 10:47:19 -070073import android.view.View;
74import android.view.View.OnClickListener;
75import android.view.View.OnFocusChangeListener;
76import android.view.ViewGroup;
77import android.view.inputmethod.InputMethodManager;
78import android.widget.AdapterView;
79import android.widget.AdapterView.OnItemSelectedListener;
80import android.widget.ArrayAdapter;
81import android.widget.BaseAdapter;
82import android.widget.Button;
83import android.widget.EditText;
84import android.widget.ImageView;
85import android.widget.Spinner;
86import android.widget.TextView;
87
Chris Wrendcc34fd2015-07-30 14:27:02 -040088import com.android.internal.logging.MetricsLogger;
Svetoslava798c0a2014-05-15 10:47:19 -070089import com.android.printspooler.R;
Svet Ganov525a66b2014-06-14 22:29:00 -070090import com.android.printspooler.model.MutexFileProvider;
Svetoslava798c0a2014-05-15 10:47:19 -070091import com.android.printspooler.model.PrintSpoolerProvider;
92import com.android.printspooler.model.PrintSpoolerService;
93import com.android.printspooler.model.RemotePrintDocument;
Svet Ganov525a66b2014-06-14 22:29:00 -070094import com.android.printspooler.model.RemotePrintDocument.RemotePrintDocumentInfo;
Svetoslav62ce3322014-09-04 21:17:17 -070095import com.android.printspooler.renderer.IPdfEditor;
96import com.android.printspooler.renderer.PdfManipulationService;
Philip P. Moltmann853a6f52015-11-03 10:38:56 -080097import com.android.printspooler.util.ApprovedPrintServices;
Svetoslava798c0a2014-05-15 10:47:19 -070098import com.android.printspooler.util.MediaSizeUtils;
99import com.android.printspooler.util.MediaSizeUtils.MediaSizeComparator;
100import com.android.printspooler.util.PageRangeUtils;
Svet Ganov525a66b2014-06-14 22:29:00 -0700101import com.android.printspooler.widget.PrintContentView;
102import com.android.printspooler.widget.PrintContentView.OptionsStateChangeListener;
103import com.android.printspooler.widget.PrintContentView.OptionsStateController;
Philip P. Moltmann853a6f52015-11-03 10:38:56 -0800104
Svetoslav62ce3322014-09-04 21:17:17 -0700105import libcore.io.IoUtils;
106import libcore.io.Streams;
Svetoslava798c0a2014-05-15 10:47:19 -0700107
Svetoslav62ce3322014-09-04 21:17:17 -0700108import java.io.File;
109import java.io.FileInputStream;
110import java.io.FileOutputStream;
Svet Ganov525a66b2014-06-14 22:29:00 -0700111import java.io.IOException;
Svetoslav62ce3322014-09-04 21:17:17 -0700112import java.io.InputStream;
113import java.io.OutputStream;
Svet Ganov525a66b2014-06-14 22:29:00 -0700114import java.util.ArrayList;
115import java.util.Arrays;
116import java.util.Collection;
117import java.util.Collections;
118import java.util.List;
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800119import java.util.Objects;
Svetoslava798c0a2014-05-15 10:47:19 -0700120import java.util.regex.Matcher;
121import java.util.regex.Pattern;
122
123public class PrintActivity extends Activity implements RemotePrintDocument.UpdateResultCallbacks,
Svetoslav5ef522b2014-07-23 20:15:09 -0700124 PrintErrorFragment.OnActionListener, PageAdapter.ContentCallbacks,
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800125 OptionsStateChangeListener, OptionsStateController,
126 LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> {
Svetoslava798c0a2014-05-15 10:47:19 -0700127 private static final String LOG_TAG = "PrintActivity";
128
Svetoslavf8ffa562014-07-23 18:22:03 -0700129 private static final boolean DEBUG = false;
Svet Ganov525a66b2014-06-14 22:29:00 -0700130
Svetoslava798c0a2014-05-15 10:47:19 -0700131 public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
132
Svet Ganov525a66b2014-06-14 22:29:00 -0700133 private static final String FRAGMENT_TAG = "FRAGMENT_TAG";
134
Philip P. Moltmann5e548962015-11-13 15:33:40 -0800135 private static final String HAS_PRINTED_PREF = "has_printed";
136
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800137 private static final int LOADER_ID_ENABLED_PRINT_SERVICES = 1;
138 private static final int LOADER_ID_PRINT_REGISTRY = 2;
139 private static final int LOADER_ID_PRINT_REGISTRY_INT = 3;
140
Svetoslava798c0a2014-05-15 10:47:19 -0700141 private static final int ORIENTATION_PORTRAIT = 0;
142 private static final int ORIENTATION_LANDSCAPE = 1;
143
144 private static final int ACTIVITY_REQUEST_CREATE_FILE = 1;
145 private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2;
146 private static final int ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS = 3;
147
148 private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
149
150 private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800151 private static final int DEST_ADAPTER_ITEM_ID_MORE = Integer.MAX_VALUE - 1;
Svetoslava798c0a2014-05-15 10:47:19 -0700152
Svetoslav6552bf32014-09-03 21:15:55 -0700153 private static final int STATE_INITIALIZING = 0;
154 private static final int STATE_CONFIGURING = 1;
155 private static final int STATE_PRINT_CONFIRMED = 2;
156 private static final int STATE_PRINT_CANCELED = 3;
157 private static final int STATE_UPDATE_FAILED = 4;
158 private static final int STATE_CREATE_FILE_FAILED = 5;
159 private static final int STATE_PRINTER_UNAVAILABLE = 6;
160 private static final int STATE_UPDATE_SLOW = 7;
161 private static final int STATE_PRINT_COMPLETED = 8;
Svetoslava798c0a2014-05-15 10:47:19 -0700162
163 private static final int UI_STATE_PREVIEW = 0;
164 private static final int UI_STATE_ERROR = 1;
165 private static final int UI_STATE_PROGRESS = 2;
166
167 private static final int MIN_COPIES = 1;
168 private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES);
169
170 private static final Pattern PATTERN_DIGITS = Pattern.compile("[\\d]+");
171
172 private static final Pattern PATTERN_ESCAPE_SPECIAL_CHARS = Pattern.compile(
173 "(?=[]\\[+&|!(){}^\"~*?:\\\\])");
174
175 private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
Svet Ganov525a66b2014-06-14 22:29:00 -0700176 "[\\s]*[0-9]+[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
177 + "[\\s]*[0-9]+[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
Svetoslava798c0a2014-05-15 10:47:19 -0700178
Svet Ganov525a66b2014-06-14 22:29:00 -0700179 public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[]{PageRange.ALL_PAGES};
Svetoslava798c0a2014-05-15 10:47:19 -0700180
Philip P. Moltmannc2f913d2016-02-01 12:03:48 -0800181 private boolean mIsOptionsUiBound = false;
182
Svetoslava798c0a2014-05-15 10:47:19 -0700183 private final PrinterAvailabilityDetector mPrinterAvailabilityDetector =
184 new PrinterAvailabilityDetector();
185
186 private final SimpleStringSplitter mStringCommaSplitter = new SimpleStringSplitter(',');
187
188 private final OnFocusChangeListener mSelectAllOnFocusListener = new SelectAllOnFocusListener();
189
190 private PrintSpoolerProvider mSpoolerProvider;
191
Svet Ganov525a66b2014-06-14 22:29:00 -0700192 private PrintPreviewController mPrintPreviewController;
193
Svetoslava798c0a2014-05-15 10:47:19 -0700194 private PrintJobInfo mPrintJob;
195 private RemotePrintDocument mPrintedDocument;
196 private PrinterRegistry mPrinterRegistry;
197
198 private EditText mCopiesEditText;
199
Svetoslava798c0a2014-05-15 10:47:19 -0700200 private TextView mPageRangeTitle;
201 private EditText mPageRangeEditText;
202
203 private Spinner mDestinationSpinner;
204 private DestinationAdapter mDestinationSpinnerAdapter;
Philip P. Moltmann5e548962015-11-13 15:33:40 -0800205 private boolean mShowDestinationPrompt;
Svetoslava798c0a2014-05-15 10:47:19 -0700206
207 private Spinner mMediaSizeSpinner;
208 private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
209
210 private Spinner mColorModeSpinner;
211 private ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
212
Svetoslav948c9a62015-02-02 19:47:04 -0800213 private Spinner mDuplexModeSpinner;
214 private ArrayAdapter<SpinnerItem<Integer>> mDuplexModeSpinnerAdapter;
215
Svetoslava798c0a2014-05-15 10:47:19 -0700216 private Spinner mOrientationSpinner;
217 private ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
218
219 private Spinner mRangeOptionsSpinner;
220
Svet Ganov525a66b2014-06-14 22:29:00 -0700221 private PrintContentView mOptionsContent;
Svetoslava798c0a2014-05-15 10:47:19 -0700222
Svetoslave652b022014-09-09 22:11:10 -0700223 private View mSummaryContainer;
Svetoslava798c0a2014-05-15 10:47:19 -0700224 private TextView mSummaryCopies;
225 private TextView mSummaryPaperSize;
226
Svetoslava798c0a2014-05-15 10:47:19 -0700227 private Button mMoreOptionsButton;
228
229 private ImageView mPrintButton;
230
231 private ProgressMessageController mProgressMessageController;
Svetoslav62ce3322014-09-04 21:17:17 -0700232 private MutexFileProvider mFileProvider;
Svetoslava798c0a2014-05-15 10:47:19 -0700233
234 private MediaSizeComparator mMediaSizeComparator;
235
Svet Ganov48fec5c2014-07-14 00:14:07 -0700236 private PrinterInfo mCurrentPrinter;
Svetoslava798c0a2014-05-15 10:47:19 -0700237
Svet Ganov525a66b2014-06-14 22:29:00 -0700238 private PageRange[] mSelectedPages;
239
Svetoslava798c0a2014-05-15 10:47:19 -0700240 private String mCallingPackageName;
241
Svetoslav73764e32014-07-15 15:56:46 -0700242 private int mCurrentPageCount;
243
Svetoslav6552bf32014-09-03 21:15:55 -0700244 private int mState = STATE_INITIALIZING;
Svetoslava798c0a2014-05-15 10:47:19 -0700245
246 private int mUiState = UI_STATE_PREVIEW;
247
Philip P. Moltmann1bb7f362016-02-26 14:21:20 -0800248 /** Observer for changes to the printers */
249 private PrintersObserver mPrintersObserver;
250
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800251 /** Advances options activity name for current printer */
252 private ComponentName mAdvancedPrintOptionsActivity;
253
254 /** Whether at least one print services is enabled or not */
255 private boolean mArePrintServicesEnabled;
256
Philip P. Moltmannb170c082016-03-21 12:48:58 -0700257 /** Is doFinish() already in progress */
258 private boolean mIsFinishing;
259
Svetoslava798c0a2014-05-15 10:47:19 -0700260 @Override
261 public void onCreate(Bundle savedInstanceState) {
262 super.onCreate(savedInstanceState);
263
264 Bundle extras = getIntent().getExtras();
265
266 mPrintJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB);
267 if (mPrintJob == null) {
268 throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_JOB
269 + " cannot be null");
270 }
Philip P. Moltmannb4efdb42015-11-10 14:58:44 -0800271 if (mPrintJob.getAttributes() == null) {
272 mPrintJob.setAttributes(new PrintAttributes.Builder().build());
273 }
Svetoslava798c0a2014-05-15 10:47:19 -0700274
275 final IBinder adapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
276 if (adapter == null) {
277 throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER
278 + " cannot be null");
279 }
280
281 mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
282
283 // This will take just a few milliseconds, so just wait to
284 // bind to the local service before showing the UI.
285 mSpoolerProvider = new PrintSpoolerProvider(this,
286 new Runnable() {
287 @Override
288 public void run() {
Philip P. Moltmanncf5b7772016-02-01 16:58:57 -0800289 if (isFinishing()) {
290 // onPause might have not been able to cancel the job, see PrintActivity#onPause
291 // To be sure, cancel the job again. Double canceling does no harm.
292 mSpoolerProvider.getSpooler().setPrintJobState(mPrintJob.getId(),
293 PrintJobInfo.STATE_CANCELED, null);
294 } else {
295 onConnectedToPrintSpooler(adapter);
296 }
Svetoslava798c0a2014-05-15 10:47:19 -0700297 }
298 });
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800299
300 getLoaderManager().initLoader(LOADER_ID_ENABLED_PRINT_SERVICES, null, this);
Svetoslava798c0a2014-05-15 10:47:19 -0700301 }
302
Svet Ganov525a66b2014-06-14 22:29:00 -0700303 private void onConnectedToPrintSpooler(final IBinder documentAdapter) {
304 // Now that we are bound to the print spooler service,
305 // create the printer registry and wait for it to get
306 // the first batch of results which will be delivered
307 // after reading historical data. This should be pretty
308 // fast, so just wait before showing the UI.
309 mPrinterRegistry = new PrinterRegistry(PrintActivity.this,
310 new Runnable() {
311 @Override
312 public void run() {
313 onPrinterRegistryReady(documentAdapter);
314 }
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800315 }, LOADER_ID_PRINT_REGISTRY, LOADER_ID_PRINT_REGISTRY_INT);
Svet Ganov525a66b2014-06-14 22:29:00 -0700316 }
317
318 private void onPrinterRegistryReady(IBinder documentAdapter) {
319 // Now that we are bound to the local print spooler service
320 // and the printer registry loaded the historical printers
321 // we can show the UI without flickering.
322 setTitle(R.string.print_dialog);
323 setContentView(R.layout.print_activity);
324
Svet Ganov525a66b2014-06-14 22:29:00 -0700325 try {
Svetoslav62ce3322014-09-04 21:17:17 -0700326 mFileProvider = new MutexFileProvider(
Svet Ganov525a66b2014-06-14 22:29:00 -0700327 PrintSpoolerService.generateFileForPrintJob(
328 PrintActivity.this, mPrintJob.getId()));
329 } catch (IOException ioe) {
330 // At this point we cannot recover, so just take it down.
331 throw new IllegalStateException("Cannot create print job file", ioe);
332 }
333
334 mPrintPreviewController = new PrintPreviewController(PrintActivity.this,
Svetoslav62ce3322014-09-04 21:17:17 -0700335 mFileProvider);
Svet Ganov525a66b2014-06-14 22:29:00 -0700336 mPrintedDocument = new RemotePrintDocument(PrintActivity.this,
337 IPrintDocumentAdapter.Stub.asInterface(documentAdapter),
Svetoslave17123d2014-09-11 12:39:05 -0700338 mFileProvider, new RemotePrintDocument.RemoteAdapterDeathObserver() {
Svet Ganov525a66b2014-06-14 22:29:00 -0700339 @Override
Svetoslave17123d2014-09-11 12:39:05 -0700340 public void onDied() {
Svetoslav05e041b2014-10-14 14:14:49 -0700341 // If we are finishing or we are in a state that we do not need any
342 // data from the printing app, then no need to finish.
343 if (isFinishing() || (isFinalState(mState) && !mPrintedDocument.isUpdating())) {
Svetoslave17123d2014-09-11 12:39:05 -0700344 return;
345 }
Svetoslav62ce3322014-09-04 21:17:17 -0700346 setState(STATE_PRINT_CANCELED);
Philip P. Moltmann645a3e12016-02-25 11:20:41 -0800347 mPrintedDocument.cancel(true);
Svetoslave17123d2014-09-11 12:39:05 -0700348 doFinish();
Svet Ganov525a66b2014-06-14 22:29:00 -0700349 }
350 }, PrintActivity.this);
Svet Ganov525a66b2014-06-14 22:29:00 -0700351 mProgressMessageController = new ProgressMessageController(
352 PrintActivity.this);
Svet Ganov525a66b2014-06-14 22:29:00 -0700353 mMediaSizeComparator = new MediaSizeComparator(PrintActivity.this);
Svet Ganov525a66b2014-06-14 22:29:00 -0700354 mDestinationSpinnerAdapter = new DestinationAdapter();
355
356 bindUi();
Svet Ganov525a66b2014-06-14 22:29:00 -0700357 updateOptionsUi();
358
359 // Now show the updated UI to avoid flicker.
360 mOptionsContent.setVisibility(View.VISIBLE);
Svet Ganov525a66b2014-06-14 22:29:00 -0700361 mSelectedPages = computeSelectedPages();
Svet Ganov525a66b2014-06-14 22:29:00 -0700362 mPrintedDocument.start();
363
364 ensurePreviewUiShown();
Svetoslav6552bf32014-09-03 21:15:55 -0700365
366 setState(STATE_CONFIGURING);
Svet Ganov525a66b2014-06-14 22:29:00 -0700367 }
368
Svetoslava798c0a2014-05-15 10:47:19 -0700369 @Override
Philip P. Moltmanne2b95e42015-11-20 11:53:12 -0800370 public void onStart() {
371 super.onStart();
Philip P. Moltmann51dbc8e2016-02-01 13:56:45 -0800372 if (mPrinterRegistry != null && mCurrentPrinter != null) {
Svetoslavd724a402014-09-16 11:53:15 -0700373 mPrinterRegistry.setTrackedPrinter(mCurrentPrinter.getId());
374 }
Chris Wrendcc34fd2015-07-30 14:27:02 -0400375 MetricsLogger.count(this, "print_preview", 1);
Svetoslavd724a402014-09-16 11:53:15 -0700376 }
377
378 @Override
Svetoslava798c0a2014-05-15 10:47:19 -0700379 public void onPause() {
Svetoslav3ef8e202014-09-10 14:35:58 -0700380 PrintSpoolerService spooler = mSpoolerProvider.getSpooler();
381
Svetoslav6552bf32014-09-03 21:15:55 -0700382 if (mState == STATE_INITIALIZING) {
Svetoslav3ef8e202014-09-10 14:35:58 -0700383 if (isFinishing()) {
Philip P. Moltmanncf5b7772016-02-01 16:58:57 -0800384 if (spooler != null) {
385 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
386 }
Svetoslav3ef8e202014-09-10 14:35:58 -0700387 }
Svetoslav6552bf32014-09-03 21:15:55 -0700388 super.onPause();
389 return;
390 }
391
Svetoslava798c0a2014-05-15 10:47:19 -0700392 if (isFinishing()) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700393 spooler.updatePrintJobUserConfigurableOptionsNoPersistence(mPrintJob);
394
395 switch (mState) {
Svetoslavb59555c2014-07-24 10:13:00 -0700396 case STATE_PRINT_COMPLETED: {
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800397 if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) {
398 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_COMPLETED,
399 null);
400 } else {
401 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_QUEUED,
402 null);
403 }
Svetoslavb59555c2014-07-24 10:13:00 -0700404 } break;
405
Svet Ganov525a66b2014-06-14 22:29:00 -0700406 case STATE_CREATE_FILE_FAILED: {
407 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_FAILED,
408 getString(R.string.print_write_error_message));
409 } break;
410
411 default: {
412 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
413 } break;
Svetoslava798c0a2014-05-15 10:47:19 -0700414 }
Svetoslava798c0a2014-05-15 10:47:19 -0700415 }
416
Philip P. Moltmanne2b95e42015-11-20 11:53:12 -0800417 super.onPause();
418 }
419
420 @Override
421 protected void onStop() {
Svetoslava798c0a2014-05-15 10:47:19 -0700422 mPrinterAvailabilityDetector.cancel();
Philip P. Moltmann51dbc8e2016-02-01 13:56:45 -0800423
424 if (mPrinterRegistry != null) {
425 mPrinterRegistry.setTrackedPrinter(null);
426 }
Svetoslava798c0a2014-05-15 10:47:19 -0700427
Philip P. Moltmanne2b95e42015-11-20 11:53:12 -0800428 super.onStop();
Svetoslava798c0a2014-05-15 10:47:19 -0700429 }
430
431 @Override
432 public boolean onKeyDown(int keyCode, KeyEvent event) {
433 if (keyCode == KeyEvent.KEYCODE_BACK) {
434 event.startTracking();
435 return true;
436 }
437 return super.onKeyDown(keyCode, event);
438 }
439
440 @Override
441 public boolean onKeyUp(int keyCode, KeyEvent event) {
Svetoslav6552bf32014-09-03 21:15:55 -0700442 if (mState == STATE_INITIALIZING) {
Svetoslave17123d2014-09-11 12:39:05 -0700443 doFinish();
444 return true;
445 }
446
Svet Ganovfce84f02014-10-31 16:56:52 -0700447 if (mState == STATE_PRINT_CANCELED || mState == STATE_PRINT_CONFIRMED
Svetoslave17123d2014-09-11 12:39:05 -0700448 || mState == STATE_PRINT_COMPLETED) {
Svetoslav3ef8e202014-09-10 14:35:58 -0700449 return true;
Svetoslav6552bf32014-09-03 21:15:55 -0700450 }
451
Svetoslava798c0a2014-05-15 10:47:19 -0700452 if (keyCode == KeyEvent.KEYCODE_BACK
453 && event.isTracking() && !event.isCanceled()) {
Svetoslav6552bf32014-09-03 21:15:55 -0700454 if (mPrintPreviewController != null && mPrintPreviewController.isOptionsOpened()
Svetoslav15cbc8a2014-07-11 09:45:07 -0700455 && !hasErrors()) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700456 mPrintPreviewController.closeOptions();
457 } else {
458 cancelPrint();
459 }
Svetoslava798c0a2014-05-15 10:47:19 -0700460 return true;
461 }
462 return super.onKeyUp(keyCode, event);
463 }
464
465 @Override
Svetoslav5ef522b2014-07-23 20:15:09 -0700466 public void onRequestContentUpdate() {
Svet Ganov525a66b2014-06-14 22:29:00 -0700467 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -0700468 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -0700469 }
470 }
471
472 @Override
Svetoslav5ef522b2014-07-23 20:15:09 -0700473 public void onMalformedPdfFile() {
Svet Ganovfce84f02014-10-31 16:56:52 -0700474 onPrintDocumentError("Cannot print a malformed PDF file");
475 }
476
477 @Override
478 public void onSecurePdfFile() {
479 onPrintDocumentError("Cannot print a password protected PDF file");
480 }
481
482 private void onPrintDocumentError(String message) {
Svetoslav5ef522b2014-07-23 20:15:09 -0700483 mProgressMessageController.cancel();
484 ensureErrorUiShown(null, PrintErrorFragment.ACTION_RETRY);
485
486 setState(STATE_UPDATE_FAILED);
487
488 updateOptionsUi();
Svet Ganovfce84f02014-10-31 16:56:52 -0700489
490 mPrintedDocument.kill(message);
Svetoslav5ef522b2014-07-23 20:15:09 -0700491 }
492
493 @Override
Svet Ganov525a66b2014-06-14 22:29:00 -0700494 public void onActionPerformed() {
Svetoslav5ef522b2014-07-23 20:15:09 -0700495 if (mState == STATE_UPDATE_FAILED
Svetoslav62ce3322014-09-04 21:17:17 -0700496 && canUpdateDocument() && updateDocument(true)) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700497 ensurePreviewUiShown();
498 setState(STATE_CONFIGURING);
499 updateOptionsUi();
Svetoslava798c0a2014-05-15 10:47:19 -0700500 }
501 }
502
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800503 @Override
Svetoslava798c0a2014-05-15 10:47:19 -0700504 public void onUpdateCanceled() {
Svet Ganov525a66b2014-06-14 22:29:00 -0700505 if (DEBUG) {
506 Log.i(LOG_TAG, "onUpdateCanceled()");
507 }
508
Svetoslava798c0a2014-05-15 10:47:19 -0700509 mProgressMessageController.cancel();
510 ensurePreviewUiShown();
Svet Ganov525a66b2014-06-14 22:29:00 -0700511
512 switch (mState) {
513 case STATE_PRINT_CONFIRMED: {
514 requestCreatePdfFileOrFinish();
515 } break;
516
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800517 case STATE_CREATE_FILE_FAILED:
518 case STATE_PRINT_COMPLETED:
Svet Ganov525a66b2014-06-14 22:29:00 -0700519 case STATE_PRINT_CANCELED: {
Svetoslave17123d2014-09-11 12:39:05 -0700520 doFinish();
Svet Ganov525a66b2014-06-14 22:29:00 -0700521 } break;
522 }
Svetoslava798c0a2014-05-15 10:47:19 -0700523 }
524
525 @Override
Svet Ganov525a66b2014-06-14 22:29:00 -0700526 public void onUpdateCompleted(RemotePrintDocumentInfo document) {
527 if (DEBUG) {
528 Log.i(LOG_TAG, "onUpdateCompleted()");
529 }
530
Svetoslava798c0a2014-05-15 10:47:19 -0700531 mProgressMessageController.cancel();
532 ensurePreviewUiShown();
533
534 // Update the print job with the info for the written document. The page
535 // count we get from the remote document is the pages in the document from
536 // the app perspective but the print job should contain the page count from
537 // print service perspective which is the pages in the written PDF not the
538 // pages in the printed document.
539 PrintDocumentInfo info = document.info;
Svet Ganov525a66b2014-06-14 22:29:00 -0700540 if (info != null) {
541 final int pageCount = PageRangeUtils.getNormalizedPageCount(document.writtenPages,
542 getAdjustedPageCount(info));
543 PrintDocumentInfo adjustedInfo = new PrintDocumentInfo.Builder(info.getName())
544 .setContentType(info.getContentType())
545 .setPageCount(pageCount)
546 .build();
547 mPrintJob.setDocumentInfo(adjustedInfo);
548 mPrintJob.setPages(document.printedPages);
Svetoslava798c0a2014-05-15 10:47:19 -0700549 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700550
551 switch (mState) {
552 case STATE_PRINT_CONFIRMED: {
553 requestCreatePdfFileOrFinish();
554 } break;
555
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800556 case STATE_CREATE_FILE_FAILED:
557 case STATE_PRINT_COMPLETED:
Svet Ganoveaaf0512014-11-26 04:09:27 -0800558 case STATE_PRINT_CANCELED: {
559 updateOptionsUi();
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800560
561 doFinish();
Svet Ganoveaaf0512014-11-26 04:09:27 -0800562 } break;
563
Svet Ganov525a66b2014-06-14 22:29:00 -0700564 default: {
565 updatePrintPreviewController(document.changed);
566
567 setState(STATE_CONFIGURING);
568 updateOptionsUi();
569 } break;
570 }
Svetoslava798c0a2014-05-15 10:47:19 -0700571 }
572
573 @Override
574 public void onUpdateFailed(CharSequence error) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700575 if (DEBUG) {
576 Log.i(LOG_TAG, "onUpdateFailed()");
577 }
578
579 mProgressMessageController.cancel();
Svetoslava798c0a2014-05-15 10:47:19 -0700580 ensureErrorUiShown(error, PrintErrorFragment.ACTION_RETRY);
Svet Ganov525a66b2014-06-14 22:29:00 -0700581
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800582 if (mState == STATE_CREATE_FILE_FAILED
583 || mState == STATE_PRINT_COMPLETED
584 || mState == STATE_PRINT_CANCELED) {
585 doFinish();
586 }
587
Svet Ganov525a66b2014-06-14 22:29:00 -0700588 setState(STATE_UPDATE_FAILED);
589
Svetoslava798c0a2014-05-15 10:47:19 -0700590 updateOptionsUi();
591 }
592
593 @Override
Svet Ganov525a66b2014-06-14 22:29:00 -0700594 public void onOptionsOpened() {
595 updateSelectedPagesFromPreview();
596 }
597
598 @Override
599 public void onOptionsClosed() {
600 PageRange[] selectedPages = computeSelectedPages();
601 if (!Arrays.equals(mSelectedPages, selectedPages)) {
602 mSelectedPages = selectedPages;
603
604 // Update preview.
605 updatePrintPreviewController(false);
606 }
607
608 // Make sure the IME is not on the way of preview as
609 // the user may have used it to type copies or range.
Yohei Yukawa777ef952015-11-25 20:32:24 -0800610 InputMethodManager imm = getSystemService(InputMethodManager.class);
Svet Ganov525a66b2014-06-14 22:29:00 -0700611 imm.hideSoftInputFromWindow(mDestinationSpinner.getWindowToken(), 0);
612 }
613
614 private void updatePrintPreviewController(boolean contentUpdated) {
615 // If we have not heard from the application, do nothing.
616 RemotePrintDocumentInfo documentInfo = mPrintedDocument.getDocumentInfo();
617 if (!documentInfo.laidout) {
618 return;
619 }
620
621 // Update the preview controller.
622 mPrintPreviewController.onContentUpdated(contentUpdated,
623 getAdjustedPageCount(documentInfo.info),
624 mPrintedDocument.getDocumentInfo().writtenPages,
625 mSelectedPages, mPrintJob.getAttributes().getMediaSize(),
626 mPrintJob.getAttributes().getMinMargins());
627 }
628
629
630 @Override
631 public boolean canOpenOptions() {
632 return true;
633 }
634
635 @Override
636 public boolean canCloseOptions() {
637 return !hasErrors();
638 }
639
640 @Override
641 public void onConfigurationChanged(Configuration newConfig) {
642 super.onConfigurationChanged(newConfig);
Svet Ganovf6cd14d2014-11-20 07:43:30 -0800643 if (mPrintPreviewController != null) {
644 mPrintPreviewController.onOrientationChanged();
645 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700646 }
647
648 @Override
Svetoslava798c0a2014-05-15 10:47:19 -0700649 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
650 switch (requestCode) {
651 case ACTIVITY_REQUEST_CREATE_FILE: {
652 onStartCreateDocumentActivityResult(resultCode, data);
Svetoslav5ef522b2014-07-23 20:15:09 -0700653 } break;
Svetoslava798c0a2014-05-15 10:47:19 -0700654
655 case ACTIVITY_REQUEST_SELECT_PRINTER: {
656 onSelectPrinterActivityResult(resultCode, data);
Svetoslav5ef522b2014-07-23 20:15:09 -0700657 } break;
Svetoslava798c0a2014-05-15 10:47:19 -0700658
659 case ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS: {
660 onAdvancedPrintOptionsActivityResult(resultCode, data);
Svetoslav5ef522b2014-07-23 20:15:09 -0700661 } break;
Svetoslava798c0a2014-05-15 10:47:19 -0700662 }
663 }
664
665 private void startCreateDocumentActivity() {
Svetoslave1dcb392014-09-26 19:49:14 -0700666 if (!isResumed()) {
667 return;
668 }
Svetoslava798c0a2014-05-15 10:47:19 -0700669 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
670 if (info == null) {
671 return;
672 }
673 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
674 intent.setType("application/pdf");
675 intent.putExtra(Intent.EXTRA_TITLE, info.getName());
676 intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
677 startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
678 }
679
680 private void onStartCreateDocumentActivityResult(int resultCode, Intent data) {
681 if (resultCode == RESULT_OK && data != null) {
Svetoslavb59555c2014-07-24 10:13:00 -0700682 updateOptionsUi();
Svetoslav62ce3322014-09-04 21:17:17 -0700683 final Uri uri = data.getData();
Svet Ganov525a66b2014-06-14 22:29:00 -0700684 // Calling finish here does not invoke lifecycle callbacks but we
685 // update the print job in onPause if finishing, hence post a message.
686 mDestinationSpinner.post(new Runnable() {
687 @Override
688 public void run() {
Svetoslavbec22be2014-09-25 13:03:20 -0700689 transformDocumentAndFinish(uri);
Svet Ganov525a66b2014-06-14 22:29:00 -0700690 }
691 });
Svetoslava798c0a2014-05-15 10:47:19 -0700692 } else if (resultCode == RESULT_CANCELED) {
Svetoslavb75632c2014-09-17 18:38:27 -0700693 mState = STATE_CONFIGURING;
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800694
695 // The previous update might have been canceled
696 updateDocument(false);
697
Svetoslava798c0a2014-05-15 10:47:19 -0700698 updateOptionsUi();
699 } else {
Svet Ganov525a66b2014-06-14 22:29:00 -0700700 setState(STATE_CREATE_FILE_FAILED);
Svetoslava798c0a2014-05-15 10:47:19 -0700701 updateOptionsUi();
Svet Ganov525a66b2014-06-14 22:29:00 -0700702 // Calling finish here does not invoke lifecycle callbacks but we
703 // update the print job in onPause if finishing, hence post a message.
704 mDestinationSpinner.post(new Runnable() {
705 @Override
706 public void run() {
Svetoslave17123d2014-09-11 12:39:05 -0700707 doFinish();
Svet Ganov525a66b2014-06-14 22:29:00 -0700708 }
709 });
Svetoslava798c0a2014-05-15 10:47:19 -0700710 }
711 }
712
713 private void startSelectPrinterActivity() {
714 Intent intent = new Intent(this, SelectPrinterActivity.class);
715 startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER);
716 }
717
718 private void onSelectPrinterActivityResult(int resultCode, Intent data) {
719 if (resultCode == RESULT_OK && data != null) {
720 PrinterId printerId = data.getParcelableExtra(INTENT_EXTRA_PRINTER_ID);
721 if (printerId != null) {
722 mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerId);
723 final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
724 if (index != AdapterView.INVALID_POSITION) {
725 mDestinationSpinner.setSelection(index);
726 return;
727 }
728 }
729 }
730
Masaaki Iwaguchi86d13f32015-04-03 16:11:39 +0900731 if (mCurrentPrinter != null) {
732 PrinterId printerId = mCurrentPrinter.getId();
733 final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
734 mDestinationSpinner.setSelection(index);
735 }
Svetoslava798c0a2014-05-15 10:47:19 -0700736 }
737
738 private void startAdvancedPrintOptionsActivity(PrinterInfo printer) {
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800739 if (mAdvancedPrintOptionsActivity == null) {
Svetoslava798c0a2014-05-15 10:47:19 -0700740 return;
741 }
742
743 Intent intent = new Intent(Intent.ACTION_MAIN);
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800744 intent.setComponent(mAdvancedPrintOptionsActivity);
Svetoslava798c0a2014-05-15 10:47:19 -0700745
746 List<ResolveInfo> resolvedActivities = getPackageManager()
747 .queryIntentActivities(intent, 0);
748 if (resolvedActivities.isEmpty()) {
749 return;
750 }
751
752 // The activity is a component name, therefore it is one or none.
753 if (resolvedActivities.get(0).activityInfo.exported) {
Philip P. Moltmannd365f692016-02-29 13:06:14 -0800754 PrintJobInfo.Builder printJobBuilder = new PrintJobInfo.Builder(mPrintJob);
755 printJobBuilder.setPages(mSelectedPages);
756
757 intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, printJobBuilder.build());
Svetoslava798c0a2014-05-15 10:47:19 -0700758 intent.putExtra(PrintService.EXTRA_PRINTER_INFO, printer);
Svet Ganovf12b3932015-07-25 12:10:54 -0700759 intent.putExtra(PrintService.EXTRA_PRINT_DOCUMENT_INFO,
Svet Ganov5772b5c2015-06-11 02:46:45 -0700760 mPrintedDocument.getDocumentInfo().info);
Svetoslava798c0a2014-05-15 10:47:19 -0700761
762 // This is external activity and may not be there.
763 try {
764 startActivityForResult(intent, ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS);
765 } catch (ActivityNotFoundException anfe) {
766 Log.e(LOG_TAG, "Error starting activity for intent: " + intent, anfe);
767 }
768 }
769 }
770
771 private void onAdvancedPrintOptionsActivityResult(int resultCode, Intent data) {
772 if (resultCode != RESULT_OK || data == null) {
773 return;
774 }
775
776 PrintJobInfo printJobInfo = data.getParcelableExtra(PrintService.EXTRA_PRINT_JOB_INFO);
777
778 if (printJobInfo == null) {
779 return;
780 }
781
782 // Take the advanced options without interpretation.
783 mPrintJob.setAdvancedOptions(printJobInfo.getAdvancedOptions());
784
Philip P. Moltmannd365f692016-02-29 13:06:14 -0800785 if (printJobInfo.getCopies() < 1) {
786 Log.w(LOG_TAG, "Cannot apply return value from advanced options activity. Copies " +
787 "must be 1 or more. Actual value is: " + printJobInfo.getCopies() + ". " +
788 "Ignoring.");
789 } else {
790 mCopiesEditText.setText(String.valueOf(printJobInfo.getCopies()));
791 mPrintJob.setCopies(printJobInfo.getCopies());
792 }
Svetoslava798c0a2014-05-15 10:47:19 -0700793
794 PrintAttributes currAttributes = mPrintJob.getAttributes();
795 PrintAttributes newAttributes = printJobInfo.getAttributes();
796
Svet Ganov2eb7fad2014-10-01 17:49:16 -0700797 if (newAttributes != null) {
798 // Take the media size only if the current printer supports is.
799 MediaSize oldMediaSize = currAttributes.getMediaSize();
800 MediaSize newMediaSize = newAttributes.getMediaSize();
Philip P. Moltmannd365f692016-02-29 13:06:14 -0800801 if (newMediaSize != null && !oldMediaSize.equals(newMediaSize)) {
Svet Ganov2eb7fad2014-10-01 17:49:16 -0700802 final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount();
803 MediaSize newMediaSizePortrait = newAttributes.getMediaSize().asPortrait();
804 for (int i = 0; i < mediaSizeCount; i++) {
805 MediaSize supportedSizePortrait = mMediaSizeSpinnerAdapter.getItem(i)
806 .value.asPortrait();
807 if (supportedSizePortrait.equals(newMediaSizePortrait)) {
808 currAttributes.setMediaSize(newMediaSize);
809 mMediaSizeSpinner.setSelection(i);
810 if (currAttributes.getMediaSize().isPortrait()) {
811 if (mOrientationSpinner.getSelectedItemPosition() != 0) {
812 mOrientationSpinner.setSelection(0);
813 }
814 } else {
815 if (mOrientationSpinner.getSelectedItemPosition() != 1) {
816 mOrientationSpinner.setSelection(1);
817 }
Svetoslava798c0a2014-05-15 10:47:19 -0700818 }
Svet Ganov2eb7fad2014-10-01 17:49:16 -0700819 break;
Svetoslava798c0a2014-05-15 10:47:19 -0700820 }
Svetoslava798c0a2014-05-15 10:47:19 -0700821 }
822 }
Svetoslava798c0a2014-05-15 10:47:19 -0700823
Svet Ganov2eb7fad2014-10-01 17:49:16 -0700824 // Take the resolution only if the current printer supports is.
825 Resolution oldResolution = currAttributes.getResolution();
826 Resolution newResolution = newAttributes.getResolution();
827 if (!oldResolution.equals(newResolution)) {
828 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
829 if (capabilities != null) {
830 List<Resolution> resolutions = capabilities.getResolutions();
831 final int resolutionCount = resolutions.size();
832 for (int i = 0; i < resolutionCount; i++) {
833 Resolution resolution = resolutions.get(i);
834 if (resolution.equals(newResolution)) {
835 currAttributes.setResolution(resolution);
836 break;
837 }
838 }
839 }
840 }
841
842 // Take the color mode only if the current printer supports it.
843 final int currColorMode = currAttributes.getColorMode();
844 final int newColorMode = newAttributes.getColorMode();
845 if (currColorMode != newColorMode) {
846 final int colorModeCount = mColorModeSpinner.getCount();
847 for (int i = 0; i < colorModeCount; i++) {
848 final int supportedColorMode = mColorModeSpinnerAdapter.getItem(i).value;
849 if (supportedColorMode == newColorMode) {
850 currAttributes.setColorMode(newColorMode);
851 mColorModeSpinner.setSelection(i);
852 break;
853 }
Svetoslava798c0a2014-05-15 10:47:19 -0700854 }
855 }
Svetoslav948c9a62015-02-02 19:47:04 -0800856
857 // Take the duplex mode only if the current printer supports it.
858 final int currDuplexMode = currAttributes.getDuplexMode();
859 final int newDuplexMode = newAttributes.getDuplexMode();
860 if (currDuplexMode != newDuplexMode) {
861 final int duplexModeCount = mDuplexModeSpinner.getCount();
862 for (int i = 0; i < duplexModeCount; i++) {
863 final int supportedDuplexMode = mDuplexModeSpinnerAdapter.getItem(i).value;
864 if (supportedDuplexMode == newDuplexMode) {
865 currAttributes.setDuplexMode(newDuplexMode);
866 mDuplexModeSpinner.setSelection(i);
867 break;
868 }
869 }
870 }
Svetoslava798c0a2014-05-15 10:47:19 -0700871 }
872
Svetoslav528424c2014-09-26 19:11:29 -0700873 // Handle selected page changes making sure they are in the doc.
Svet Ganov525a66b2014-06-14 22:29:00 -0700874 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
875 final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0;
Svetoslava798c0a2014-05-15 10:47:19 -0700876 PageRange[] pageRanges = printJobInfo.getPages();
Svetoslav528424c2014-09-26 19:11:29 -0700877 if (pageRanges != null && pageCount > 0) {
878 pageRanges = PageRangeUtils.normalize(pageRanges);
879
880 List<PageRange> validatedList = new ArrayList<>();
881 final int rangeCount = pageRanges.length;
882 for (int i = 0; i < rangeCount; i++) {
883 PageRange pageRange = pageRanges[i];
884 if (pageRange.getEnd() >= pageCount) {
885 final int rangeStart = pageRange.getStart();
886 final int rangeEnd = pageCount - 1;
887 if (rangeStart <= rangeEnd) {
888 pageRange = new PageRange(rangeStart, rangeEnd);
889 validatedList.add(pageRange);
890 }
891 break;
892 }
893 validatedList.add(pageRange);
894 }
895
896 if (!validatedList.isEmpty()) {
897 PageRange[] validatedArray = new PageRange[validatedList.size()];
898 validatedList.toArray(validatedArray);
899 updateSelectedPages(validatedArray, pageCount);
900 }
901 }
Svetoslava798c0a2014-05-15 10:47:19 -0700902
903 // Update the content if needed.
904 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -0700905 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -0700906 }
907 }
908
Svet Ganov525a66b2014-06-14 22:29:00 -0700909 private void setState(int state) {
910 if (isFinalState(mState)) {
911 if (isFinalState(state)) {
912 mState = state;
913 }
914 } else {
915 mState = state;
916 }
917 }
918
919 private static boolean isFinalState(int state) {
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800920 return state == STATE_PRINT_CANCELED
921 || state == STATE_PRINT_COMPLETED
922 || state == STATE_CREATE_FILE_FAILED;
Svet Ganov525a66b2014-06-14 22:29:00 -0700923 }
924
925 private void updateSelectedPagesFromPreview() {
926 PageRange[] selectedPages = mPrintPreviewController.getSelectedPages();
927 if (!Arrays.equals(mSelectedPages, selectedPages)) {
928 updateSelectedPages(selectedPages,
929 getAdjustedPageCount(mPrintedDocument.getDocumentInfo().info));
930 }
931 }
932
933 private void updateSelectedPages(PageRange[] selectedPages, int pageInDocumentCount) {
934 if (selectedPages == null || selectedPages.length <= 0) {
935 return;
936 }
937
938 selectedPages = PageRangeUtils.normalize(selectedPages);
939
940 // Handle the case where all pages are specified explicitly
941 // instead of the *all pages* constant.
942 if (PageRangeUtils.isAllPages(selectedPages, pageInDocumentCount)) {
943 selectedPages = new PageRange[] {PageRange.ALL_PAGES};
944 }
945
946 if (Arrays.equals(mSelectedPages, selectedPages)) {
947 return;
948 }
949
950 mSelectedPages = selectedPages;
951 mPrintJob.setPages(selectedPages);
952
953 if (Arrays.equals(selectedPages, ALL_PAGES_ARRAY)) {
954 if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
955 mRangeOptionsSpinner.setSelection(0);
956 mPageRangeEditText.setText("");
957 }
958 } else if (selectedPages[0].getStart() >= 0
959 && selectedPages[selectedPages.length - 1].getEnd() < pageInDocumentCount) {
960 if (mRangeOptionsSpinner.getSelectedItemPosition() != 1) {
961 mRangeOptionsSpinner.setSelection(1);
962 }
963
964 StringBuilder builder = new StringBuilder();
965 final int pageRangeCount = selectedPages.length;
966 for (int i = 0; i < pageRangeCount; i++) {
967 if (builder.length() > 0) {
968 builder.append(',');
969 }
970
971 final int shownStartPage;
972 final int shownEndPage;
973 PageRange pageRange = selectedPages[i];
974 if (pageRange.equals(PageRange.ALL_PAGES)) {
975 shownStartPage = 1;
976 shownEndPage = pageInDocumentCount;
977 } else {
978 shownStartPage = pageRange.getStart() + 1;
979 shownEndPage = pageRange.getEnd() + 1;
980 }
981
982 builder.append(shownStartPage);
983
984 if (shownStartPage != shownEndPage) {
985 builder.append('-');
986 builder.append(shownEndPage);
987 }
988 }
989
990 mPageRangeEditText.setText(builder.toString());
991 }
992 }
993
Svetoslava798c0a2014-05-15 10:47:19 -0700994 private void ensureProgressUiShown() {
Svetoslav23d33612014-09-16 10:50:52 -0700995 if (isFinishing()) {
996 return;
997 }
Svetoslava798c0a2014-05-15 10:47:19 -0700998 if (mUiState != UI_STATE_PROGRESS) {
999 mUiState = UI_STATE_PROGRESS;
Svet Ganov525a66b2014-06-14 22:29:00 -07001000 mPrintPreviewController.setUiShown(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001001 Fragment fragment = PrintProgressFragment.newInstance();
1002 showFragment(fragment);
1003 }
1004 }
1005
1006 private void ensurePreviewUiShown() {
Svetoslav23d33612014-09-16 10:50:52 -07001007 if (isFinishing()) {
1008 return;
1009 }
Svetoslava798c0a2014-05-15 10:47:19 -07001010 if (mUiState != UI_STATE_PREVIEW) {
1011 mUiState = UI_STATE_PREVIEW;
Svet Ganov525a66b2014-06-14 22:29:00 -07001012 mPrintPreviewController.setUiShown(true);
1013 showFragment(null);
Svetoslava798c0a2014-05-15 10:47:19 -07001014 }
1015 }
1016
1017 private void ensureErrorUiShown(CharSequence message, int action) {
Svetoslav23d33612014-09-16 10:50:52 -07001018 if (isFinishing()) {
1019 return;
1020 }
Svetoslava798c0a2014-05-15 10:47:19 -07001021 if (mUiState != UI_STATE_ERROR) {
1022 mUiState = UI_STATE_ERROR;
Svet Ganov525a66b2014-06-14 22:29:00 -07001023 mPrintPreviewController.setUiShown(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001024 Fragment fragment = PrintErrorFragment.newInstance(message, action);
1025 showFragment(fragment);
1026 }
1027 }
1028
Svet Ganov525a66b2014-06-14 22:29:00 -07001029 private void showFragment(Fragment newFragment) {
Svetoslava798c0a2014-05-15 10:47:19 -07001030 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Svet Ganov525a66b2014-06-14 22:29:00 -07001031 Fragment oldFragment = getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
Svetoslava798c0a2014-05-15 10:47:19 -07001032 if (oldFragment != null) {
1033 transaction.remove(oldFragment);
1034 }
Svet Ganov525a66b2014-06-14 22:29:00 -07001035 if (newFragment != null) {
1036 transaction.add(R.id.embedded_content_container, newFragment, FRAGMENT_TAG);
1037 }
Svetoslavd25ddc32015-11-24 12:54:18 -08001038 transaction.commitAllowingStateLoss();
Svet Ganov525a66b2014-06-14 22:29:00 -07001039 getFragmentManager().executePendingTransactions();
Svetoslava798c0a2014-05-15 10:47:19 -07001040 }
1041
1042 private void requestCreatePdfFileOrFinish() {
Philip P. Moltmann645a3e12016-02-25 11:20:41 -08001043 mPrintedDocument.cancel(false);
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -08001044
Svet Ganov48fec5c2014-07-14 00:14:07 -07001045 if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) {
Svetoslava798c0a2014-05-15 10:47:19 -07001046 startCreateDocumentActivity();
1047 } else {
Svetoslavbec22be2014-09-25 13:03:20 -07001048 transformDocumentAndFinish(null);
Svetoslava798c0a2014-05-15 10:47:19 -07001049 }
1050 }
1051
Svetoslava798c0a2014-05-15 10:47:19 -07001052 private void updatePrintAttributesFromCapabilities(PrinterCapabilitiesInfo capabilities) {
1053 PrintAttributes defaults = capabilities.getDefaults();
1054
1055 // Sort the media sizes based on the current locale.
1056 List<MediaSize> sortedMediaSizes = new ArrayList<>(capabilities.getMediaSizes());
1057 Collections.sort(sortedMediaSizes, mMediaSizeComparator);
1058
1059 PrintAttributes attributes = mPrintJob.getAttributes();
1060
1061 // Media size.
1062 MediaSize currMediaSize = attributes.getMediaSize();
1063 if (currMediaSize == null) {
1064 attributes.setMediaSize(defaults.getMediaSize());
1065 } else {
Philip P. Moltmanndc5765e2016-01-22 09:39:35 -08001066 MediaSize newMediaSize = null;
1067 boolean isPortrait = currMediaSize.isPortrait();
1068
Svetoslava798c0a2014-05-15 10:47:19 -07001069 // Try to find the current media size in the capabilities as
1070 // it may be in a different orientation.
1071 MediaSize currMediaSizePortrait = currMediaSize.asPortrait();
1072 final int mediaSizeCount = sortedMediaSizes.size();
1073 for (int i = 0; i < mediaSizeCount; i++) {
1074 MediaSize mediaSize = sortedMediaSizes.get(i);
1075 if (currMediaSizePortrait.equals(mediaSize.asPortrait())) {
Philip P. Moltmanndc5765e2016-01-22 09:39:35 -08001076 newMediaSize = mediaSize;
Svetoslava798c0a2014-05-15 10:47:19 -07001077 break;
1078 }
1079 }
1080 // If we did not find the current media size fall back to default.
Philip P. Moltmanndc5765e2016-01-22 09:39:35 -08001081 if (newMediaSize == null) {
1082 newMediaSize = defaults.getMediaSize();
1083 }
1084
1085 if (newMediaSize != null) {
1086 if (isPortrait) {
1087 attributes.setMediaSize(newMediaSize.asPortrait());
1088 } else {
1089 attributes.setMediaSize(newMediaSize.asLandscape());
1090 }
Svetoslava798c0a2014-05-15 10:47:19 -07001091 }
1092 }
1093
1094 // Color mode.
1095 final int colorMode = attributes.getColorMode();
1096 if ((capabilities.getColorModes() & colorMode) == 0) {
1097 attributes.setColorMode(defaults.getColorMode());
1098 }
1099
Svetoslav948c9a62015-02-02 19:47:04 -08001100 // Duplex mode.
1101 final int duplexMode = attributes.getDuplexMode();
1102 if ((capabilities.getDuplexModes() & duplexMode) == 0) {
1103 attributes.setDuplexMode(defaults.getDuplexMode());
1104 }
1105
Svetoslava798c0a2014-05-15 10:47:19 -07001106 // Resolution
1107 Resolution resolution = attributes.getResolution();
1108 if (resolution == null || !capabilities.getResolutions().contains(resolution)) {
1109 attributes.setResolution(defaults.getResolution());
1110 }
1111
1112 // Margins.
1113 attributes.setMinMargins(defaults.getMinMargins());
1114 }
1115
Svetoslav62ce3322014-09-04 21:17:17 -07001116 private boolean updateDocument(boolean clearLastError) {
Svetoslava798c0a2014-05-15 10:47:19 -07001117 if (!clearLastError && mPrintedDocument.hasUpdateError()) {
1118 return false;
1119 }
1120
1121 if (clearLastError && mPrintedDocument.hasUpdateError()) {
1122 mPrintedDocument.clearUpdateError();
1123 }
1124
Svetoslav62ce3322014-09-04 21:17:17 -07001125 final boolean preview = mState != STATE_PRINT_CONFIRMED;
Svet Ganov525a66b2014-06-14 22:29:00 -07001126 final PageRange[] pages;
1127 if (preview) {
1128 pages = mPrintPreviewController.getRequestedPages();
1129 } else {
1130 pages = mPrintPreviewController.getSelectedPages();
1131 }
Svetoslava798c0a2014-05-15 10:47:19 -07001132
Svet Ganov525a66b2014-06-14 22:29:00 -07001133 final boolean willUpdate = mPrintedDocument.update(mPrintJob.getAttributes(),
1134 pages, preview);
1135
Svetoslav6552bf32014-09-03 21:15:55 -07001136 if (willUpdate && !mPrintedDocument.hasLaidOutPages()) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001137 // When the update is done we update the print preview.
1138 mProgressMessageController.post();
1139 return true;
Svetoslav7fd5ada2014-09-16 14:41:17 -07001140 } else if (!willUpdate) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001141 // Update preview.
1142 updatePrintPreviewController(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001143 }
1144
1145 return false;
1146 }
1147
1148 private void addCurrentPrinterToHistory() {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001149 if (mCurrentPrinter != null) {
Svetoslava798c0a2014-05-15 10:47:19 -07001150 PrinterId fakePdfPrinterId = mDestinationSpinnerAdapter.getPdfPrinter().getId();
Svet Ganov48fec5c2014-07-14 00:14:07 -07001151 if (!mCurrentPrinter.getId().equals(fakePdfPrinterId)) {
1152 mPrinterRegistry.addHistoricalPrinter(mCurrentPrinter);
Svetoslava798c0a2014-05-15 10:47:19 -07001153 }
1154 }
1155 }
1156
Svetoslava798c0a2014-05-15 10:47:19 -07001157 private void cancelPrint() {
Svet Ganov525a66b2014-06-14 22:29:00 -07001158 setState(STATE_PRINT_CANCELED);
Svetoslava798c0a2014-05-15 10:47:19 -07001159 updateOptionsUi();
Philip P. Moltmann645a3e12016-02-25 11:20:41 -08001160 mPrintedDocument.cancel(true);
Svetoslave17123d2014-09-11 12:39:05 -07001161 doFinish();
Svetoslava798c0a2014-05-15 10:47:19 -07001162 }
1163
1164 private void confirmPrint() {
Svet Ganov525a66b2014-06-14 22:29:00 -07001165 setState(STATE_PRINT_CONFIRMED);
1166
Chris Wrendcc34fd2015-07-30 14:27:02 -04001167 MetricsLogger.count(this, "print_confirmed", 1);
1168
Svetoslava798c0a2014-05-15 10:47:19 -07001169 updateOptionsUi();
Svet Ganov525a66b2014-06-14 22:29:00 -07001170 addCurrentPrinterToHistory();
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001171 setUserPrinted();
Svet Ganov525a66b2014-06-14 22:29:00 -07001172
1173 PageRange[] selectedPages = computeSelectedPages();
1174 if (!Arrays.equals(mSelectedPages, selectedPages)) {
1175 mSelectedPages = selectedPages;
1176 // Update preview.
1177 updatePrintPreviewController(false);
1178 }
1179
1180 updateSelectedPagesFromPreview();
1181 mPrintPreviewController.closeOptions();
1182
Svetoslava798c0a2014-05-15 10:47:19 -07001183 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07001184 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001185 }
Svet Ganov525a66b2014-06-14 22:29:00 -07001186
Svetoslava798c0a2014-05-15 10:47:19 -07001187 if (!mPrintedDocument.isUpdating()) {
1188 requestCreatePdfFileOrFinish();
1189 }
1190 }
1191
1192 private void bindUi() {
1193 // Summary
Svetoslave652b022014-09-09 22:11:10 -07001194 mSummaryContainer = findViewById(R.id.summary_content);
Svetoslava798c0a2014-05-15 10:47:19 -07001195 mSummaryCopies = (TextView) findViewById(R.id.copies_count_summary);
1196 mSummaryPaperSize = (TextView) findViewById(R.id.paper_size_summary);
1197
1198 // Options container
Svet Ganov525a66b2014-06-14 22:29:00 -07001199 mOptionsContent = (PrintContentView) findViewById(R.id.options_content);
1200 mOptionsContent.setOptionsStateChangeListener(this);
1201 mOptionsContent.setOpenOptionsController(this);
Svetoslava798c0a2014-05-15 10:47:19 -07001202
1203 OnItemSelectedListener itemSelectedListener = new MyOnItemSelectedListener();
1204 OnClickListener clickListener = new MyClickListener();
1205
1206 // Copies
1207 mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
1208 mCopiesEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
1209 mCopiesEditText.setText(MIN_COPIES_STRING);
1210 mCopiesEditText.setSelection(mCopiesEditText.getText().length());
1211 mCopiesEditText.addTextChangedListener(new EditTextWatcher());
1212
1213 // Destination.
Philip P. Moltmann1bb7f362016-02-26 14:21:20 -08001214 mPrintersObserver = new PrintersObserver();
1215 mDestinationSpinnerAdapter.registerDataSetObserver(mPrintersObserver);
Svetoslava798c0a2014-05-15 10:47:19 -07001216 mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
1217 mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
1218 mDestinationSpinner.setOnItemSelectedListener(itemSelectedListener);
Svetoslava798c0a2014-05-15 10:47:19 -07001219
1220 // Media size.
1221 mMediaSizeSpinnerAdapter = new ArrayAdapter<>(
Svetoslavc404cac2014-08-27 18:37:16 -07001222 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
Svetoslava798c0a2014-05-15 10:47:19 -07001223 mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
1224 mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
1225 mMediaSizeSpinner.setOnItemSelectedListener(itemSelectedListener);
1226
1227 // Color mode.
1228 mColorModeSpinnerAdapter = new ArrayAdapter<>(
Svetoslavc404cac2014-08-27 18:37:16 -07001229 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
Svetoslava798c0a2014-05-15 10:47:19 -07001230 mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
1231 mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
1232 mColorModeSpinner.setOnItemSelectedListener(itemSelectedListener);
1233
Svetoslav948c9a62015-02-02 19:47:04 -08001234 // Duplex mode.
1235 mDuplexModeSpinnerAdapter = new ArrayAdapter<>(
1236 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
1237 mDuplexModeSpinner = (Spinner) findViewById(R.id.duplex_spinner);
1238 mDuplexModeSpinner.setAdapter(mDuplexModeSpinnerAdapter);
1239 mDuplexModeSpinner.setOnItemSelectedListener(itemSelectedListener);
1240
Svetoslava798c0a2014-05-15 10:47:19 -07001241 // Orientation
1242 mOrientationSpinnerAdapter = new ArrayAdapter<>(
Svetoslavc404cac2014-08-27 18:37:16 -07001243 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
Svetoslava798c0a2014-05-15 10:47:19 -07001244 String[] orientationLabels = getResources().getStringArray(
Svet Ganov525a66b2014-06-14 22:29:00 -07001245 R.array.orientation_labels);
Svetoslava798c0a2014-05-15 10:47:19 -07001246 mOrientationSpinnerAdapter.add(new SpinnerItem<>(
1247 ORIENTATION_PORTRAIT, orientationLabels[0]));
1248 mOrientationSpinnerAdapter.add(new SpinnerItem<>(
1249 ORIENTATION_LANDSCAPE, orientationLabels[1]));
1250 mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
1251 mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
1252 mOrientationSpinner.setOnItemSelectedListener(itemSelectedListener);
1253
1254 // Range options
Svetoslavc404cac2014-08-27 18:37:16 -07001255 ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter = new ArrayAdapter<>(
1256 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
Svetoslava798c0a2014-05-15 10:47:19 -07001257 mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
1258 mRangeOptionsSpinner.setAdapter(rangeOptionsSpinnerAdapter);
1259 mRangeOptionsSpinner.setOnItemSelectedListener(itemSelectedListener);
Svetoslav73764e32014-07-15 15:56:46 -07001260 updatePageRangeOptions(PrintDocumentInfo.PAGE_COUNT_UNKNOWN);
Svetoslava798c0a2014-05-15 10:47:19 -07001261
1262 // Page range
1263 mPageRangeTitle = (TextView) findViewById(R.id.page_range_title);
1264 mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
1265 mPageRangeEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
1266 mPageRangeEditText.addTextChangedListener(new RangeTextWatcher());
1267
1268 // Advanced options button.
Svetoslava798c0a2014-05-15 10:47:19 -07001269 mMoreOptionsButton = (Button) findViewById(R.id.more_options_button);
Svet Ganov525a66b2014-06-14 22:29:00 -07001270 mMoreOptionsButton.setOnClickListener(clickListener);
Svetoslava798c0a2014-05-15 10:47:19 -07001271
1272 // Print button
1273 mPrintButton = (ImageView) findViewById(R.id.print_button);
1274 mPrintButton.setOnClickListener(clickListener);
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001275
Philip P. Moltmannc2f913d2016-02-01 12:03:48 -08001276 // The UI is now initialized
1277 mIsOptionsUiBound = true;
1278
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001279 // Special prompt instead of destination spinner for the first time the user printed
1280 if (!hasUserEverPrinted()) {
1281 mShowDestinationPrompt = true;
1282
1283 mSummaryCopies.setEnabled(false);
1284 mSummaryPaperSize.setEnabled(false);
1285
1286 mDestinationSpinner.setOnTouchListener(new View.OnTouchListener() {
1287 @Override
1288 public boolean onTouch(View v, MotionEvent event) {
1289 mShowDestinationPrompt = false;
1290 mSummaryCopies.setEnabled(true);
1291 mSummaryPaperSize.setEnabled(true);
1292 updateOptionsUi();
1293
1294 mDestinationSpinner.setOnTouchListener(null);
1295 mDestinationSpinnerAdapter.notifyDataSetChanged();
1296
1297 return false;
1298 }
1299 });
1300 }
Svetoslava798c0a2014-05-15 10:47:19 -07001301 }
1302
Philip P. Moltmann66c96592016-02-24 11:32:43 -08001303 @Override
1304 public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
1305 return new PrintServicesLoader((PrintManager) getSystemService(Context.PRINT_SERVICE), this,
1306 PrintManager.ENABLED_SERVICES);
1307 }
1308
1309 @Override
1310 public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
1311 List<PrintServiceInfo> services) {
1312 ComponentName newAdvancedPrintOptionsActivity = null;
1313 if (mCurrentPrinter != null && services != null) {
1314 final int numServices = services.size();
1315 for (int i = 0; i < numServices; i++) {
1316 PrintServiceInfo service = services.get(i);
1317
1318 if (service.getComponentName().equals(mCurrentPrinter.getId().getServiceName())) {
1319 String advancedOptionsActivityName = service.getAdvancedOptionsActivityName();
1320
1321 if (!TextUtils.isEmpty(advancedOptionsActivityName)) {
1322 newAdvancedPrintOptionsActivity = new ComponentName(
1323 service.getComponentName().getPackageName(),
1324 advancedOptionsActivityName);
1325
1326 break;
1327 }
1328 }
1329 }
1330 }
1331
1332 if (!Objects.equals(newAdvancedPrintOptionsActivity, mAdvancedPrintOptionsActivity)) {
1333 mAdvancedPrintOptionsActivity = newAdvancedPrintOptionsActivity;
1334 updateOptionsUi();
1335 }
1336
1337 boolean newArePrintServicesEnabled = services != null && !services.isEmpty();
1338 if (mArePrintServicesEnabled != newArePrintServicesEnabled) {
1339 mArePrintServicesEnabled = newArePrintServicesEnabled;
1340
1341 // Reload mDestinationSpinnerAdapter as mArePrintServicesEnabled changed and the adapter
1342 // reads that in DestinationAdapter#getMoreItemTitle
1343 if (mDestinationSpinnerAdapter != null) {
1344 mDestinationSpinnerAdapter.notifyDataSetChanged();
1345 }
1346 }
1347 }
1348
1349 @Override
1350 public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
1351 if (!isFinishing()) {
1352 onLoadFinished(loader, null);
1353 }
1354 }
1355
Philip P. Moltmann853a6f52015-11-03 10:38:56 -08001356 /**
1357 * A dialog that asks the user to approve a {@link PrintService}. This dialog is automatically
1358 * dismissed if the same {@link PrintService} gets approved by another
1359 * {@link PrintServiceApprovalDialog}.
1360 */
1361 private static final class PrintServiceApprovalDialog extends DialogFragment
1362 implements OnSharedPreferenceChangeListener {
1363 private static final String PRINTSERVICE_KEY = "PRINTSERVICE";
1364 private ApprovedPrintServices mApprovedServices;
1365
1366 /**
1367 * Create a new {@link PrintServiceApprovalDialog} that ask the user to approve a
1368 * {@link PrintService}.
1369 *
1370 * @param printService The {@link ComponentName} of the service to approve
1371 * @return A new {@link PrintServiceApprovalDialog} that might approve the service
1372 */
1373 static PrintServiceApprovalDialog newInstance(ComponentName printService) {
1374 PrintServiceApprovalDialog dialog = new PrintServiceApprovalDialog();
1375
1376 Bundle args = new Bundle();
1377 args.putParcelable(PRINTSERVICE_KEY, printService);
1378 dialog.setArguments(args);
1379
1380 return dialog;
1381 }
1382
1383 @Override
1384 public void onStop() {
1385 super.onStop();
1386
1387 mApprovedServices.unregisterChangeListener(this);
1388 }
1389
1390 @Override
1391 public void onStart() {
1392 super.onStart();
1393
1394 ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY);
1395 synchronized (ApprovedPrintServices.sLock) {
1396 if (mApprovedServices.isApprovedService(printService)) {
1397 dismiss();
1398 } else {
1399 mApprovedServices.registerChangeListenerLocked(this);
1400 }
1401 }
1402 }
1403
1404 @Override
1405 public Dialog onCreateDialog(Bundle savedInstanceState) {
1406 super.onCreateDialog(savedInstanceState);
1407
1408 mApprovedServices = new ApprovedPrintServices(getActivity());
1409
1410 PackageManager packageManager = getActivity().getPackageManager();
1411 CharSequence serviceLabel;
1412 try {
1413 ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY);
1414
1415 serviceLabel = packageManager.getApplicationInfo(printService.getPackageName(), 0)
1416 .loadLabel(packageManager);
1417 } catch (NameNotFoundException e) {
1418 serviceLabel = null;
1419 }
1420
1421 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
1422 builder.setTitle(getString(R.string.print_service_security_warning_title,
1423 serviceLabel))
1424 .setMessage(getString(R.string.print_service_security_warning_summary,
1425 serviceLabel))
1426 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1427 @Override
1428 public void onClick(DialogInterface dialog, int id) {
1429 ComponentName printService =
1430 getArguments().getParcelable(PRINTSERVICE_KEY);
1431 // Prevent onSharedPreferenceChanged from getting triggered
1432 mApprovedServices
1433 .unregisterChangeListener(PrintServiceApprovalDialog.this);
1434
1435 mApprovedServices.addApprovedService(printService);
1436 ((PrintActivity) getActivity()).confirmPrint();
1437 }
1438 })
1439 .setNegativeButton(android.R.string.cancel, null);
1440
1441 return builder.create();
1442 }
1443
1444 @Override
1445 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
1446 ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY);
1447
1448 synchronized (ApprovedPrintServices.sLock) {
1449 if (mApprovedServices.isApprovedService(printService)) {
1450 dismiss();
1451 }
1452 }
1453 }
1454 }
1455
Svetoslava798c0a2014-05-15 10:47:19 -07001456 private final class MyClickListener implements OnClickListener {
1457 @Override
1458 public void onClick(View view) {
1459 if (view == mPrintButton) {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001460 if (mCurrentPrinter != null) {
Philip P. Moltmann853a6f52015-11-03 10:38:56 -08001461 if (mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter) {
1462 confirmPrint();
1463 } else {
1464 ApprovedPrintServices approvedServices =
1465 new ApprovedPrintServices(PrintActivity.this);
1466
1467 ComponentName printService = mCurrentPrinter.getId().getServiceName();
1468 if (approvedServices.isApprovedService(printService)) {
1469 confirmPrint();
1470 } else {
1471 PrintServiceApprovalDialog.newInstance(printService)
1472 .show(getFragmentManager(), "approve");
1473 }
1474 }
Svetoslava798c0a2014-05-15 10:47:19 -07001475 } else {
1476 cancelPrint();
1477 }
1478 } else if (view == mMoreOptionsButton) {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001479 if (mCurrentPrinter != null) {
1480 startAdvancedPrintOptionsActivity(mCurrentPrinter);
Svetoslava798c0a2014-05-15 10:47:19 -07001481 }
1482 }
1483 }
1484 }
1485
1486 private static boolean canPrint(PrinterInfo printer) {
1487 return printer.getCapabilities() != null
1488 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
1489 }
1490
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001491 /**
1492 * Disable all options UI elements, beside the {@link #mDestinationSpinner}
1493 */
1494 private void disableOptionsUi() {
1495 mCopiesEditText.setEnabled(false);
1496 mCopiesEditText.setFocusable(false);
1497 mMediaSizeSpinner.setEnabled(false);
1498 mColorModeSpinner.setEnabled(false);
1499 mDuplexModeSpinner.setEnabled(false);
1500 mOrientationSpinner.setEnabled(false);
1501 mRangeOptionsSpinner.setEnabled(false);
1502 mPageRangeEditText.setEnabled(false);
1503 mPrintButton.setVisibility(View.GONE);
1504 mMoreOptionsButton.setEnabled(false);
1505 }
1506
Svet Ganov525a66b2014-06-14 22:29:00 -07001507 void updateOptionsUi() {
Philip P. Moltmannc2f913d2016-02-01 12:03:48 -08001508 if (!mIsOptionsUiBound) {
1509 return;
1510 }
1511
Svetoslava798c0a2014-05-15 10:47:19 -07001512 // Always update the summary.
Svetoslave652b022014-09-09 22:11:10 -07001513 updateSummary();
Svetoslava798c0a2014-05-15 10:47:19 -07001514
1515 if (mState == STATE_PRINT_CONFIRMED
Svetoslavb59555c2014-07-24 10:13:00 -07001516 || mState == STATE_PRINT_COMPLETED
Svetoslava798c0a2014-05-15 10:47:19 -07001517 || mState == STATE_PRINT_CANCELED
1518 || mState == STATE_UPDATE_FAILED
1519 || mState == STATE_CREATE_FILE_FAILED
Svet Ganov525a66b2014-06-14 22:29:00 -07001520 || mState == STATE_PRINTER_UNAVAILABLE
1521 || mState == STATE_UPDATE_SLOW) {
Svetoslava798c0a2014-05-15 10:47:19 -07001522 if (mState != STATE_PRINTER_UNAVAILABLE) {
1523 mDestinationSpinner.setEnabled(false);
1524 }
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001525 disableOptionsUi();
Svetoslava798c0a2014-05-15 10:47:19 -07001526 return;
1527 }
1528
1529 // If no current printer, or it has no capabilities, or it is not
1530 // available, we disable all print options except the destination.
Svet Ganov48fec5c2014-07-14 00:14:07 -07001531 if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) {
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001532 disableOptionsUi();
Svetoslava798c0a2014-05-15 10:47:19 -07001533 return;
1534 }
1535
Svet Ganov48fec5c2014-07-14 00:14:07 -07001536 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
Svetoslava798c0a2014-05-15 10:47:19 -07001537 PrintAttributes defaultAttributes = capabilities.getDefaults();
1538
Svet Ganov525a66b2014-06-14 22:29:00 -07001539 // Destination.
1540 mDestinationSpinner.setEnabled(true);
1541
Svetoslava798c0a2014-05-15 10:47:19 -07001542 // Media size.
1543 mMediaSizeSpinner.setEnabled(true);
1544
1545 List<MediaSize> mediaSizes = new ArrayList<>(capabilities.getMediaSizes());
1546 // Sort the media sizes based on the current locale.
1547 Collections.sort(mediaSizes, mMediaSizeComparator);
1548
1549 PrintAttributes attributes = mPrintJob.getAttributes();
1550
1551 // If the media sizes changed, we update the adapter and the spinner.
1552 boolean mediaSizesChanged = false;
1553 final int mediaSizeCount = mediaSizes.size();
1554 if (mediaSizeCount != mMediaSizeSpinnerAdapter.getCount()) {
1555 mediaSizesChanged = true;
1556 } else {
1557 for (int i = 0; i < mediaSizeCount; i++) {
1558 if (!mediaSizes.get(i).equals(mMediaSizeSpinnerAdapter.getItem(i).value)) {
1559 mediaSizesChanged = true;
1560 break;
1561 }
1562 }
1563 }
1564 if (mediaSizesChanged) {
1565 // Remember the old media size to try selecting it again.
1566 int oldMediaSizeNewIndex = AdapterView.INVALID_POSITION;
1567 MediaSize oldMediaSize = attributes.getMediaSize();
1568
1569 // Rebuild the adapter data.
1570 mMediaSizeSpinnerAdapter.clear();
1571 for (int i = 0; i < mediaSizeCount; i++) {
1572 MediaSize mediaSize = mediaSizes.get(i);
1573 if (oldMediaSize != null
1574 && mediaSize.asPortrait().equals(oldMediaSize.asPortrait())) {
1575 // Update the index of the old selection.
1576 oldMediaSizeNewIndex = i;
1577 }
1578 mMediaSizeSpinnerAdapter.add(new SpinnerItem<>(
1579 mediaSize, mediaSize.getLabel(getPackageManager())));
1580 }
1581
1582 if (oldMediaSizeNewIndex != AdapterView.INVALID_POSITION) {
1583 // Select the old media size - nothing really changed.
1584 if (mMediaSizeSpinner.getSelectedItemPosition() != oldMediaSizeNewIndex) {
1585 mMediaSizeSpinner.setSelection(oldMediaSizeNewIndex);
1586 }
1587 } else {
1588 // Select the first or the default.
1589 final int mediaSizeIndex = Math.max(mediaSizes.indexOf(
1590 defaultAttributes.getMediaSize()), 0);
1591 if (mMediaSizeSpinner.getSelectedItemPosition() != mediaSizeIndex) {
1592 mMediaSizeSpinner.setSelection(mediaSizeIndex);
1593 }
1594 // Respect the orientation of the old selection.
1595 if (oldMediaSize != null) {
1596 if (oldMediaSize.isPortrait()) {
1597 attributes.setMediaSize(mMediaSizeSpinnerAdapter
1598 .getItem(mediaSizeIndex).value.asPortrait());
1599 } else {
1600 attributes.setMediaSize(mMediaSizeSpinnerAdapter
1601 .getItem(mediaSizeIndex).value.asLandscape());
1602 }
1603 }
1604 }
1605 }
1606
1607 // Color mode.
1608 mColorModeSpinner.setEnabled(true);
1609 final int colorModes = capabilities.getColorModes();
1610
1611 // If the color modes changed, we update the adapter and the spinner.
1612 boolean colorModesChanged = false;
1613 if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) {
1614 colorModesChanged = true;
1615 } else {
1616 int remainingColorModes = colorModes;
1617 int adapterIndex = 0;
1618 while (remainingColorModes != 0) {
1619 final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
1620 final int colorMode = 1 << colorBitOffset;
1621 remainingColorModes &= ~colorMode;
1622 if (colorMode != mColorModeSpinnerAdapter.getItem(adapterIndex).value) {
1623 colorModesChanged = true;
1624 break;
1625 }
1626 adapterIndex++;
1627 }
1628 }
1629 if (colorModesChanged) {
1630 // Remember the old color mode to try selecting it again.
1631 int oldColorModeNewIndex = AdapterView.INVALID_POSITION;
1632 final int oldColorMode = attributes.getColorMode();
1633
1634 // Rebuild the adapter data.
1635 mColorModeSpinnerAdapter.clear();
1636 String[] colorModeLabels = getResources().getStringArray(R.array.color_mode_labels);
1637 int remainingColorModes = colorModes;
1638 while (remainingColorModes != 0) {
1639 final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
1640 final int colorMode = 1 << colorBitOffset;
1641 if (colorMode == oldColorMode) {
1642 // Update the index of the old selection.
Svetoslav948c9a62015-02-02 19:47:04 -08001643 oldColorModeNewIndex = mColorModeSpinnerAdapter.getCount();
Svetoslava798c0a2014-05-15 10:47:19 -07001644 }
1645 remainingColorModes &= ~colorMode;
1646 mColorModeSpinnerAdapter.add(new SpinnerItem<>(colorMode,
1647 colorModeLabels[colorBitOffset]));
1648 }
1649 if (oldColorModeNewIndex != AdapterView.INVALID_POSITION) {
1650 // Select the old color mode - nothing really changed.
1651 if (mColorModeSpinner.getSelectedItemPosition() != oldColorModeNewIndex) {
1652 mColorModeSpinner.setSelection(oldColorModeNewIndex);
1653 }
1654 } else {
1655 // Select the default.
1656 final int selectedColorMode = colorModes & defaultAttributes.getColorMode();
1657 final int itemCount = mColorModeSpinnerAdapter.getCount();
1658 for (int i = 0; i < itemCount; i++) {
1659 SpinnerItem<Integer> item = mColorModeSpinnerAdapter.getItem(i);
1660 if (selectedColorMode == item.value) {
1661 if (mColorModeSpinner.getSelectedItemPosition() != i) {
1662 mColorModeSpinner.setSelection(i);
1663 }
1664 attributes.setColorMode(selectedColorMode);
Svetoslav948c9a62015-02-02 19:47:04 -08001665 break;
Svetoslava798c0a2014-05-15 10:47:19 -07001666 }
1667 }
1668 }
1669 }
1670
Svetoslav948c9a62015-02-02 19:47:04 -08001671 // Duplex mode.
1672 mDuplexModeSpinner.setEnabled(true);
1673 final int duplexModes = capabilities.getDuplexModes();
1674
1675 // If the duplex modes changed, we update the adapter and the spinner.
1676 // Note that we use bit count +1 to account for the no duplex option.
1677 boolean duplexModesChanged = false;
1678 if (Integer.bitCount(duplexModes) != mDuplexModeSpinnerAdapter.getCount()) {
1679 duplexModesChanged = true;
1680 } else {
1681 int remainingDuplexModes = duplexModes;
1682 int adapterIndex = 0;
1683 while (remainingDuplexModes != 0) {
1684 final int duplexBitOffset = Integer.numberOfTrailingZeros(remainingDuplexModes);
1685 final int duplexMode = 1 << duplexBitOffset;
1686 remainingDuplexModes &= ~duplexMode;
1687 if (duplexMode != mDuplexModeSpinnerAdapter.getItem(adapterIndex).value) {
1688 duplexModesChanged = true;
1689 break;
1690 }
1691 adapterIndex++;
1692 }
1693 }
1694 if (duplexModesChanged) {
1695 // Remember the old duplex mode to try selecting it again. Also the fallback
1696 // is no duplexing which is always the first item in the dropdown.
1697 int oldDuplexModeNewIndex = AdapterView.INVALID_POSITION;
1698 final int oldDuplexMode = attributes.getDuplexMode();
1699
1700 // Rebuild the adapter data.
1701 mDuplexModeSpinnerAdapter.clear();
1702 String[] duplexModeLabels = getResources().getStringArray(R.array.duplex_mode_labels);
1703 int remainingDuplexModes = duplexModes;
1704 while (remainingDuplexModes != 0) {
1705 final int duplexBitOffset = Integer.numberOfTrailingZeros(remainingDuplexModes);
1706 final int duplexMode = 1 << duplexBitOffset;
1707 if (duplexMode == oldDuplexMode) {
1708 // Update the index of the old selection.
1709 oldDuplexModeNewIndex = mDuplexModeSpinnerAdapter.getCount();
1710 }
1711 remainingDuplexModes &= ~duplexMode;
1712 mDuplexModeSpinnerAdapter.add(new SpinnerItem<>(duplexMode,
1713 duplexModeLabels[duplexBitOffset]));
1714 }
1715
1716 if (oldDuplexModeNewIndex != AdapterView.INVALID_POSITION) {
1717 // Select the old duplex mode - nothing really changed.
1718 if (mDuplexModeSpinner.getSelectedItemPosition() != oldDuplexModeNewIndex) {
1719 mDuplexModeSpinner.setSelection(oldDuplexModeNewIndex);
1720 }
1721 } else {
1722 // Select the default.
1723 final int selectedDuplexMode = defaultAttributes.getDuplexMode();
1724 final int itemCount = mDuplexModeSpinnerAdapter.getCount();
1725 for (int i = 0; i < itemCount; i++) {
1726 SpinnerItem<Integer> item = mDuplexModeSpinnerAdapter.getItem(i);
1727 if (selectedDuplexMode == item.value) {
1728 if (mDuplexModeSpinner.getSelectedItemPosition() != i) {
1729 mDuplexModeSpinner.setSelection(i);
1730 }
1731 attributes.setDuplexMode(selectedDuplexMode);
1732 break;
1733 }
1734 }
1735 }
1736 }
1737
1738 mDuplexModeSpinner.setEnabled(mDuplexModeSpinnerAdapter.getCount() > 1);
1739
Svetoslava798c0a2014-05-15 10:47:19 -07001740 // Orientation
1741 mOrientationSpinner.setEnabled(true);
1742 MediaSize mediaSize = attributes.getMediaSize();
1743 if (mediaSize != null) {
1744 if (mediaSize.isPortrait()
1745 && mOrientationSpinner.getSelectedItemPosition() != 0) {
1746 mOrientationSpinner.setSelection(0);
1747 } else if (!mediaSize.isPortrait()
1748 && mOrientationSpinner.getSelectedItemPosition() != 1) {
1749 mOrientationSpinner.setSelection(1);
1750 }
1751 }
1752
1753 // Range options
1754 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
Svet Ganov525a66b2014-06-14 22:29:00 -07001755 final int pageCount = getAdjustedPageCount(info);
1756 if (info != null && pageCount > 0) {
1757 if (pageCount == 1) {
Svetoslava798c0a2014-05-15 10:47:19 -07001758 mRangeOptionsSpinner.setEnabled(false);
1759 } else {
1760 mRangeOptionsSpinner.setEnabled(true);
1761 if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
1762 if (!mPageRangeEditText.isEnabled()) {
1763 mPageRangeEditText.setEnabled(true);
1764 mPageRangeEditText.setVisibility(View.VISIBLE);
1765 mPageRangeTitle.setVisibility(View.VISIBLE);
1766 mPageRangeEditText.requestFocus();
1767 InputMethodManager imm = (InputMethodManager)
1768 getSystemService(Context.INPUT_METHOD_SERVICE);
1769 imm.showSoftInput(mPageRangeEditText, 0);
1770 }
1771 } else {
1772 mPageRangeEditText.setEnabled(false);
1773 mPageRangeEditText.setVisibility(View.INVISIBLE);
1774 mPageRangeTitle.setVisibility(View.INVISIBLE);
1775 }
1776 }
Svetoslava798c0a2014-05-15 10:47:19 -07001777 } else {
1778 if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
1779 mRangeOptionsSpinner.setSelection(0);
Svet Ganov525a66b2014-06-14 22:29:00 -07001780 mPageRangeEditText.setText("");
Svetoslava798c0a2014-05-15 10:47:19 -07001781 }
1782 mRangeOptionsSpinner.setEnabled(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001783 mPageRangeEditText.setEnabled(false);
1784 mPageRangeEditText.setVisibility(View.INVISIBLE);
1785 mPageRangeTitle.setVisibility(View.INVISIBLE);
1786 }
1787
Svetoslav73764e32014-07-15 15:56:46 -07001788 final int newPageCount = getAdjustedPageCount(info);
1789 if (newPageCount != mCurrentPageCount) {
1790 mCurrentPageCount = newPageCount;
1791 updatePageRangeOptions(newPageCount);
1792 }
1793
Svetoslava798c0a2014-05-15 10:47:19 -07001794 // Advanced print options
Philip P. Moltmann66c96592016-02-24 11:32:43 -08001795 if (mAdvancedPrintOptionsActivity != null) {
Svetoslav3c238242014-08-19 13:44:29 -07001796 mMoreOptionsButton.setVisibility(View.VISIBLE);
Svetoslava798c0a2014-05-15 10:47:19 -07001797 mMoreOptionsButton.setEnabled(true);
1798 } else {
Svetoslav3c238242014-08-19 13:44:29 -07001799 mMoreOptionsButton.setVisibility(View.GONE);
Svetoslava798c0a2014-05-15 10:47:19 -07001800 mMoreOptionsButton.setEnabled(false);
1801 }
1802
1803 // Print
Svet Ganov48fec5c2014-07-14 00:14:07 -07001804 if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
Svetoslava798c0a2014-05-15 10:47:19 -07001805 mPrintButton.setImageResource(com.android.internal.R.drawable.ic_print);
Svetoslave652b022014-09-09 22:11:10 -07001806 mPrintButton.setContentDescription(getString(R.string.print_button));
Svetoslava798c0a2014-05-15 10:47:19 -07001807 } else {
Svetoslavf8ffa562014-07-23 18:22:03 -07001808 mPrintButton.setImageResource(R.drawable.ic_menu_savetopdf);
Svetoslave652b022014-09-09 22:11:10 -07001809 mPrintButton.setContentDescription(getString(R.string.savetopdf_button));
Svetoslava798c0a2014-05-15 10:47:19 -07001810 }
Svetoslave1dcb392014-09-26 19:49:14 -07001811 if (!mPrintedDocument.getDocumentInfo().laidout
1812 ||(mRangeOptionsSpinner.getSelectedItemPosition() == 1
Svetoslava798c0a2014-05-15 10:47:19 -07001813 && (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors()))
Svet Ganov525a66b2014-06-14 22:29:00 -07001814 || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
Svetoslava798c0a2014-05-15 10:47:19 -07001815 && (mPrintedDocument.getDocumentInfo() == null || hasErrors()))) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001816 mPrintButton.setVisibility(View.GONE);
Svetoslava798c0a2014-05-15 10:47:19 -07001817 } else {
Svet Ganov525a66b2014-06-14 22:29:00 -07001818 mPrintButton.setVisibility(View.VISIBLE);
Svetoslava798c0a2014-05-15 10:47:19 -07001819 }
1820
1821 // Copies
Svet Ganov48fec5c2014-07-14 00:14:07 -07001822 if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
Svetoslava798c0a2014-05-15 10:47:19 -07001823 mCopiesEditText.setEnabled(true);
Svetoslavc404cac2014-08-27 18:37:16 -07001824 mCopiesEditText.setFocusableInTouchMode(true);
Svetoslava798c0a2014-05-15 10:47:19 -07001825 } else {
Svet Ganov45e50e92014-10-23 12:39:08 -07001826 CharSequence text = mCopiesEditText.getText();
1827 if (TextUtils.isEmpty(text) || !MIN_COPIES_STRING.equals(text.toString())) {
1828 mCopiesEditText.setText(MIN_COPIES_STRING);
1829 }
Svetoslava798c0a2014-05-15 10:47:19 -07001830 mCopiesEditText.setEnabled(false);
Svetoslavc404cac2014-08-27 18:37:16 -07001831 mCopiesEditText.setFocusable(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001832 }
1833 if (mCopiesEditText.getError() == null
1834 && TextUtils.isEmpty(mCopiesEditText.getText())) {
Svet Ganov45e50e92014-10-23 12:39:08 -07001835 mCopiesEditText.setText(MIN_COPIES_STRING);
Svetoslava798c0a2014-05-15 10:47:19 -07001836 mCopiesEditText.requestFocus();
1837 }
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001838
1839 if (mShowDestinationPrompt) {
1840 disableOptionsUi();
1841 }
Svetoslava798c0a2014-05-15 10:47:19 -07001842 }
1843
Svetoslave652b022014-09-09 22:11:10 -07001844 private void updateSummary() {
Philip P. Moltmannc2f913d2016-02-01 12:03:48 -08001845 if (!mIsOptionsUiBound) {
1846 return;
1847 }
1848
Svetoslave652b022014-09-09 22:11:10 -07001849 CharSequence copiesText = null;
1850 CharSequence mediaSizeText = null;
1851
1852 if (!TextUtils.isEmpty(mCopiesEditText.getText())) {
1853 copiesText = mCopiesEditText.getText();
1854 mSummaryCopies.setText(copiesText);
1855 }
1856
1857 final int selectedMediaIndex = mMediaSizeSpinner.getSelectedItemPosition();
1858 if (selectedMediaIndex >= 0) {
1859 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(selectedMediaIndex);
1860 mediaSizeText = mediaItem.label;
1861 mSummaryPaperSize.setText(mediaSizeText);
1862 }
1863
1864 if (!TextUtils.isEmpty(copiesText) && !TextUtils.isEmpty(mediaSizeText)) {
1865 String summaryText = getString(R.string.summary_template, copiesText, mediaSizeText);
1866 mSummaryContainer.setContentDescription(summaryText);
1867 }
1868 }
1869
Svetoslav73764e32014-07-15 15:56:46 -07001870 private void updatePageRangeOptions(int pageCount) {
Philip P. Moltmannc43639c2015-12-18 13:58:40 -08001871 @SuppressWarnings("unchecked")
Svetoslav73764e32014-07-15 15:56:46 -07001872 ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter =
Philip P. Moltmannc43639c2015-12-18 13:58:40 -08001873 (ArrayAdapter<SpinnerItem<Integer>>) mRangeOptionsSpinner.getAdapter();
Svetoslav73764e32014-07-15 15:56:46 -07001874 rangeOptionsSpinnerAdapter.clear();
1875
1876 final int[] rangeOptionsValues = getResources().getIntArray(
1877 R.array.page_options_values);
1878
1879 String pageCountLabel = (pageCount > 0) ? String.valueOf(pageCount) : "";
1880 String[] rangeOptionsLabels = new String[] {
1881 getString(R.string.template_all_pages, pageCountLabel),
1882 getString(R.string.template_page_range, pageCountLabel)
1883 };
1884
1885 final int rangeOptionsCount = rangeOptionsLabels.length;
1886 for (int i = 0; i < rangeOptionsCount; i++) {
1887 rangeOptionsSpinnerAdapter.add(new SpinnerItem<>(
1888 rangeOptionsValues[i], rangeOptionsLabels[i]));
1889 }
1890 }
1891
Svet Ganov525a66b2014-06-14 22:29:00 -07001892 private PageRange[] computeSelectedPages() {
Svetoslava798c0a2014-05-15 10:47:19 -07001893 if (hasErrors()) {
1894 return null;
1895 }
1896
1897 if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
1898 List<PageRange> pageRanges = new ArrayList<>();
1899 mStringCommaSplitter.setString(mPageRangeEditText.getText().toString());
1900
1901 while (mStringCommaSplitter.hasNext()) {
1902 String range = mStringCommaSplitter.next().trim();
1903 if (TextUtils.isEmpty(range)) {
1904 continue;
1905 }
1906 final int dashIndex = range.indexOf('-');
1907 final int fromIndex;
1908 final int toIndex;
1909
1910 if (dashIndex > 0) {
1911 fromIndex = Integer.parseInt(range.substring(0, dashIndex).trim()) - 1;
1912 // It is possible that the dash is at the end since the input
1913 // verification can has to allow the user to keep entering if
1914 // this would lead to a valid input. So we handle this.
1915 if (dashIndex < range.length() - 1) {
1916 String fromString = range.substring(dashIndex + 1, range.length()).trim();
1917 toIndex = Integer.parseInt(fromString) - 1;
1918 } else {
1919 toIndex = fromIndex;
1920 }
1921 } else {
1922 fromIndex = toIndex = Integer.parseInt(range) - 1;
1923 }
1924
1925 PageRange pageRange = new PageRange(Math.min(fromIndex, toIndex),
1926 Math.max(fromIndex, toIndex));
1927 pageRanges.add(pageRange);
1928 }
1929
1930 PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
1931 pageRanges.toArray(pageRangesArray);
1932
1933 return PageRangeUtils.normalize(pageRangesArray);
1934 }
1935
1936 return ALL_PAGES_ARRAY;
1937 }
1938
Svet Ganov525a66b2014-06-14 22:29:00 -07001939 private int getAdjustedPageCount(PrintDocumentInfo info) {
1940 if (info != null) {
1941 final int pageCount = info.getPageCount();
1942 if (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
1943 return pageCount;
1944 }
1945 }
1946 // If the app does not tell us how many pages are in the
1947 // doc we ask for all pages and use the document page count.
1948 return mPrintPreviewController.getFilePageCount();
1949 }
1950
Svetoslava798c0a2014-05-15 10:47:19 -07001951 private boolean hasErrors() {
1952 return (mCopiesEditText.getError() != null)
1953 || (mPageRangeEditText.getVisibility() == View.VISIBLE
Svet Ganov525a66b2014-06-14 22:29:00 -07001954 && mPageRangeEditText.getError() != null);
Svetoslava798c0a2014-05-15 10:47:19 -07001955 }
1956
1957 public void onPrinterAvailable(PrinterInfo printer) {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001958 if (mCurrentPrinter.equals(printer)) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001959 setState(STATE_CONFIGURING);
Svetoslava798c0a2014-05-15 10:47:19 -07001960 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07001961 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001962 }
1963 ensurePreviewUiShown();
1964 updateOptionsUi();
1965 }
1966 }
1967
1968 public void onPrinterUnavailable(PrinterInfo printer) {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001969 if (mCurrentPrinter.getId().equals(printer.getId())) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001970 setState(STATE_PRINTER_UNAVAILABLE);
Philip P. Moltmann645a3e12016-02-25 11:20:41 -08001971 mPrintedDocument.cancel(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001972 ensureErrorUiShown(getString(R.string.print_error_printer_unavailable),
1973 PrintErrorFragment.ACTION_NONE);
1974 updateOptionsUi();
1975 }
1976 }
1977
Svet Ganov525a66b2014-06-14 22:29:00 -07001978 private boolean canUpdateDocument() {
1979 if (mPrintedDocument.isDestroyed()) {
1980 return false;
1981 }
1982
1983 if (hasErrors()) {
1984 return false;
1985 }
1986
1987 PrintAttributes attributes = mPrintJob.getAttributes();
1988
1989 final int colorMode = attributes.getColorMode();
1990 if (colorMode != PrintAttributes.COLOR_MODE_COLOR
1991 && colorMode != PrintAttributes.COLOR_MODE_MONOCHROME) {
1992 return false;
1993 }
1994 if (attributes.getMediaSize() == null) {
1995 return false;
1996 }
1997 if (attributes.getMinMargins() == null) {
1998 return false;
1999 }
2000 if (attributes.getResolution() == null) {
2001 return false;
2002 }
2003
Svet Ganov48fec5c2014-07-14 00:14:07 -07002004 if (mCurrentPrinter == null) {
Svet Ganov525a66b2014-06-14 22:29:00 -07002005 return false;
2006 }
Svet Ganov48fec5c2014-07-14 00:14:07 -07002007 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
Svet Ganov525a66b2014-06-14 22:29:00 -07002008 if (capabilities == null) {
2009 return false;
2010 }
Svet Ganov48fec5c2014-07-14 00:14:07 -07002011 if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
Svet Ganov525a66b2014-06-14 22:29:00 -07002012 return false;
2013 }
2014
2015 return true;
2016 }
2017
Svetoslavbec22be2014-09-25 13:03:20 -07002018 private void transformDocumentAndFinish(final Uri writeToUri) {
2019 // If saving to PDF, apply the attibutes as we are acting as a print service.
2020 PrintAttributes attributes = mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter
2021 ? mPrintJob.getAttributes() : null;
2022 new DocumentTransformer(this, mPrintJob, mFileProvider, attributes, new Runnable() {
Svetoslav62ce3322014-09-04 21:17:17 -07002023 @Override
2024 public void run() {
2025 if (writeToUri != null) {
2026 mPrintedDocument.writeContent(getContentResolver(), writeToUri);
2027 }
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -08002028 setState(STATE_PRINT_COMPLETED);
Svetoslave17123d2014-09-11 12:39:05 -07002029 doFinish();
Svetoslav62ce3322014-09-04 21:17:17 -07002030 }
Svetoslavbec22be2014-09-25 13:03:20 -07002031 }).transform();
Svetoslav62ce3322014-09-04 21:17:17 -07002032 }
2033
Svetoslave17123d2014-09-11 12:39:05 -07002034 private void doFinish() {
Philip P. Moltmann32e07552016-03-07 10:31:49 -08002035 if (mPrintedDocument != null && mPrintedDocument.isUpdating()) {
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -08002036 // The printedDocument will call doFinish() when the current command finishes
2037 return;
2038 }
2039
Philip P. Moltmannb170c082016-03-21 12:48:58 -07002040 if (mIsFinishing) {
Philip P. Moltmann0ad7fc52016-03-07 11:36:06 -08002041 return;
2042 }
2043
Philip P. Moltmannb170c082016-03-21 12:48:58 -07002044 mIsFinishing = true;
Philip P. Moltmann0ad7fc52016-03-07 11:36:06 -08002045
Philip P. Moltmann51dbc8e2016-02-01 13:56:45 -08002046 if (mPrinterRegistry != null) {
2047 mPrinterRegistry.setTrackedPrinter(null);
2048 }
2049
Philip P. Moltmann1bb7f362016-02-26 14:21:20 -08002050 if (mPrintersObserver != null) {
2051 mDestinationSpinnerAdapter.unregisterDataSetObserver(mPrintersObserver);
2052 }
2053
Philip P. Moltmann940fa802016-03-23 16:56:24 -07002054 if (mSpoolerProvider != null) {
2055 mSpoolerProvider.destroy();
2056 }
2057
Svetoslave17123d2014-09-11 12:39:05 -07002058 if (mState != STATE_INITIALIZING) {
2059 mProgressMessageController.cancel();
Svetoslave17123d2014-09-11 12:39:05 -07002060 mPrintedDocument.finish();
2061 mPrintedDocument.destroy();
Svet Ganovc80814e2014-11-24 02:01:37 -08002062 mPrintPreviewController.destroy(new Runnable() {
2063 @Override
2064 public void run() {
2065 finish();
2066 }
2067 });
2068 } else {
2069 finish();
Svetoslave17123d2014-09-11 12:39:05 -07002070 }
Svetoslave17123d2014-09-11 12:39:05 -07002071 }
2072
Svetoslava798c0a2014-05-15 10:47:19 -07002073 private final class SpinnerItem<T> {
2074 final T value;
2075 final CharSequence label;
2076
2077 public SpinnerItem(T value, CharSequence label) {
2078 this.value = value;
2079 this.label = label;
2080 }
2081
Philip P. Moltmannc43639c2015-12-18 13:58:40 -08002082 @Override
Svetoslava798c0a2014-05-15 10:47:19 -07002083 public String toString() {
2084 return label.toString();
2085 }
2086 }
2087
2088 private final class PrinterAvailabilityDetector implements Runnable {
2089 private static final long UNAVAILABLE_TIMEOUT_MILLIS = 10000; // 10sec
2090
2091 private boolean mPosted;
2092
2093 private boolean mPrinterUnavailable;
2094
2095 private PrinterInfo mPrinter;
2096
2097 public void updatePrinter(PrinterInfo printer) {
2098 if (printer.equals(mDestinationSpinnerAdapter.getPdfPrinter())) {
2099 return;
2100 }
2101
2102 final boolean available = printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
2103 && printer.getCapabilities() != null;
2104 final boolean notifyIfAvailable;
2105
2106 if (mPrinter == null || !mPrinter.getId().equals(printer.getId())) {
2107 notifyIfAvailable = true;
2108 unpostIfNeeded();
2109 mPrinterUnavailable = false;
2110 mPrinter = new PrinterInfo.Builder(printer).build();
2111 } else {
2112 notifyIfAvailable =
Svet Ganov525a66b2014-06-14 22:29:00 -07002113 (mPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
2114 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE)
2115 || (mPrinter.getCapabilities() == null
2116 && printer.getCapabilities() != null);
Philip P. Moltmannc2ad2262016-01-13 09:17:15 -08002117 mPrinter = printer;
Svetoslava798c0a2014-05-15 10:47:19 -07002118 }
2119
2120 if (available) {
2121 unpostIfNeeded();
2122 mPrinterUnavailable = false;
2123 if (notifyIfAvailable) {
2124 onPrinterAvailable(mPrinter);
2125 }
2126 } else {
2127 if (!mPrinterUnavailable) {
2128 postIfNeeded();
2129 }
2130 }
2131 }
2132
2133 public void cancel() {
2134 unpostIfNeeded();
2135 mPrinterUnavailable = false;
2136 }
2137
2138 private void postIfNeeded() {
2139 if (!mPosted) {
2140 mPosted = true;
2141 mDestinationSpinner.postDelayed(this, UNAVAILABLE_TIMEOUT_MILLIS);
2142 }
2143 }
2144
2145 private void unpostIfNeeded() {
2146 if (mPosted) {
2147 mPosted = false;
2148 mDestinationSpinner.removeCallbacks(this);
2149 }
2150 }
2151
2152 @Override
2153 public void run() {
2154 mPosted = false;
2155 mPrinterUnavailable = true;
2156 onPrinterUnavailable(mPrinter);
2157 }
2158 }
2159
2160 private static final class PrinterHolder {
2161 PrinterInfo printer;
2162 boolean removed;
2163
2164 public PrinterHolder(PrinterInfo printer) {
2165 this.printer = printer;
2166 }
2167 }
2168
Philip P. Moltmann5e548962015-11-13 15:33:40 -08002169
2170 /**
2171 * Check if the user has ever printed a document
2172 *
2173 * @return true iff the user has ever printed a document
2174 */
2175 private boolean hasUserEverPrinted() {
2176 SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE);
2177
2178 return preferences.getBoolean(HAS_PRINTED_PREF, false);
2179 }
2180
2181 /**
2182 * Remember that the user printed a document
2183 */
2184 private void setUserPrinted() {
2185 SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE);
2186
2187 if (!preferences.getBoolean(HAS_PRINTED_PREF, false)) {
2188 SharedPreferences.Editor edit = preferences.edit();
2189
2190 edit.putBoolean(HAS_PRINTED_PREF, true);
2191 edit.apply();
2192 }
2193 }
2194
Svetoslava798c0a2014-05-15 10:47:19 -07002195 private final class DestinationAdapter extends BaseAdapter
2196 implements PrinterRegistry.OnPrintersChangeListener {
2197 private final List<PrinterHolder> mPrinterHolders = new ArrayList<>();
2198
2199 private final PrinterHolder mFakePdfPrinterHolder;
2200
Svet Ganov48fec5c2014-07-14 00:14:07 -07002201 private boolean mHistoricalPrintersLoaded;
2202
Philip P. Moltmann5e548962015-11-13 15:33:40 -08002203 /**
2204 * Has the {@link #mDestinationSpinner} ever used a view from printer_dropdown_prompt
2205 */
2206 private boolean hadPromptView;
2207
Svetoslava798c0a2014-05-15 10:47:19 -07002208 public DestinationAdapter() {
Svet Ganov48fec5c2014-07-14 00:14:07 -07002209 mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
2210 if (mHistoricalPrintersLoaded) {
2211 addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters());
2212 }
Svetoslava798c0a2014-05-15 10:47:19 -07002213 mPrinterRegistry.setOnPrintersChangeListener(this);
2214 mFakePdfPrinterHolder = new PrinterHolder(createFakePdfPrinter());
2215 }
2216
2217 public PrinterInfo getPdfPrinter() {
2218 return mFakePdfPrinterHolder.printer;
2219 }
2220
2221 public int getPrinterIndex(PrinterId printerId) {
2222 for (int i = 0; i < getCount(); i++) {
2223 PrinterHolder printerHolder = (PrinterHolder) getItem(i);
2224 if (printerHolder != null && !printerHolder.removed
2225 && printerHolder.printer.getId().equals(printerId)) {
2226 return i;
2227 }
2228 }
2229 return AdapterView.INVALID_POSITION;
2230 }
2231
2232 public void ensurePrinterInVisibleAdapterPosition(PrinterId printerId) {
2233 final int printerCount = mPrinterHolders.size();
2234 for (int i = 0; i < printerCount; i++) {
2235 PrinterHolder printerHolder = mPrinterHolders.get(i);
2236 if (printerHolder.printer.getId().equals(printerId)) {
2237 // If already in the list - do nothing.
2238 if (i < getCount() - 2) {
2239 return;
2240 }
2241 // Else replace the last one (two items are not printers).
2242 final int lastPrinterIndex = getCount() - 3;
2243 mPrinterHolders.set(i, mPrinterHolders.get(lastPrinterIndex));
2244 mPrinterHolders.set(lastPrinterIndex, printerHolder);
2245 notifyDataSetChanged();
2246 return;
2247 }
2248 }
2249 }
2250
2251 @Override
2252 public int getCount() {
Svet Ganov48fec5c2014-07-14 00:14:07 -07002253 if (mHistoricalPrintersLoaded) {
2254 return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
2255 }
2256 return 0;
Svetoslava798c0a2014-05-15 10:47:19 -07002257 }
2258
2259 @Override
2260 public boolean isEnabled(int position) {
2261 Object item = getItem(position);
2262 if (item instanceof PrinterHolder) {
2263 PrinterHolder printerHolder = (PrinterHolder) item;
2264 return !printerHolder.removed
2265 && printerHolder.printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
2266 }
2267 return true;
2268 }
2269
2270 @Override
2271 public Object getItem(int position) {
2272 if (mPrinterHolders.isEmpty()) {
2273 if (position == 0) {
2274 return mFakePdfPrinterHolder;
2275 }
2276 } else {
2277 if (position < 1) {
2278 return mPrinterHolders.get(position);
2279 }
2280 if (position == 1) {
2281 return mFakePdfPrinterHolder;
2282 }
2283 if (position < getCount() - 1) {
2284 return mPrinterHolders.get(position - 1);
2285 }
2286 }
2287 return null;
2288 }
2289
2290 @Override
2291 public long getItemId(int position) {
2292 if (mPrinterHolders.isEmpty()) {
2293 if (position == 0) {
2294 return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
2295 } else if (position == 1) {
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002296 return DEST_ADAPTER_ITEM_ID_MORE;
Svetoslava798c0a2014-05-15 10:47:19 -07002297 }
2298 } else {
2299 if (position == 1) {
2300 return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
2301 }
2302 if (position == getCount() - 1) {
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002303 return DEST_ADAPTER_ITEM_ID_MORE;
Svetoslava798c0a2014-05-15 10:47:19 -07002304 }
2305 }
2306 return position;
2307 }
2308
2309 @Override
2310 public View getDropDownView(int position, View convertView, ViewGroup parent) {
2311 View view = getView(position, convertView, parent);
2312 view.setEnabled(isEnabled(position));
2313 return view;
2314 }
2315
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002316 private String getMoreItemTitle() {
2317 if (mArePrintServicesEnabled) {
2318 return getString(R.string.all_printers);
2319 } else {
2320 return getString(R.string.print_add_printer);
2321 }
2322 }
2323
Svetoslava798c0a2014-05-15 10:47:19 -07002324 @Override
2325 public View getView(int position, View convertView, ViewGroup parent) {
Philip P. Moltmann5e548962015-11-13 15:33:40 -08002326 if (mShowDestinationPrompt) {
2327 if (convertView == null) {
2328 convertView = getLayoutInflater().inflate(
2329 R.layout.printer_dropdown_prompt, parent, false);
2330 hadPromptView = true;
2331 }
2332
2333 return convertView;
2334 } else {
2335 // We don't know if we got an recyled printer_dropdown_prompt, hence do not use it
2336 if (hadPromptView || convertView == null) {
2337 convertView = getLayoutInflater().inflate(
2338 R.layout.printer_dropdown_item, parent, false);
2339 }
Svetoslava798c0a2014-05-15 10:47:19 -07002340 }
2341
2342 CharSequence title = null;
2343 CharSequence subtitle = null;
2344 Drawable icon = null;
2345
2346 if (mPrinterHolders.isEmpty()) {
2347 if (position == 0 && getPdfPrinter() != null) {
2348 PrinterHolder printerHolder = (PrinterHolder) getItem(position);
2349 title = printerHolder.printer.getName();
Philip P. Moltmannc43639c2015-12-18 13:58:40 -08002350 icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf, null);
Svetoslava798c0a2014-05-15 10:47:19 -07002351 } else if (position == 1) {
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002352 title = getMoreItemTitle();
Svetoslava798c0a2014-05-15 10:47:19 -07002353 }
2354 } else {
2355 if (position == 1 && getPdfPrinter() != null) {
2356 PrinterHolder printerHolder = (PrinterHolder) getItem(position);
2357 title = printerHolder.printer.getName();
Philip P. Moltmannc43639c2015-12-18 13:58:40 -08002358 icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf, null);
Svetoslava798c0a2014-05-15 10:47:19 -07002359 } else if (position == getCount() - 1) {
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002360 title = getMoreItemTitle();
Svetoslava798c0a2014-05-15 10:47:19 -07002361 } else {
2362 PrinterHolder printerHolder = (PrinterHolder) getItem(position);
Philip P. Moltmannbb9f6862015-12-01 14:44:24 -08002363 PrinterInfo printInfo = printerHolder.printer;
2364
2365 title = printInfo.getName();
2366 icon = printInfo.loadIcon(PrintActivity.this);
2367 subtitle = printInfo.getDescription();
Svetoslava798c0a2014-05-15 10:47:19 -07002368 }
2369 }
2370
2371 TextView titleView = (TextView) convertView.findViewById(R.id.title);
2372 titleView.setText(title);
2373
2374 TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
2375 if (!TextUtils.isEmpty(subtitle)) {
2376 subtitleView.setText(subtitle);
2377 subtitleView.setVisibility(View.VISIBLE);
2378 } else {
2379 subtitleView.setText(null);
2380 subtitleView.setVisibility(View.GONE);
2381 }
2382
2383 ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
2384 if (icon != null) {
Svetoslava798c0a2014-05-15 10:47:19 -07002385 iconView.setVisibility(View.VISIBLE);
Philip P. Moltmann443075a2016-01-26 13:04:21 -08002386 if (!isEnabled(position)) {
2387 icon.mutate();
2388
2389 TypedValue value = new TypedValue();
2390 getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true);
2391 icon.setAlpha((int)(value.getFloat() * 255));
2392 }
2393 iconView.setImageDrawable(icon);
Svetoslava798c0a2014-05-15 10:47:19 -07002394 } else {
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002395 iconView.setVisibility(View.INVISIBLE);
Svetoslava798c0a2014-05-15 10:47:19 -07002396 }
2397
2398 return convertView;
2399 }
2400
2401 @Override
2402 public void onPrintersChanged(List<PrinterInfo> printers) {
2403 // We rearrange the printers if the user selects a printer
2404 // not shown in the initial short list. Therefore, we have
2405 // to keep the printer order.
2406
Svet Ganov48fec5c2014-07-14 00:14:07 -07002407 // Check if historical printers are loaded as this adapter is open
2408 // for busyness only if they are. This member is updated here and
2409 // when the adapter is created because the historical printers may
2410 // be loaded before or after the adapter is created.
2411 mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
2412
Svetoslava798c0a2014-05-15 10:47:19 -07002413 // No old printers - do not bother keeping their position.
2414 if (mPrinterHolders.isEmpty()) {
2415 addPrinters(mPrinterHolders, printers);
2416 notifyDataSetChanged();
2417 return;
2418 }
2419
2420 // Add the new printers to a map.
2421 ArrayMap<PrinterId, PrinterInfo> newPrintersMap = new ArrayMap<>();
2422 final int printerCount = printers.size();
2423 for (int i = 0; i < printerCount; i++) {
2424 PrinterInfo printer = printers.get(i);
2425 newPrintersMap.put(printer.getId(), printer);
2426 }
2427
2428 List<PrinterHolder> newPrinterHolders = new ArrayList<>();
2429
2430 // Update printers we already have which are either updated or removed.
2431 // We do not remove printers if the currently selected printer is removed
2432 // to prevent the user printing to a wrong printer.
2433 final int oldPrinterCount = mPrinterHolders.size();
2434 for (int i = 0; i < oldPrinterCount; i++) {
2435 PrinterHolder printerHolder = mPrinterHolders.get(i);
2436 PrinterId oldPrinterId = printerHolder.printer.getId();
2437 PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId);
2438 if (updatedPrinter != null) {
2439 printerHolder.printer = updatedPrinter;
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002440 printerHolder.removed = false;
Svetoslava798c0a2014-05-15 10:47:19 -07002441 } else {
2442 printerHolder.removed = true;
2443 }
2444 newPrinterHolders.add(printerHolder);
2445 }
2446
2447 // Add the rest of the new printers, i.e. what is left.
2448 addPrinters(newPrinterHolders, newPrintersMap.values());
2449
2450 mPrinterHolders.clear();
2451 mPrinterHolders.addAll(newPrinterHolders);
2452
2453 notifyDataSetChanged();
2454 }
2455
2456 @Override
2457 public void onPrintersInvalid() {
2458 mPrinterHolders.clear();
2459 notifyDataSetInvalidated();
2460 }
2461
2462 public PrinterHolder getPrinterHolder(PrinterId printerId) {
2463 final int itemCount = getCount();
2464 for (int i = 0; i < itemCount; i++) {
2465 Object item = getItem(i);
2466 if (item instanceof PrinterHolder) {
2467 PrinterHolder printerHolder = (PrinterHolder) item;
2468 if (printerId.equals(printerHolder.printer.getId())) {
2469 return printerHolder;
2470 }
2471 }
2472 }
2473 return null;
2474 }
2475
2476 public void pruneRemovedPrinters() {
2477 final int holderCounts = mPrinterHolders.size();
2478 for (int i = holderCounts - 1; i >= 0; i--) {
2479 PrinterHolder printerHolder = mPrinterHolders.get(i);
2480 if (printerHolder.removed) {
2481 mPrinterHolders.remove(i);
2482 }
2483 }
2484 }
2485
2486 private void addPrinters(List<PrinterHolder> list, Collection<PrinterInfo> printers) {
2487 for (PrinterInfo printer : printers) {
2488 PrinterHolder printerHolder = new PrinterHolder(printer);
2489 list.add(printerHolder);
2490 }
2491 }
2492
2493 private PrinterInfo createFakePdfPrinter() {
Philip P. Moltmann4959caf2016-01-21 14:30:56 -08002494 ArraySet<MediaSize> allMediaSizes = MediaSize.getAllPredefinedSizes();
Svetoslava798c0a2014-05-15 10:47:19 -07002495 MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintActivity.this);
2496
2497 PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
2498
2499 PrinterCapabilitiesInfo.Builder builder =
2500 new PrinterCapabilitiesInfo.Builder(printerId);
2501
Philip P. Moltmann4959caf2016-01-21 14:30:56 -08002502 final int mediaSizeCount = allMediaSizes.size();
2503 for (int i = 0; i < mediaSizeCount; i++) {
2504 MediaSize mediaSize = allMediaSizes.valueAt(i);
Svetoslava798c0a2014-05-15 10:47:19 -07002505 builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize));
2506 }
2507
2508 builder.addResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300),
2509 true);
2510 builder.setColorModes(PrintAttributes.COLOR_MODE_COLOR
2511 | PrintAttributes.COLOR_MODE_MONOCHROME, PrintAttributes.COLOR_MODE_COLOR);
2512
2513 return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf),
2514 PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build();
2515 }
2516 }
2517
2518 private final class PrintersObserver extends DataSetObserver {
2519 @Override
2520 public void onChanged() {
Svet Ganov48fec5c2014-07-14 00:14:07 -07002521 PrinterInfo oldPrinterState = mCurrentPrinter;
Svetoslava798c0a2014-05-15 10:47:19 -07002522 if (oldPrinterState == null) {
2523 return;
2524 }
2525
2526 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
2527 oldPrinterState.getId());
2528 if (printerHolder == null) {
2529 return;
2530 }
2531 PrinterInfo newPrinterState = printerHolder.printer;
2532
2533 if (!printerHolder.removed) {
2534 mDestinationSpinnerAdapter.pruneRemovedPrinters();
2535 } else {
2536 onPrinterUnavailable(newPrinterState);
2537 }
2538
2539 if (oldPrinterState.equals(newPrinterState)) {
2540 return;
2541 }
2542
2543 PrinterCapabilitiesInfo oldCapab = oldPrinterState.getCapabilities();
2544 PrinterCapabilitiesInfo newCapab = newPrinterState.getCapabilities();
2545
Philip P. Moltmann1bb7f362016-02-26 14:21:20 -08002546 final boolean hadCabab = oldCapab != null;
Svetoslava798c0a2014-05-15 10:47:19 -07002547 final boolean hasCapab = newCapab != null;
2548 final boolean gotCapab = oldCapab == null && newCapab != null;
2549 final boolean lostCapab = oldCapab != null && newCapab == null;
2550 final boolean capabChanged = capabilitiesChanged(oldCapab, newCapab);
2551
2552 final int oldStatus = oldPrinterState.getStatus();
2553 final int newStatus = newPrinterState.getStatus();
2554
2555 final boolean isActive = newStatus != PrinterInfo.STATUS_UNAVAILABLE;
2556 final boolean becameActive = (oldStatus == PrinterInfo.STATUS_UNAVAILABLE
2557 && oldStatus != newStatus);
2558 final boolean becameInactive = (newStatus == PrinterInfo.STATUS_UNAVAILABLE
2559 && oldStatus != newStatus);
2560
2561 mPrinterAvailabilityDetector.updatePrinter(newPrinterState);
2562
Philip P. Moltmannc2ad2262016-01-13 09:17:15 -08002563 mCurrentPrinter = newPrinterState;
Svetoslava798c0a2014-05-15 10:47:19 -07002564
Svetoslava798c0a2014-05-15 10:47:19 -07002565 final boolean updateNeeded = ((capabChanged && hasCapab && isActive)
2566 || (becameActive && hasCapab) || (isActive && gotCapab));
2567
Philip P. Moltmann1bb7f362016-02-26 14:21:20 -08002568 if (capabChanged && hasCapab) {
2569 updatePrintAttributesFromCapabilities(newCapab);
2570 }
2571
2572 if (updateNeeded) {
2573 updatePrintPreviewController(false);
2574 }
2575
2576 if ((isActive && gotCapab) || (becameActive && hasCapab)) {
2577 onPrinterAvailable(newPrinterState);
2578 } else if ((becameInactive && hadCabab) || (isActive && lostCapab)) {
2579 onPrinterUnavailable(newPrinterState);
2580 }
2581
Svetoslava798c0a2014-05-15 10:47:19 -07002582 if (updateNeeded && canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07002583 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07002584 }
2585
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002586 // Force a reload of the enabled print services to update mAdvancedPrintOptionsActivity
2587 // in onLoadFinished();
2588 getLoaderManager().getLoader(LOADER_ID_ENABLED_PRINT_SERVICES).forceLoad();
2589
Svetoslava798c0a2014-05-15 10:47:19 -07002590 updateOptionsUi();
Philip P. Moltmann17332cb2016-01-29 15:17:00 -08002591 updateSummary();
Svetoslava798c0a2014-05-15 10:47:19 -07002592 }
2593
2594 private boolean capabilitiesChanged(PrinterCapabilitiesInfo oldCapabilities,
2595 PrinterCapabilitiesInfo newCapabilities) {
2596 if (oldCapabilities == null) {
2597 if (newCapabilities != null) {
2598 return true;
2599 }
2600 } else if (!oldCapabilities.equals(newCapabilities)) {
2601 return true;
2602 }
2603 return false;
2604 }
2605 }
2606
2607 private final class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
2608 @Override
2609 public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
2610 if (spinner == mDestinationSpinner) {
2611 if (position == AdapterView.INVALID_POSITION) {
2612 return;
2613 }
2614
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002615 if (id == DEST_ADAPTER_ITEM_ID_MORE) {
Svetoslava798c0a2014-05-15 10:47:19 -07002616 startSelectPrinterActivity();
2617 return;
2618 }
2619
Svet Ganov48fec5c2014-07-14 00:14:07 -07002620 PrinterHolder currentItem = (PrinterHolder) mDestinationSpinner.getSelectedItem();
2621 PrinterInfo currentPrinter = (currentItem != null) ? currentItem.printer : null;
Svetoslava798c0a2014-05-15 10:47:19 -07002622
2623 // Why on earth item selected is called if no selection changed.
Svet Ganov48fec5c2014-07-14 00:14:07 -07002624 if (mCurrentPrinter == currentPrinter) {
Svetoslava798c0a2014-05-15 10:47:19 -07002625 return;
2626 }
Svet Ganov525a66b2014-06-14 22:29:00 -07002627
Svet Ganov48fec5c2014-07-14 00:14:07 -07002628 mCurrentPrinter = currentPrinter;
Svetoslava798c0a2014-05-15 10:47:19 -07002629
2630 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
2631 currentPrinter.getId());
2632 if (!printerHolder.removed) {
Svet Ganov525a66b2014-06-14 22:29:00 -07002633 setState(STATE_CONFIGURING);
Svetoslava798c0a2014-05-15 10:47:19 -07002634 mDestinationSpinnerAdapter.pruneRemovedPrinters();
2635 ensurePreviewUiShown();
2636 }
2637
2638 mPrintJob.setPrinterId(currentPrinter.getId());
2639 mPrintJob.setPrinterName(currentPrinter.getName());
2640
2641 mPrinterRegistry.setTrackedPrinter(currentPrinter.getId());
2642
2643 PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
2644 if (capabilities != null) {
Svet Ganov525a66b2014-06-14 22:29:00 -07002645 updatePrintAttributesFromCapabilities(capabilities);
Svetoslava798c0a2014-05-15 10:47:19 -07002646 }
2647
2648 mPrinterAvailabilityDetector.updatePrinter(currentPrinter);
Philip P. Moltmannba245f92016-03-07 13:44:59 -08002649
2650 // Force a reload of the enabled print services to update
2651 // mAdvancedPrintOptionsActivity in onLoadFinished();
2652 getLoaderManager().getLoader(LOADER_ID_ENABLED_PRINT_SERVICES).forceLoad();
Svetoslava798c0a2014-05-15 10:47:19 -07002653 } else if (spinner == mMediaSizeSpinner) {
2654 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
Svet Ganov525a66b2014-06-14 22:29:00 -07002655 PrintAttributes attributes = mPrintJob.getAttributes();
Svetoslava798c0a2014-05-15 10:47:19 -07002656 if (mOrientationSpinner.getSelectedItemPosition() == 0) {
Svet Ganov525a66b2014-06-14 22:29:00 -07002657 attributes.setMediaSize(mediaItem.value.asPortrait());
Svetoslava798c0a2014-05-15 10:47:19 -07002658 } else {
Svet Ganov525a66b2014-06-14 22:29:00 -07002659 attributes.setMediaSize(mediaItem.value.asLandscape());
Svetoslava798c0a2014-05-15 10:47:19 -07002660 }
2661 } else if (spinner == mColorModeSpinner) {
2662 SpinnerItem<Integer> colorModeItem = mColorModeSpinnerAdapter.getItem(position);
2663 mPrintJob.getAttributes().setColorMode(colorModeItem.value);
Svetoslav948c9a62015-02-02 19:47:04 -08002664 } else if (spinner == mDuplexModeSpinner) {
2665 SpinnerItem<Integer> duplexModeItem = mDuplexModeSpinnerAdapter.getItem(position);
2666 mPrintJob.getAttributes().setDuplexMode(duplexModeItem.value);
Svetoslava798c0a2014-05-15 10:47:19 -07002667 } else if (spinner == mOrientationSpinner) {
2668 SpinnerItem<Integer> orientationItem = mOrientationSpinnerAdapter.getItem(position);
2669 PrintAttributes attributes = mPrintJob.getAttributes();
Svetoslave3bbb3d2014-06-12 10:43:20 -07002670 if (mMediaSizeSpinner.getSelectedItem() != null) {
2671 if (orientationItem.value == ORIENTATION_PORTRAIT) {
2672 attributes.copyFrom(attributes.asPortrait());
2673 } else {
2674 attributes.copyFrom(attributes.asLandscape());
2675 }
Svetoslava798c0a2014-05-15 10:47:19 -07002676 }
Svet Ganov525a66b2014-06-14 22:29:00 -07002677 } else if (spinner == mRangeOptionsSpinner) {
2678 if (mRangeOptionsSpinner.getSelectedItemPosition() == 0) {
2679 mPageRangeEditText.setText("");
2680 } else if (TextUtils.isEmpty(mPageRangeEditText.getText())) {
2681 mPageRangeEditText.setError("");
2682 }
Svetoslava798c0a2014-05-15 10:47:19 -07002683 }
2684
2685 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07002686 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07002687 }
2688
2689 updateOptionsUi();
2690 }
2691
2692 @Override
2693 public void onNothingSelected(AdapterView<?> parent) {
2694 /* do nothing*/
2695 }
2696 }
2697
Svetoslava798c0a2014-05-15 10:47:19 -07002698 private final class SelectAllOnFocusListener implements OnFocusChangeListener {
2699 @Override
2700 public void onFocusChange(View view, boolean hasFocus) {
2701 EditText editText = (EditText) view;
2702 if (!TextUtils.isEmpty(editText.getText())) {
2703 editText.setSelection(editText.getText().length());
2704 }
2705 }
2706 }
2707
2708 private final class RangeTextWatcher implements TextWatcher {
2709 @Override
2710 public void onTextChanged(CharSequence s, int start, int before, int count) {
2711 /* do nothing */
2712 }
2713
2714 @Override
2715 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2716 /* do nothing */
2717 }
2718
2719 @Override
2720 public void afterTextChanged(Editable editable) {
2721 final boolean hadErrors = hasErrors();
2722
2723 String text = editable.toString();
2724
2725 if (TextUtils.isEmpty(text)) {
2726 mPageRangeEditText.setError("");
2727 updateOptionsUi();
2728 return;
2729 }
2730
2731 String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
2732 if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
2733 mPageRangeEditText.setError("");
2734 updateOptionsUi();
2735 return;
2736 }
2737
2738 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
Svet Ganov525a66b2014-06-14 22:29:00 -07002739 final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0;
Svetoslava798c0a2014-05-15 10:47:19 -07002740
2741 // The range
2742 Matcher matcher = PATTERN_DIGITS.matcher(text);
2743 while (matcher.find()) {
2744 String numericString = text.substring(matcher.start(), matcher.end()).trim();
2745 if (TextUtils.isEmpty(numericString)) {
2746 continue;
2747 }
2748 final int pageIndex = Integer.parseInt(numericString);
2749 if (pageIndex < 1 || pageIndex > pageCount) {
2750 mPageRangeEditText.setError("");
2751 updateOptionsUi();
2752 return;
2753 }
2754 }
2755
2756 // We intentionally do not catch the case of the from page being
2757 // greater than the to page. When computing the requested pages
2758 // we just swap them if necessary.
2759
Svetoslava798c0a2014-05-15 10:47:19 -07002760 mPageRangeEditText.setError(null);
2761 mPrintButton.setEnabled(true);
2762 updateOptionsUi();
2763
2764 if (hadErrors && !hasErrors()) {
2765 updateOptionsUi();
2766 }
2767 }
2768 }
2769
2770 private final class EditTextWatcher implements TextWatcher {
2771 @Override
2772 public void onTextChanged(CharSequence s, int start, int before, int count) {
2773 /* do nothing */
2774 }
2775
2776 @Override
2777 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2778 /* do nothing */
2779 }
2780
2781 @Override
2782 public void afterTextChanged(Editable editable) {
2783 final boolean hadErrors = hasErrors();
2784
2785 if (editable.length() == 0) {
2786 mCopiesEditText.setError("");
2787 updateOptionsUi();
2788 return;
2789 }
2790
2791 int copies = 0;
2792 try {
2793 copies = Integer.parseInt(editable.toString());
2794 } catch (NumberFormatException nfe) {
2795 /* ignore */
2796 }
2797
2798 if (copies < MIN_COPIES) {
2799 mCopiesEditText.setError("");
2800 updateOptionsUi();
2801 return;
2802 }
2803
2804 mPrintJob.setCopies(copies);
2805
2806 mCopiesEditText.setError(null);
2807
2808 updateOptionsUi();
2809
2810 if (hadErrors && canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07002811 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07002812 }
2813 }
2814 }
2815
2816 private final class ProgressMessageController implements Runnable {
2817 private static final long PROGRESS_TIMEOUT_MILLIS = 1000;
2818
2819 private final Handler mHandler;
2820
2821 private boolean mPosted;
2822
2823 public ProgressMessageController(Context context) {
2824 mHandler = new Handler(context.getMainLooper(), null, false);
2825 }
2826
2827 public void post() {
2828 if (mPosted) {
2829 return;
2830 }
2831 mPosted = true;
2832 mHandler.postDelayed(this, PROGRESS_TIMEOUT_MILLIS);
2833 }
2834
2835 public void cancel() {
2836 if (!mPosted) {
2837 return;
2838 }
2839 mPosted = false;
2840 mHandler.removeCallbacks(this);
2841 }
2842
2843 @Override
2844 public void run() {
Svet Ganov525a66b2014-06-14 22:29:00 -07002845 mPosted = false;
2846 setState(STATE_UPDATE_SLOW);
Svetoslava798c0a2014-05-15 10:47:19 -07002847 ensureProgressUiShown();
Svet Ganov525a66b2014-06-14 22:29:00 -07002848 updateOptionsUi();
Svetoslava798c0a2014-05-15 10:47:19 -07002849 }
2850 }
Svetoslav62ce3322014-09-04 21:17:17 -07002851
Svetoslavbec22be2014-09-25 13:03:20 -07002852 private static final class DocumentTransformer implements ServiceConnection {
Svetoslav62ce3322014-09-04 21:17:17 -07002853 private static final String TEMP_FILE_PREFIX = "print_job";
2854 private static final String TEMP_FILE_EXTENSION = ".pdf";
2855
2856 private final Context mContext;
2857
2858 private final MutexFileProvider mFileProvider;
2859
2860 private final PrintJobInfo mPrintJob;
2861
2862 private final PageRange[] mPagesToShred;
2863
Svetoslavbec22be2014-09-25 13:03:20 -07002864 private final PrintAttributes mAttributesToApply;
2865
Svetoslav62ce3322014-09-04 21:17:17 -07002866 private final Runnable mCallback;
2867
Svetoslavbec22be2014-09-25 13:03:20 -07002868 public DocumentTransformer(Context context, PrintJobInfo printJob,
2869 MutexFileProvider fileProvider, PrintAttributes attributes,
2870 Runnable callback) {
Svetoslav62ce3322014-09-04 21:17:17 -07002871 mContext = context;
2872 mPrintJob = printJob;
2873 mFileProvider = fileProvider;
2874 mCallback = callback;
2875 mPagesToShred = computePagesToShred(mPrintJob);
Svetoslavbec22be2014-09-25 13:03:20 -07002876 mAttributesToApply = attributes;
Svetoslav62ce3322014-09-04 21:17:17 -07002877 }
2878
Svetoslavbec22be2014-09-25 13:03:20 -07002879 public void transform() {
Svetoslav62ce3322014-09-04 21:17:17 -07002880 // If we have only the pages we want, done.
Svetoslavbec22be2014-09-25 13:03:20 -07002881 if (mPagesToShred.length <= 0 && mAttributesToApply == null) {
Svetoslav62ce3322014-09-04 21:17:17 -07002882 mCallback.run();
2883 return;
2884 }
2885
2886 // Bind to the manipulation service and the work
2887 // will be performed upon connection to the service.
2888 Intent intent = new Intent(PdfManipulationService.ACTION_GET_EDITOR);
2889 intent.setClass(mContext, PdfManipulationService.class);
2890 mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
2891 }
2892
2893 @Override
2894 public void onServiceConnected(ComponentName name, IBinder service) {
2895 final IPdfEditor editor = IPdfEditor.Stub.asInterface(service);
2896 new AsyncTask<Void, Void, Void>() {
2897 @Override
2898 protected Void doInBackground(Void... params) {
Svetoslavfb3532ee2014-09-15 18:28:51 -07002899 // It's OK to access the data members as they are
2900 // final and this code is the last one to touch
2901 // them as shredding is the very last step, so the
2902 // UI is not interactive at this point.
Svetoslavbec22be2014-09-25 13:03:20 -07002903 doTransform(editor);
Svetoslavfb3532ee2014-09-15 18:28:51 -07002904 updatePrintJob();
Svetoslav62ce3322014-09-04 21:17:17 -07002905 return null;
2906 }
Svetoslavfb3532ee2014-09-15 18:28:51 -07002907
2908 @Override
2909 protected void onPostExecute(Void aVoid) {
Svetoslavbec22be2014-09-25 13:03:20 -07002910 mContext.unbindService(DocumentTransformer.this);
Svetoslavfb3532ee2014-09-15 18:28:51 -07002911 mCallback.run();
2912 }
Svetoslavb75632c2014-09-17 18:38:27 -07002913 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
Svetoslav62ce3322014-09-04 21:17:17 -07002914 }
2915
2916 @Override
2917 public void onServiceDisconnected(ComponentName name) {
2918 /* do nothing */
2919 }
2920
Svetoslavbec22be2014-09-25 13:03:20 -07002921 private void doTransform(IPdfEditor editor) {
Svetoslav62ce3322014-09-04 21:17:17 -07002922 File tempFile = null;
2923 ParcelFileDescriptor src = null;
2924 ParcelFileDescriptor dst = null;
2925 InputStream in = null;
2926 OutputStream out = null;
2927 try {
2928 File jobFile = mFileProvider.acquireFile(null);
2929 src = ParcelFileDescriptor.open(jobFile, ParcelFileDescriptor.MODE_READ_WRITE);
2930
2931 // Open the document.
2932 editor.openDocument(src);
2933
2934 // We passed the fd over IPC, close this one.
2935 src.close();
2936
2937 // Drop the pages.
2938 editor.removePages(mPagesToShred);
2939
Svetoslavbec22be2014-09-25 13:03:20 -07002940 // Apply print attributes if needed.
2941 if (mAttributesToApply != null) {
2942 editor.applyPrintAttributes(mAttributesToApply);
2943 }
2944
Svetoslav62ce3322014-09-04 21:17:17 -07002945 // Write the modified PDF to a temp file.
2946 tempFile = File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_EXTENSION,
2947 mContext.getCacheDir());
2948 dst = ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_WRITE);
2949 editor.write(dst);
2950 dst.close();
2951
2952 // Close the document.
2953 editor.closeDocument();
2954
2955 // Copy the temp file over the print job file.
2956 jobFile.delete();
2957 in = new FileInputStream(tempFile);
2958 out = new FileOutputStream(jobFile);
2959 Streams.copy(in, out);
2960 } catch (IOException|RemoteException e) {
2961 Log.e(LOG_TAG, "Error dropping pages", e);
2962 } finally {
2963 IoUtils.closeQuietly(src);
2964 IoUtils.closeQuietly(dst);
2965 IoUtils.closeQuietly(in);
2966 IoUtils.closeQuietly(out);
2967 if (tempFile != null) {
2968 tempFile.delete();
2969 }
Svetoslav56683482014-09-23 16:22:42 -07002970 mFileProvider.releaseFile();
Svetoslav62ce3322014-09-04 21:17:17 -07002971 }
2972 }
2973
2974 private void updatePrintJob() {
2975 // Update the print job pages.
2976 final int newPageCount = PageRangeUtils.getNormalizedPageCount(
2977 mPrintJob.getPages(), 0);
2978 mPrintJob.setPages(new PageRange[]{PageRange.ALL_PAGES});
2979
2980 // Update the print job document info.
2981 PrintDocumentInfo oldDocInfo = mPrintJob.getDocumentInfo();
2982 PrintDocumentInfo newDocInfo = new PrintDocumentInfo
2983 .Builder(oldDocInfo.getName())
2984 .setContentType(oldDocInfo.getContentType())
2985 .setPageCount(newPageCount)
2986 .build();
2987 mPrintJob.setDocumentInfo(newDocInfo);
2988 }
2989
2990 private static PageRange[] computePagesToShred(PrintJobInfo printJob) {
2991 List<PageRange> rangesToShred = new ArrayList<>();
2992 PageRange previousRange = null;
2993
2994 final int pageCount = printJob.getDocumentInfo().getPageCount();
2995
2996 PageRange[] printedPages = printJob.getPages();
2997 final int rangeCount = printedPages.length;
2998 for (int i = 0; i < rangeCount; i++) {
2999 PageRange range = PageRangeUtils.asAbsoluteRange(printedPages[i], pageCount);
3000
3001 if (previousRange == null) {
3002 final int startPageIdx = 0;
3003 final int endPageIdx = range.getStart() - 1;
3004 if (startPageIdx <= endPageIdx) {
3005 PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
3006 rangesToShred.add(removedRange);
3007 }
3008 } else {
3009 final int startPageIdx = previousRange.getEnd() + 1;
3010 final int endPageIdx = range.getStart() - 1;
3011 if (startPageIdx <= endPageIdx) {
3012 PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
3013 rangesToShred.add(removedRange);
3014 }
3015 }
3016
3017 if (i == rangeCount - 1) {
3018 final int startPageIdx = range.getEnd() + 1;
3019 final int endPageIdx = printJob.getDocumentInfo().getPageCount() - 1;
3020 if (startPageIdx <= endPageIdx) {
3021 PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
3022 rangesToShred.add(removedRange);
3023 }
3024 }
3025
3026 previousRange = range;
3027 }
3028
3029 PageRange[] result = new PageRange[rangesToShred.size()];
3030 rangesToShred.toArray(result);
3031 return result;
3032 }
3033 }
Svet Ganov561b8932014-09-02 21:51:45 +00003034}