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