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