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