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 com.android.server.print;
18
19import android.annotation.FloatRange;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.StringRes;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.ServiceConnection;
27import android.content.pm.ParceledListSlice;
28import android.graphics.drawable.Icon;
29import android.os.Binder;
30import android.os.Handler;
31import android.os.IBinder;
32import android.os.IBinder.DeathRecipient;
33import android.os.Looper;
34import android.os.Message;
35import android.os.ParcelFileDescriptor;
36import android.os.RemoteException;
37import android.os.UserHandle;
38import android.print.PrintJobId;
39import android.print.PrintJobInfo;
40import android.print.PrintManager;
41import android.print.PrinterId;
42import android.print.PrinterInfo;
43import android.printservice.IPrintService;
44import android.printservice.IPrintServiceClient;
45import android.util.Slog;
46
47import java.io.PrintWriter;
48import java.lang.ref.WeakReference;
49import java.util.ArrayList;
50import java.util.List;
51
52/**
53 * This class represents a remote print service. It abstracts away the binding
54 * and unbinding from the remote implementation. Clients can call methods of
55 * this class without worrying about when and how to bind/unbind.
56 */
57final class RemotePrintService implements DeathRecipient {
58
59    private static final String LOG_TAG = "RemotePrintService";
60
61    private static final boolean DEBUG = false;
62
63    private final Context mContext;
64
65    private final ComponentName mComponentName;
66
67    private final Intent mIntent;
68
69    private final RemotePrintSpooler mSpooler;
70
71    private final PrintServiceCallbacks mCallbacks;
72
73    private final int mUserId;
74
75    private final List<Runnable> mPendingCommands = new ArrayList<Runnable>();
76
77    private final ServiceConnection mServiceConnection = new RemoteServiceConneciton();
78
79    private final RemotePrintServiceClient mPrintServiceClient;
80
81    private final Handler mHandler;
82
83    private IPrintService mPrintService;
84
85    private boolean mBinding;
86
87    private boolean mDestroyed;
88
89    private boolean mHasActivePrintJobs;
90
91    private boolean mHasPrinterDiscoverySession;
92
93    private boolean mServiceDied;
94
95    private List<PrinterId> mDiscoveryPriorityList;
96
97    private List<PrinterId> mTrackedPrinterList;
98
99    public static interface PrintServiceCallbacks {
100        public void onPrintersAdded(List<PrinterInfo> printers);
101        public void onPrintersRemoved(List<PrinterId> printerIds);
102        public void onServiceDied(RemotePrintService service);
103
104        /**
105         * Handle that a custom icon for a printer was loaded.
106         *
107         * @param printerId the id of the printer the icon belongs to
108         * @param icon the icon that was loaded
109         * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
110         */
111        public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon);
112    }
113
114    public RemotePrintService(Context context, ComponentName componentName, int userId,
115            RemotePrintSpooler spooler, PrintServiceCallbacks callbacks) {
116        mContext = context;
117        mCallbacks = callbacks;
118        mComponentName = componentName;
119        mIntent = new Intent().setComponent(mComponentName);
120        mUserId = userId;
121        mSpooler = spooler;
122        mHandler = new MyHandler(context.getMainLooper());
123        mPrintServiceClient = new RemotePrintServiceClient(this);
124    }
125
126    public ComponentName getComponentName() {
127        return mComponentName;
128    }
129
130    public void destroy() {
131        mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY);
132    }
133
134    private void handleDestroy() {
135        // Stop tracking printers.
136        stopTrackingAllPrinters();
137
138        // Stop printer discovery.
139        if (mDiscoveryPriorityList != null) {
140            handleStopPrinterDiscovery();
141        }
142
143        // Destroy the discovery session.
144        if (mHasPrinterDiscoverySession) {
145            handleDestroyPrinterDiscoverySession();
146        }
147
148        // Unbind.
149        ensureUnbound();
150
151        // Done
152        mDestroyed = true;
153    }
154
155    @Override
156    public void binderDied() {
157        mHandler.sendEmptyMessage(MyHandler.MSG_BINDER_DIED);
158    }
159
160    private void handleBinderDied() {
161        if (mPrintService != null) {
162            mPrintService.asBinder().unlinkToDeath(this, 0);
163        }
164
165        mPrintService = null;
166        mServiceDied = true;
167        mCallbacks.onServiceDied(this);
168    }
169
170    public void onAllPrintJobsHandled() {
171        mHandler.sendEmptyMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED);
172    }
173
174    private void handleOnAllPrintJobsHandled() {
175        mHasActivePrintJobs = false;
176        if (!isBound()) {
177            // The service is dead and neither has active jobs nor discovery
178            // session, so ensure we are unbound since the service has no work.
179            if (mServiceDied && !mHasPrinterDiscoverySession) {
180                ensureUnbound();
181                return;
182            }
183            ensureBound();
184            mPendingCommands.add(new Runnable() {
185                @Override
186                public void run() {
187                    handleOnAllPrintJobsHandled();
188                }
189            });
190        } else {
191            if (DEBUG) {
192                Slog.i(LOG_TAG, "[user: " + mUserId + "] onAllPrintJobsHandled()");
193            }
194            // If the service has a printer discovery session
195            // created we should not disconnect from it just yet.
196            if (!mHasPrinterDiscoverySession) {
197                ensureUnbound();
198            }
199        }
200    }
201
202    public void onRequestCancelPrintJob(PrintJobInfo printJob) {
203        mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_CANCEL_PRINT_JOB,
204                printJob).sendToTarget();
205    }
206
207    private void handleRequestCancelPrintJob(final PrintJobInfo printJob) {
208        if (!isBound()) {
209            ensureBound();
210            mPendingCommands.add(new Runnable() {
211                @Override
212                public void run() {
213                    handleRequestCancelPrintJob(printJob);
214                }
215            });
216        } else {
217            if (DEBUG) {
218                Slog.i(LOG_TAG, "[user: " + mUserId + "] requestCancelPrintJob()");
219            }
220            try {
221                mPrintService.requestCancelPrintJob(printJob);
222            } catch (RemoteException re) {
223                Slog.e(LOG_TAG, "Error canceling a pring job.", re);
224            }
225        }
226    }
227
228    public void onPrintJobQueued(PrintJobInfo printJob) {
229        mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED,
230                printJob).sendToTarget();
231    }
232
233    private void handleOnPrintJobQueued(final PrintJobInfo printJob) {
234        mHasActivePrintJobs = true;
235        if (!isBound()) {
236            ensureBound();
237            mPendingCommands.add(new Runnable() {
238                @Override
239                public void run() {
240                    handleOnPrintJobQueued(printJob);
241                }
242            });
243        } else {
244            if (DEBUG) {
245                Slog.i(LOG_TAG, "[user: " + mUserId + "] onPrintJobQueued()");
246            }
247            try {
248                mPrintService.onPrintJobQueued(printJob);
249            } catch (RemoteException re) {
250                Slog.e(LOG_TAG, "Error announcing queued pring job.", re);
251            }
252        }
253    }
254
255    public void createPrinterDiscoverySession() {
256        mHandler.sendEmptyMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
257    }
258
259    private void handleCreatePrinterDiscoverySession() {
260        mHasPrinterDiscoverySession = true;
261        if (!isBound()) {
262            ensureBound();
263            mPendingCommands.add(new Runnable() {
264                @Override
265                public void run() {
266                    handleCreatePrinterDiscoverySession();
267                }
268            });
269        } else {
270            if (DEBUG) {
271                Slog.i(LOG_TAG, "[user: " + mUserId + "] createPrinterDiscoverySession()");
272            }
273            try {
274                mPrintService.createPrinterDiscoverySession();
275            } catch (RemoteException re) {
276                Slog.e(LOG_TAG, "Error creating printer discovery session.", re);
277            }
278        }
279    }
280
281    public void destroyPrinterDiscoverySession() {
282        mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
283    }
284
285    private void handleDestroyPrinterDiscoverySession() {
286        mHasPrinterDiscoverySession = false;
287        if (!isBound()) {
288            // The service is dead and neither has active jobs nor discovery
289            // session, so ensure we are unbound since the service has no work.
290            if (mServiceDied && !mHasActivePrintJobs) {
291                ensureUnbound();
292                return;
293            }
294            ensureBound();
295            mPendingCommands.add(new Runnable() {
296                @Override
297                public void run() {
298                    handleDestroyPrinterDiscoverySession();
299                }
300            });
301        } else {
302            if (DEBUG) {
303                Slog.i(LOG_TAG, "[user: " + mUserId + "] destroyPrinterDiscoverySession()");
304            }
305            try {
306                mPrintService.destroyPrinterDiscoverySession();
307            } catch (RemoteException re) {
308                Slog.e(LOG_TAG, "Error destroying printer dicovery session.", re);
309            }
310            // If the service has no print jobs and no active discovery
311            // session anymore we should disconnect from it.
312            if (!mHasActivePrintJobs) {
313                ensureUnbound();
314            }
315        }
316    }
317
318    public void startPrinterDiscovery(List<PrinterId> priorityList) {
319        mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_DISCOVERY,
320                priorityList).sendToTarget();
321    }
322
323    private void handleStartPrinterDiscovery(final List<PrinterId> priorityList) {
324        // Take a note that we are doing discovery.
325        mDiscoveryPriorityList = new ArrayList<PrinterId>();
326        if (priorityList != null) {
327            mDiscoveryPriorityList.addAll(priorityList);
328        }
329        if (!isBound()) {
330            ensureBound();
331            mPendingCommands.add(new Runnable() {
332                @Override
333                public void run() {
334                    handleStartPrinterDiscovery(priorityList);
335                }
336            });
337        } else {
338            if (DEBUG) {
339                Slog.i(LOG_TAG, "[user: " + mUserId + "] startPrinterDiscovery()");
340            }
341            try {
342                mPrintService.startPrinterDiscovery(priorityList);
343            } catch (RemoteException re) {
344                Slog.e(LOG_TAG, "Error starting printer dicovery.", re);
345            }
346        }
347    }
348
349    public void stopPrinterDiscovery() {
350        mHandler.sendEmptyMessage(MyHandler.MSG_STOP_PRINTER_DISCOVERY);
351    }
352
353    private void handleStopPrinterDiscovery() {
354        // We are not doing discovery anymore.
355        mDiscoveryPriorityList = null;
356        if (!isBound()) {
357            ensureBound();
358            mPendingCommands.add(new Runnable() {
359                @Override
360                public void run() {
361                    handleStopPrinterDiscovery();
362                }
363            });
364        } else {
365            if (DEBUG) {
366                Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterDiscovery()");
367            }
368
369            // Stop tracking printers.
370            stopTrackingAllPrinters();
371
372            try {
373                mPrintService.stopPrinterDiscovery();
374            } catch (RemoteException re) {
375                Slog.e(LOG_TAG, "Error stopping printer discovery.", re);
376            }
377        }
378    }
379
380    public void validatePrinters(List<PrinterId> printerIds) {
381        mHandler.obtainMessage(MyHandler.MSG_VALIDATE_PRINTERS,
382                printerIds).sendToTarget();
383    }
384
385    private void handleValidatePrinters(final List<PrinterId> printerIds) {
386        if (!isBound()) {
387            ensureBound();
388            mPendingCommands.add(new Runnable() {
389                @Override
390                public void run() {
391                    handleValidatePrinters(printerIds);
392                }
393            });
394        } else {
395            if (DEBUG) {
396                Slog.i(LOG_TAG, "[user: " + mUserId + "] validatePrinters()");
397            }
398            try {
399                mPrintService.validatePrinters(printerIds);
400            } catch (RemoteException re) {
401                Slog.e(LOG_TAG, "Error requesting printers validation.", re);
402            }
403        }
404    }
405
406    public void startPrinterStateTracking(@NonNull PrinterId printerId) {
407        mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_STATE_TRACKING,
408                printerId).sendToTarget();
409    }
410
411    /**
412     * Queue a request for a custom printer icon for a printer.
413     *
414     * @param printerId the id of the printer the icon should be loaded for
415     * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon
416     */
417    public void requestCustomPrinterIcon(@NonNull PrinterId printerId) {
418        mHandler.obtainMessage(MyHandler.MSG_REQUEST_CUSTOM_PRINTER_ICON,
419                printerId).sendToTarget();
420    }
421
422    /**
423     * Request a custom printer icon for a printer.
424     *
425     * @param printerId the id of the printer the icon should be loaded for
426     * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon
427     */
428    private void handleRequestCustomPrinterIcon(@NonNull PrinterId printerId) {
429        if (!isBound()) {
430            ensureBound();
431            mPendingCommands.add(() -> handleRequestCustomPrinterIcon(printerId));
432        } else {
433            if (DEBUG) {
434                Slog.i(LOG_TAG, "[user: " + mUserId + "] requestCustomPrinterIcon()");
435            }
436
437            try {
438                mPrintService.requestCustomPrinterIcon(printerId);
439            } catch (RemoteException re) {
440                Slog.e(LOG_TAG, "Error requesting icon for " + printerId, re);
441            }
442        }
443    }
444
445    private void handleStartPrinterStateTracking(final @NonNull PrinterId printerId) {
446        // Take a note we are tracking the printer.
447        if (mTrackedPrinterList == null) {
448            mTrackedPrinterList = new ArrayList<PrinterId>();
449        }
450        mTrackedPrinterList.add(printerId);
451        if (!isBound()) {
452            ensureBound();
453            mPendingCommands.add(new Runnable() {
454                @Override
455                public void run() {
456                    handleStartPrinterStateTracking(printerId);
457                }
458            });
459        } else {
460            if (DEBUG) {
461                Slog.i(LOG_TAG, "[user: " + mUserId + "] startPrinterTracking()");
462            }
463            try {
464                mPrintService.startPrinterStateTracking(printerId);
465            } catch (RemoteException re) {
466                Slog.e(LOG_TAG, "Error requesting start printer tracking.", re);
467            }
468        }
469    }
470
471    public void stopPrinterStateTracking(PrinterId printerId) {
472        mHandler.obtainMessage(MyHandler.MSG_STOP_PRINTER_STATE_TRACKING,
473                printerId).sendToTarget();
474    }
475
476    private void handleStopPrinterStateTracking(final PrinterId printerId) {
477        // We are no longer tracking the printer.
478        if (mTrackedPrinterList == null || !mTrackedPrinterList.remove(printerId)) {
479            return;
480        }
481        if (mTrackedPrinterList.isEmpty()) {
482            mTrackedPrinterList = null;
483        }
484        if (!isBound()) {
485            ensureBound();
486            mPendingCommands.add(new Runnable() {
487                @Override
488                public void run() {
489                    handleStopPrinterStateTracking(printerId);
490                }
491            });
492        } else {
493            if (DEBUG) {
494                Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterTracking()");
495            }
496            try {
497                mPrintService.stopPrinterStateTracking(printerId);
498            } catch (RemoteException re) {
499                Slog.e(LOG_TAG, "Error requesting stop printer tracking.", re);
500            }
501        }
502    }
503
504    private void stopTrackingAllPrinters() {
505        if (mTrackedPrinterList == null) {
506            return;
507        }
508        final int trackedPrinterCount = mTrackedPrinterList.size();
509        for (int i = trackedPrinterCount - 1; i >= 0; i--) {
510            PrinterId printerId = mTrackedPrinterList.get(i);
511            if (printerId.getServiceName().equals(mComponentName)) {
512                handleStopPrinterStateTracking(printerId);
513            }
514        }
515    }
516
517    public void dump(PrintWriter pw, String prefix) {
518        String tab = "  ";
519        pw.append(prefix).append("service:").println();
520        pw.append(prefix).append(tab).append("componentName=")
521                .append(mComponentName.flattenToString()).println();
522        pw.append(prefix).append(tab).append("destroyed=")
523                .append(String.valueOf(mDestroyed)).println();
524        pw.append(prefix).append(tab).append("bound=")
525                .append(String.valueOf(isBound())).println();
526        pw.append(prefix).append(tab).append("hasDicoverySession=")
527                .append(String.valueOf(mHasPrinterDiscoverySession)).println();
528        pw.append(prefix).append(tab).append("hasActivePrintJobs=")
529                .append(String.valueOf(mHasActivePrintJobs)).println();
530        pw.append(prefix).append(tab).append("isDiscoveringPrinters=")
531                .append(String.valueOf(mDiscoveryPriorityList != null)).println();
532        pw.append(prefix).append(tab).append("trackedPrinters=")
533                .append((mTrackedPrinterList != null) ? mTrackedPrinterList.toString() : "null");
534    }
535
536    private boolean isBound() {
537        return mPrintService != null;
538    }
539
540    private void ensureBound() {
541        if (isBound() || mBinding) {
542            return;
543        }
544        if (DEBUG) {
545            Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureBound()");
546        }
547        mBinding = true;
548
549        boolean wasBound = mContext.bindServiceAsUser(mIntent, mServiceConnection,
550                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
551                new UserHandle(mUserId));
552
553        if (!wasBound) {
554            if (DEBUG) {
555                Slog.i(LOG_TAG, "[user: " + mUserId + "] could not bind to " + mIntent);
556            }
557            mBinding = false;
558
559            if (!mServiceDied) {
560                handleBinderDied();
561            }
562        }
563    }
564
565    private void ensureUnbound() {
566        if (!isBound() && !mBinding) {
567            return;
568        }
569        if (DEBUG) {
570            Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureUnbound()");
571        }
572        mBinding = false;
573        mPendingCommands.clear();
574        mHasActivePrintJobs = false;
575        mHasPrinterDiscoverySession = false;
576        mDiscoveryPriorityList = null;
577        mTrackedPrinterList = null;
578        if (isBound()) {
579            try {
580                mPrintService.setClient(null);
581            } catch (RemoteException re) {
582                /* ignore */
583            }
584            mPrintService.asBinder().unlinkToDeath(this, 0);
585            mPrintService = null;
586            mContext.unbindService(mServiceConnection);
587        }
588    }
589
590    private class RemoteServiceConneciton implements ServiceConnection {
591        @Override
592        public void onServiceConnected(ComponentName name, IBinder service) {
593            if (mDestroyed || !mBinding) {
594                mContext.unbindService(mServiceConnection);
595                return;
596            }
597            mBinding = false;
598            mPrintService = IPrintService.Stub.asInterface(service);
599            try {
600                service.linkToDeath(RemotePrintService.this, 0);
601            } catch (RemoteException re) {
602                handleBinderDied();
603                return;
604            }
605            try {
606                mPrintService.setClient(mPrintServiceClient);
607            } catch (RemoteException re) {
608                Slog.e(LOG_TAG, "Error setting client for: " + service, re);
609                handleBinderDied();
610                return;
611            }
612            // If the service died and there is a discovery session, recreate it.
613            if (mServiceDied && mHasPrinterDiscoverySession) {
614                handleCreatePrinterDiscoverySession();
615            }
616            // If the service died and there is discovery started, restart it.
617            if (mServiceDied && mDiscoveryPriorityList != null) {
618                handleStartPrinterDiscovery(mDiscoveryPriorityList);
619            }
620            // If the service died and printers were tracked, start tracking.
621            if (mServiceDied && mTrackedPrinterList != null) {
622                final int trackedPrinterCount = mTrackedPrinterList.size();
623                for (int i = 0; i < trackedPrinterCount; i++) {
624                    handleStartPrinterStateTracking(mTrackedPrinterList.get(i));
625                }
626            }
627            // Finally, do all the pending work.
628            while (!mPendingCommands.isEmpty()) {
629                Runnable pendingCommand = mPendingCommands.remove(0);
630                pendingCommand.run();
631            }
632            // We did a best effort to get to the last state if we crashed.
633            // If we do not have print jobs and no discovery is in progress,
634            // then no need to be bound.
635            if (!mHasPrinterDiscoverySession && !mHasActivePrintJobs) {
636                ensureUnbound();
637            }
638            mServiceDied = false;
639        }
640
641        @Override
642        public void onServiceDisconnected(ComponentName name) {
643            mBinding = true;
644        }
645    }
646
647    private final class MyHandler extends Handler {
648        public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1;
649        public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
650        public static final int MSG_START_PRINTER_DISCOVERY = 3;
651        public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
652        public static final int MSG_VALIDATE_PRINTERS = 5;
653        public static final int MSG_START_PRINTER_STATE_TRACKING = 6;
654        public static final int MSG_STOP_PRINTER_STATE_TRACKING = 7;
655        public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 8;
656        public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 9;
657        public static final int MSG_ON_PRINT_JOB_QUEUED = 10;
658        public static final int MSG_DESTROY = 11;
659        public static final int MSG_BINDER_DIED = 12;
660        public static final int MSG_REQUEST_CUSTOM_PRINTER_ICON = 13;
661
662        public MyHandler(Looper looper) {
663            super(looper, null, false);
664        }
665
666        @Override
667        @SuppressWarnings("unchecked")
668        public void handleMessage(Message message) {
669            if (mDestroyed) {
670                Slog.w(LOG_TAG, "Not handling " + message + " as service for " + mComponentName
671                        + " is already destroyed");
672                return;
673            }
674            switch (message.what) {
675                case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
676                    handleCreatePrinterDiscoverySession();
677                } break;
678
679                case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
680                    handleDestroyPrinterDiscoverySession();
681                } break;
682
683                case MSG_START_PRINTER_DISCOVERY: {
684                    List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj;
685                    handleStartPrinterDiscovery(priorityList);
686                } break;
687
688                case MSG_STOP_PRINTER_DISCOVERY: {
689                    handleStopPrinterDiscovery();
690                } break;
691
692                case MSG_VALIDATE_PRINTERS: {
693                    List<PrinterId> printerIds = (List<PrinterId>) message.obj;
694                    handleValidatePrinters(printerIds);
695                } break;
696
697                case MSG_START_PRINTER_STATE_TRACKING: {
698                    PrinterId printerId = (PrinterId) message.obj;
699                    handleStartPrinterStateTracking(printerId);
700                } break;
701
702                case MSG_STOP_PRINTER_STATE_TRACKING: {
703                    PrinterId printerId = (PrinterId) message.obj;
704                    handleStopPrinterStateTracking(printerId);
705                } break;
706
707                case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
708                    handleOnAllPrintJobsHandled();
709                } break;
710
711                case MSG_ON_REQUEST_CANCEL_PRINT_JOB: {
712                    PrintJobInfo printJob = (PrintJobInfo) message.obj;
713                    handleRequestCancelPrintJob(printJob);
714                } break;
715
716                case MSG_ON_PRINT_JOB_QUEUED: {
717                    PrintJobInfo printJob = (PrintJobInfo) message.obj;
718                    handleOnPrintJobQueued(printJob);
719                } break;
720
721                case MSG_DESTROY: {
722                    handleDestroy();
723                } break;
724
725                case MSG_BINDER_DIED: {
726                    handleBinderDied();
727                } break;
728
729                case MSG_REQUEST_CUSTOM_PRINTER_ICON: {
730                    PrinterId printerId = (PrinterId) message.obj;
731                    handleRequestCustomPrinterIcon(printerId);
732                } break;
733            }
734        }
735    }
736
737    private static final class RemotePrintServiceClient extends IPrintServiceClient.Stub {
738        private final WeakReference<RemotePrintService> mWeakService;
739
740        public RemotePrintServiceClient(RemotePrintService service) {
741            mWeakService = new WeakReference<RemotePrintService>(service);
742        }
743
744        @Override
745        public List<PrintJobInfo> getPrintJobInfos() {
746            RemotePrintService service = mWeakService.get();
747            if (service != null) {
748                final long identity = Binder.clearCallingIdentity();
749                try {
750                    return service.mSpooler.getPrintJobInfos(service.mComponentName,
751                            PrintJobInfo.STATE_ANY_SCHEDULED, PrintManager.APP_ID_ANY);
752                } finally {
753                    Binder.restoreCallingIdentity(identity);
754                }
755            }
756            return null;
757        }
758
759        @Override
760        public PrintJobInfo getPrintJobInfo(PrintJobId printJobId) {
761            RemotePrintService service = mWeakService.get();
762            if (service != null) {
763                final long identity = Binder.clearCallingIdentity();
764                try {
765                    return service.mSpooler.getPrintJobInfo(printJobId,
766                            PrintManager.APP_ID_ANY);
767                } finally {
768                    Binder.restoreCallingIdentity(identity);
769                }
770            }
771            return null;
772        }
773
774        @Override
775        public boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
776            RemotePrintService service = mWeakService.get();
777            if (service != null) {
778                final long identity = Binder.clearCallingIdentity();
779                try {
780                    return service.mSpooler.setPrintJobState(printJobId, state, error);
781                } finally {
782                    Binder.restoreCallingIdentity(identity);
783                }
784            }
785            return false;
786        }
787
788        @Override
789        public boolean setPrintJobTag(PrintJobId printJobId, String tag) {
790            RemotePrintService service = mWeakService.get();
791            if (service != null) {
792                final long identity = Binder.clearCallingIdentity();
793                try {
794                    return service.mSpooler.setPrintJobTag(printJobId, tag);
795                } finally {
796                    Binder.restoreCallingIdentity(identity);
797                }
798            }
799            return false;
800        }
801
802        @Override
803        public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
804            RemotePrintService service = mWeakService.get();
805            if (service != null) {
806                final long identity = Binder.clearCallingIdentity();
807                try {
808                    service.mSpooler.writePrintJobData(fd, printJobId);
809                } finally {
810                    Binder.restoreCallingIdentity(identity);
811                }
812            }
813        }
814
815        @Override
816        public void setProgress(@NonNull PrintJobId printJobId,
817                @FloatRange(from=0.0, to=1.0) float progress) {
818            RemotePrintService service = mWeakService.get();
819            if (service != null) {
820                final long identity = Binder.clearCallingIdentity();
821                try {
822                    service.mSpooler.setProgress(printJobId, progress);
823                } finally {
824                    Binder.restoreCallingIdentity(identity);
825                }
826            }
827        }
828
829        @Override
830        public void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
831            RemotePrintService service = mWeakService.get();
832            if (service != null) {
833                final long identity = Binder.clearCallingIdentity();
834                try {
835                    service.mSpooler.setStatus(printJobId, status);
836                } finally {
837                    Binder.restoreCallingIdentity(identity);
838                }
839            }
840        }
841
842        @Override
843        public void setStatusRes(@NonNull PrintJobId printJobId, @StringRes int status,
844                @NonNull CharSequence appPackageName) {
845            RemotePrintService service = mWeakService.get();
846            if (service != null) {
847                final long identity = Binder.clearCallingIdentity();
848                try {
849                    service.mSpooler.setStatus(printJobId, status, appPackageName);
850                } finally {
851                    Binder.restoreCallingIdentity(identity);
852                }
853            }
854        }
855
856        @Override
857        @SuppressWarnings({"rawtypes", "unchecked"})
858        public void onPrintersAdded(ParceledListSlice printers) {
859            RemotePrintService service = mWeakService.get();
860            if (service != null) {
861                List<PrinterInfo> addedPrinters = (List<PrinterInfo>) printers.getList();
862                throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, addedPrinters);
863                final long identity = Binder.clearCallingIdentity();
864                try {
865                    service.mCallbacks.onPrintersAdded(addedPrinters);
866                } finally {
867                    Binder.restoreCallingIdentity(identity);
868                }
869            }
870        }
871
872        @Override
873        @SuppressWarnings({"rawtypes", "unchecked"})
874        public void onPrintersRemoved(ParceledListSlice printerIds) {
875            RemotePrintService service = mWeakService.get();
876            if (service != null) {
877                List<PrinterId> removedPrinterIds = (List<PrinterId>) printerIds.getList();
878                throwIfPrinterIdsTampered(service.mComponentName, removedPrinterIds);
879                final long identity = Binder.clearCallingIdentity();
880                try {
881                    service.mCallbacks.onPrintersRemoved(removedPrinterIds);
882                } finally {
883                    Binder.restoreCallingIdentity(identity);
884                }
885            }
886        }
887
888        private void throwIfPrinterIdsForPrinterInfoTampered(ComponentName serviceName,
889                List<PrinterInfo> printerInfos) {
890            final int printerInfoCount = printerInfos.size();
891            for (int i = 0; i < printerInfoCount; i++) {
892                PrinterId printerId = printerInfos.get(i).getId();
893                throwIfPrinterIdTampered(serviceName, printerId);
894            }
895        }
896
897        private void throwIfPrinterIdsTampered(ComponentName serviceName,
898                List<PrinterId> printerIds) {
899            final int printerIdCount = printerIds.size();
900            for (int i = 0; i < printerIdCount; i++) {
901                PrinterId printerId = printerIds.get(i);
902                throwIfPrinterIdTampered(serviceName, printerId);
903            }
904        }
905
906        private void throwIfPrinterIdTampered(ComponentName serviceName, PrinterId printerId) {
907            if (printerId == null || !printerId.getServiceName().equals(serviceName)) {
908                throw new IllegalArgumentException("Invalid printer id: " + printerId);
909            }
910        }
911
912        @Override
913        public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon)
914                throws RemoteException {
915            RemotePrintService service = mWeakService.get();
916            if (service != null) {
917                final long identity = Binder.clearCallingIdentity();
918                try {
919                    service.mCallbacks.onCustomPrinterIconLoaded(printerId, icon);
920                } finally {
921                    Binder.restoreCallingIdentity(identity);
922                }
923            }
924        }
925    }
926}
927