MyPrintService.java revision c02ac3775cd61bba799dbf70a707507410b3ff13
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package foo.bar.printservice;
18
19import android.annotation.NonNull;
20import android.app.PendingIntent;
21import android.content.Intent;
22import android.graphics.BitmapFactory;
23import android.graphics.drawable.Icon;
24import android.net.Uri;
25import android.os.AsyncTask;
26import android.os.Handler;
27import android.os.Looper;
28import android.os.Message;
29import android.os.ParcelFileDescriptor;
30import android.print.PrintAttributes;
31import android.print.PrintAttributes.Margins;
32import android.print.PrintAttributes.MediaSize;
33import android.print.PrintAttributes.Resolution;
34import android.print.PrintJobId;
35import android.print.PrinterCapabilitiesInfo;
36import android.print.PrinterId;
37import android.print.PrinterInfo;
38import android.printservice.CustomPrinterIconCallback;
39import android.printservice.PrintJob;
40import android.printservice.PrintService;
41import android.printservice.PrinterDiscoverySession;
42import android.util.ArrayMap;
43import android.util.Log;
44
45import java.io.BufferedInputStream;
46import java.io.BufferedOutputStream;
47import java.io.File;
48import java.io.FileInputStream;
49import java.io.FileOutputStream;
50import java.io.IOException;
51import java.io.InputStream;
52import java.io.OutputStream;
53import java.util.ArrayList;
54import java.util.List;
55import java.util.Map;
56
57public class MyPrintService extends PrintService {
58
59    private static final String LOG_TAG = "MyPrintService";
60
61    private static final long STANDARD_DELAY_MILLIS = 10000000;
62
63    static final String INTENT_EXTRA_ACTION_TYPE = "INTENT_EXTRA_ACTION_TYPE";
64    static final String INTENT_EXTRA_PRINT_JOB_ID = "INTENT_EXTRA_PRINT_JOB_ID";
65
66    static final int ACTION_TYPE_ON_PRINT_JOB_PENDING = 1;
67    static final int ACTION_TYPE_ON_REQUEST_CANCEL_PRINT_JOB = 2;
68
69    private static final Object sLock = new Object();
70
71    private static MyPrintService sInstance;
72
73    private Handler mHandler;
74
75    private AsyncTask<ParcelFileDescriptor, Void, Void> mFakePrintTask;
76
77    private FakePrinterDiscoverySession mSession;
78
79    private final Map<PrintJobId, PrintJob> mProcessedPrintJobs =
80            new ArrayMap<PrintJobId, PrintJob>();
81
82    public static MyPrintService peekInstance() {
83        synchronized (sLock) {
84            return sInstance;
85        }
86    }
87
88    @Override
89    protected void onConnected() {
90        Log.i(LOG_TAG, "#onConnected()");
91        mHandler = new MyHandler(getMainLooper());
92        synchronized (sLock) {
93            sInstance = this;
94        }
95    }
96
97    @Override
98    protected void onDisconnected() {
99        Log.i(LOG_TAG, "#onDisconnected()");
100        if (mSession != null) {
101            mSession.cancellAddingFakePrinters();
102        }
103        synchronized (sLock) {
104            sInstance = null;
105        }
106    }
107
108    @Override
109    protected PrinterDiscoverySession onCreatePrinterDiscoverySession() {
110        Log.i(LOG_TAG, "#onCreatePrinterDiscoverySession()");
111        return new FakePrinterDiscoverySession();
112    }
113
114    @Override
115    protected void onRequestCancelPrintJob(final PrintJob printJob) {
116        Log.i(LOG_TAG, "#onRequestCancelPrintJob()");
117        mProcessedPrintJobs.put(printJob.getId(), printJob);
118        Intent intent = new Intent(this, MyDialogActivity.class);
119        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
120        intent.putExtra(INTENT_EXTRA_PRINT_JOB_ID, printJob.getId());
121        intent.putExtra(INTENT_EXTRA_ACTION_TYPE, ACTION_TYPE_ON_REQUEST_CANCEL_PRINT_JOB);
122        startActivity(intent);
123    }
124
125    @Override
126    public void onPrintJobQueued(final PrintJob printJob) {
127        Log.i(LOG_TAG, "#onPrintJobQueued()");
128        mProcessedPrintJobs.put(printJob.getId(), printJob);
129        if (printJob.isQueued()) {
130            printJob.start();
131        }
132
133        Intent intent = new Intent(this, MyDialogActivity.class);
134        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
135        intent.putExtra(INTENT_EXTRA_PRINT_JOB_ID, printJob.getId());
136        intent.putExtra(INTENT_EXTRA_ACTION_TYPE, ACTION_TYPE_ON_PRINT_JOB_PENDING);
137        startActivity(intent);
138    }
139
140    void handleRequestCancelPrintJob(PrintJobId printJobId) {
141        PrintJob printJob = mProcessedPrintJobs.get(printJobId);
142        if (printJob == null) {
143            return;
144        }
145        mProcessedPrintJobs.remove(printJobId);
146        if (printJob.isQueued() || printJob.isStarted() || printJob.isBlocked()) {
147            mHandler.removeMessages(MyHandler.MSG_HANDLE_DO_PRINT_JOB);
148            mHandler.removeMessages(MyHandler.MSG_HANDLE_FAIL_PRINT_JOB);
149            printJob.cancel();
150        }
151    }
152
153    void handleFailPrintJobDelayed(PrintJobId printJobId) {
154        Message message = mHandler.obtainMessage(
155                MyHandler.MSG_HANDLE_FAIL_PRINT_JOB, printJobId);
156        mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS);
157    }
158
159    void handleFailPrintJob(PrintJobId printJobId) {
160        PrintJob printJob = mProcessedPrintJobs.get(printJobId);
161        if (printJob == null) {
162            return;
163        }
164        mProcessedPrintJobs.remove(printJobId);
165        if (printJob.isQueued() || printJob.isStarted()) {
166            printJob.fail(getString(R.string.fail_reason));
167        }
168    }
169
170    void handleBlockPrintJobDelayed(PrintJobId printJobId) {
171        Message message = mHandler.obtainMessage(
172                MyHandler.MSG_HANDLE_BLOCK_PRINT_JOB, printJobId);
173        mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS);
174    }
175
176    void handleBlockPrintJob(PrintJobId printJobId) {
177        final PrintJob printJob = mProcessedPrintJobs.get(printJobId);
178        if (printJob == null) {
179            return;
180        }
181
182        if (printJob.isStarted()) {
183            printJob.block("Gimme some rest, dude");
184        }
185    }
186
187    void handleBlockAndDelayedUnblockPrintJob(PrintJobId printJobId) {
188        handleBlockPrintJob(printJobId);
189
190        Message message = mHandler.obtainMessage(
191                MyHandler.MSG_HANDLE_UNBLOCK_PRINT_JOB, printJobId);
192        mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS);
193    }
194
195    void handleUnblockPrintJob(PrintJobId printJobId) {
196        final PrintJob printJob = mProcessedPrintJobs.get(printJobId);
197        if (printJob == null) {
198            return;
199        }
200
201        if (printJob.isBlocked()) {
202            printJob.start();
203        }
204    }
205
206    void handleQueuedPrintJobDelayed(PrintJobId printJobId) {
207        final PrintJob printJob = mProcessedPrintJobs.get(printJobId);
208        if (printJob == null) {
209            return;
210        }
211
212        if (printJob.isQueued()) {
213            printJob.start();
214        }
215        Message message = mHandler.obtainMessage(
216                MyHandler.MSG_HANDLE_DO_PRINT_JOB, printJobId);
217        mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS);
218    }
219
220    /**
221     * Pretend that the print job has progressed.
222     *
223     * @param printJobId ID of the print job to progress
224     * @param progress the new value to progress to
225     */
226    void handlePrintJobProgress(@NonNull PrintJobId printJobId, int progress) {
227        final PrintJob printJob = mProcessedPrintJobs.get(printJobId);
228        if (printJob == null) {
229            return;
230        }
231
232        if (printJob.isQueued()) {
233            printJob.start();
234        }
235
236        if (progress == 100) {
237            handleQueuedPrintJob(printJobId);
238        } else {
239            printJob.setProgress((float)progress / 100);
240            printJob.setStatus("Printing progress: " + progress + "%");
241
242            Message message = mHandler.obtainMessage(
243                    MyHandler.MSG_HANDLE_PRINT_JOB_PROGRESS, progress + 10, 0, printJobId);
244            mHandler.sendMessageDelayed(message, 1000);
245        }
246    }
247
248    void handleQueuedPrintJob(PrintJobId printJobId) {
249        final PrintJob printJob = mProcessedPrintJobs.get(printJobId);
250        if (printJob == null) {
251            return;
252        }
253
254        if (printJob.isQueued()) {
255            printJob.start();
256        }
257
258        try {
259            final File file = File.createTempFile(this.getClass().getSimpleName(), ".pdf",
260                    getFilesDir());
261            mFakePrintTask = new AsyncTask<ParcelFileDescriptor, Void, Void>() {
262                @Override
263                protected Void doInBackground(ParcelFileDescriptor... params) {
264                    InputStream in = new BufferedInputStream(new FileInputStream(
265                            params[0].getFileDescriptor()));
266                    OutputStream out = null;
267                    try {
268                        out = new BufferedOutputStream(new FileOutputStream(file));
269                        final byte[] buffer = new byte[8192];
270                        while (true) {
271                            if (isCancelled()) {
272                                break;
273                            }
274                            final int readByteCount = in.read(buffer);
275                            if (readByteCount < 0) {
276                                break;
277                            }
278                            out.write(buffer, 0, readByteCount);
279                        }
280                    } catch (IOException ioe) {
281                        throw new RuntimeException(ioe);
282                    } finally {
283                        if (in != null) {
284                            try {
285                                in.close();
286                            } catch (IOException ioe) {
287                                /* ignore */
288                            }
289                        }
290                        if (out != null) {
291                            try {
292                                out.close();
293                            } catch (IOException ioe) {
294                                /* ignore */
295                            }
296                        }
297                        if (isCancelled()) {
298                            file.delete();
299                        }
300                    }
301                    return null;
302                }
303
304                @Override
305                protected void onPostExecute(Void result) {
306                    if (printJob.isStarted()) {
307                        printJob.complete();
308                    }
309
310                    file.setReadable(true, false);
311
312                    // Quick and dirty to show the file - use a content provider instead.
313                    Intent intent = new Intent(Intent.ACTION_VIEW);
314                    intent.setDataAndType(Uri.fromFile(file), "application/pdf");
315                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
316                    startActivity(intent, null);
317
318                    mFakePrintTask = null;
319                }
320
321                @Override
322                protected void onCancelled(Void result) {
323                    if (printJob.isStarted()) {
324                        printJob.cancel();
325                    }
326                }
327            };
328            mFakePrintTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
329                    printJob.getDocument().getData());
330        } catch (IOException e) {
331            Log.e(LOG_TAG, "Could not create temporary file: %s", e);
332            return;
333        }
334    }
335
336    private final class MyHandler extends Handler {
337        public static final int MSG_HANDLE_DO_PRINT_JOB = 1;
338        public static final int MSG_HANDLE_FAIL_PRINT_JOB = 2;
339        public static final int MSG_HANDLE_BLOCK_PRINT_JOB = 3;
340        public static final int MSG_HANDLE_UNBLOCK_PRINT_JOB = 4;
341        public static final int MSG_HANDLE_PRINT_JOB_PROGRESS = 5;
342
343        public MyHandler(Looper looper) {
344            super(looper);
345        }
346
347        @Override
348        public void handleMessage(Message message) {
349            switch (message.what) {
350                case MSG_HANDLE_DO_PRINT_JOB: {
351                    PrintJobId printJobId = (PrintJobId) message.obj;
352                    handleQueuedPrintJob(printJobId);
353                } break;
354
355                case MSG_HANDLE_FAIL_PRINT_JOB: {
356                    PrintJobId printJobId = (PrintJobId) message.obj;
357                    handleFailPrintJob(printJobId);
358                } break;
359
360                case MSG_HANDLE_BLOCK_PRINT_JOB: {
361                    PrintJobId printJobId = (PrintJobId) message.obj;
362                    handleBlockPrintJob(printJobId);
363                } break;
364
365                case MSG_HANDLE_UNBLOCK_PRINT_JOB: {
366                    PrintJobId printJobId = (PrintJobId) message.obj;
367                    handleUnblockPrintJob(printJobId);
368                } break;
369
370                case MSG_HANDLE_PRINT_JOB_PROGRESS: {
371                    PrintJobId printJobId = (PrintJobId) message.obj;
372                    handlePrintJobProgress(printJobId, message.arg1);
373                } break;
374            }
375        }
376    }
377
378    private final class FakePrinterDiscoverySession extends  PrinterDiscoverySession {
379        private final Handler mSesionHandler = new SessionHandler(getMainLooper());
380
381        private final List<PrinterInfo> mFakePrinters = new ArrayList<PrinterInfo>();
382
383        public FakePrinterDiscoverySession() {
384            for (int i = 0; i < 6; i++) {
385                String name = "Printer " + i;
386
387                PrinterInfo.Builder builder = new PrinterInfo.Builder(generatePrinterId(name), name,
388                        (i == 1 || i == 2) ? PrinterInfo.STATUS_UNAVAILABLE
389                                : PrinterInfo.STATUS_IDLE);
390
391                if (i != 3) {
392                    builder.setDescription("Launch a menu to select behavior.");
393                }
394
395                if (i != 4) {
396                    builder.setIconResourceId(R.drawable.printer);
397                }
398
399                if (i % 2 == 0) {
400                    Intent infoIntent = new Intent(MyPrintService.this, InfoActivity.class);
401                    infoIntent.putExtra(InfoActivity.PRINTER_NAME, name);
402                    PendingIntent infoPendingIntent = PendingIntent.getActivity(
403                            getApplicationContext(),
404                            i, infoIntent, PendingIntent.FLAG_UPDATE_CURRENT);
405
406                    builder.setInfoIntent(infoPendingIntent);
407                }
408
409                if (i == 5) {
410                    builder.setHasCustomPrinterIcon();
411                }
412
413                mFakePrinters.add(builder.build());
414            }
415        }
416
417        @Override
418        public void onDestroy() {
419            Log.i(LOG_TAG, "FakePrinterDiscoverySession#onDestroy()");
420            mSesionHandler.removeMessages(SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS);
421        }
422
423        @Override
424        public void onStartPrinterDiscovery(List<PrinterId> priorityList) {
425            Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStartPrinterDiscovery()");
426            Message message1 = mSesionHandler.obtainMessage(
427                    SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS, this);
428            mSesionHandler.sendMessageDelayed(message1, 0);
429        }
430
431        @Override
432        public void onStopPrinterDiscovery() {
433            Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStopPrinterDiscovery()");
434            cancellAddingFakePrinters();
435        }
436
437        @Override
438        public void onStartPrinterStateTracking(PrinterId printerId) {
439            Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStartPrinterStateTracking()");
440
441            final int printerCount = mFakePrinters.size();
442            for (int i = printerCount - 1; i >= 0; i--) {
443                PrinterInfo printer = mFakePrinters.remove(i);
444
445                if (printer.getId().equals(printerId)) {
446                    PrinterCapabilitiesInfo capabilities = new PrinterCapabilitiesInfo.Builder(
447                            printerId)
448                                    .setMinMargins(new Margins(200, 200, 200, 200))
449                                    .addMediaSize(MediaSize.ISO_A4, true)
450                                    .addMediaSize(MediaSize.NA_GOVT_LETTER, false)
451                                    .addMediaSize(MediaSize.JPN_YOU4, false)
452                                    .addResolution(new Resolution("R1", getString(
453                                            R.string.resolution_200x200), 200, 200), false)
454                                    .addResolution(new Resolution("R2", getString(
455                                            R.string.resolution_300x300), 300, 300), true)
456                                    .setColorModes(PrintAttributes.COLOR_MODE_COLOR
457                                            | PrintAttributes.COLOR_MODE_MONOCHROME,
458                                            PrintAttributes.COLOR_MODE_MONOCHROME)
459                                    .setDuplexModes(PrintAttributes.DUPLEX_MODE_LONG_EDGE
460                                            | PrintAttributes.DUPLEX_MODE_NONE,
461                                            PrintAttributes.DUPLEX_MODE_LONG_EDGE)
462                                    .build();
463
464                    printer = new PrinterInfo.Builder(printer)
465                            .setCapabilities(capabilities)
466                            .build();
467                }
468
469                mFakePrinters.add(printer);
470            }
471
472            addPrinters(mFakePrinters);
473        }
474
475        @Override
476        public void onRequestCustomPrinterIcon(final PrinterId printerId,
477                final CustomPrinterIconCallback callbacks) {
478            Log.i(LOG_TAG, "FakePrinterDiscoverySession#onRequestCustomPrinterIcon() " + printerId);
479
480            // Pretend the bitmap icon takes 5 seconds to load
481            mSesionHandler.postDelayed(new Runnable() {
482                @Override
483                public void run() {
484                    final int printerCount = mFakePrinters.size();
485                    for (int i = printerCount - 1; i >= 0; i--) {
486                        callbacks.onCustomPrinterIconLoaded(Icon.createWithBitmap(
487                                BitmapFactory.decodeResource(getResources(), R.raw.red_printer)));
488                    }
489                }
490            }, 5000);
491        }
492
493        @Override
494        public void onValidatePrinters(List<PrinterId> printerIds) {
495            Log.i(LOG_TAG, "FakePrinterDiscoverySession#onValidatePrinters() " + printerIds);
496        }
497
498        @Override
499        public void onStopPrinterStateTracking(PrinterId printerId) {
500            Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStopPrinterStateTracking()");
501        }
502
503        private void addFirstBatchFakePrinters() {
504            List<PrinterInfo> printers = mFakePrinters.subList(0, mFakePrinters.size());
505            addPrinters(printers);
506        }
507
508        private void cancellAddingFakePrinters() {
509            mSesionHandler.removeMessages(SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS);
510        }
511
512        final class SessionHandler extends Handler {
513            public static final int MSG_ADD_FIRST_BATCH_FAKE_PRINTERS = 1;
514
515            public SessionHandler(Looper looper) {
516                super(looper);
517            }
518
519            @Override
520            public void handleMessage(Message message) {
521                switch (message.what) {
522                    case MSG_ADD_FIRST_BATCH_FAKE_PRINTERS: {
523                        addFirstBatchFakePrinters();
524                    } break;
525                }
526            }
527        }
528    }
529}
530