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