blob: cde0fa3b4896930719739a1d635398e38402b547 [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
Svet Ganov525a66b2014-06-14 22:29:00 -0700131 private static final String FRAGMENT_TAG = "FRAGMENT_TAG";
132
Philip P. Moltmann5e548962015-11-13 15:33:40 -0800133 private static final String HAS_PRINTED_PREF = "has_printed";
134
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800135 private static final int LOADER_ID_ENABLED_PRINT_SERVICES = 1;
136 private static final int LOADER_ID_PRINT_REGISTRY = 2;
137 private static final int LOADER_ID_PRINT_REGISTRY_INT = 3;
138
Svetoslava798c0a2014-05-15 10:47:19 -0700139 private static final int ORIENTATION_PORTRAIT = 0;
140 private static final int ORIENTATION_LANDSCAPE = 1;
141
142 private static final int ACTIVITY_REQUEST_CREATE_FILE = 1;
143 private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2;
144 private static final int ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS = 3;
145
146 private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
147
148 private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800149 private static final int DEST_ADAPTER_ITEM_ID_MORE = Integer.MAX_VALUE - 1;
Svetoslava798c0a2014-05-15 10:47:19 -0700150
Svetoslav6552bf32014-09-03 21:15:55 -0700151 private static final int STATE_INITIALIZING = 0;
152 private static final int STATE_CONFIGURING = 1;
153 private static final int STATE_PRINT_CONFIRMED = 2;
154 private static final int STATE_PRINT_CANCELED = 3;
155 private static final int STATE_UPDATE_FAILED = 4;
156 private static final int STATE_CREATE_FILE_FAILED = 5;
157 private static final int STATE_PRINTER_UNAVAILABLE = 6;
158 private static final int STATE_UPDATE_SLOW = 7;
159 private static final int STATE_PRINT_COMPLETED = 8;
Svetoslava798c0a2014-05-15 10:47:19 -0700160
161 private static final int UI_STATE_PREVIEW = 0;
162 private static final int UI_STATE_ERROR = 1;
163 private static final int UI_STATE_PROGRESS = 2;
164
165 private static final int MIN_COPIES = 1;
166 private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES);
167
168 private static final Pattern PATTERN_DIGITS = Pattern.compile("[\\d]+");
169
170 private static final Pattern PATTERN_ESCAPE_SPECIAL_CHARS = Pattern.compile(
171 "(?=[]\\[+&|!(){}^\"~*?:\\\\])");
172
173 private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
Svet Ganov525a66b2014-06-14 22:29:00 -0700174 "[\\s]*[0-9]+[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
175 + "[\\s]*[0-9]+[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
Svetoslava798c0a2014-05-15 10:47:19 -0700176
Philip P. Moltmannc2f913d2016-02-01 12:03:48 -0800177 private boolean mIsOptionsUiBound = false;
178
Svetoslava798c0a2014-05-15 10:47:19 -0700179 private final PrinterAvailabilityDetector mPrinterAvailabilityDetector =
180 new PrinterAvailabilityDetector();
181
182 private final SimpleStringSplitter mStringCommaSplitter = new SimpleStringSplitter(',');
183
184 private final OnFocusChangeListener mSelectAllOnFocusListener = new SelectAllOnFocusListener();
185
186 private PrintSpoolerProvider mSpoolerProvider;
187
Svet Ganov525a66b2014-06-14 22:29:00 -0700188 private PrintPreviewController mPrintPreviewController;
189
Svetoslava798c0a2014-05-15 10:47:19 -0700190 private PrintJobInfo mPrintJob;
191 private RemotePrintDocument mPrintedDocument;
192 private PrinterRegistry mPrinterRegistry;
193
194 private EditText mCopiesEditText;
195
Svetoslava798c0a2014-05-15 10:47:19 -0700196 private TextView mPageRangeTitle;
197 private EditText mPageRangeEditText;
198
199 private Spinner mDestinationSpinner;
200 private DestinationAdapter mDestinationSpinnerAdapter;
Philip P. Moltmann5e548962015-11-13 15:33:40 -0800201 private boolean mShowDestinationPrompt;
Svetoslava798c0a2014-05-15 10:47:19 -0700202
203 private Spinner mMediaSizeSpinner;
204 private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
205
206 private Spinner mColorModeSpinner;
207 private ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
208
Svetoslav948c9a62015-02-02 19:47:04 -0800209 private Spinner mDuplexModeSpinner;
210 private ArrayAdapter<SpinnerItem<Integer>> mDuplexModeSpinnerAdapter;
211
Svetoslava798c0a2014-05-15 10:47:19 -0700212 private Spinner mOrientationSpinner;
213 private ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
214
215 private Spinner mRangeOptionsSpinner;
216
Svet Ganov525a66b2014-06-14 22:29:00 -0700217 private PrintContentView mOptionsContent;
Svetoslava798c0a2014-05-15 10:47:19 -0700218
Svetoslave652b022014-09-09 22:11:10 -0700219 private View mSummaryContainer;
Svetoslava798c0a2014-05-15 10:47:19 -0700220 private TextView mSummaryCopies;
221 private TextView mSummaryPaperSize;
222
Svetoslava798c0a2014-05-15 10:47:19 -0700223 private Button mMoreOptionsButton;
224
225 private ImageView mPrintButton;
226
227 private ProgressMessageController mProgressMessageController;
Svetoslav62ce3322014-09-04 21:17:17 -0700228 private MutexFileProvider mFileProvider;
Svetoslava798c0a2014-05-15 10:47:19 -0700229
230 private MediaSizeComparator mMediaSizeComparator;
231
Svet Ganov48fec5c2014-07-14 00:14:07 -0700232 private PrinterInfo mCurrentPrinter;
Svetoslava798c0a2014-05-15 10:47:19 -0700233
Svet Ganov525a66b2014-06-14 22:29:00 -0700234 private PageRange[] mSelectedPages;
235
Svetoslava798c0a2014-05-15 10:47:19 -0700236 private String mCallingPackageName;
237
Svetoslav73764e32014-07-15 15:56:46 -0700238 private int mCurrentPageCount;
239
Svetoslav6552bf32014-09-03 21:15:55 -0700240 private int mState = STATE_INITIALIZING;
Svetoslava798c0a2014-05-15 10:47:19 -0700241
242 private int mUiState = UI_STATE_PREVIEW;
243
Philip P. Moltmann1bb7f362016-02-26 14:21:20 -0800244 /** Observer for changes to the printers */
245 private PrintersObserver mPrintersObserver;
246
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800247 /** Advances options activity name for current printer */
248 private ComponentName mAdvancedPrintOptionsActivity;
249
250 /** Whether at least one print services is enabled or not */
251 private boolean mArePrintServicesEnabled;
252
Philip P. Moltmannb170c082016-03-21 12:48:58 -0700253 /** Is doFinish() already in progress */
254 private boolean mIsFinishing;
255
Svetoslava798c0a2014-05-15 10:47:19 -0700256 @Override
257 public void onCreate(Bundle savedInstanceState) {
258 super.onCreate(savedInstanceState);
259
260 Bundle extras = getIntent().getExtras();
261
262 mPrintJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB);
263 if (mPrintJob == null) {
264 throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_JOB
265 + " cannot be null");
266 }
Philip P. Moltmannb4efdb42015-11-10 14:58:44 -0800267 if (mPrintJob.getAttributes() == null) {
268 mPrintJob.setAttributes(new PrintAttributes.Builder().build());
269 }
Svetoslava798c0a2014-05-15 10:47:19 -0700270
271 final IBinder adapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
272 if (adapter == null) {
273 throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER
274 + " cannot be null");
275 }
276
277 mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
278
279 // This will take just a few milliseconds, so just wait to
280 // bind to the local service before showing the UI.
281 mSpoolerProvider = new PrintSpoolerProvider(this,
282 new Runnable() {
283 @Override
284 public void run() {
Philip P. Moltmanncf5b7772016-02-01 16:58:57 -0800285 if (isFinishing()) {
286 // onPause might have not been able to cancel the job, see PrintActivity#onPause
287 // To be sure, cancel the job again. Double canceling does no harm.
288 mSpoolerProvider.getSpooler().setPrintJobState(mPrintJob.getId(),
289 PrintJobInfo.STATE_CANCELED, null);
290 } else {
291 onConnectedToPrintSpooler(adapter);
292 }
Svetoslava798c0a2014-05-15 10:47:19 -0700293 }
294 });
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800295
296 getLoaderManager().initLoader(LOADER_ID_ENABLED_PRINT_SERVICES, null, this);
Svetoslava798c0a2014-05-15 10:47:19 -0700297 }
298
Svet Ganov525a66b2014-06-14 22:29:00 -0700299 private void onConnectedToPrintSpooler(final IBinder documentAdapter) {
300 // Now that we are bound to the print spooler service,
301 // create the printer registry and wait for it to get
302 // the first batch of results which will be delivered
303 // after reading historical data. This should be pretty
304 // fast, so just wait before showing the UI.
305 mPrinterRegistry = new PrinterRegistry(PrintActivity.this,
306 new Runnable() {
307 @Override
308 public void run() {
309 onPrinterRegistryReady(documentAdapter);
310 }
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800311 }, LOADER_ID_PRINT_REGISTRY, LOADER_ID_PRINT_REGISTRY_INT);
Svet Ganov525a66b2014-06-14 22:29:00 -0700312 }
313
314 private void onPrinterRegistryReady(IBinder documentAdapter) {
315 // Now that we are bound to the local print spooler service
316 // and the printer registry loaded the historical printers
317 // we can show the UI without flickering.
318 setTitle(R.string.print_dialog);
319 setContentView(R.layout.print_activity);
320
Svet Ganov525a66b2014-06-14 22:29:00 -0700321 try {
Svetoslav62ce3322014-09-04 21:17:17 -0700322 mFileProvider = new MutexFileProvider(
Svet Ganov525a66b2014-06-14 22:29:00 -0700323 PrintSpoolerService.generateFileForPrintJob(
324 PrintActivity.this, mPrintJob.getId()));
325 } catch (IOException ioe) {
326 // At this point we cannot recover, so just take it down.
327 throw new IllegalStateException("Cannot create print job file", ioe);
328 }
329
330 mPrintPreviewController = new PrintPreviewController(PrintActivity.this,
Svetoslav62ce3322014-09-04 21:17:17 -0700331 mFileProvider);
Svet Ganov525a66b2014-06-14 22:29:00 -0700332 mPrintedDocument = new RemotePrintDocument(PrintActivity.this,
333 IPrintDocumentAdapter.Stub.asInterface(documentAdapter),
Svetoslave17123d2014-09-11 12:39:05 -0700334 mFileProvider, new RemotePrintDocument.RemoteAdapterDeathObserver() {
Svet Ganov525a66b2014-06-14 22:29:00 -0700335 @Override
Svetoslave17123d2014-09-11 12:39:05 -0700336 public void onDied() {
Svetoslav05e041b2014-10-14 14:14:49 -0700337 // If we are finishing or we are in a state that we do not need any
338 // data from the printing app, then no need to finish.
339 if (isFinishing() || (isFinalState(mState) && !mPrintedDocument.isUpdating())) {
Svetoslave17123d2014-09-11 12:39:05 -0700340 return;
341 }
Svetoslav62ce3322014-09-04 21:17:17 -0700342 setState(STATE_PRINT_CANCELED);
Philip P. Moltmann645a3e12016-02-25 11:20:41 -0800343 mPrintedDocument.cancel(true);
Svetoslave17123d2014-09-11 12:39:05 -0700344 doFinish();
Svet Ganov525a66b2014-06-14 22:29:00 -0700345 }
346 }, PrintActivity.this);
Svet Ganov525a66b2014-06-14 22:29:00 -0700347 mProgressMessageController = new ProgressMessageController(
348 PrintActivity.this);
Svet Ganov525a66b2014-06-14 22:29:00 -0700349 mMediaSizeComparator = new MediaSizeComparator(PrintActivity.this);
Svet Ganov525a66b2014-06-14 22:29:00 -0700350 mDestinationSpinnerAdapter = new DestinationAdapter();
351
352 bindUi();
Svet Ganov525a66b2014-06-14 22:29:00 -0700353 updateOptionsUi();
354
355 // Now show the updated UI to avoid flicker.
356 mOptionsContent.setVisibility(View.VISIBLE);
Svet Ganov525a66b2014-06-14 22:29:00 -0700357 mSelectedPages = computeSelectedPages();
Svet Ganov525a66b2014-06-14 22:29:00 -0700358 mPrintedDocument.start();
359
360 ensurePreviewUiShown();
Svetoslav6552bf32014-09-03 21:15:55 -0700361
362 setState(STATE_CONFIGURING);
Svet Ganov525a66b2014-06-14 22:29:00 -0700363 }
364
Svetoslava798c0a2014-05-15 10:47:19 -0700365 @Override
Philip P. Moltmanne2b95e42015-11-20 11:53:12 -0800366 public void onStart() {
367 super.onStart();
Philip P. Moltmann51dbc8e2016-02-01 13:56:45 -0800368 if (mPrinterRegistry != null && mCurrentPrinter != null) {
Svetoslavd724a402014-09-16 11:53:15 -0700369 mPrinterRegistry.setTrackedPrinter(mCurrentPrinter.getId());
370 }
Chris Wrendcc34fd2015-07-30 14:27:02 -0400371 MetricsLogger.count(this, "print_preview", 1);
Svetoslavd724a402014-09-16 11:53:15 -0700372 }
373
374 @Override
Svetoslava798c0a2014-05-15 10:47:19 -0700375 public void onPause() {
Svetoslav3ef8e202014-09-10 14:35:58 -0700376 PrintSpoolerService spooler = mSpoolerProvider.getSpooler();
377
Svetoslav6552bf32014-09-03 21:15:55 -0700378 if (mState == STATE_INITIALIZING) {
Svetoslav3ef8e202014-09-10 14:35:58 -0700379 if (isFinishing()) {
Philip P. Moltmanncf5b7772016-02-01 16:58:57 -0800380 if (spooler != null) {
381 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
382 }
Svetoslav3ef8e202014-09-10 14:35:58 -0700383 }
Svetoslav6552bf32014-09-03 21:15:55 -0700384 super.onPause();
385 return;
386 }
387
Svetoslava798c0a2014-05-15 10:47:19 -0700388 if (isFinishing()) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700389 spooler.updatePrintJobUserConfigurableOptionsNoPersistence(mPrintJob);
390
391 switch (mState) {
Svetoslavb59555c2014-07-24 10:13:00 -0700392 case STATE_PRINT_COMPLETED: {
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800393 if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) {
394 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_COMPLETED,
395 null);
396 } else {
397 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_QUEUED,
398 null);
399 }
Svetoslavb59555c2014-07-24 10:13:00 -0700400 } break;
401
Svet Ganov525a66b2014-06-14 22:29:00 -0700402 case STATE_CREATE_FILE_FAILED: {
403 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_FAILED,
404 getString(R.string.print_write_error_message));
405 } break;
406
407 default: {
408 spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
409 } break;
Svetoslava798c0a2014-05-15 10:47:19 -0700410 }
Svetoslava798c0a2014-05-15 10:47:19 -0700411 }
412
Philip P. Moltmanne2b95e42015-11-20 11:53:12 -0800413 super.onPause();
414 }
415
416 @Override
417 protected void onStop() {
Svetoslava798c0a2014-05-15 10:47:19 -0700418 mPrinterAvailabilityDetector.cancel();
Philip P. Moltmann51dbc8e2016-02-01 13:56:45 -0800419
420 if (mPrinterRegistry != null) {
421 mPrinterRegistry.setTrackedPrinter(null);
422 }
Svetoslava798c0a2014-05-15 10:47:19 -0700423
Philip P. Moltmanne2b95e42015-11-20 11:53:12 -0800424 super.onStop();
Svetoslava798c0a2014-05-15 10:47:19 -0700425 }
426
427 @Override
428 public boolean onKeyDown(int keyCode, KeyEvent event) {
429 if (keyCode == KeyEvent.KEYCODE_BACK) {
430 event.startTracking();
431 return true;
432 }
433 return super.onKeyDown(keyCode, event);
434 }
435
436 @Override
437 public boolean onKeyUp(int keyCode, KeyEvent event) {
Svetoslav6552bf32014-09-03 21:15:55 -0700438 if (mState == STATE_INITIALIZING) {
Svetoslave17123d2014-09-11 12:39:05 -0700439 doFinish();
440 return true;
441 }
442
Svet Ganovfce84f02014-10-31 16:56:52 -0700443 if (mState == STATE_PRINT_CANCELED || mState == STATE_PRINT_CONFIRMED
Svetoslave17123d2014-09-11 12:39:05 -0700444 || mState == STATE_PRINT_COMPLETED) {
Svetoslav3ef8e202014-09-10 14:35:58 -0700445 return true;
Svetoslav6552bf32014-09-03 21:15:55 -0700446 }
447
Svetoslava798c0a2014-05-15 10:47:19 -0700448 if (keyCode == KeyEvent.KEYCODE_BACK
449 && event.isTracking() && !event.isCanceled()) {
Svetoslav6552bf32014-09-03 21:15:55 -0700450 if (mPrintPreviewController != null && mPrintPreviewController.isOptionsOpened()
Svetoslav15cbc8a2014-07-11 09:45:07 -0700451 && !hasErrors()) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700452 mPrintPreviewController.closeOptions();
453 } else {
454 cancelPrint();
455 }
Svetoslava798c0a2014-05-15 10:47:19 -0700456 return true;
457 }
458 return super.onKeyUp(keyCode, event);
459 }
460
461 @Override
Svetoslav5ef522b2014-07-23 20:15:09 -0700462 public void onRequestContentUpdate() {
Svet Ganov525a66b2014-06-14 22:29:00 -0700463 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -0700464 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -0700465 }
466 }
467
468 @Override
Svetoslav5ef522b2014-07-23 20:15:09 -0700469 public void onMalformedPdfFile() {
Svet Ganovfce84f02014-10-31 16:56:52 -0700470 onPrintDocumentError("Cannot print a malformed PDF file");
471 }
472
473 @Override
474 public void onSecurePdfFile() {
475 onPrintDocumentError("Cannot print a password protected PDF file");
476 }
477
478 private void onPrintDocumentError(String message) {
Philip P. Moltmann7b92d3c2016-03-31 14:18:47 -0700479 setState(mProgressMessageController.cancel());
Svetoslav5ef522b2014-07-23 20:15:09 -0700480 ensureErrorUiShown(null, PrintErrorFragment.ACTION_RETRY);
481
482 setState(STATE_UPDATE_FAILED);
483
484 updateOptionsUi();
Svet Ganovfce84f02014-10-31 16:56:52 -0700485
486 mPrintedDocument.kill(message);
Svetoslav5ef522b2014-07-23 20:15:09 -0700487 }
488
489 @Override
Svet Ganov525a66b2014-06-14 22:29:00 -0700490 public void onActionPerformed() {
Svetoslav5ef522b2014-07-23 20:15:09 -0700491 if (mState == STATE_UPDATE_FAILED
Svetoslav62ce3322014-09-04 21:17:17 -0700492 && canUpdateDocument() && updateDocument(true)) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700493 ensurePreviewUiShown();
494 setState(STATE_CONFIGURING);
495 updateOptionsUi();
Svetoslava798c0a2014-05-15 10:47:19 -0700496 }
497 }
498
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800499 @Override
Svetoslava798c0a2014-05-15 10:47:19 -0700500 public void onUpdateCanceled() {
Svet Ganov525a66b2014-06-14 22:29:00 -0700501 if (DEBUG) {
502 Log.i(LOG_TAG, "onUpdateCanceled()");
503 }
504
Philip P. Moltmann7b92d3c2016-03-31 14:18:47 -0700505 setState(mProgressMessageController.cancel());
Svetoslava798c0a2014-05-15 10:47:19 -0700506 ensurePreviewUiShown();
Svet Ganov525a66b2014-06-14 22:29:00 -0700507
508 switch (mState) {
509 case STATE_PRINT_CONFIRMED: {
510 requestCreatePdfFileOrFinish();
511 } break;
512
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800513 case STATE_CREATE_FILE_FAILED:
514 case STATE_PRINT_COMPLETED:
Svet Ganov525a66b2014-06-14 22:29:00 -0700515 case STATE_PRINT_CANCELED: {
Svetoslave17123d2014-09-11 12:39:05 -0700516 doFinish();
Svet Ganov525a66b2014-06-14 22:29:00 -0700517 } break;
518 }
Svetoslava798c0a2014-05-15 10:47:19 -0700519 }
520
521 @Override
Svet Ganov525a66b2014-06-14 22:29:00 -0700522 public void onUpdateCompleted(RemotePrintDocumentInfo document) {
523 if (DEBUG) {
524 Log.i(LOG_TAG, "onUpdateCompleted()");
525 }
526
Philip P. Moltmann7b92d3c2016-03-31 14:18:47 -0700527 setState(mProgressMessageController.cancel());
Svetoslava798c0a2014-05-15 10:47:19 -0700528 ensurePreviewUiShown();
529
530 // Update the print job with the info for the written document. The page
531 // count we get from the remote document is the pages in the document from
532 // the app perspective but the print job should contain the page count from
533 // print service perspective which is the pages in the written PDF not the
534 // pages in the printed document.
535 PrintDocumentInfo info = document.info;
Svet Ganov525a66b2014-06-14 22:29:00 -0700536 if (info != null) {
537 final int pageCount = PageRangeUtils.getNormalizedPageCount(document.writtenPages,
538 getAdjustedPageCount(info));
539 PrintDocumentInfo adjustedInfo = new PrintDocumentInfo.Builder(info.getName())
540 .setContentType(info.getContentType())
541 .setPageCount(pageCount)
542 .build();
543 mPrintJob.setDocumentInfo(adjustedInfo);
544 mPrintJob.setPages(document.printedPages);
Svetoslava798c0a2014-05-15 10:47:19 -0700545 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700546
547 switch (mState) {
548 case STATE_PRINT_CONFIRMED: {
549 requestCreatePdfFileOrFinish();
550 } break;
551
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800552 case STATE_CREATE_FILE_FAILED:
553 case STATE_PRINT_COMPLETED:
Svet Ganoveaaf0512014-11-26 04:09:27 -0800554 case STATE_PRINT_CANCELED: {
555 updateOptionsUi();
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800556
557 doFinish();
Svet Ganoveaaf0512014-11-26 04:09:27 -0800558 } break;
559
Svet Ganov525a66b2014-06-14 22:29:00 -0700560 default: {
561 updatePrintPreviewController(document.changed);
562
563 setState(STATE_CONFIGURING);
564 updateOptionsUi();
565 } break;
566 }
Svetoslava798c0a2014-05-15 10:47:19 -0700567 }
568
569 @Override
570 public void onUpdateFailed(CharSequence error) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700571 if (DEBUG) {
572 Log.i(LOG_TAG, "onUpdateFailed()");
573 }
574
Philip P. Moltmann7b92d3c2016-03-31 14:18:47 -0700575 setState(mProgressMessageController.cancel());
Svetoslava798c0a2014-05-15 10:47:19 -0700576 ensureErrorUiShown(error, PrintErrorFragment.ACTION_RETRY);
Svet Ganov525a66b2014-06-14 22:29:00 -0700577
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800578 if (mState == STATE_CREATE_FILE_FAILED
579 || mState == STATE_PRINT_COMPLETED
580 || mState == STATE_PRINT_CANCELED) {
581 doFinish();
582 }
583
Svet Ganov525a66b2014-06-14 22:29:00 -0700584 setState(STATE_UPDATE_FAILED);
585
Svetoslava798c0a2014-05-15 10:47:19 -0700586 updateOptionsUi();
587 }
588
589 @Override
Svet Ganov525a66b2014-06-14 22:29:00 -0700590 public void onOptionsOpened() {
591 updateSelectedPagesFromPreview();
592 }
593
594 @Override
595 public void onOptionsClosed() {
Svet Ganov525a66b2014-06-14 22:29:00 -0700596 // Make sure the IME is not on the way of preview as
597 // the user may have used it to type copies or range.
Yohei Yukawa777ef952015-11-25 20:32:24 -0800598 InputMethodManager imm = getSystemService(InputMethodManager.class);
Svet Ganov525a66b2014-06-14 22:29:00 -0700599 imm.hideSoftInputFromWindow(mDestinationSpinner.getWindowToken(), 0);
600 }
601
602 private void updatePrintPreviewController(boolean contentUpdated) {
603 // If we have not heard from the application, do nothing.
604 RemotePrintDocumentInfo documentInfo = mPrintedDocument.getDocumentInfo();
605 if (!documentInfo.laidout) {
606 return;
607 }
608
609 // Update the preview controller.
610 mPrintPreviewController.onContentUpdated(contentUpdated,
611 getAdjustedPageCount(documentInfo.info),
612 mPrintedDocument.getDocumentInfo().writtenPages,
613 mSelectedPages, mPrintJob.getAttributes().getMediaSize(),
614 mPrintJob.getAttributes().getMinMargins());
615 }
616
617
618 @Override
619 public boolean canOpenOptions() {
620 return true;
621 }
622
623 @Override
624 public boolean canCloseOptions() {
625 return !hasErrors();
626 }
627
628 @Override
629 public void onConfigurationChanged(Configuration newConfig) {
630 super.onConfigurationChanged(newConfig);
Svet Ganovf6cd14d2014-11-20 07:43:30 -0800631 if (mPrintPreviewController != null) {
632 mPrintPreviewController.onOrientationChanged();
633 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700634 }
635
636 @Override
Svetoslava798c0a2014-05-15 10:47:19 -0700637 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
638 switch (requestCode) {
639 case ACTIVITY_REQUEST_CREATE_FILE: {
640 onStartCreateDocumentActivityResult(resultCode, data);
Svetoslav5ef522b2014-07-23 20:15:09 -0700641 } break;
Svetoslava798c0a2014-05-15 10:47:19 -0700642
643 case ACTIVITY_REQUEST_SELECT_PRINTER: {
644 onSelectPrinterActivityResult(resultCode, data);
Svetoslav5ef522b2014-07-23 20:15:09 -0700645 } break;
Svetoslava798c0a2014-05-15 10:47:19 -0700646
647 case ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS: {
648 onAdvancedPrintOptionsActivityResult(resultCode, data);
Svetoslav5ef522b2014-07-23 20:15:09 -0700649 } break;
Svetoslava798c0a2014-05-15 10:47:19 -0700650 }
651 }
652
653 private void startCreateDocumentActivity() {
Svetoslave1dcb392014-09-26 19:49:14 -0700654 if (!isResumed()) {
655 return;
656 }
Svetoslava798c0a2014-05-15 10:47:19 -0700657 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
658 if (info == null) {
659 return;
660 }
661 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
662 intent.setType("application/pdf");
663 intent.putExtra(Intent.EXTRA_TITLE, info.getName());
664 intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
665 startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
666 }
667
668 private void onStartCreateDocumentActivityResult(int resultCode, Intent data) {
669 if (resultCode == RESULT_OK && data != null) {
Svetoslavb59555c2014-07-24 10:13:00 -0700670 updateOptionsUi();
Svetoslav62ce3322014-09-04 21:17:17 -0700671 final Uri uri = data.getData();
Svet Ganov525a66b2014-06-14 22:29:00 -0700672 // Calling finish here does not invoke lifecycle callbacks but we
673 // update the print job in onPause if finishing, hence post a message.
674 mDestinationSpinner.post(new Runnable() {
675 @Override
676 public void run() {
Svetoslavbec22be2014-09-25 13:03:20 -0700677 transformDocumentAndFinish(uri);
Svet Ganov525a66b2014-06-14 22:29:00 -0700678 }
679 });
Svetoslava798c0a2014-05-15 10:47:19 -0700680 } else if (resultCode == RESULT_CANCELED) {
Svetoslavb75632c2014-09-17 18:38:27 -0700681 mState = STATE_CONFIGURING;
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800682
683 // The previous update might have been canceled
684 updateDocument(false);
685
Svetoslava798c0a2014-05-15 10:47:19 -0700686 updateOptionsUi();
687 } else {
Svet Ganov525a66b2014-06-14 22:29:00 -0700688 setState(STATE_CREATE_FILE_FAILED);
Svetoslava798c0a2014-05-15 10:47:19 -0700689 updateOptionsUi();
Svet Ganov525a66b2014-06-14 22:29:00 -0700690 // Calling finish here does not invoke lifecycle callbacks but we
691 // update the print job in onPause if finishing, hence post a message.
692 mDestinationSpinner.post(new Runnable() {
693 @Override
694 public void run() {
Svetoslave17123d2014-09-11 12:39:05 -0700695 doFinish();
Svet Ganov525a66b2014-06-14 22:29:00 -0700696 }
697 });
Svetoslava798c0a2014-05-15 10:47:19 -0700698 }
699 }
700
701 private void startSelectPrinterActivity() {
702 Intent intent = new Intent(this, SelectPrinterActivity.class);
703 startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER);
704 }
705
706 private void onSelectPrinterActivityResult(int resultCode, Intent data) {
707 if (resultCode == RESULT_OK && data != null) {
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -0800708 PrinterInfo printerInfo = data.getParcelableExtra(
709 SelectPrinterActivity.INTENT_EXTRA_PRINTER);
710 if (printerInfo != null) {
711 mCurrentPrinter = printerInfo;
712 mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerInfo);
Svetoslava798c0a2014-05-15 10:47:19 -0700713 }
714 }
715
Masaaki Iwaguchi86d13f32015-04-03 16:11:39 +0900716 if (mCurrentPrinter != null) {
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -0800717 // Trigger PrintersObserver.onChanged() to adjust selection back to current printer
718 mDestinationSpinnerAdapter.notifyDataSetChanged();
Masaaki Iwaguchi86d13f32015-04-03 16:11:39 +0900719 }
Svetoslava798c0a2014-05-15 10:47:19 -0700720 }
721
722 private void startAdvancedPrintOptionsActivity(PrinterInfo printer) {
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800723 if (mAdvancedPrintOptionsActivity == null) {
Svetoslava798c0a2014-05-15 10:47:19 -0700724 return;
725 }
726
727 Intent intent = new Intent(Intent.ACTION_MAIN);
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800728 intent.setComponent(mAdvancedPrintOptionsActivity);
Svetoslava798c0a2014-05-15 10:47:19 -0700729
730 List<ResolveInfo> resolvedActivities = getPackageManager()
731 .queryIntentActivities(intent, 0);
732 if (resolvedActivities.isEmpty()) {
733 return;
734 }
735
736 // The activity is a component name, therefore it is one or none.
737 if (resolvedActivities.get(0).activityInfo.exported) {
Philip P. Moltmannd365f692016-02-29 13:06:14 -0800738 PrintJobInfo.Builder printJobBuilder = new PrintJobInfo.Builder(mPrintJob);
739 printJobBuilder.setPages(mSelectedPages);
740
741 intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, printJobBuilder.build());
Svetoslava798c0a2014-05-15 10:47:19 -0700742 intent.putExtra(PrintService.EXTRA_PRINTER_INFO, printer);
Svet Ganovf12b3932015-07-25 12:10:54 -0700743 intent.putExtra(PrintService.EXTRA_PRINT_DOCUMENT_INFO,
Svet Ganov5772b5c2015-06-11 02:46:45 -0700744 mPrintedDocument.getDocumentInfo().info);
Svetoslava798c0a2014-05-15 10:47:19 -0700745
746 // This is external activity and may not be there.
747 try {
748 startActivityForResult(intent, ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS);
749 } catch (ActivityNotFoundException anfe) {
750 Log.e(LOG_TAG, "Error starting activity for intent: " + intent, anfe);
751 }
752 }
753 }
754
755 private void onAdvancedPrintOptionsActivityResult(int resultCode, Intent data) {
756 if (resultCode != RESULT_OK || data == null) {
757 return;
758 }
759
760 PrintJobInfo printJobInfo = data.getParcelableExtra(PrintService.EXTRA_PRINT_JOB_INFO);
761
762 if (printJobInfo == null) {
763 return;
764 }
765
766 // Take the advanced options without interpretation.
767 mPrintJob.setAdvancedOptions(printJobInfo.getAdvancedOptions());
768
Philip P. Moltmannd365f692016-02-29 13:06:14 -0800769 if (printJobInfo.getCopies() < 1) {
770 Log.w(LOG_TAG, "Cannot apply return value from advanced options activity. Copies " +
771 "must be 1 or more. Actual value is: " + printJobInfo.getCopies() + ". " +
772 "Ignoring.");
773 } else {
774 mCopiesEditText.setText(String.valueOf(printJobInfo.getCopies()));
775 mPrintJob.setCopies(printJobInfo.getCopies());
776 }
Svetoslava798c0a2014-05-15 10:47:19 -0700777
778 PrintAttributes currAttributes = mPrintJob.getAttributes();
779 PrintAttributes newAttributes = printJobInfo.getAttributes();
780
Svet Ganov2eb7fad2014-10-01 17:49:16 -0700781 if (newAttributes != null) {
782 // Take the media size only if the current printer supports is.
783 MediaSize oldMediaSize = currAttributes.getMediaSize();
784 MediaSize newMediaSize = newAttributes.getMediaSize();
Philip P. Moltmannd365f692016-02-29 13:06:14 -0800785 if (newMediaSize != null && !oldMediaSize.equals(newMediaSize)) {
Svet Ganov2eb7fad2014-10-01 17:49:16 -0700786 final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount();
787 MediaSize newMediaSizePortrait = newAttributes.getMediaSize().asPortrait();
788 for (int i = 0; i < mediaSizeCount; i++) {
789 MediaSize supportedSizePortrait = mMediaSizeSpinnerAdapter.getItem(i)
790 .value.asPortrait();
791 if (supportedSizePortrait.equals(newMediaSizePortrait)) {
792 currAttributes.setMediaSize(newMediaSize);
793 mMediaSizeSpinner.setSelection(i);
794 if (currAttributes.getMediaSize().isPortrait()) {
795 if (mOrientationSpinner.getSelectedItemPosition() != 0) {
796 mOrientationSpinner.setSelection(0);
797 }
798 } else {
799 if (mOrientationSpinner.getSelectedItemPosition() != 1) {
800 mOrientationSpinner.setSelection(1);
801 }
Svetoslava798c0a2014-05-15 10:47:19 -0700802 }
Svet Ganov2eb7fad2014-10-01 17:49:16 -0700803 break;
Svetoslava798c0a2014-05-15 10:47:19 -0700804 }
Svetoslava798c0a2014-05-15 10:47:19 -0700805 }
806 }
Svetoslava798c0a2014-05-15 10:47:19 -0700807
Svet Ganov2eb7fad2014-10-01 17:49:16 -0700808 // Take the resolution only if the current printer supports is.
809 Resolution oldResolution = currAttributes.getResolution();
810 Resolution newResolution = newAttributes.getResolution();
811 if (!oldResolution.equals(newResolution)) {
812 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
813 if (capabilities != null) {
814 List<Resolution> resolutions = capabilities.getResolutions();
815 final int resolutionCount = resolutions.size();
816 for (int i = 0; i < resolutionCount; i++) {
817 Resolution resolution = resolutions.get(i);
818 if (resolution.equals(newResolution)) {
819 currAttributes.setResolution(resolution);
820 break;
821 }
822 }
823 }
824 }
825
826 // Take the color mode only if the current printer supports it.
827 final int currColorMode = currAttributes.getColorMode();
828 final int newColorMode = newAttributes.getColorMode();
829 if (currColorMode != newColorMode) {
830 final int colorModeCount = mColorModeSpinner.getCount();
831 for (int i = 0; i < colorModeCount; i++) {
832 final int supportedColorMode = mColorModeSpinnerAdapter.getItem(i).value;
833 if (supportedColorMode == newColorMode) {
834 currAttributes.setColorMode(newColorMode);
835 mColorModeSpinner.setSelection(i);
836 break;
837 }
Svetoslava798c0a2014-05-15 10:47:19 -0700838 }
839 }
Svetoslav948c9a62015-02-02 19:47:04 -0800840
841 // Take the duplex mode only if the current printer supports it.
842 final int currDuplexMode = currAttributes.getDuplexMode();
843 final int newDuplexMode = newAttributes.getDuplexMode();
844 if (currDuplexMode != newDuplexMode) {
845 final int duplexModeCount = mDuplexModeSpinner.getCount();
846 for (int i = 0; i < duplexModeCount; i++) {
847 final int supportedDuplexMode = mDuplexModeSpinnerAdapter.getItem(i).value;
848 if (supportedDuplexMode == newDuplexMode) {
849 currAttributes.setDuplexMode(newDuplexMode);
850 mDuplexModeSpinner.setSelection(i);
851 break;
852 }
853 }
854 }
Svetoslava798c0a2014-05-15 10:47:19 -0700855 }
856
Svetoslav528424c2014-09-26 19:11:29 -0700857 // Handle selected page changes making sure they are in the doc.
Svet Ganov525a66b2014-06-14 22:29:00 -0700858 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
859 final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0;
Svetoslava798c0a2014-05-15 10:47:19 -0700860 PageRange[] pageRanges = printJobInfo.getPages();
Svetoslav528424c2014-09-26 19:11:29 -0700861 if (pageRanges != null && pageCount > 0) {
862 pageRanges = PageRangeUtils.normalize(pageRanges);
863
864 List<PageRange> validatedList = new ArrayList<>();
865 final int rangeCount = pageRanges.length;
866 for (int i = 0; i < rangeCount; i++) {
867 PageRange pageRange = pageRanges[i];
868 if (pageRange.getEnd() >= pageCount) {
869 final int rangeStart = pageRange.getStart();
870 final int rangeEnd = pageCount - 1;
871 if (rangeStart <= rangeEnd) {
872 pageRange = new PageRange(rangeStart, rangeEnd);
873 validatedList.add(pageRange);
874 }
875 break;
876 }
877 validatedList.add(pageRange);
878 }
879
880 if (!validatedList.isEmpty()) {
881 PageRange[] validatedArray = new PageRange[validatedList.size()];
882 validatedList.toArray(validatedArray);
883 updateSelectedPages(validatedArray, pageCount);
884 }
885 }
Svetoslava798c0a2014-05-15 10:47:19 -0700886
887 // Update the content if needed.
888 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -0700889 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -0700890 }
891 }
892
Svet Ganov525a66b2014-06-14 22:29:00 -0700893 private void setState(int state) {
894 if (isFinalState(mState)) {
895 if (isFinalState(state)) {
896 mState = state;
897 }
898 } else {
899 mState = state;
900 }
901 }
902
903 private static boolean isFinalState(int state) {
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -0800904 return state == STATE_PRINT_CANCELED
905 || state == STATE_PRINT_COMPLETED
906 || state == STATE_CREATE_FILE_FAILED;
Svet Ganov525a66b2014-06-14 22:29:00 -0700907 }
908
909 private void updateSelectedPagesFromPreview() {
910 PageRange[] selectedPages = mPrintPreviewController.getSelectedPages();
911 if (!Arrays.equals(mSelectedPages, selectedPages)) {
912 updateSelectedPages(selectedPages,
913 getAdjustedPageCount(mPrintedDocument.getDocumentInfo().info));
914 }
915 }
916
917 private void updateSelectedPages(PageRange[] selectedPages, int pageInDocumentCount) {
918 if (selectedPages == null || selectedPages.length <= 0) {
919 return;
920 }
921
922 selectedPages = PageRangeUtils.normalize(selectedPages);
923
924 // Handle the case where all pages are specified explicitly
925 // instead of the *all pages* constant.
926 if (PageRangeUtils.isAllPages(selectedPages, pageInDocumentCount)) {
927 selectedPages = new PageRange[] {PageRange.ALL_PAGES};
928 }
929
930 if (Arrays.equals(mSelectedPages, selectedPages)) {
931 return;
932 }
933
934 mSelectedPages = selectedPages;
935 mPrintJob.setPages(selectedPages);
936
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -0700937 if (Arrays.equals(selectedPages, PageRange.ALL_PAGES_ARRAY)) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700938 if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
939 mRangeOptionsSpinner.setSelection(0);
940 mPageRangeEditText.setText("");
941 }
942 } else if (selectedPages[0].getStart() >= 0
943 && selectedPages[selectedPages.length - 1].getEnd() < pageInDocumentCount) {
944 if (mRangeOptionsSpinner.getSelectedItemPosition() != 1) {
945 mRangeOptionsSpinner.setSelection(1);
946 }
947
948 StringBuilder builder = new StringBuilder();
949 final int pageRangeCount = selectedPages.length;
950 for (int i = 0; i < pageRangeCount; i++) {
951 if (builder.length() > 0) {
952 builder.append(',');
953 }
954
955 final int shownStartPage;
956 final int shownEndPage;
957 PageRange pageRange = selectedPages[i];
958 if (pageRange.equals(PageRange.ALL_PAGES)) {
959 shownStartPage = 1;
960 shownEndPage = pageInDocumentCount;
961 } else {
962 shownStartPage = pageRange.getStart() + 1;
963 shownEndPage = pageRange.getEnd() + 1;
964 }
965
966 builder.append(shownStartPage);
967
968 if (shownStartPage != shownEndPage) {
969 builder.append('-');
970 builder.append(shownEndPage);
971 }
972 }
973
974 mPageRangeEditText.setText(builder.toString());
975 }
976 }
977
Svetoslava798c0a2014-05-15 10:47:19 -0700978 private void ensureProgressUiShown() {
Svetoslav23d33612014-09-16 10:50:52 -0700979 if (isFinishing()) {
980 return;
981 }
Svetoslava798c0a2014-05-15 10:47:19 -0700982 if (mUiState != UI_STATE_PROGRESS) {
983 mUiState = UI_STATE_PROGRESS;
Svet Ganov525a66b2014-06-14 22:29:00 -0700984 mPrintPreviewController.setUiShown(false);
Svetoslava798c0a2014-05-15 10:47:19 -0700985 Fragment fragment = PrintProgressFragment.newInstance();
986 showFragment(fragment);
987 }
988 }
989
990 private void ensurePreviewUiShown() {
Svetoslav23d33612014-09-16 10:50:52 -0700991 if (isFinishing()) {
992 return;
993 }
Svetoslava798c0a2014-05-15 10:47:19 -0700994 if (mUiState != UI_STATE_PREVIEW) {
995 mUiState = UI_STATE_PREVIEW;
Svet Ganov525a66b2014-06-14 22:29:00 -0700996 mPrintPreviewController.setUiShown(true);
997 showFragment(null);
Svetoslava798c0a2014-05-15 10:47:19 -0700998 }
999 }
1000
1001 private void ensureErrorUiShown(CharSequence message, int action) {
Svetoslav23d33612014-09-16 10:50:52 -07001002 if (isFinishing()) {
1003 return;
1004 }
Svetoslava798c0a2014-05-15 10:47:19 -07001005 if (mUiState != UI_STATE_ERROR) {
1006 mUiState = UI_STATE_ERROR;
Svet Ganov525a66b2014-06-14 22:29:00 -07001007 mPrintPreviewController.setUiShown(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001008 Fragment fragment = PrintErrorFragment.newInstance(message, action);
1009 showFragment(fragment);
1010 }
1011 }
1012
Svet Ganov525a66b2014-06-14 22:29:00 -07001013 private void showFragment(Fragment newFragment) {
Svetoslava798c0a2014-05-15 10:47:19 -07001014 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Svet Ganov525a66b2014-06-14 22:29:00 -07001015 Fragment oldFragment = getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
Svetoslava798c0a2014-05-15 10:47:19 -07001016 if (oldFragment != null) {
1017 transaction.remove(oldFragment);
1018 }
Svet Ganov525a66b2014-06-14 22:29:00 -07001019 if (newFragment != null) {
1020 transaction.add(R.id.embedded_content_container, newFragment, FRAGMENT_TAG);
1021 }
Svetoslavd25ddc32015-11-24 12:54:18 -08001022 transaction.commitAllowingStateLoss();
Svet Ganov525a66b2014-06-14 22:29:00 -07001023 getFragmentManager().executePendingTransactions();
Svetoslava798c0a2014-05-15 10:47:19 -07001024 }
1025
1026 private void requestCreatePdfFileOrFinish() {
Philip P. Moltmann645a3e12016-02-25 11:20:41 -08001027 mPrintedDocument.cancel(false);
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -08001028
Svet Ganov48fec5c2014-07-14 00:14:07 -07001029 if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) {
Svetoslava798c0a2014-05-15 10:47:19 -07001030 startCreateDocumentActivity();
1031 } else {
Svetoslavbec22be2014-09-25 13:03:20 -07001032 transformDocumentAndFinish(null);
Svetoslava798c0a2014-05-15 10:47:19 -07001033 }
1034 }
1035
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07001036 /**
1037 * Clear the selected page range and update the preview if needed.
1038 */
1039 private void clearPageRanges() {
1040 mRangeOptionsSpinner.setSelection(0);
1041 mPageRangeEditText.setError(null);
1042 mPageRangeEditText.setText("");
1043 mSelectedPages = PageRange.ALL_PAGES_ARRAY;
1044
1045 if (!Arrays.equals(mSelectedPages, mPrintPreviewController.getSelectedPages())) {
1046 updatePrintPreviewController(false);
1047 }
1048 }
1049
Svetoslava798c0a2014-05-15 10:47:19 -07001050 private void updatePrintAttributesFromCapabilities(PrinterCapabilitiesInfo capabilities) {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07001051 boolean clearRanges = false;
Svetoslava798c0a2014-05-15 10:47:19 -07001052 PrintAttributes defaults = capabilities.getDefaults();
1053
1054 // Sort the media sizes based on the current locale.
1055 List<MediaSize> sortedMediaSizes = new ArrayList<>(capabilities.getMediaSizes());
1056 Collections.sort(sortedMediaSizes, mMediaSizeComparator);
1057
1058 PrintAttributes attributes = mPrintJob.getAttributes();
1059
1060 // Media size.
1061 MediaSize currMediaSize = attributes.getMediaSize();
1062 if (currMediaSize == null) {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07001063 clearRanges = true;
Svetoslava798c0a2014-05-15 10:47:19 -07001064 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) {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07001082 clearRanges = true;
Philip P. Moltmanndc5765e2016-01-22 09:39:35 -08001083 newMediaSize = defaults.getMediaSize();
1084 }
1085
1086 if (newMediaSize != null) {
1087 if (isPortrait) {
1088 attributes.setMediaSize(newMediaSize.asPortrait());
1089 } else {
1090 attributes.setMediaSize(newMediaSize.asLandscape());
1091 }
Svetoslava798c0a2014-05-15 10:47:19 -07001092 }
1093 }
1094
1095 // Color mode.
1096 final int colorMode = attributes.getColorMode();
1097 if ((capabilities.getColorModes() & colorMode) == 0) {
1098 attributes.setColorMode(defaults.getColorMode());
1099 }
1100
Svetoslav948c9a62015-02-02 19:47:04 -08001101 // Duplex mode.
1102 final int duplexMode = attributes.getDuplexMode();
1103 if ((capabilities.getDuplexModes() & duplexMode) == 0) {
1104 attributes.setDuplexMode(defaults.getDuplexMode());
1105 }
1106
Svetoslava798c0a2014-05-15 10:47:19 -07001107 // Resolution
1108 Resolution resolution = attributes.getResolution();
1109 if (resolution == null || !capabilities.getResolutions().contains(resolution)) {
1110 attributes.setResolution(defaults.getResolution());
1111 }
1112
1113 // Margins.
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07001114 if (!Objects.equals(attributes.getMinMargins(), defaults.getMinMargins())) {
1115 clearRanges = true;
1116 }
Svetoslava798c0a2014-05-15 10:47:19 -07001117 attributes.setMinMargins(defaults.getMinMargins());
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07001118
1119 if (clearRanges) {
1120 clearPageRanges();
1121 }
Svetoslava798c0a2014-05-15 10:47:19 -07001122 }
1123
Svetoslav62ce3322014-09-04 21:17:17 -07001124 private boolean updateDocument(boolean clearLastError) {
Svetoslava798c0a2014-05-15 10:47:19 -07001125 if (!clearLastError && mPrintedDocument.hasUpdateError()) {
1126 return false;
1127 }
1128
1129 if (clearLastError && mPrintedDocument.hasUpdateError()) {
1130 mPrintedDocument.clearUpdateError();
1131 }
1132
Svetoslav62ce3322014-09-04 21:17:17 -07001133 final boolean preview = mState != STATE_PRINT_CONFIRMED;
Svet Ganov525a66b2014-06-14 22:29:00 -07001134 final PageRange[] pages;
1135 if (preview) {
1136 pages = mPrintPreviewController.getRequestedPages();
1137 } else {
1138 pages = mPrintPreviewController.getSelectedPages();
1139 }
Svetoslava798c0a2014-05-15 10:47:19 -07001140
Svet Ganov525a66b2014-06-14 22:29:00 -07001141 final boolean willUpdate = mPrintedDocument.update(mPrintJob.getAttributes(),
1142 pages, preview);
1143
Svetoslav6552bf32014-09-03 21:15:55 -07001144 if (willUpdate && !mPrintedDocument.hasLaidOutPages()) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001145 // When the update is done we update the print preview.
1146 mProgressMessageController.post();
1147 return true;
Svetoslav7fd5ada2014-09-16 14:41:17 -07001148 } else if (!willUpdate) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001149 // Update preview.
1150 updatePrintPreviewController(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001151 }
1152
1153 return false;
1154 }
1155
1156 private void addCurrentPrinterToHistory() {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001157 if (mCurrentPrinter != null) {
Svetoslava798c0a2014-05-15 10:47:19 -07001158 PrinterId fakePdfPrinterId = mDestinationSpinnerAdapter.getPdfPrinter().getId();
Svet Ganov48fec5c2014-07-14 00:14:07 -07001159 if (!mCurrentPrinter.getId().equals(fakePdfPrinterId)) {
1160 mPrinterRegistry.addHistoricalPrinter(mCurrentPrinter);
Svetoslava798c0a2014-05-15 10:47:19 -07001161 }
1162 }
1163 }
1164
Svetoslava798c0a2014-05-15 10:47:19 -07001165 private void cancelPrint() {
Svet Ganov525a66b2014-06-14 22:29:00 -07001166 setState(STATE_PRINT_CANCELED);
Svetoslava798c0a2014-05-15 10:47:19 -07001167 updateOptionsUi();
Philip P. Moltmann645a3e12016-02-25 11:20:41 -08001168 mPrintedDocument.cancel(true);
Svetoslave17123d2014-09-11 12:39:05 -07001169 doFinish();
Svetoslava798c0a2014-05-15 10:47:19 -07001170 }
1171
Philip P. Moltmannc309d712016-03-31 13:49:38 -07001172 /**
1173 * Update the selected pages from the text field.
1174 */
1175 private void updateSelectedPagesFromTextField() {
1176 PageRange[] selectedPages = computeSelectedPages();
1177 if (!Arrays.equals(mSelectedPages, selectedPages)) {
1178 mSelectedPages = selectedPages;
1179 // Update preview.
1180 updatePrintPreviewController(false);
1181 }
1182 }
1183
Svetoslava798c0a2014-05-15 10:47:19 -07001184 private void confirmPrint() {
Svet Ganov525a66b2014-06-14 22:29:00 -07001185 setState(STATE_PRINT_CONFIRMED);
1186
Chris Wrendcc34fd2015-07-30 14:27:02 -04001187 MetricsLogger.count(this, "print_confirmed", 1);
1188
Svetoslava798c0a2014-05-15 10:47:19 -07001189 updateOptionsUi();
Svet Ganov525a66b2014-06-14 22:29:00 -07001190 addCurrentPrinterToHistory();
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001191 setUserPrinted();
Svet Ganov525a66b2014-06-14 22:29:00 -07001192
Philip P. Moltmannc309d712016-03-31 13:49:38 -07001193 // updateSelectedPagesFromTextField migth update the preview, hence apply the preview first
Svet Ganov525a66b2014-06-14 22:29:00 -07001194 updateSelectedPagesFromPreview();
Philip P. Moltmannc309d712016-03-31 13:49:38 -07001195 updateSelectedPagesFromTextField();
1196
Svet Ganov525a66b2014-06-14 22:29:00 -07001197 mPrintPreviewController.closeOptions();
1198
Svetoslava798c0a2014-05-15 10:47:19 -07001199 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07001200 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001201 }
Svet Ganov525a66b2014-06-14 22:29:00 -07001202
Svetoslava798c0a2014-05-15 10:47:19 -07001203 if (!mPrintedDocument.isUpdating()) {
1204 requestCreatePdfFileOrFinish();
1205 }
1206 }
1207
1208 private void bindUi() {
1209 // Summary
Svetoslave652b022014-09-09 22:11:10 -07001210 mSummaryContainer = findViewById(R.id.summary_content);
Svetoslava798c0a2014-05-15 10:47:19 -07001211 mSummaryCopies = (TextView) findViewById(R.id.copies_count_summary);
1212 mSummaryPaperSize = (TextView) findViewById(R.id.paper_size_summary);
1213
1214 // Options container
Svet Ganov525a66b2014-06-14 22:29:00 -07001215 mOptionsContent = (PrintContentView) findViewById(R.id.options_content);
1216 mOptionsContent.setOptionsStateChangeListener(this);
1217 mOptionsContent.setOpenOptionsController(this);
Svetoslava798c0a2014-05-15 10:47:19 -07001218
1219 OnItemSelectedListener itemSelectedListener = new MyOnItemSelectedListener();
1220 OnClickListener clickListener = new MyClickListener();
1221
1222 // Copies
1223 mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
1224 mCopiesEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
1225 mCopiesEditText.setText(MIN_COPIES_STRING);
1226 mCopiesEditText.setSelection(mCopiesEditText.getText().length());
1227 mCopiesEditText.addTextChangedListener(new EditTextWatcher());
1228
1229 // Destination.
Philip P. Moltmann1bb7f362016-02-26 14:21:20 -08001230 mPrintersObserver = new PrintersObserver();
1231 mDestinationSpinnerAdapter.registerDataSetObserver(mPrintersObserver);
Svetoslava798c0a2014-05-15 10:47:19 -07001232 mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
1233 mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
1234 mDestinationSpinner.setOnItemSelectedListener(itemSelectedListener);
Svetoslava798c0a2014-05-15 10:47:19 -07001235
1236 // Media size.
1237 mMediaSizeSpinnerAdapter = new ArrayAdapter<>(
Svetoslavc404cac2014-08-27 18:37:16 -07001238 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
Svetoslava798c0a2014-05-15 10:47:19 -07001239 mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
1240 mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
1241 mMediaSizeSpinner.setOnItemSelectedListener(itemSelectedListener);
1242
1243 // Color mode.
1244 mColorModeSpinnerAdapter = new ArrayAdapter<>(
Svetoslavc404cac2014-08-27 18:37:16 -07001245 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
Svetoslava798c0a2014-05-15 10:47:19 -07001246 mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
1247 mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
1248 mColorModeSpinner.setOnItemSelectedListener(itemSelectedListener);
1249
Svetoslav948c9a62015-02-02 19:47:04 -08001250 // Duplex mode.
1251 mDuplexModeSpinnerAdapter = new ArrayAdapter<>(
1252 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
1253 mDuplexModeSpinner = (Spinner) findViewById(R.id.duplex_spinner);
1254 mDuplexModeSpinner.setAdapter(mDuplexModeSpinnerAdapter);
1255 mDuplexModeSpinner.setOnItemSelectedListener(itemSelectedListener);
1256
Svetoslava798c0a2014-05-15 10:47:19 -07001257 // Orientation
1258 mOrientationSpinnerAdapter = new ArrayAdapter<>(
Svetoslavc404cac2014-08-27 18:37:16 -07001259 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
Svetoslava798c0a2014-05-15 10:47:19 -07001260 String[] orientationLabels = getResources().getStringArray(
Svet Ganov525a66b2014-06-14 22:29:00 -07001261 R.array.orientation_labels);
Svetoslava798c0a2014-05-15 10:47:19 -07001262 mOrientationSpinnerAdapter.add(new SpinnerItem<>(
1263 ORIENTATION_PORTRAIT, orientationLabels[0]));
1264 mOrientationSpinnerAdapter.add(new SpinnerItem<>(
1265 ORIENTATION_LANDSCAPE, orientationLabels[1]));
1266 mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
1267 mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
1268 mOrientationSpinner.setOnItemSelectedListener(itemSelectedListener);
1269
1270 // Range options
Svetoslavc404cac2014-08-27 18:37:16 -07001271 ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter = new ArrayAdapter<>(
1272 this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
Svetoslava798c0a2014-05-15 10:47:19 -07001273 mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
1274 mRangeOptionsSpinner.setAdapter(rangeOptionsSpinnerAdapter);
1275 mRangeOptionsSpinner.setOnItemSelectedListener(itemSelectedListener);
Svetoslav73764e32014-07-15 15:56:46 -07001276 updatePageRangeOptions(PrintDocumentInfo.PAGE_COUNT_UNKNOWN);
Svetoslava798c0a2014-05-15 10:47:19 -07001277
1278 // Page range
1279 mPageRangeTitle = (TextView) findViewById(R.id.page_range_title);
1280 mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07001281 mPageRangeEditText.setVisibility(View.INVISIBLE);
Svetoslava798c0a2014-05-15 10:47:19 -07001282 mPageRangeEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
1283 mPageRangeEditText.addTextChangedListener(new RangeTextWatcher());
1284
1285 // Advanced options button.
Svetoslava798c0a2014-05-15 10:47:19 -07001286 mMoreOptionsButton = (Button) findViewById(R.id.more_options_button);
Svet Ganov525a66b2014-06-14 22:29:00 -07001287 mMoreOptionsButton.setOnClickListener(clickListener);
Svetoslava798c0a2014-05-15 10:47:19 -07001288
1289 // Print button
1290 mPrintButton = (ImageView) findViewById(R.id.print_button);
1291 mPrintButton.setOnClickListener(clickListener);
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001292
Philip P. Moltmannc2f913d2016-02-01 12:03:48 -08001293 // The UI is now initialized
1294 mIsOptionsUiBound = true;
1295
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001296 // Special prompt instead of destination spinner for the first time the user printed
1297 if (!hasUserEverPrinted()) {
1298 mShowDestinationPrompt = true;
1299
1300 mSummaryCopies.setEnabled(false);
1301 mSummaryPaperSize.setEnabled(false);
1302
1303 mDestinationSpinner.setOnTouchListener(new View.OnTouchListener() {
1304 @Override
1305 public boolean onTouch(View v, MotionEvent event) {
1306 mShowDestinationPrompt = false;
1307 mSummaryCopies.setEnabled(true);
1308 mSummaryPaperSize.setEnabled(true);
1309 updateOptionsUi();
1310
1311 mDestinationSpinner.setOnTouchListener(null);
1312 mDestinationSpinnerAdapter.notifyDataSetChanged();
1313
1314 return false;
1315 }
1316 });
1317 }
Svetoslava798c0a2014-05-15 10:47:19 -07001318 }
1319
Philip P. Moltmann66c96592016-02-24 11:32:43 -08001320 @Override
1321 public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
1322 return new PrintServicesLoader((PrintManager) getSystemService(Context.PRINT_SERVICE), this,
1323 PrintManager.ENABLED_SERVICES);
1324 }
1325
1326 @Override
1327 public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
1328 List<PrintServiceInfo> services) {
1329 ComponentName newAdvancedPrintOptionsActivity = null;
1330 if (mCurrentPrinter != null && services != null) {
1331 final int numServices = services.size();
1332 for (int i = 0; i < numServices; i++) {
1333 PrintServiceInfo service = services.get(i);
1334
1335 if (service.getComponentName().equals(mCurrentPrinter.getId().getServiceName())) {
1336 String advancedOptionsActivityName = service.getAdvancedOptionsActivityName();
1337
1338 if (!TextUtils.isEmpty(advancedOptionsActivityName)) {
1339 newAdvancedPrintOptionsActivity = new ComponentName(
1340 service.getComponentName().getPackageName(),
1341 advancedOptionsActivityName);
1342
1343 break;
1344 }
1345 }
1346 }
1347 }
1348
1349 if (!Objects.equals(newAdvancedPrintOptionsActivity, mAdvancedPrintOptionsActivity)) {
1350 mAdvancedPrintOptionsActivity = newAdvancedPrintOptionsActivity;
1351 updateOptionsUi();
1352 }
1353
1354 boolean newArePrintServicesEnabled = services != null && !services.isEmpty();
1355 if (mArePrintServicesEnabled != newArePrintServicesEnabled) {
1356 mArePrintServicesEnabled = newArePrintServicesEnabled;
1357
1358 // Reload mDestinationSpinnerAdapter as mArePrintServicesEnabled changed and the adapter
1359 // reads that in DestinationAdapter#getMoreItemTitle
1360 if (mDestinationSpinnerAdapter != null) {
1361 mDestinationSpinnerAdapter.notifyDataSetChanged();
1362 }
1363 }
1364 }
1365
1366 @Override
1367 public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
1368 if (!isFinishing()) {
1369 onLoadFinished(loader, null);
1370 }
1371 }
1372
Philip P. Moltmann853a6f52015-11-03 10:38:56 -08001373 /**
1374 * A dialog that asks the user to approve a {@link PrintService}. This dialog is automatically
1375 * dismissed if the same {@link PrintService} gets approved by another
1376 * {@link PrintServiceApprovalDialog}.
1377 */
1378 private static final class PrintServiceApprovalDialog extends DialogFragment
1379 implements OnSharedPreferenceChangeListener {
1380 private static final String PRINTSERVICE_KEY = "PRINTSERVICE";
1381 private ApprovedPrintServices mApprovedServices;
1382
1383 /**
1384 * Create a new {@link PrintServiceApprovalDialog} that ask the user to approve a
1385 * {@link PrintService}.
1386 *
1387 * @param printService The {@link ComponentName} of the service to approve
1388 * @return A new {@link PrintServiceApprovalDialog} that might approve the service
1389 */
1390 static PrintServiceApprovalDialog newInstance(ComponentName printService) {
1391 PrintServiceApprovalDialog dialog = new PrintServiceApprovalDialog();
1392
1393 Bundle args = new Bundle();
1394 args.putParcelable(PRINTSERVICE_KEY, printService);
1395 dialog.setArguments(args);
1396
1397 return dialog;
1398 }
1399
1400 @Override
1401 public void onStop() {
1402 super.onStop();
1403
1404 mApprovedServices.unregisterChangeListener(this);
1405 }
1406
1407 @Override
1408 public void onStart() {
1409 super.onStart();
1410
1411 ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY);
1412 synchronized (ApprovedPrintServices.sLock) {
1413 if (mApprovedServices.isApprovedService(printService)) {
1414 dismiss();
1415 } else {
1416 mApprovedServices.registerChangeListenerLocked(this);
1417 }
1418 }
1419 }
1420
1421 @Override
1422 public Dialog onCreateDialog(Bundle savedInstanceState) {
1423 super.onCreateDialog(savedInstanceState);
1424
1425 mApprovedServices = new ApprovedPrintServices(getActivity());
1426
1427 PackageManager packageManager = getActivity().getPackageManager();
1428 CharSequence serviceLabel;
1429 try {
1430 ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY);
1431
1432 serviceLabel = packageManager.getApplicationInfo(printService.getPackageName(), 0)
1433 .loadLabel(packageManager);
1434 } catch (NameNotFoundException e) {
1435 serviceLabel = null;
1436 }
1437
1438 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
1439 builder.setTitle(getString(R.string.print_service_security_warning_title,
1440 serviceLabel))
1441 .setMessage(getString(R.string.print_service_security_warning_summary,
1442 serviceLabel))
1443 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1444 @Override
1445 public void onClick(DialogInterface dialog, int id) {
1446 ComponentName printService =
1447 getArguments().getParcelable(PRINTSERVICE_KEY);
1448 // Prevent onSharedPreferenceChanged from getting triggered
1449 mApprovedServices
1450 .unregisterChangeListener(PrintServiceApprovalDialog.this);
1451
1452 mApprovedServices.addApprovedService(printService);
1453 ((PrintActivity) getActivity()).confirmPrint();
1454 }
1455 })
1456 .setNegativeButton(android.R.string.cancel, null);
1457
1458 return builder.create();
1459 }
1460
1461 @Override
1462 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
1463 ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY);
1464
1465 synchronized (ApprovedPrintServices.sLock) {
1466 if (mApprovedServices.isApprovedService(printService)) {
1467 dismiss();
1468 }
1469 }
1470 }
1471 }
1472
Svetoslava798c0a2014-05-15 10:47:19 -07001473 private final class MyClickListener implements OnClickListener {
1474 @Override
1475 public void onClick(View view) {
1476 if (view == mPrintButton) {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001477 if (mCurrentPrinter != null) {
Philip P. Moltmann853a6f52015-11-03 10:38:56 -08001478 if (mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter) {
1479 confirmPrint();
1480 } else {
1481 ApprovedPrintServices approvedServices =
1482 new ApprovedPrintServices(PrintActivity.this);
1483
1484 ComponentName printService = mCurrentPrinter.getId().getServiceName();
1485 if (approvedServices.isApprovedService(printService)) {
1486 confirmPrint();
1487 } else {
1488 PrintServiceApprovalDialog.newInstance(printService)
1489 .show(getFragmentManager(), "approve");
1490 }
1491 }
Svetoslava798c0a2014-05-15 10:47:19 -07001492 } else {
1493 cancelPrint();
1494 }
1495 } else if (view == mMoreOptionsButton) {
Philip P. Moltmannc309d712016-03-31 13:49:38 -07001496 // The selected pages is only applied once the user leaves the text field. A click
1497 // on this button, does not count as leaving.
1498 updateSelectedPagesFromTextField();
1499
Svet Ganov48fec5c2014-07-14 00:14:07 -07001500 if (mCurrentPrinter != null) {
1501 startAdvancedPrintOptionsActivity(mCurrentPrinter);
Svetoslava798c0a2014-05-15 10:47:19 -07001502 }
1503 }
1504 }
1505 }
1506
1507 private static boolean canPrint(PrinterInfo printer) {
1508 return printer.getCapabilities() != null
1509 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
1510 }
1511
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001512 /**
1513 * Disable all options UI elements, beside the {@link #mDestinationSpinner}
1514 */
1515 private void disableOptionsUi() {
1516 mCopiesEditText.setEnabled(false);
1517 mCopiesEditText.setFocusable(false);
1518 mMediaSizeSpinner.setEnabled(false);
1519 mColorModeSpinner.setEnabled(false);
1520 mDuplexModeSpinner.setEnabled(false);
1521 mOrientationSpinner.setEnabled(false);
1522 mRangeOptionsSpinner.setEnabled(false);
1523 mPageRangeEditText.setEnabled(false);
1524 mPrintButton.setVisibility(View.GONE);
1525 mMoreOptionsButton.setEnabled(false);
1526 }
1527
Svet Ganov525a66b2014-06-14 22:29:00 -07001528 void updateOptionsUi() {
Philip P. Moltmannc2f913d2016-02-01 12:03:48 -08001529 if (!mIsOptionsUiBound) {
1530 return;
1531 }
1532
Svetoslava798c0a2014-05-15 10:47:19 -07001533 // Always update the summary.
Svetoslave652b022014-09-09 22:11:10 -07001534 updateSummary();
Svetoslava798c0a2014-05-15 10:47:19 -07001535
1536 if (mState == STATE_PRINT_CONFIRMED
Svetoslavb59555c2014-07-24 10:13:00 -07001537 || mState == STATE_PRINT_COMPLETED
Svetoslava798c0a2014-05-15 10:47:19 -07001538 || mState == STATE_PRINT_CANCELED
1539 || mState == STATE_UPDATE_FAILED
1540 || mState == STATE_CREATE_FILE_FAILED
Svet Ganov525a66b2014-06-14 22:29:00 -07001541 || mState == STATE_PRINTER_UNAVAILABLE
1542 || mState == STATE_UPDATE_SLOW) {
Svetoslava798c0a2014-05-15 10:47:19 -07001543 if (mState != STATE_PRINTER_UNAVAILABLE) {
1544 mDestinationSpinner.setEnabled(false);
1545 }
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001546 disableOptionsUi();
Svetoslava798c0a2014-05-15 10:47:19 -07001547 return;
1548 }
1549
1550 // If no current printer, or it has no capabilities, or it is not
1551 // available, we disable all print options except the destination.
Svet Ganov48fec5c2014-07-14 00:14:07 -07001552 if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) {
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001553 disableOptionsUi();
Svetoslava798c0a2014-05-15 10:47:19 -07001554 return;
1555 }
1556
Svet Ganov48fec5c2014-07-14 00:14:07 -07001557 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
Svetoslava798c0a2014-05-15 10:47:19 -07001558 PrintAttributes defaultAttributes = capabilities.getDefaults();
1559
Svet Ganov525a66b2014-06-14 22:29:00 -07001560 // Destination.
1561 mDestinationSpinner.setEnabled(true);
1562
Svetoslava798c0a2014-05-15 10:47:19 -07001563 // Media size.
1564 mMediaSizeSpinner.setEnabled(true);
1565
1566 List<MediaSize> mediaSizes = new ArrayList<>(capabilities.getMediaSizes());
1567 // Sort the media sizes based on the current locale.
1568 Collections.sort(mediaSizes, mMediaSizeComparator);
1569
1570 PrintAttributes attributes = mPrintJob.getAttributes();
1571
1572 // If the media sizes changed, we update the adapter and the spinner.
1573 boolean mediaSizesChanged = false;
1574 final int mediaSizeCount = mediaSizes.size();
1575 if (mediaSizeCount != mMediaSizeSpinnerAdapter.getCount()) {
1576 mediaSizesChanged = true;
1577 } else {
1578 for (int i = 0; i < mediaSizeCount; i++) {
1579 if (!mediaSizes.get(i).equals(mMediaSizeSpinnerAdapter.getItem(i).value)) {
1580 mediaSizesChanged = true;
1581 break;
1582 }
1583 }
1584 }
1585 if (mediaSizesChanged) {
1586 // Remember the old media size to try selecting it again.
1587 int oldMediaSizeNewIndex = AdapterView.INVALID_POSITION;
1588 MediaSize oldMediaSize = attributes.getMediaSize();
1589
1590 // Rebuild the adapter data.
1591 mMediaSizeSpinnerAdapter.clear();
1592 for (int i = 0; i < mediaSizeCount; i++) {
1593 MediaSize mediaSize = mediaSizes.get(i);
1594 if (oldMediaSize != null
1595 && mediaSize.asPortrait().equals(oldMediaSize.asPortrait())) {
1596 // Update the index of the old selection.
1597 oldMediaSizeNewIndex = i;
1598 }
1599 mMediaSizeSpinnerAdapter.add(new SpinnerItem<>(
1600 mediaSize, mediaSize.getLabel(getPackageManager())));
1601 }
1602
1603 if (oldMediaSizeNewIndex != AdapterView.INVALID_POSITION) {
1604 // Select the old media size - nothing really changed.
1605 if (mMediaSizeSpinner.getSelectedItemPosition() != oldMediaSizeNewIndex) {
1606 mMediaSizeSpinner.setSelection(oldMediaSizeNewIndex);
1607 }
1608 } else {
1609 // Select the first or the default.
1610 final int mediaSizeIndex = Math.max(mediaSizes.indexOf(
1611 defaultAttributes.getMediaSize()), 0);
1612 if (mMediaSizeSpinner.getSelectedItemPosition() != mediaSizeIndex) {
1613 mMediaSizeSpinner.setSelection(mediaSizeIndex);
1614 }
1615 // Respect the orientation of the old selection.
1616 if (oldMediaSize != null) {
1617 if (oldMediaSize.isPortrait()) {
1618 attributes.setMediaSize(mMediaSizeSpinnerAdapter
1619 .getItem(mediaSizeIndex).value.asPortrait());
1620 } else {
1621 attributes.setMediaSize(mMediaSizeSpinnerAdapter
1622 .getItem(mediaSizeIndex).value.asLandscape());
1623 }
1624 }
1625 }
1626 }
1627
1628 // Color mode.
1629 mColorModeSpinner.setEnabled(true);
1630 final int colorModes = capabilities.getColorModes();
1631
1632 // If the color modes changed, we update the adapter and the spinner.
1633 boolean colorModesChanged = false;
1634 if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) {
1635 colorModesChanged = true;
1636 } else {
1637 int remainingColorModes = colorModes;
1638 int adapterIndex = 0;
1639 while (remainingColorModes != 0) {
1640 final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
1641 final int colorMode = 1 << colorBitOffset;
1642 remainingColorModes &= ~colorMode;
1643 if (colorMode != mColorModeSpinnerAdapter.getItem(adapterIndex).value) {
1644 colorModesChanged = true;
1645 break;
1646 }
1647 adapterIndex++;
1648 }
1649 }
1650 if (colorModesChanged) {
1651 // Remember the old color mode to try selecting it again.
1652 int oldColorModeNewIndex = AdapterView.INVALID_POSITION;
1653 final int oldColorMode = attributes.getColorMode();
1654
1655 // Rebuild the adapter data.
1656 mColorModeSpinnerAdapter.clear();
1657 String[] colorModeLabels = getResources().getStringArray(R.array.color_mode_labels);
1658 int remainingColorModes = colorModes;
1659 while (remainingColorModes != 0) {
1660 final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
1661 final int colorMode = 1 << colorBitOffset;
1662 if (colorMode == oldColorMode) {
1663 // Update the index of the old selection.
Svetoslav948c9a62015-02-02 19:47:04 -08001664 oldColorModeNewIndex = mColorModeSpinnerAdapter.getCount();
Svetoslava798c0a2014-05-15 10:47:19 -07001665 }
1666 remainingColorModes &= ~colorMode;
1667 mColorModeSpinnerAdapter.add(new SpinnerItem<>(colorMode,
1668 colorModeLabels[colorBitOffset]));
1669 }
1670 if (oldColorModeNewIndex != AdapterView.INVALID_POSITION) {
1671 // Select the old color mode - nothing really changed.
1672 if (mColorModeSpinner.getSelectedItemPosition() != oldColorModeNewIndex) {
1673 mColorModeSpinner.setSelection(oldColorModeNewIndex);
1674 }
1675 } else {
1676 // Select the default.
1677 final int selectedColorMode = colorModes & defaultAttributes.getColorMode();
1678 final int itemCount = mColorModeSpinnerAdapter.getCount();
1679 for (int i = 0; i < itemCount; i++) {
1680 SpinnerItem<Integer> item = mColorModeSpinnerAdapter.getItem(i);
1681 if (selectedColorMode == item.value) {
1682 if (mColorModeSpinner.getSelectedItemPosition() != i) {
1683 mColorModeSpinner.setSelection(i);
1684 }
1685 attributes.setColorMode(selectedColorMode);
Svetoslav948c9a62015-02-02 19:47:04 -08001686 break;
Svetoslava798c0a2014-05-15 10:47:19 -07001687 }
1688 }
1689 }
1690 }
1691
Svetoslav948c9a62015-02-02 19:47:04 -08001692 // Duplex mode.
1693 mDuplexModeSpinner.setEnabled(true);
1694 final int duplexModes = capabilities.getDuplexModes();
1695
1696 // If the duplex modes changed, we update the adapter and the spinner.
1697 // Note that we use bit count +1 to account for the no duplex option.
1698 boolean duplexModesChanged = false;
1699 if (Integer.bitCount(duplexModes) != mDuplexModeSpinnerAdapter.getCount()) {
1700 duplexModesChanged = true;
1701 } else {
1702 int remainingDuplexModes = duplexModes;
1703 int adapterIndex = 0;
1704 while (remainingDuplexModes != 0) {
1705 final int duplexBitOffset = Integer.numberOfTrailingZeros(remainingDuplexModes);
1706 final int duplexMode = 1 << duplexBitOffset;
1707 remainingDuplexModes &= ~duplexMode;
1708 if (duplexMode != mDuplexModeSpinnerAdapter.getItem(adapterIndex).value) {
1709 duplexModesChanged = true;
1710 break;
1711 }
1712 adapterIndex++;
1713 }
1714 }
1715 if (duplexModesChanged) {
1716 // Remember the old duplex mode to try selecting it again. Also the fallback
1717 // is no duplexing which is always the first item in the dropdown.
1718 int oldDuplexModeNewIndex = AdapterView.INVALID_POSITION;
1719 final int oldDuplexMode = attributes.getDuplexMode();
1720
1721 // Rebuild the adapter data.
1722 mDuplexModeSpinnerAdapter.clear();
1723 String[] duplexModeLabels = getResources().getStringArray(R.array.duplex_mode_labels);
1724 int remainingDuplexModes = duplexModes;
1725 while (remainingDuplexModes != 0) {
1726 final int duplexBitOffset = Integer.numberOfTrailingZeros(remainingDuplexModes);
1727 final int duplexMode = 1 << duplexBitOffset;
1728 if (duplexMode == oldDuplexMode) {
1729 // Update the index of the old selection.
1730 oldDuplexModeNewIndex = mDuplexModeSpinnerAdapter.getCount();
1731 }
1732 remainingDuplexModes &= ~duplexMode;
1733 mDuplexModeSpinnerAdapter.add(new SpinnerItem<>(duplexMode,
1734 duplexModeLabels[duplexBitOffset]));
1735 }
1736
1737 if (oldDuplexModeNewIndex != AdapterView.INVALID_POSITION) {
1738 // Select the old duplex mode - nothing really changed.
1739 if (mDuplexModeSpinner.getSelectedItemPosition() != oldDuplexModeNewIndex) {
1740 mDuplexModeSpinner.setSelection(oldDuplexModeNewIndex);
1741 }
1742 } else {
1743 // Select the default.
1744 final int selectedDuplexMode = defaultAttributes.getDuplexMode();
1745 final int itemCount = mDuplexModeSpinnerAdapter.getCount();
1746 for (int i = 0; i < itemCount; i++) {
1747 SpinnerItem<Integer> item = mDuplexModeSpinnerAdapter.getItem(i);
1748 if (selectedDuplexMode == item.value) {
1749 if (mDuplexModeSpinner.getSelectedItemPosition() != i) {
1750 mDuplexModeSpinner.setSelection(i);
1751 }
1752 attributes.setDuplexMode(selectedDuplexMode);
1753 break;
1754 }
1755 }
1756 }
1757 }
1758
1759 mDuplexModeSpinner.setEnabled(mDuplexModeSpinnerAdapter.getCount() > 1);
1760
Svetoslava798c0a2014-05-15 10:47:19 -07001761 // Orientation
1762 mOrientationSpinner.setEnabled(true);
1763 MediaSize mediaSize = attributes.getMediaSize();
1764 if (mediaSize != null) {
1765 if (mediaSize.isPortrait()
1766 && mOrientationSpinner.getSelectedItemPosition() != 0) {
1767 mOrientationSpinner.setSelection(0);
1768 } else if (!mediaSize.isPortrait()
1769 && mOrientationSpinner.getSelectedItemPosition() != 1) {
1770 mOrientationSpinner.setSelection(1);
1771 }
1772 }
1773
1774 // Range options
1775 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
Svet Ganov525a66b2014-06-14 22:29:00 -07001776 final int pageCount = getAdjustedPageCount(info);
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07001777 if (pageCount > 0) {
1778 if (info != null) {
1779 if (pageCount == 1) {
1780 mRangeOptionsSpinner.setEnabled(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001781 } else {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07001782 mRangeOptionsSpinner.setEnabled(true);
1783 if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
1784 if (!mPageRangeEditText.isEnabled()) {
1785 mPageRangeEditText.setEnabled(true);
1786 mPageRangeEditText.setVisibility(View.VISIBLE);
1787 mPageRangeTitle.setVisibility(View.VISIBLE);
1788 mPageRangeEditText.requestFocus();
1789 InputMethodManager imm = (InputMethodManager)
1790 getSystemService(Context.INPUT_METHOD_SERVICE);
1791 imm.showSoftInput(mPageRangeEditText, 0);
1792 }
1793 } else {
1794 mPageRangeEditText.setEnabled(false);
1795 mPageRangeEditText.setVisibility(View.INVISIBLE);
1796 mPageRangeTitle.setVisibility(View.INVISIBLE);
1797 }
Svetoslava798c0a2014-05-15 10:47:19 -07001798 }
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07001799 } else {
1800 if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
1801 mRangeOptionsSpinner.setSelection(0);
1802 mPageRangeEditText.setText("");
1803 }
1804 mRangeOptionsSpinner.setEnabled(false);
1805 mPageRangeEditText.setEnabled(false);
1806 mPageRangeEditText.setVisibility(View.INVISIBLE);
1807 mPageRangeTitle.setVisibility(View.INVISIBLE);
Svetoslava798c0a2014-05-15 10:47:19 -07001808 }
Svetoslava798c0a2014-05-15 10:47:19 -07001809 }
1810
Svetoslav73764e32014-07-15 15:56:46 -07001811 final int newPageCount = getAdjustedPageCount(info);
1812 if (newPageCount != mCurrentPageCount) {
1813 mCurrentPageCount = newPageCount;
1814 updatePageRangeOptions(newPageCount);
1815 }
1816
Svetoslava798c0a2014-05-15 10:47:19 -07001817 // Advanced print options
Philip P. Moltmann66c96592016-02-24 11:32:43 -08001818 if (mAdvancedPrintOptionsActivity != null) {
Svetoslav3c238242014-08-19 13:44:29 -07001819 mMoreOptionsButton.setVisibility(View.VISIBLE);
Svetoslava798c0a2014-05-15 10:47:19 -07001820 mMoreOptionsButton.setEnabled(true);
1821 } else {
Svetoslav3c238242014-08-19 13:44:29 -07001822 mMoreOptionsButton.setVisibility(View.GONE);
Svetoslava798c0a2014-05-15 10:47:19 -07001823 mMoreOptionsButton.setEnabled(false);
1824 }
1825
1826 // Print
Svet Ganov48fec5c2014-07-14 00:14:07 -07001827 if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
Svetoslava798c0a2014-05-15 10:47:19 -07001828 mPrintButton.setImageResource(com.android.internal.R.drawable.ic_print);
Svetoslave652b022014-09-09 22:11:10 -07001829 mPrintButton.setContentDescription(getString(R.string.print_button));
Svetoslava798c0a2014-05-15 10:47:19 -07001830 } else {
Svetoslavf8ffa562014-07-23 18:22:03 -07001831 mPrintButton.setImageResource(R.drawable.ic_menu_savetopdf);
Svetoslave652b022014-09-09 22:11:10 -07001832 mPrintButton.setContentDescription(getString(R.string.savetopdf_button));
Svetoslava798c0a2014-05-15 10:47:19 -07001833 }
Svetoslave1dcb392014-09-26 19:49:14 -07001834 if (!mPrintedDocument.getDocumentInfo().laidout
1835 ||(mRangeOptionsSpinner.getSelectedItemPosition() == 1
Svetoslava798c0a2014-05-15 10:47:19 -07001836 && (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors()))
Svet Ganov525a66b2014-06-14 22:29:00 -07001837 || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
Svetoslava798c0a2014-05-15 10:47:19 -07001838 && (mPrintedDocument.getDocumentInfo() == null || hasErrors()))) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001839 mPrintButton.setVisibility(View.GONE);
Svetoslava798c0a2014-05-15 10:47:19 -07001840 } else {
Svet Ganov525a66b2014-06-14 22:29:00 -07001841 mPrintButton.setVisibility(View.VISIBLE);
Svetoslava798c0a2014-05-15 10:47:19 -07001842 }
1843
1844 // Copies
Svet Ganov48fec5c2014-07-14 00:14:07 -07001845 if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
Svetoslava798c0a2014-05-15 10:47:19 -07001846 mCopiesEditText.setEnabled(true);
Svetoslavc404cac2014-08-27 18:37:16 -07001847 mCopiesEditText.setFocusableInTouchMode(true);
Svetoslava798c0a2014-05-15 10:47:19 -07001848 } else {
Svet Ganov45e50e92014-10-23 12:39:08 -07001849 CharSequence text = mCopiesEditText.getText();
1850 if (TextUtils.isEmpty(text) || !MIN_COPIES_STRING.equals(text.toString())) {
1851 mCopiesEditText.setText(MIN_COPIES_STRING);
1852 }
Svetoslava798c0a2014-05-15 10:47:19 -07001853 mCopiesEditText.setEnabled(false);
Svetoslavc404cac2014-08-27 18:37:16 -07001854 mCopiesEditText.setFocusable(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001855 }
1856 if (mCopiesEditText.getError() == null
1857 && TextUtils.isEmpty(mCopiesEditText.getText())) {
Svet Ganov45e50e92014-10-23 12:39:08 -07001858 mCopiesEditText.setText(MIN_COPIES_STRING);
Svetoslava798c0a2014-05-15 10:47:19 -07001859 mCopiesEditText.requestFocus();
1860 }
Philip P. Moltmann5e548962015-11-13 15:33:40 -08001861
1862 if (mShowDestinationPrompt) {
1863 disableOptionsUi();
1864 }
Svetoslava798c0a2014-05-15 10:47:19 -07001865 }
1866
Svetoslave652b022014-09-09 22:11:10 -07001867 private void updateSummary() {
Philip P. Moltmannc2f913d2016-02-01 12:03:48 -08001868 if (!mIsOptionsUiBound) {
1869 return;
1870 }
1871
Svetoslave652b022014-09-09 22:11:10 -07001872 CharSequence copiesText = null;
1873 CharSequence mediaSizeText = null;
1874
1875 if (!TextUtils.isEmpty(mCopiesEditText.getText())) {
1876 copiesText = mCopiesEditText.getText();
1877 mSummaryCopies.setText(copiesText);
1878 }
1879
1880 final int selectedMediaIndex = mMediaSizeSpinner.getSelectedItemPosition();
1881 if (selectedMediaIndex >= 0) {
1882 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(selectedMediaIndex);
1883 mediaSizeText = mediaItem.label;
1884 mSummaryPaperSize.setText(mediaSizeText);
1885 }
1886
1887 if (!TextUtils.isEmpty(copiesText) && !TextUtils.isEmpty(mediaSizeText)) {
1888 String summaryText = getString(R.string.summary_template, copiesText, mediaSizeText);
1889 mSummaryContainer.setContentDescription(summaryText);
1890 }
1891 }
1892
Svetoslav73764e32014-07-15 15:56:46 -07001893 private void updatePageRangeOptions(int pageCount) {
Philip P. Moltmannc43639c2015-12-18 13:58:40 -08001894 @SuppressWarnings("unchecked")
Svetoslav73764e32014-07-15 15:56:46 -07001895 ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter =
Philip P. Moltmannc43639c2015-12-18 13:58:40 -08001896 (ArrayAdapter<SpinnerItem<Integer>>) mRangeOptionsSpinner.getAdapter();
Svetoslav73764e32014-07-15 15:56:46 -07001897 rangeOptionsSpinnerAdapter.clear();
1898
1899 final int[] rangeOptionsValues = getResources().getIntArray(
1900 R.array.page_options_values);
1901
1902 String pageCountLabel = (pageCount > 0) ? String.valueOf(pageCount) : "";
1903 String[] rangeOptionsLabels = new String[] {
1904 getString(R.string.template_all_pages, pageCountLabel),
1905 getString(R.string.template_page_range, pageCountLabel)
1906 };
1907
1908 final int rangeOptionsCount = rangeOptionsLabels.length;
1909 for (int i = 0; i < rangeOptionsCount; i++) {
1910 rangeOptionsSpinnerAdapter.add(new SpinnerItem<>(
1911 rangeOptionsValues[i], rangeOptionsLabels[i]));
1912 }
1913 }
1914
Svet Ganov525a66b2014-06-14 22:29:00 -07001915 private PageRange[] computeSelectedPages() {
Svetoslava798c0a2014-05-15 10:47:19 -07001916 if (hasErrors()) {
1917 return null;
1918 }
1919
1920 if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
1921 List<PageRange> pageRanges = new ArrayList<>();
1922 mStringCommaSplitter.setString(mPageRangeEditText.getText().toString());
1923
1924 while (mStringCommaSplitter.hasNext()) {
1925 String range = mStringCommaSplitter.next().trim();
1926 if (TextUtils.isEmpty(range)) {
1927 continue;
1928 }
1929 final int dashIndex = range.indexOf('-');
1930 final int fromIndex;
1931 final int toIndex;
1932
1933 if (dashIndex > 0) {
1934 fromIndex = Integer.parseInt(range.substring(0, dashIndex).trim()) - 1;
1935 // It is possible that the dash is at the end since the input
1936 // verification can has to allow the user to keep entering if
1937 // this would lead to a valid input. So we handle this.
1938 if (dashIndex < range.length() - 1) {
1939 String fromString = range.substring(dashIndex + 1, range.length()).trim();
1940 toIndex = Integer.parseInt(fromString) - 1;
1941 } else {
1942 toIndex = fromIndex;
1943 }
1944 } else {
1945 fromIndex = toIndex = Integer.parseInt(range) - 1;
1946 }
1947
1948 PageRange pageRange = new PageRange(Math.min(fromIndex, toIndex),
1949 Math.max(fromIndex, toIndex));
1950 pageRanges.add(pageRange);
1951 }
1952
1953 PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
1954 pageRanges.toArray(pageRangesArray);
1955
1956 return PageRangeUtils.normalize(pageRangesArray);
1957 }
1958
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07001959 return PageRange.ALL_PAGES_ARRAY;
Svetoslava798c0a2014-05-15 10:47:19 -07001960 }
1961
Svet Ganov525a66b2014-06-14 22:29:00 -07001962 private int getAdjustedPageCount(PrintDocumentInfo info) {
1963 if (info != null) {
1964 final int pageCount = info.getPageCount();
1965 if (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
1966 return pageCount;
1967 }
1968 }
1969 // If the app does not tell us how many pages are in the
1970 // doc we ask for all pages and use the document page count.
1971 return mPrintPreviewController.getFilePageCount();
1972 }
1973
Svetoslava798c0a2014-05-15 10:47:19 -07001974 private boolean hasErrors() {
1975 return (mCopiesEditText.getError() != null)
1976 || (mPageRangeEditText.getVisibility() == View.VISIBLE
Svet Ganov525a66b2014-06-14 22:29:00 -07001977 && mPageRangeEditText.getError() != null);
Svetoslava798c0a2014-05-15 10:47:19 -07001978 }
1979
1980 public void onPrinterAvailable(PrinterInfo printer) {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001981 if (mCurrentPrinter.equals(printer)) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001982 setState(STATE_CONFIGURING);
Svetoslava798c0a2014-05-15 10:47:19 -07001983 if (canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07001984 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001985 }
1986 ensurePreviewUiShown();
1987 updateOptionsUi();
1988 }
1989 }
1990
1991 public void onPrinterUnavailable(PrinterInfo printer) {
Svet Ganov48fec5c2014-07-14 00:14:07 -07001992 if (mCurrentPrinter.getId().equals(printer.getId())) {
Svet Ganov525a66b2014-06-14 22:29:00 -07001993 setState(STATE_PRINTER_UNAVAILABLE);
Philip P. Moltmann645a3e12016-02-25 11:20:41 -08001994 mPrintedDocument.cancel(false);
Svetoslava798c0a2014-05-15 10:47:19 -07001995 ensureErrorUiShown(getString(R.string.print_error_printer_unavailable),
1996 PrintErrorFragment.ACTION_NONE);
1997 updateOptionsUi();
1998 }
1999 }
2000
Svet Ganov525a66b2014-06-14 22:29:00 -07002001 private boolean canUpdateDocument() {
2002 if (mPrintedDocument.isDestroyed()) {
2003 return false;
2004 }
2005
2006 if (hasErrors()) {
2007 return false;
2008 }
2009
2010 PrintAttributes attributes = mPrintJob.getAttributes();
2011
2012 final int colorMode = attributes.getColorMode();
2013 if (colorMode != PrintAttributes.COLOR_MODE_COLOR
2014 && colorMode != PrintAttributes.COLOR_MODE_MONOCHROME) {
2015 return false;
2016 }
2017 if (attributes.getMediaSize() == null) {
2018 return false;
2019 }
2020 if (attributes.getMinMargins() == null) {
2021 return false;
2022 }
2023 if (attributes.getResolution() == null) {
2024 return false;
2025 }
2026
Svet Ganov48fec5c2014-07-14 00:14:07 -07002027 if (mCurrentPrinter == null) {
Svet Ganov525a66b2014-06-14 22:29:00 -07002028 return false;
2029 }
Svet Ganov48fec5c2014-07-14 00:14:07 -07002030 PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
Svet Ganov525a66b2014-06-14 22:29:00 -07002031 if (capabilities == null) {
2032 return false;
2033 }
Svet Ganov48fec5c2014-07-14 00:14:07 -07002034 if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
Svet Ganov525a66b2014-06-14 22:29:00 -07002035 return false;
2036 }
2037
2038 return true;
2039 }
2040
Svetoslavbec22be2014-09-25 13:03:20 -07002041 private void transformDocumentAndFinish(final Uri writeToUri) {
2042 // If saving to PDF, apply the attibutes as we are acting as a print service.
2043 PrintAttributes attributes = mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter
2044 ? mPrintJob.getAttributes() : null;
2045 new DocumentTransformer(this, mPrintJob, mFileProvider, attributes, new Runnable() {
Svetoslav62ce3322014-09-04 21:17:17 -07002046 @Override
2047 public void run() {
2048 if (writeToUri != null) {
2049 mPrintedDocument.writeContent(getContentResolver(), writeToUri);
2050 }
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -08002051 setState(STATE_PRINT_COMPLETED);
Svetoslave17123d2014-09-11 12:39:05 -07002052 doFinish();
Svetoslav62ce3322014-09-04 21:17:17 -07002053 }
Svetoslavbec22be2014-09-25 13:03:20 -07002054 }).transform();
Svetoslav62ce3322014-09-04 21:17:17 -07002055 }
2056
Svetoslave17123d2014-09-11 12:39:05 -07002057 private void doFinish() {
Philip P. Moltmann32e07552016-03-07 10:31:49 -08002058 if (mPrintedDocument != null && mPrintedDocument.isUpdating()) {
Philip P. Moltmanncc3fa0d2016-02-03 11:03:16 -08002059 // The printedDocument will call doFinish() when the current command finishes
2060 return;
2061 }
2062
Philip P. Moltmannb170c082016-03-21 12:48:58 -07002063 if (mIsFinishing) {
Philip P. Moltmann0ad7fc52016-03-07 11:36:06 -08002064 return;
2065 }
2066
Philip P. Moltmannb170c082016-03-21 12:48:58 -07002067 mIsFinishing = true;
Philip P. Moltmann0ad7fc52016-03-07 11:36:06 -08002068
Philip P. Moltmann51dbc8e2016-02-01 13:56:45 -08002069 if (mPrinterRegistry != null) {
2070 mPrinterRegistry.setTrackedPrinter(null);
2071 }
2072
Philip P. Moltmann1bb7f362016-02-26 14:21:20 -08002073 if (mPrintersObserver != null) {
2074 mDestinationSpinnerAdapter.unregisterDataSetObserver(mPrintersObserver);
2075 }
2076
Philip P. Moltmann940fa802016-03-23 16:56:24 -07002077 if (mSpoolerProvider != null) {
2078 mSpoolerProvider.destroy();
2079 }
2080
Philip P. Moltmann7b92d3c2016-03-31 14:18:47 -07002081 setState(mProgressMessageController.cancel());
2082
Svetoslave17123d2014-09-11 12:39:05 -07002083 if (mState != STATE_INITIALIZING) {
Svetoslave17123d2014-09-11 12:39:05 -07002084 mPrintedDocument.finish();
2085 mPrintedDocument.destroy();
Svet Ganovc80814e2014-11-24 02:01:37 -08002086 mPrintPreviewController.destroy(new Runnable() {
2087 @Override
2088 public void run() {
2089 finish();
2090 }
2091 });
2092 } else {
2093 finish();
Svetoslave17123d2014-09-11 12:39:05 -07002094 }
Svetoslave17123d2014-09-11 12:39:05 -07002095 }
2096
Svetoslava798c0a2014-05-15 10:47:19 -07002097 private final class SpinnerItem<T> {
2098 final T value;
2099 final CharSequence label;
2100
2101 public SpinnerItem(T value, CharSequence label) {
2102 this.value = value;
2103 this.label = label;
2104 }
2105
Philip P. Moltmannc43639c2015-12-18 13:58:40 -08002106 @Override
Svetoslava798c0a2014-05-15 10:47:19 -07002107 public String toString() {
2108 return label.toString();
2109 }
2110 }
2111
2112 private final class PrinterAvailabilityDetector implements Runnable {
2113 private static final long UNAVAILABLE_TIMEOUT_MILLIS = 10000; // 10sec
2114
2115 private boolean mPosted;
2116
2117 private boolean mPrinterUnavailable;
2118
2119 private PrinterInfo mPrinter;
2120
2121 public void updatePrinter(PrinterInfo printer) {
2122 if (printer.equals(mDestinationSpinnerAdapter.getPdfPrinter())) {
2123 return;
2124 }
2125
2126 final boolean available = printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
2127 && printer.getCapabilities() != null;
2128 final boolean notifyIfAvailable;
2129
2130 if (mPrinter == null || !mPrinter.getId().equals(printer.getId())) {
2131 notifyIfAvailable = true;
2132 unpostIfNeeded();
2133 mPrinterUnavailable = false;
2134 mPrinter = new PrinterInfo.Builder(printer).build();
2135 } else {
2136 notifyIfAvailable =
Svet Ganov525a66b2014-06-14 22:29:00 -07002137 (mPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
2138 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE)
2139 || (mPrinter.getCapabilities() == null
2140 && printer.getCapabilities() != null);
Philip P. Moltmannc2ad2262016-01-13 09:17:15 -08002141 mPrinter = printer;
Svetoslava798c0a2014-05-15 10:47:19 -07002142 }
2143
2144 if (available) {
2145 unpostIfNeeded();
2146 mPrinterUnavailable = false;
2147 if (notifyIfAvailable) {
2148 onPrinterAvailable(mPrinter);
2149 }
2150 } else {
2151 if (!mPrinterUnavailable) {
2152 postIfNeeded();
2153 }
2154 }
2155 }
2156
2157 public void cancel() {
2158 unpostIfNeeded();
2159 mPrinterUnavailable = false;
2160 }
2161
2162 private void postIfNeeded() {
2163 if (!mPosted) {
2164 mPosted = true;
2165 mDestinationSpinner.postDelayed(this, UNAVAILABLE_TIMEOUT_MILLIS);
2166 }
2167 }
2168
2169 private void unpostIfNeeded() {
2170 if (mPosted) {
2171 mPosted = false;
2172 mDestinationSpinner.removeCallbacks(this);
2173 }
2174 }
2175
2176 @Override
2177 public void run() {
2178 mPosted = false;
2179 mPrinterUnavailable = true;
2180 onPrinterUnavailable(mPrinter);
2181 }
2182 }
2183
2184 private static final class PrinterHolder {
2185 PrinterInfo printer;
2186 boolean removed;
2187
2188 public PrinterHolder(PrinterInfo printer) {
2189 this.printer = printer;
2190 }
2191 }
2192
Philip P. Moltmann5e548962015-11-13 15:33:40 -08002193
2194 /**
2195 * Check if the user has ever printed a document
2196 *
2197 * @return true iff the user has ever printed a document
2198 */
2199 private boolean hasUserEverPrinted() {
2200 SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE);
2201
2202 return preferences.getBoolean(HAS_PRINTED_PREF, false);
2203 }
2204
2205 /**
2206 * Remember that the user printed a document
2207 */
2208 private void setUserPrinted() {
2209 SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE);
2210
2211 if (!preferences.getBoolean(HAS_PRINTED_PREF, false)) {
2212 SharedPreferences.Editor edit = preferences.edit();
2213
2214 edit.putBoolean(HAS_PRINTED_PREF, true);
2215 edit.apply();
2216 }
2217 }
2218
Svetoslava798c0a2014-05-15 10:47:19 -07002219 private final class DestinationAdapter extends BaseAdapter
2220 implements PrinterRegistry.OnPrintersChangeListener {
2221 private final List<PrinterHolder> mPrinterHolders = new ArrayList<>();
2222
2223 private final PrinterHolder mFakePdfPrinterHolder;
2224
Svet Ganov48fec5c2014-07-14 00:14:07 -07002225 private boolean mHistoricalPrintersLoaded;
2226
Philip P. Moltmann5e548962015-11-13 15:33:40 -08002227 /**
2228 * Has the {@link #mDestinationSpinner} ever used a view from printer_dropdown_prompt
2229 */
2230 private boolean hadPromptView;
2231
Svetoslava798c0a2014-05-15 10:47:19 -07002232 public DestinationAdapter() {
Svet Ganov48fec5c2014-07-14 00:14:07 -07002233 mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
2234 if (mHistoricalPrintersLoaded) {
2235 addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters());
2236 }
Svetoslava798c0a2014-05-15 10:47:19 -07002237 mPrinterRegistry.setOnPrintersChangeListener(this);
2238 mFakePdfPrinterHolder = new PrinterHolder(createFakePdfPrinter());
2239 }
2240
2241 public PrinterInfo getPdfPrinter() {
2242 return mFakePdfPrinterHolder.printer;
2243 }
2244
2245 public int getPrinterIndex(PrinterId printerId) {
2246 for (int i = 0; i < getCount(); i++) {
2247 PrinterHolder printerHolder = (PrinterHolder) getItem(i);
2248 if (printerHolder != null && !printerHolder.removed
2249 && printerHolder.printer.getId().equals(printerId)) {
2250 return i;
2251 }
2252 }
2253 return AdapterView.INVALID_POSITION;
2254 }
2255
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002256 public void ensurePrinterInVisibleAdapterPosition(PrinterInfo printer) {
Svetoslava798c0a2014-05-15 10:47:19 -07002257 final int printerCount = mPrinterHolders.size();
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002258 boolean isKnownPrinter = false;
Svetoslava798c0a2014-05-15 10:47:19 -07002259 for (int i = 0; i < printerCount; i++) {
2260 PrinterHolder printerHolder = mPrinterHolders.get(i);
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002261
2262 if (printerHolder.printer.getId().equals(printer.getId())) {
2263 isKnownPrinter = true;
2264
Svetoslava798c0a2014-05-15 10:47:19 -07002265 // If already in the list - do nothing.
2266 if (i < getCount() - 2) {
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002267 break;
Svetoslava798c0a2014-05-15 10:47:19 -07002268 }
2269 // Else replace the last one (two items are not printers).
2270 final int lastPrinterIndex = getCount() - 3;
2271 mPrinterHolders.set(i, mPrinterHolders.get(lastPrinterIndex));
2272 mPrinterHolders.set(lastPrinterIndex, printerHolder);
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002273 break;
Svetoslava798c0a2014-05-15 10:47:19 -07002274 }
2275 }
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002276
2277 if (!isKnownPrinter) {
2278 PrinterHolder printerHolder = new PrinterHolder(printer);
2279 printerHolder.removed = true;
2280
2281 mPrinterHolders.add(Math.max(0, getCount() - 3), printerHolder);
2282 }
2283
2284 // Force reload to adjust selection in PrintersObserver.onChanged()
2285 notifyDataSetChanged();
Svetoslava798c0a2014-05-15 10:47:19 -07002286 }
2287
2288 @Override
2289 public int getCount() {
Svet Ganov48fec5c2014-07-14 00:14:07 -07002290 if (mHistoricalPrintersLoaded) {
2291 return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
2292 }
2293 return 0;
Svetoslava798c0a2014-05-15 10:47:19 -07002294 }
2295
2296 @Override
2297 public boolean isEnabled(int position) {
2298 Object item = getItem(position);
2299 if (item instanceof PrinterHolder) {
2300 PrinterHolder printerHolder = (PrinterHolder) item;
2301 return !printerHolder.removed
2302 && printerHolder.printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
2303 }
2304 return true;
2305 }
2306
2307 @Override
2308 public Object getItem(int position) {
2309 if (mPrinterHolders.isEmpty()) {
2310 if (position == 0) {
2311 return mFakePdfPrinterHolder;
2312 }
2313 } else {
2314 if (position < 1) {
2315 return mPrinterHolders.get(position);
2316 }
2317 if (position == 1) {
2318 return mFakePdfPrinterHolder;
2319 }
2320 if (position < getCount() - 1) {
2321 return mPrinterHolders.get(position - 1);
2322 }
2323 }
2324 return null;
2325 }
2326
2327 @Override
2328 public long getItemId(int position) {
2329 if (mPrinterHolders.isEmpty()) {
2330 if (position == 0) {
2331 return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
2332 } else if (position == 1) {
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002333 return DEST_ADAPTER_ITEM_ID_MORE;
Svetoslava798c0a2014-05-15 10:47:19 -07002334 }
2335 } else {
2336 if (position == 1) {
2337 return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
2338 }
2339 if (position == getCount() - 1) {
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002340 return DEST_ADAPTER_ITEM_ID_MORE;
Svetoslava798c0a2014-05-15 10:47:19 -07002341 }
2342 }
2343 return position;
2344 }
2345
2346 @Override
2347 public View getDropDownView(int position, View convertView, ViewGroup parent) {
2348 View view = getView(position, convertView, parent);
2349 view.setEnabled(isEnabled(position));
2350 return view;
2351 }
2352
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002353 private String getMoreItemTitle() {
2354 if (mArePrintServicesEnabled) {
2355 return getString(R.string.all_printers);
2356 } else {
2357 return getString(R.string.print_add_printer);
2358 }
2359 }
2360
Svetoslava798c0a2014-05-15 10:47:19 -07002361 @Override
2362 public View getView(int position, View convertView, ViewGroup parent) {
Philip P. Moltmann5e548962015-11-13 15:33:40 -08002363 if (mShowDestinationPrompt) {
2364 if (convertView == null) {
2365 convertView = getLayoutInflater().inflate(
2366 R.layout.printer_dropdown_prompt, parent, false);
2367 hadPromptView = true;
2368 }
2369
2370 return convertView;
2371 } else {
2372 // We don't know if we got an recyled printer_dropdown_prompt, hence do not use it
2373 if (hadPromptView || convertView == null) {
2374 convertView = getLayoutInflater().inflate(
2375 R.layout.printer_dropdown_item, parent, false);
2376 }
Svetoslava798c0a2014-05-15 10:47:19 -07002377 }
2378
2379 CharSequence title = null;
2380 CharSequence subtitle = null;
2381 Drawable icon = null;
2382
2383 if (mPrinterHolders.isEmpty()) {
2384 if (position == 0 && getPdfPrinter() != null) {
2385 PrinterHolder printerHolder = (PrinterHolder) getItem(position);
2386 title = printerHolder.printer.getName();
Philip P. Moltmannc43639c2015-12-18 13:58:40 -08002387 icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf, null);
Svetoslava798c0a2014-05-15 10:47:19 -07002388 } else if (position == 1) {
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002389 title = getMoreItemTitle();
Svetoslava798c0a2014-05-15 10:47:19 -07002390 }
2391 } else {
2392 if (position == 1 && getPdfPrinter() != null) {
2393 PrinterHolder printerHolder = (PrinterHolder) getItem(position);
2394 title = printerHolder.printer.getName();
Philip P. Moltmannc43639c2015-12-18 13:58:40 -08002395 icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf, null);
Svetoslava798c0a2014-05-15 10:47:19 -07002396 } else if (position == getCount() - 1) {
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002397 title = getMoreItemTitle();
Svetoslava798c0a2014-05-15 10:47:19 -07002398 } else {
2399 PrinterHolder printerHolder = (PrinterHolder) getItem(position);
Philip P. Moltmannbb9f6862015-12-01 14:44:24 -08002400 PrinterInfo printInfo = printerHolder.printer;
2401
2402 title = printInfo.getName();
2403 icon = printInfo.loadIcon(PrintActivity.this);
2404 subtitle = printInfo.getDescription();
Svetoslava798c0a2014-05-15 10:47:19 -07002405 }
2406 }
2407
2408 TextView titleView = (TextView) convertView.findViewById(R.id.title);
2409 titleView.setText(title);
2410
2411 TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
2412 if (!TextUtils.isEmpty(subtitle)) {
2413 subtitleView.setText(subtitle);
2414 subtitleView.setVisibility(View.VISIBLE);
2415 } else {
2416 subtitleView.setText(null);
2417 subtitleView.setVisibility(View.GONE);
2418 }
2419
2420 ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
2421 if (icon != null) {
Svetoslava798c0a2014-05-15 10:47:19 -07002422 iconView.setVisibility(View.VISIBLE);
Philip P. Moltmann443075a2016-01-26 13:04:21 -08002423 if (!isEnabled(position)) {
2424 icon.mutate();
2425
2426 TypedValue value = new TypedValue();
2427 getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true);
2428 icon.setAlpha((int)(value.getFloat() * 255));
2429 }
2430 iconView.setImageDrawable(icon);
Svetoslava798c0a2014-05-15 10:47:19 -07002431 } else {
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002432 iconView.setVisibility(View.INVISIBLE);
Svetoslava798c0a2014-05-15 10:47:19 -07002433 }
2434
2435 return convertView;
2436 }
2437
2438 @Override
2439 public void onPrintersChanged(List<PrinterInfo> printers) {
2440 // We rearrange the printers if the user selects a printer
2441 // not shown in the initial short list. Therefore, we have
2442 // to keep the printer order.
2443
Svet Ganov48fec5c2014-07-14 00:14:07 -07002444 // Check if historical printers are loaded as this adapter is open
2445 // for busyness only if they are. This member is updated here and
2446 // when the adapter is created because the historical printers may
2447 // be loaded before or after the adapter is created.
2448 mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
2449
Svetoslava798c0a2014-05-15 10:47:19 -07002450 // No old printers - do not bother keeping their position.
2451 if (mPrinterHolders.isEmpty()) {
2452 addPrinters(mPrinterHolders, printers);
2453 notifyDataSetChanged();
2454 return;
2455 }
2456
2457 // Add the new printers to a map.
2458 ArrayMap<PrinterId, PrinterInfo> newPrintersMap = new ArrayMap<>();
2459 final int printerCount = printers.size();
2460 for (int i = 0; i < printerCount; i++) {
2461 PrinterInfo printer = printers.get(i);
2462 newPrintersMap.put(printer.getId(), printer);
2463 }
2464
2465 List<PrinterHolder> newPrinterHolders = new ArrayList<>();
2466
2467 // Update printers we already have which are either updated or removed.
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002468 // We do not remove the currently selected printer.
Svetoslava798c0a2014-05-15 10:47:19 -07002469 final int oldPrinterCount = mPrinterHolders.size();
2470 for (int i = 0; i < oldPrinterCount; i++) {
2471 PrinterHolder printerHolder = mPrinterHolders.get(i);
2472 PrinterId oldPrinterId = printerHolder.printer.getId();
2473 PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId);
2474 if (updatedPrinter != null) {
2475 printerHolder.printer = updatedPrinter;
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002476 printerHolder.removed = false;
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002477 newPrinterHolders.add(printerHolder);
2478 } else if (mCurrentPrinter != null && mCurrentPrinter.getId().equals(oldPrinterId)){
Svetoslava798c0a2014-05-15 10:47:19 -07002479 printerHolder.removed = true;
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002480 newPrinterHolders.add(printerHolder);
Svetoslava798c0a2014-05-15 10:47:19 -07002481 }
Svetoslava798c0a2014-05-15 10:47:19 -07002482 }
2483
2484 // Add the rest of the new printers, i.e. what is left.
2485 addPrinters(newPrinterHolders, newPrintersMap.values());
2486
2487 mPrinterHolders.clear();
2488 mPrinterHolders.addAll(newPrinterHolders);
2489
2490 notifyDataSetChanged();
2491 }
2492
2493 @Override
2494 public void onPrintersInvalid() {
2495 mPrinterHolders.clear();
2496 notifyDataSetInvalidated();
2497 }
2498
2499 public PrinterHolder getPrinterHolder(PrinterId printerId) {
2500 final int itemCount = getCount();
2501 for (int i = 0; i < itemCount; i++) {
2502 Object item = getItem(i);
2503 if (item instanceof PrinterHolder) {
2504 PrinterHolder printerHolder = (PrinterHolder) item;
2505 if (printerId.equals(printerHolder.printer.getId())) {
2506 return printerHolder;
2507 }
2508 }
2509 }
2510 return null;
2511 }
2512
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002513 /**
2514 * Remove a printer from the holders if it is marked as removed.
2515 *
2516 * @param printerId the id of the printer to remove.
2517 *
2518 * @return true iff the printer was removed.
2519 */
2520 public boolean pruneRemovedPrinter(PrinterId printerId) {
Svetoslava798c0a2014-05-15 10:47:19 -07002521 final int holderCounts = mPrinterHolders.size();
2522 for (int i = holderCounts - 1; i >= 0; i--) {
2523 PrinterHolder printerHolder = mPrinterHolders.get(i);
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002524
2525 if (printerHolder.printer.getId().equals(printerId) && printerHolder.removed) {
Svetoslava798c0a2014-05-15 10:47:19 -07002526 mPrinterHolders.remove(i);
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002527 return true;
Svetoslava798c0a2014-05-15 10:47:19 -07002528 }
2529 }
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002530
2531 return false;
Svetoslava798c0a2014-05-15 10:47:19 -07002532 }
2533
2534 private void addPrinters(List<PrinterHolder> list, Collection<PrinterInfo> printers) {
2535 for (PrinterInfo printer : printers) {
2536 PrinterHolder printerHolder = new PrinterHolder(printer);
2537 list.add(printerHolder);
2538 }
2539 }
2540
2541 private PrinterInfo createFakePdfPrinter() {
Philip P. Moltmann4959caf2016-01-21 14:30:56 -08002542 ArraySet<MediaSize> allMediaSizes = MediaSize.getAllPredefinedSizes();
Svetoslava798c0a2014-05-15 10:47:19 -07002543 MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintActivity.this);
2544
2545 PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
2546
2547 PrinterCapabilitiesInfo.Builder builder =
2548 new PrinterCapabilitiesInfo.Builder(printerId);
2549
Philip P. Moltmann4959caf2016-01-21 14:30:56 -08002550 final int mediaSizeCount = allMediaSizes.size();
2551 for (int i = 0; i < mediaSizeCount; i++) {
2552 MediaSize mediaSize = allMediaSizes.valueAt(i);
Svetoslava798c0a2014-05-15 10:47:19 -07002553 builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize));
2554 }
2555
2556 builder.addResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300),
2557 true);
2558 builder.setColorModes(PrintAttributes.COLOR_MODE_COLOR
2559 | PrintAttributes.COLOR_MODE_MONOCHROME, PrintAttributes.COLOR_MODE_COLOR);
2560
2561 return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf),
2562 PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build();
2563 }
2564 }
2565
2566 private final class PrintersObserver extends DataSetObserver {
2567 @Override
2568 public void onChanged() {
Svet Ganov48fec5c2014-07-14 00:14:07 -07002569 PrinterInfo oldPrinterState = mCurrentPrinter;
Svetoslava798c0a2014-05-15 10:47:19 -07002570 if (oldPrinterState == null) {
2571 return;
2572 }
2573
2574 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
2575 oldPrinterState.getId());
Svetoslava798c0a2014-05-15 10:47:19 -07002576 PrinterInfo newPrinterState = printerHolder.printer;
2577
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002578 if (printerHolder.removed) {
Svetoslava798c0a2014-05-15 10:47:19 -07002579 onPrinterUnavailable(newPrinterState);
2580 }
2581
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002582 if (mDestinationSpinner.getSelectedItem() != printerHolder) {
2583 mDestinationSpinner.setSelection(
2584 mDestinationSpinnerAdapter.getPrinterIndex(newPrinterState.getId()));
2585 }
2586
Svetoslava798c0a2014-05-15 10:47:19 -07002587 if (oldPrinterState.equals(newPrinterState)) {
2588 return;
2589 }
2590
2591 PrinterCapabilitiesInfo oldCapab = oldPrinterState.getCapabilities();
2592 PrinterCapabilitiesInfo newCapab = newPrinterState.getCapabilities();
2593
Philip P. Moltmann1bb7f362016-02-26 14:21:20 -08002594 final boolean hadCabab = oldCapab != null;
Svetoslava798c0a2014-05-15 10:47:19 -07002595 final boolean hasCapab = newCapab != null;
2596 final boolean gotCapab = oldCapab == null && newCapab != null;
2597 final boolean lostCapab = oldCapab != null && newCapab == null;
2598 final boolean capabChanged = capabilitiesChanged(oldCapab, newCapab);
2599
2600 final int oldStatus = oldPrinterState.getStatus();
2601 final int newStatus = newPrinterState.getStatus();
2602
2603 final boolean isActive = newStatus != PrinterInfo.STATUS_UNAVAILABLE;
2604 final boolean becameActive = (oldStatus == PrinterInfo.STATUS_UNAVAILABLE
2605 && oldStatus != newStatus);
2606 final boolean becameInactive = (newStatus == PrinterInfo.STATUS_UNAVAILABLE
2607 && oldStatus != newStatus);
2608
2609 mPrinterAvailabilityDetector.updatePrinter(newPrinterState);
2610
Philip P. Moltmannc2ad2262016-01-13 09:17:15 -08002611 mCurrentPrinter = newPrinterState;
Svetoslava798c0a2014-05-15 10:47:19 -07002612
Svetoslava798c0a2014-05-15 10:47:19 -07002613 final boolean updateNeeded = ((capabChanged && hasCapab && isActive)
2614 || (becameActive && hasCapab) || (isActive && gotCapab));
2615
Philip P. Moltmann1bb7f362016-02-26 14:21:20 -08002616 if (capabChanged && hasCapab) {
2617 updatePrintAttributesFromCapabilities(newCapab);
2618 }
2619
2620 if (updateNeeded) {
2621 updatePrintPreviewController(false);
2622 }
2623
2624 if ((isActive && gotCapab) || (becameActive && hasCapab)) {
2625 onPrinterAvailable(newPrinterState);
2626 } else if ((becameInactive && hadCabab) || (isActive && lostCapab)) {
2627 onPrinterUnavailable(newPrinterState);
2628 }
2629
Svetoslava798c0a2014-05-15 10:47:19 -07002630 if (updateNeeded && canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07002631 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07002632 }
2633
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002634 // Force a reload of the enabled print services to update mAdvancedPrintOptionsActivity
2635 // in onLoadFinished();
2636 getLoaderManager().getLoader(LOADER_ID_ENABLED_PRINT_SERVICES).forceLoad();
2637
Svetoslava798c0a2014-05-15 10:47:19 -07002638 updateOptionsUi();
Philip P. Moltmann17332cb2016-01-29 15:17:00 -08002639 updateSummary();
Svetoslava798c0a2014-05-15 10:47:19 -07002640 }
2641
2642 private boolean capabilitiesChanged(PrinterCapabilitiesInfo oldCapabilities,
2643 PrinterCapabilitiesInfo newCapabilities) {
2644 if (oldCapabilities == null) {
2645 if (newCapabilities != null) {
2646 return true;
2647 }
2648 } else if (!oldCapabilities.equals(newCapabilities)) {
2649 return true;
2650 }
2651 return false;
2652 }
2653 }
2654
2655 private final class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
2656 @Override
2657 public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002658 boolean clearRanges = false;
2659
Svetoslava798c0a2014-05-15 10:47:19 -07002660 if (spinner == mDestinationSpinner) {
2661 if (position == AdapterView.INVALID_POSITION) {
2662 return;
2663 }
2664
Philip P. Moltmann66c96592016-02-24 11:32:43 -08002665 if (id == DEST_ADAPTER_ITEM_ID_MORE) {
Svetoslava798c0a2014-05-15 10:47:19 -07002666 startSelectPrinterActivity();
2667 return;
2668 }
2669
Svet Ganov48fec5c2014-07-14 00:14:07 -07002670 PrinterHolder currentItem = (PrinterHolder) mDestinationSpinner.getSelectedItem();
2671 PrinterInfo currentPrinter = (currentItem != null) ? currentItem.printer : null;
Svetoslava798c0a2014-05-15 10:47:19 -07002672
2673 // Why on earth item selected is called if no selection changed.
Svet Ganov48fec5c2014-07-14 00:14:07 -07002674 if (mCurrentPrinter == currentPrinter) {
Svetoslava798c0a2014-05-15 10:47:19 -07002675 return;
2676 }
Svet Ganov525a66b2014-06-14 22:29:00 -07002677
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002678 PrinterId oldId = null;
2679 if (mCurrentPrinter != null) {
2680 oldId = mCurrentPrinter.getId();
2681 }
2682
Svet Ganov48fec5c2014-07-14 00:14:07 -07002683 mCurrentPrinter = currentPrinter;
Svetoslava798c0a2014-05-15 10:47:19 -07002684
Philip P. Moltmann63ce0b72016-03-08 11:16:56 -08002685 if (oldId != null) {
2686 boolean printerRemoved = mDestinationSpinnerAdapter.pruneRemovedPrinter(oldId);
2687
2688 if (printerRemoved) {
2689 // Trigger PrinterObserver.onChanged to adjust selection. This will call
2690 // this function again.
2691 mDestinationSpinnerAdapter.notifyDataSetChanged();
2692 return;
2693 }
2694 }
2695
Svetoslava798c0a2014-05-15 10:47:19 -07002696 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
2697 currentPrinter.getId());
2698 if (!printerHolder.removed) {
Svet Ganov525a66b2014-06-14 22:29:00 -07002699 setState(STATE_CONFIGURING);
Svetoslava798c0a2014-05-15 10:47:19 -07002700 ensurePreviewUiShown();
2701 }
2702
2703 mPrintJob.setPrinterId(currentPrinter.getId());
2704 mPrintJob.setPrinterName(currentPrinter.getName());
2705
2706 mPrinterRegistry.setTrackedPrinter(currentPrinter.getId());
2707
2708 PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
2709 if (capabilities != null) {
Svet Ganov525a66b2014-06-14 22:29:00 -07002710 updatePrintAttributesFromCapabilities(capabilities);
Svetoslava798c0a2014-05-15 10:47:19 -07002711 }
2712
2713 mPrinterAvailabilityDetector.updatePrinter(currentPrinter);
Philip P. Moltmannba245f92016-03-07 13:44:59 -08002714
2715 // Force a reload of the enabled print services to update
2716 // mAdvancedPrintOptionsActivity in onLoadFinished();
2717 getLoaderManager().getLoader(LOADER_ID_ENABLED_PRINT_SERVICES).forceLoad();
Svetoslava798c0a2014-05-15 10:47:19 -07002718 } else if (spinner == mMediaSizeSpinner) {
2719 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
Svet Ganov525a66b2014-06-14 22:29:00 -07002720 PrintAttributes attributes = mPrintJob.getAttributes();
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002721
2722 MediaSize newMediaSize;
Svetoslava798c0a2014-05-15 10:47:19 -07002723 if (mOrientationSpinner.getSelectedItemPosition() == 0) {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002724 newMediaSize = mediaItem.value.asPortrait();
Svetoslava798c0a2014-05-15 10:47:19 -07002725 } else {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002726 newMediaSize = mediaItem.value.asLandscape();
2727 }
2728
2729 if (newMediaSize != attributes.getMediaSize()) {
2730 clearRanges = true;
2731 attributes.setMediaSize(newMediaSize);
Svetoslava798c0a2014-05-15 10:47:19 -07002732 }
2733 } else if (spinner == mColorModeSpinner) {
2734 SpinnerItem<Integer> colorModeItem = mColorModeSpinnerAdapter.getItem(position);
2735 mPrintJob.getAttributes().setColorMode(colorModeItem.value);
Svetoslav948c9a62015-02-02 19:47:04 -08002736 } else if (spinner == mDuplexModeSpinner) {
2737 SpinnerItem<Integer> duplexModeItem = mDuplexModeSpinnerAdapter.getItem(position);
2738 mPrintJob.getAttributes().setDuplexMode(duplexModeItem.value);
Svetoslava798c0a2014-05-15 10:47:19 -07002739 } else if (spinner == mOrientationSpinner) {
2740 SpinnerItem<Integer> orientationItem = mOrientationSpinnerAdapter.getItem(position);
2741 PrintAttributes attributes = mPrintJob.getAttributes();
Svetoslave3bbb3d2014-06-12 10:43:20 -07002742 if (mMediaSizeSpinner.getSelectedItem() != null) {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002743 boolean isPortrait = attributes.isPortrait();
2744
2745 if (isPortrait != (orientationItem.value == ORIENTATION_PORTRAIT)) {
2746 clearRanges = true;
2747 if (orientationItem.value == ORIENTATION_PORTRAIT) {
2748 attributes.copyFrom(attributes.asPortrait());
2749 } else {
2750 attributes.copyFrom(attributes.asLandscape());
2751 }
Svetoslave3bbb3d2014-06-12 10:43:20 -07002752 }
Svetoslava798c0a2014-05-15 10:47:19 -07002753 }
Svet Ganov525a66b2014-06-14 22:29:00 -07002754 } else if (spinner == mRangeOptionsSpinner) {
2755 if (mRangeOptionsSpinner.getSelectedItemPosition() == 0) {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002756 clearRanges = true;
Svet Ganov525a66b2014-06-14 22:29:00 -07002757 mPageRangeEditText.setText("");
2758 } else if (TextUtils.isEmpty(mPageRangeEditText.getText())) {
2759 mPageRangeEditText.setError("");
2760 }
Svetoslava798c0a2014-05-15 10:47:19 -07002761 }
2762
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002763 if (clearRanges) {
2764 clearPageRanges();
Svetoslava798c0a2014-05-15 10:47:19 -07002765 }
2766
2767 updateOptionsUi();
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002768
2769 if (canUpdateDocument()) {
2770 updateDocument(false);
2771 }
Svetoslava798c0a2014-05-15 10:47:19 -07002772 }
2773
2774 @Override
2775 public void onNothingSelected(AdapterView<?> parent) {
2776 /* do nothing*/
2777 }
2778 }
2779
Svetoslava798c0a2014-05-15 10:47:19 -07002780 private final class SelectAllOnFocusListener implements OnFocusChangeListener {
2781 @Override
2782 public void onFocusChange(View view, boolean hasFocus) {
2783 EditText editText = (EditText) view;
2784 if (!TextUtils.isEmpty(editText.getText())) {
2785 editText.setSelection(editText.getText().length());
2786 }
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002787
2788 if (view == mPageRangeEditText && !hasFocus) {
Philip P. Moltmannc309d712016-03-31 13:49:38 -07002789 updateSelectedPagesFromTextField();
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002790 }
Svetoslava798c0a2014-05-15 10:47:19 -07002791 }
2792 }
2793
2794 private final class RangeTextWatcher implements TextWatcher {
2795 @Override
2796 public void onTextChanged(CharSequence s, int start, int before, int count) {
2797 /* do nothing */
2798 }
2799
2800 @Override
2801 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2802 /* do nothing */
2803 }
2804
2805 @Override
2806 public void afterTextChanged(Editable editable) {
2807 final boolean hadErrors = hasErrors();
Svetoslava798c0a2014-05-15 10:47:19 -07002808 String text = editable.toString();
2809
2810 if (TextUtils.isEmpty(text)) {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002811 if (mPageRangeEditText.getError() == null) {
2812 mPageRangeEditText.setError("");
2813 updateOptionsUi();
2814 }
Svetoslava798c0a2014-05-15 10:47:19 -07002815 return;
2816 }
2817
2818 String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
2819 if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002820 if (mPageRangeEditText.getError() == null) {
2821 mPageRangeEditText.setError("");
2822 updateOptionsUi();
2823 }
Svetoslava798c0a2014-05-15 10:47:19 -07002824 return;
2825 }
2826
2827 PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
Svet Ganov525a66b2014-06-14 22:29:00 -07002828 final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0;
Svetoslava798c0a2014-05-15 10:47:19 -07002829
2830 // The range
2831 Matcher matcher = PATTERN_DIGITS.matcher(text);
2832 while (matcher.find()) {
2833 String numericString = text.substring(matcher.start(), matcher.end()).trim();
2834 if (TextUtils.isEmpty(numericString)) {
2835 continue;
2836 }
2837 final int pageIndex = Integer.parseInt(numericString);
2838 if (pageIndex < 1 || pageIndex > pageCount) {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002839 if (mPageRangeEditText.getError() == null) {
2840 mPageRangeEditText.setError("");
2841 updateOptionsUi();
2842 }
Svetoslava798c0a2014-05-15 10:47:19 -07002843 return;
2844 }
2845 }
2846
2847 // We intentionally do not catch the case of the from page being
2848 // greater than the to page. When computing the requested pages
2849 // we just swap them if necessary.
2850
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002851 if (mPageRangeEditText.getError() != null) {
2852 mPageRangeEditText.setError(null);
Svetoslava798c0a2014-05-15 10:47:19 -07002853 updateOptionsUi();
2854 }
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002855
2856 if (hadErrors && canUpdateDocument()) {
2857 updateDocument(false);
2858 }
Svetoslava798c0a2014-05-15 10:47:19 -07002859 }
2860 }
2861
2862 private final class EditTextWatcher implements TextWatcher {
2863 @Override
2864 public void onTextChanged(CharSequence s, int start, int before, int count) {
2865 /* do nothing */
2866 }
2867
2868 @Override
2869 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2870 /* do nothing */
2871 }
2872
2873 @Override
2874 public void afterTextChanged(Editable editable) {
2875 final boolean hadErrors = hasErrors();
2876
2877 if (editable.length() == 0) {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002878 if (mCopiesEditText.getError() == null) {
2879 mCopiesEditText.setError("");
2880 updateOptionsUi();
2881 }
Svetoslava798c0a2014-05-15 10:47:19 -07002882 return;
2883 }
2884
2885 int copies = 0;
2886 try {
2887 copies = Integer.parseInt(editable.toString());
2888 } catch (NumberFormatException nfe) {
2889 /* ignore */
2890 }
2891
2892 if (copies < MIN_COPIES) {
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002893 if (mCopiesEditText.getError() == null) {
2894 mCopiesEditText.setError("");
2895 updateOptionsUi();
2896 }
Svetoslava798c0a2014-05-15 10:47:19 -07002897 return;
2898 }
2899
2900 mPrintJob.setCopies(copies);
2901
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -07002902 if (mCopiesEditText.getError() != null) {
2903 mCopiesEditText.setError(null);
2904 updateOptionsUi();
2905 }
Svetoslava798c0a2014-05-15 10:47:19 -07002906
2907 if (hadErrors && canUpdateDocument()) {
Svetoslav62ce3322014-09-04 21:17:17 -07002908 updateDocument(false);
Svetoslava798c0a2014-05-15 10:47:19 -07002909 }
2910 }
2911 }
2912
2913 private final class ProgressMessageController implements Runnable {
2914 private static final long PROGRESS_TIMEOUT_MILLIS = 1000;
2915
2916 private final Handler mHandler;
2917
2918 private boolean mPosted;
2919
Philip P. Moltmann7b92d3c2016-03-31 14:18:47 -07002920 /** State before run was executed */
2921 private int mPreviousState = -1;
2922
Svetoslava798c0a2014-05-15 10:47:19 -07002923 public ProgressMessageController(Context context) {
2924 mHandler = new Handler(context.getMainLooper(), null, false);
2925 }
2926
2927 public void post() {
Philip P. Moltmann7b92d3c2016-03-31 14:18:47 -07002928 if (mState == STATE_UPDATE_SLOW) {
2929 setState(STATE_UPDATE_SLOW);
2930 ensureProgressUiShown();
2931 updateOptionsUi();
2932
2933 return;
2934 } else if (mPosted) {
Svetoslava798c0a2014-05-15 10:47:19 -07002935 return;
2936 }
Philip P. Moltmann7b92d3c2016-03-31 14:18:47 -07002937 mPreviousState = -1;
Svetoslava798c0a2014-05-15 10:47:19 -07002938 mPosted = true;
2939 mHandler.postDelayed(this, PROGRESS_TIMEOUT_MILLIS);
2940 }
2941
Philip P. Moltmann7b92d3c2016-03-31 14:18:47 -07002942 private int getStateAfterCancel() {
2943 if (mPreviousState == -1) {
2944 return mState;
2945 } else {
2946 return mPreviousState;
2947 }
2948 }
2949
2950 public int cancel() {
Svetoslava798c0a2014-05-15 10:47:19 -07002951 if (!mPosted) {
Philip P. Moltmann7b92d3c2016-03-31 14:18:47 -07002952 return getStateAfterCancel();
Svetoslava798c0a2014-05-15 10:47:19 -07002953 }
2954 mPosted = false;
2955 mHandler.removeCallbacks(this);
Philip P. Moltmann7b92d3c2016-03-31 14:18:47 -07002956
2957 return getStateAfterCancel();
Svetoslava798c0a2014-05-15 10:47:19 -07002958 }
2959
2960 @Override
2961 public void run() {
Svet Ganov525a66b2014-06-14 22:29:00 -07002962 mPosted = false;
Philip P. Moltmann7b92d3c2016-03-31 14:18:47 -07002963 mPreviousState = mState;
Svet Ganov525a66b2014-06-14 22:29:00 -07002964 setState(STATE_UPDATE_SLOW);
Svetoslava798c0a2014-05-15 10:47:19 -07002965 ensureProgressUiShown();
Svet Ganov525a66b2014-06-14 22:29:00 -07002966 updateOptionsUi();
Svetoslava798c0a2014-05-15 10:47:19 -07002967 }
2968 }
Svetoslav62ce3322014-09-04 21:17:17 -07002969
Svetoslavbec22be2014-09-25 13:03:20 -07002970 private static final class DocumentTransformer implements ServiceConnection {
Svetoslav62ce3322014-09-04 21:17:17 -07002971 private static final String TEMP_FILE_PREFIX = "print_job";
2972 private static final String TEMP_FILE_EXTENSION = ".pdf";
2973
2974 private final Context mContext;
2975
2976 private final MutexFileProvider mFileProvider;
2977
2978 private final PrintJobInfo mPrintJob;
2979
2980 private final PageRange[] mPagesToShred;
2981
Svetoslavbec22be2014-09-25 13:03:20 -07002982 private final PrintAttributes mAttributesToApply;
2983
Svetoslav62ce3322014-09-04 21:17:17 -07002984 private final Runnable mCallback;
2985
Svetoslavbec22be2014-09-25 13:03:20 -07002986 public DocumentTransformer(Context context, PrintJobInfo printJob,
2987 MutexFileProvider fileProvider, PrintAttributes attributes,
2988 Runnable callback) {
Svetoslav62ce3322014-09-04 21:17:17 -07002989 mContext = context;
2990 mPrintJob = printJob;
2991 mFileProvider = fileProvider;
2992 mCallback = callback;
2993 mPagesToShred = computePagesToShred(mPrintJob);
Svetoslavbec22be2014-09-25 13:03:20 -07002994 mAttributesToApply = attributes;
Svetoslav62ce3322014-09-04 21:17:17 -07002995 }
2996
Svetoslavbec22be2014-09-25 13:03:20 -07002997 public void transform() {
Svetoslav62ce3322014-09-04 21:17:17 -07002998 // If we have only the pages we want, done.
Svetoslavbec22be2014-09-25 13:03:20 -07002999 if (mPagesToShred.length <= 0 && mAttributesToApply == null) {
Svetoslav62ce3322014-09-04 21:17:17 -07003000 mCallback.run();
3001 return;
3002 }
3003
3004 // Bind to the manipulation service and the work
3005 // will be performed upon connection to the service.
3006 Intent intent = new Intent(PdfManipulationService.ACTION_GET_EDITOR);
3007 intent.setClass(mContext, PdfManipulationService.class);
3008 mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
3009 }
3010
3011 @Override
3012 public void onServiceConnected(ComponentName name, IBinder service) {
3013 final IPdfEditor editor = IPdfEditor.Stub.asInterface(service);
3014 new AsyncTask<Void, Void, Void>() {
3015 @Override
3016 protected Void doInBackground(Void... params) {
Svetoslavfb3532ee2014-09-15 18:28:51 -07003017 // It's OK to access the data members as they are
3018 // final and this code is the last one to touch
3019 // them as shredding is the very last step, so the
3020 // UI is not interactive at this point.
Svetoslavbec22be2014-09-25 13:03:20 -07003021 doTransform(editor);
Svetoslavfb3532ee2014-09-15 18:28:51 -07003022 updatePrintJob();
Svetoslav62ce3322014-09-04 21:17:17 -07003023 return null;
3024 }
Svetoslavfb3532ee2014-09-15 18:28:51 -07003025
3026 @Override
3027 protected void onPostExecute(Void aVoid) {
Svetoslavbec22be2014-09-25 13:03:20 -07003028 mContext.unbindService(DocumentTransformer.this);
Svetoslavfb3532ee2014-09-15 18:28:51 -07003029 mCallback.run();
3030 }
Svetoslavb75632c2014-09-17 18:38:27 -07003031 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
Svetoslav62ce3322014-09-04 21:17:17 -07003032 }
3033
3034 @Override
3035 public void onServiceDisconnected(ComponentName name) {
3036 /* do nothing */
3037 }
3038
Svetoslavbec22be2014-09-25 13:03:20 -07003039 private void doTransform(IPdfEditor editor) {
Svetoslav62ce3322014-09-04 21:17:17 -07003040 File tempFile = null;
3041 ParcelFileDescriptor src = null;
3042 ParcelFileDescriptor dst = null;
3043 InputStream in = null;
3044 OutputStream out = null;
3045 try {
3046 File jobFile = mFileProvider.acquireFile(null);
3047 src = ParcelFileDescriptor.open(jobFile, ParcelFileDescriptor.MODE_READ_WRITE);
3048
3049 // Open the document.
3050 editor.openDocument(src);
3051
3052 // We passed the fd over IPC, close this one.
3053 src.close();
3054
3055 // Drop the pages.
3056 editor.removePages(mPagesToShred);
3057
Svetoslavbec22be2014-09-25 13:03:20 -07003058 // Apply print attributes if needed.
3059 if (mAttributesToApply != null) {
3060 editor.applyPrintAttributes(mAttributesToApply);
3061 }
3062
Svetoslav62ce3322014-09-04 21:17:17 -07003063 // Write the modified PDF to a temp file.
3064 tempFile = File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_EXTENSION,
3065 mContext.getCacheDir());
3066 dst = ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_WRITE);
3067 editor.write(dst);
3068 dst.close();
3069
3070 // Close the document.
3071 editor.closeDocument();
3072
3073 // Copy the temp file over the print job file.
3074 jobFile.delete();
3075 in = new FileInputStream(tempFile);
3076 out = new FileOutputStream(jobFile);
3077 Streams.copy(in, out);
3078 } catch (IOException|RemoteException e) {
3079 Log.e(LOG_TAG, "Error dropping pages", e);
3080 } finally {
3081 IoUtils.closeQuietly(src);
3082 IoUtils.closeQuietly(dst);
3083 IoUtils.closeQuietly(in);
3084 IoUtils.closeQuietly(out);
3085 if (tempFile != null) {
3086 tempFile.delete();
3087 }
Svetoslav56683482014-09-23 16:22:42 -07003088 mFileProvider.releaseFile();
Svetoslav62ce3322014-09-04 21:17:17 -07003089 }
3090 }
3091
3092 private void updatePrintJob() {
3093 // Update the print job pages.
3094 final int newPageCount = PageRangeUtils.getNormalizedPageCount(
3095 mPrintJob.getPages(), 0);
3096 mPrintJob.setPages(new PageRange[]{PageRange.ALL_PAGES});
3097
3098 // Update the print job document info.
3099 PrintDocumentInfo oldDocInfo = mPrintJob.getDocumentInfo();
3100 PrintDocumentInfo newDocInfo = new PrintDocumentInfo
3101 .Builder(oldDocInfo.getName())
3102 .setContentType(oldDocInfo.getContentType())
3103 .setPageCount(newPageCount)
3104 .build();
3105 mPrintJob.setDocumentInfo(newDocInfo);
3106 }
3107
3108 private static PageRange[] computePagesToShred(PrintJobInfo printJob) {
3109 List<PageRange> rangesToShred = new ArrayList<>();
3110 PageRange previousRange = null;
3111
Svetoslav62ce3322014-09-04 21:17:17 -07003112 PageRange[] printedPages = printJob.getPages();
3113 final int rangeCount = printedPages.length;
3114 for (int i = 0; i < rangeCount; i++) {
Philip P. Moltmann9e4bbc62016-03-31 15:39:16 -07003115 PageRange range = printedPages[i];
Svetoslav62ce3322014-09-04 21:17:17 -07003116
3117 if (previousRange == null) {
3118 final int startPageIdx = 0;
3119 final int endPageIdx = range.getStart() - 1;
3120 if (startPageIdx <= endPageIdx) {
3121 PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
3122 rangesToShred.add(removedRange);
3123 }
3124 } else {
3125 final int startPageIdx = previousRange.getEnd() + 1;
3126 final int endPageIdx = range.getStart() - 1;
3127 if (startPageIdx <= endPageIdx) {
3128 PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
3129 rangesToShred.add(removedRange);
3130 }
3131 }
3132
3133 if (i == rangeCount - 1) {
Philip P. Moltmann9e4bbc62016-03-31 15:39:16 -07003134 if (range.getEnd() != Integer.MAX_VALUE) {
3135 rangesToShred.add(new PageRange(range.getEnd() + 1, Integer.MAX_VALUE));
Svetoslav62ce3322014-09-04 21:17:17 -07003136 }
3137 }
3138
3139 previousRange = range;
3140 }
3141
3142 PageRange[] result = new PageRange[rangesToShred.size()];
3143 rangesToShred.toArray(result);
3144 return result;
3145 }
3146 }
Svet Ganov561b8932014-09-02 21:51:45 +00003147}