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