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 android.annotation.FloatRange;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.StringRes;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.ServiceConnection;
27 import android.graphics.drawable.Icon;
28 import android.os.Binder;
29 import android.os.Build;
30 import android.os.IBinder;
31 import android.os.ParcelFileDescriptor;
32 import android.os.RemoteException;
33 import android.os.SystemClock;
34 import android.os.UserHandle;
35 import android.print.IPrintSpooler;
36 import android.print.IPrintSpoolerCallbacks;
37 import android.print.IPrintSpoolerClient;
38 import android.print.PrintJobId;
39 import android.print.PrintJobInfo;
40 import android.print.PrintManager;
41 import android.print.PrinterId;
42 import android.printservice.PrintService;
43 import android.service.print.PrintSpoolerStateProto;
44 import android.util.Slog;
45 import android.util.TimedRemoteCaller;
46 
47 import com.android.internal.annotations.GuardedBy;
48 import com.android.internal.os.TransferPipe;
49 import com.android.internal.util.dump.DualDumpOutputStream;
50 
51 import libcore.io.IoUtils;
52 
53 import java.io.IOException;
54 import java.lang.ref.WeakReference;
55 import java.util.List;
56 import java.util.concurrent.TimeoutException;
57 
58 /**
59  * This represents the remote print spooler as a local object to the
60  * PrintManagerService. It is responsible to connecting to the remote
61  * spooler if needed, to make the timed remote calls, to handle
62  * remote exceptions, and to bind/unbind to the remote instance as
63  * needed.
64  *
65  * The calls might be blocking and need the main thread of to be unblocked to finish. Hence do not
66  * call this while holding any monitors that might need to be acquired the main thread.
67  */
68 final class RemotePrintSpooler {
69 
70     private static final String LOG_TAG = "RemotePrintSpooler";
71 
72     private static final boolean DEBUG = false;
73 
74     private static final long BIND_SPOOLER_SERVICE_TIMEOUT =
75             (Build.IS_ENG) ? 120000 : 10000;
76 
77     private final Object mLock = new Object();
78 
79     private final GetPrintJobInfosCaller mGetPrintJobInfosCaller = new GetPrintJobInfosCaller();
80 
81     private final GetPrintJobInfoCaller mGetPrintJobInfoCaller = new GetPrintJobInfoCaller();
82 
83     private final SetPrintJobStateCaller mSetPrintJobStatusCaller = new SetPrintJobStateCaller();
84 
85     private final SetPrintJobTagCaller mSetPrintJobTagCaller = new SetPrintJobTagCaller();
86 
87     private final OnCustomPrinterIconLoadedCaller mCustomPrinterIconLoadedCaller =
88             new OnCustomPrinterIconLoadedCaller();
89 
90     private final ClearCustomPrinterIconCacheCaller mClearCustomPrinterIconCache =
91             new ClearCustomPrinterIconCacheCaller();
92 
93     private final GetCustomPrinterIconCaller mGetCustomPrinterIconCaller =
94             new GetCustomPrinterIconCaller();
95 
96     private final ServiceConnection mServiceConnection = new MyServiceConnection();
97 
98     private final Context mContext;
99 
100     private final UserHandle mUserHandle;
101 
102     private final PrintSpoolerClient mClient;
103 
104     private final Intent mIntent;
105 
106     private final PrintSpoolerCallbacks mCallbacks;
107 
108     private boolean mIsLowPriority;
109 
110     private IPrintSpooler mRemoteInstance;
111 
112     private boolean mDestroyed;
113 
114     private boolean mCanUnbind;
115 
116     /** Whether a thread is currently trying to {@link #bindLocked() bind to the print service} */
117     @GuardedBy("mLock")
118     private boolean mIsBinding;
119 
120     public static interface PrintSpoolerCallbacks {
onPrintJobQueued(PrintJobInfo printJob)121         public void onPrintJobQueued(PrintJobInfo printJob);
onAllPrintJobsForServiceHandled(ComponentName printService)122         public void onAllPrintJobsForServiceHandled(ComponentName printService);
onPrintJobStateChanged(PrintJobInfo printJob)123         public void onPrintJobStateChanged(PrintJobInfo printJob);
124     }
125 
RemotePrintSpooler(Context context, int userId, boolean lowPriority, PrintSpoolerCallbacks callbacks)126     public RemotePrintSpooler(Context context, int userId, boolean lowPriority,
127             PrintSpoolerCallbacks callbacks) {
128         mContext = context;
129         mUserHandle = new UserHandle(userId);
130         mCallbacks = callbacks;
131         mIsLowPriority = lowPriority;
132         mClient = new PrintSpoolerClient(this);
133         mIntent = new Intent();
134         mIntent.setComponent(new ComponentName(PrintManager.PRINT_SPOOLER_PACKAGE_NAME,
135                 PrintManager.PRINT_SPOOLER_PACKAGE_NAME + ".model.PrintSpoolerService"));
136     }
137 
increasePriority()138     public void increasePriority() {
139         if (mIsLowPriority) {
140             mIsLowPriority = false;
141 
142             synchronized (mLock) {
143                 throwIfDestroyedLocked();
144 
145                 while (!mCanUnbind) {
146                     try {
147                         mLock.wait();
148                     } catch (InterruptedException e) {
149                         Slog.e(LOG_TAG, "Interrupted while waiting for operation to complete");
150                     }
151                 }
152 
153                 if (DEBUG) {
154                     Slog.i(LOG_TAG, "Unbinding as previous binding was low priority");
155                 }
156 
157                 unbindLocked();
158             }
159         }
160     }
161 
getPrintJobInfos(ComponentName componentName, int state, int appId)162     public final List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state,
163             int appId) {
164         throwIfCalledOnMainThread();
165         synchronized (mLock) {
166             throwIfDestroyedLocked();
167             mCanUnbind = false;
168         }
169         try {
170             return mGetPrintJobInfosCaller.getPrintJobInfos(getRemoteInstanceLazy(),
171                     componentName, state, appId);
172         } catch (RemoteException | TimeoutException | InterruptedException e) {
173             Slog.e(LOG_TAG, "Error getting print jobs.", e);
174         } finally {
175             if (DEBUG) {
176                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfos()");
177             }
178             synchronized (mLock) {
179                 mCanUnbind = true;
180                 mLock.notifyAll();
181             }
182         }
183         return null;
184     }
185 
createPrintJob(PrintJobInfo printJob)186     public final void createPrintJob(PrintJobInfo printJob) {
187         throwIfCalledOnMainThread();
188         synchronized (mLock) {
189             throwIfDestroyedLocked();
190             mCanUnbind = false;
191         }
192         try {
193             getRemoteInstanceLazy().createPrintJob(printJob);
194         } catch (RemoteException | TimeoutException | InterruptedException e) {
195             Slog.e(LOG_TAG, "Error creating print job.", e);
196         } finally {
197             if (DEBUG) {
198                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] createPrintJob()");
199             }
200             synchronized (mLock) {
201                 mCanUnbind = true;
202                 mLock.notifyAll();
203             }
204         }
205     }
206 
writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId)207     public final void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
208         throwIfCalledOnMainThread();
209         synchronized (mLock) {
210             throwIfDestroyedLocked();
211             mCanUnbind = false;
212         }
213         try {
214             getRemoteInstanceLazy().writePrintJobData(fd, printJobId);
215         } catch (RemoteException | TimeoutException | InterruptedException e) {
216             Slog.e(LOG_TAG, "Error writing print job data.", e);
217         } finally {
218             if (DEBUG) {
219                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] writePrintJobData()");
220             }
221             // We passed the file descriptor across and now the other
222             // side is responsible to close it, so close the local copy.
223             IoUtils.closeQuietly(fd);
224             synchronized (mLock) {
225                 mCanUnbind = true;
226                 mLock.notifyAll();
227             }
228         }
229     }
230 
getPrintJobInfo(PrintJobId printJobId, int appId)231     public final PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
232         throwIfCalledOnMainThread();
233         synchronized (mLock) {
234             throwIfDestroyedLocked();
235             mCanUnbind = false;
236         }
237         try {
238             return mGetPrintJobInfoCaller.getPrintJobInfo(getRemoteInstanceLazy(),
239                     printJobId, appId);
240         } catch (RemoteException | TimeoutException | InterruptedException e) {
241             Slog.e(LOG_TAG, "Error getting print job info.", e);
242         } finally {
243             if (DEBUG) {
244                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfo()");
245             }
246             synchronized (mLock) {
247                 mCanUnbind = true;
248                 mLock.notifyAll();
249             }
250         }
251         return null;
252     }
253 
setPrintJobState(PrintJobId printJobId, int state, String error)254     public final boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
255         throwIfCalledOnMainThread();
256         synchronized (mLock) {
257             throwIfDestroyedLocked();
258             mCanUnbind = false;
259         }
260         try {
261             return mSetPrintJobStatusCaller.setPrintJobState(getRemoteInstanceLazy(),
262                     printJobId, state, error);
263         } catch (RemoteException | TimeoutException | InterruptedException e) {
264             Slog.e(LOG_TAG, "Error setting print job state.", e);
265         } finally {
266             if (DEBUG) {
267                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobState()");
268             }
269             synchronized (mLock) {
270                 mCanUnbind = true;
271                 mLock.notifyAll();
272             }
273         }
274         return false;
275     }
276 
277     /**
278      * Set progress of a print job.
279      *
280      * @param printJobId The print job to update
281      * @param progress The new progress
282      */
setProgress(@onNull PrintJobId printJobId, @FloatRange(from=0.0, to=1.0) float progress)283     public final void setProgress(@NonNull PrintJobId printJobId,
284             @FloatRange(from=0.0, to=1.0) float progress) {
285         throwIfCalledOnMainThread();
286         synchronized (mLock) {
287             throwIfDestroyedLocked();
288             mCanUnbind = false;
289         }
290         try {
291             getRemoteInstanceLazy().setProgress(printJobId, progress);
292         } catch (RemoteException | TimeoutException | InterruptedException re) {
293             Slog.e(LOG_TAG, "Error setting progress.", re);
294         } finally {
295             if (DEBUG) {
296                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setProgress()");
297             }
298             synchronized (mLock) {
299                 mCanUnbind = true;
300                 mLock.notifyAll();
301             }
302         }
303     }
304 
305     /**
306      * Set status of a print job.
307      *
308      * @param printJobId The print job to update
309      * @param status The new status
310      */
setStatus(@onNull PrintJobId printJobId, @Nullable CharSequence status)311     public final void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
312         throwIfCalledOnMainThread();
313         synchronized (mLock) {
314             throwIfDestroyedLocked();
315             mCanUnbind = false;
316         }
317         try {
318             getRemoteInstanceLazy().setStatus(printJobId, status);
319         } catch (RemoteException | TimeoutException | InterruptedException e) {
320             Slog.e(LOG_TAG, "Error setting status.", e);
321         } finally {
322             if (DEBUG) {
323                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setStatus()");
324             }
325             synchronized (mLock) {
326                 mCanUnbind = true;
327                 mLock.notifyAll();
328             }
329         }
330     }
331 
332     /**
333      * Set status of a print job.
334      *
335      * @param printJobId The print job to update
336      * @param status The new status as a string resource
337      * @param appPackageName The app package name the string res belongs to
338      */
setStatus(@onNull PrintJobId printJobId, @StringRes int status, @NonNull CharSequence appPackageName)339     public final void setStatus(@NonNull PrintJobId printJobId, @StringRes int status,
340             @NonNull CharSequence appPackageName) {
341         throwIfCalledOnMainThread();
342         synchronized (mLock) {
343             throwIfDestroyedLocked();
344             mCanUnbind = false;
345         }
346         try {
347             getRemoteInstanceLazy().setStatusRes(printJobId, status, appPackageName);
348         } catch (RemoteException | TimeoutException | InterruptedException e) {
349             Slog.e(LOG_TAG, "Error setting status.", e);
350         } finally {
351             if (DEBUG) {
352                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setStatus()");
353             }
354             synchronized (mLock) {
355                 mCanUnbind = true;
356                 mLock.notifyAll();
357             }
358         }
359     }
360 
361     /**
362      * Handle that a custom icon for a printer was loaded.
363      *
364      * @param printerId the id of the printer the icon belongs to
365      * @param icon the icon that was loaded
366      * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon
367      */
onCustomPrinterIconLoaded(@onNull PrinterId printerId, @Nullable Icon icon)368     public final void onCustomPrinterIconLoaded(@NonNull PrinterId printerId,
369             @Nullable Icon icon) {
370         throwIfCalledOnMainThread();
371         synchronized (mLock) {
372             throwIfDestroyedLocked();
373             mCanUnbind = false;
374         }
375         try {
376             mCustomPrinterIconLoadedCaller.onCustomPrinterIconLoaded(getRemoteInstanceLazy(),
377                     printerId, icon);
378         } catch (RemoteException | TimeoutException | InterruptedException re) {
379             Slog.e(LOG_TAG, "Error loading new custom printer icon.", re);
380         } finally {
381             if (DEBUG) {
382                 Slog.i(LOG_TAG,
383                         "[user: " + mUserHandle.getIdentifier() + "] onCustomPrinterIconLoaded()");
384             }
385             synchronized (mLock) {
386                 mCanUnbind = true;
387                 mLock.notifyAll();
388             }
389         }
390     }
391 
392     /**
393      * Get the custom icon for a printer. If the icon is not cached, the icon is
394      * requested asynchronously. Once it is available the printer is updated.
395      *
396      * @param printerId the id of the printer the icon should be loaded for
397      * @return the custom icon to be used for the printer or null if the icon is
398      *         not yet available
399      * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon
400      */
getCustomPrinterIcon(@onNull PrinterId printerId)401     public final @Nullable Icon getCustomPrinterIcon(@NonNull PrinterId printerId) {
402         throwIfCalledOnMainThread();
403         synchronized (mLock) {
404             throwIfDestroyedLocked();
405             mCanUnbind = false;
406         }
407         try {
408             return mGetCustomPrinterIconCaller.getCustomPrinterIcon(getRemoteInstanceLazy(),
409                     printerId);
410         } catch (RemoteException | TimeoutException | InterruptedException e) {
411             Slog.e(LOG_TAG, "Error getting custom printer icon.", e);
412             return null;
413         } finally {
414             if (DEBUG) {
415                 Slog.i(LOG_TAG,
416                         "[user: " + mUserHandle.getIdentifier() + "] getCustomPrinterIcon()");
417             }
418             synchronized (mLock) {
419                 mCanUnbind = true;
420                 mLock.notifyAll();
421             }
422         }
423     }
424 
425     /**
426      * Clear the custom printer icon cache
427      */
clearCustomPrinterIconCache()428     public void clearCustomPrinterIconCache() {
429         throwIfCalledOnMainThread();
430         synchronized (mLock) {
431             throwIfDestroyedLocked();
432             mCanUnbind = false;
433         }
434         try {
435             mClearCustomPrinterIconCache.clearCustomPrinterIconCache(getRemoteInstanceLazy());
436         } catch (RemoteException | TimeoutException | InterruptedException e) {
437             Slog.e(LOG_TAG, "Error clearing custom printer icon cache.", e);
438         } finally {
439             if (DEBUG) {
440                 Slog.i(LOG_TAG,
441                         "[user: " + mUserHandle.getIdentifier()
442                                 + "] clearCustomPrinterIconCache()");
443             }
444             synchronized (mLock) {
445                 mCanUnbind = true;
446                 mLock.notifyAll();
447             }
448         }
449     }
450 
setPrintJobTag(PrintJobId printJobId, String tag)451     public final boolean setPrintJobTag(PrintJobId printJobId, String tag) {
452         throwIfCalledOnMainThread();
453         synchronized (mLock) {
454             throwIfDestroyedLocked();
455             mCanUnbind = false;
456         }
457         try {
458             return mSetPrintJobTagCaller.setPrintJobTag(getRemoteInstanceLazy(),
459                     printJobId, tag);
460         } catch (RemoteException | TimeoutException | InterruptedException e) {
461             Slog.e(LOG_TAG, "Error setting print job tag.", e);
462         } finally {
463             if (DEBUG) {
464                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobTag()");
465             }
466             synchronized (mLock) {
467                 mCanUnbind = true;
468                 mLock.notifyAll();
469             }
470         }
471         return false;
472     }
473 
setPrintJobCancelling(PrintJobId printJobId, boolean cancelling)474     public final void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
475         throwIfCalledOnMainThread();
476         synchronized (mLock) {
477             throwIfDestroyedLocked();
478             mCanUnbind = false;
479         }
480         try {
481             getRemoteInstanceLazy().setPrintJobCancelling(printJobId,
482                     cancelling);
483         } catch (RemoteException | TimeoutException | InterruptedException e) {
484             Slog.e(LOG_TAG, "Error setting print job cancelling.", e);
485         } finally {
486             if (DEBUG) {
487                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
488                         + "] setPrintJobCancelling()");
489             }
490             synchronized (mLock) {
491                 mCanUnbind = true;
492                 mLock.notifyAll();
493             }
494         }
495     }
496 
497     /**
498      * Remove all approved {@link PrintService print services} that are not in the given set.
499      *
500      * @param servicesToKeep The {@link ComponentName names } of the services to keep
501      */
pruneApprovedPrintServices(List<ComponentName> servicesToKeep)502     public final void pruneApprovedPrintServices(List<ComponentName> servicesToKeep) {
503         throwIfCalledOnMainThread();
504         synchronized (mLock) {
505             throwIfDestroyedLocked();
506             mCanUnbind = false;
507         }
508         try {
509             getRemoteInstanceLazy().pruneApprovedPrintServices(servicesToKeep);
510         } catch (RemoteException | TimeoutException | InterruptedException e) {
511             Slog.e(LOG_TAG, "Error pruning approved print services.", e);
512         } finally {
513             if (DEBUG) {
514                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
515                         + "] pruneApprovedPrintServices()");
516             }
517             synchronized (mLock) {
518                 mCanUnbind = true;
519                 mLock.notifyAll();
520             }
521         }
522     }
523 
removeObsoletePrintJobs()524     public final void removeObsoletePrintJobs() {
525         throwIfCalledOnMainThread();
526         synchronized (mLock) {
527             throwIfDestroyedLocked();
528             mCanUnbind = false;
529         }
530         try {
531             getRemoteInstanceLazy().removeObsoletePrintJobs();
532         } catch (RemoteException | TimeoutException | InterruptedException te) {
533             Slog.e(LOG_TAG, "Error removing obsolete print jobs .", te);
534         } finally {
535             if (DEBUG) {
536                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
537                         + "] removeObsoletePrintJobs()");
538             }
539             synchronized (mLock) {
540                 mCanUnbind = true;
541                 mLock.notifyAll();
542             }
543         }
544     }
545 
destroy()546     public final void destroy() {
547         throwIfCalledOnMainThread();
548         if (DEBUG) {
549             Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] destroy()");
550         }
551         synchronized (mLock) {
552             throwIfDestroyedLocked();
553             unbindLocked();
554             mDestroyed = true;
555             mCanUnbind = false;
556         }
557     }
558 
dump(@onNull DualDumpOutputStream dumpStream)559     public void dump(@NonNull DualDumpOutputStream dumpStream) {
560         synchronized (mLock) {
561             dumpStream.write("is_destroyed", PrintSpoolerStateProto.IS_DESTROYED, mDestroyed);
562             dumpStream.write("is_bound", PrintSpoolerStateProto.IS_BOUND, mRemoteInstance != null);
563         }
564 
565         try {
566             if (dumpStream.isProto()) {
567                 dumpStream.write(null, PrintSpoolerStateProto.INTERNAL_STATE,
568                         TransferPipe.dumpAsync(getRemoteInstanceLazy().asBinder(), "--proto"));
569             } else {
570                 dumpStream.writeNested("internal_state", TransferPipe.dumpAsync(
571                         getRemoteInstanceLazy().asBinder()));
572             }
573         } catch (IOException | TimeoutException | RemoteException | InterruptedException e) {
574             Slog.e(LOG_TAG, "Failed to dump remote instance", e);
575         }
576     }
577 
onAllPrintJobsHandled()578     private void onAllPrintJobsHandled() {
579         synchronized (mLock) {
580             throwIfDestroyedLocked();
581             unbindLocked();
582         }
583     }
584 
onPrintJobStateChanged(PrintJobInfo printJob)585     private void onPrintJobStateChanged(PrintJobInfo printJob) {
586         mCallbacks.onPrintJobStateChanged(printJob);
587     }
588 
getRemoteInstanceLazy()589     private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException, InterruptedException {
590         synchronized (mLock) {
591             if (mRemoteInstance != null) {
592                 return mRemoteInstance;
593             }
594             bindLocked();
595             return mRemoteInstance;
596         }
597     }
598 
599     @GuardedBy("mLock")
bindLocked()600     private void bindLocked() throws TimeoutException, InterruptedException {
601         while (mIsBinding) {
602             mLock.wait();
603         }
604 
605         if (mRemoteInstance != null) {
606             return;
607         }
608 
609         mIsBinding = true;
610 
611         if (DEBUG) {
612             Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] bindLocked() " +
613                     (mIsLowPriority ? "low priority" : ""));
614         }
615 
616         try {
617             int flags;
618             if (mIsLowPriority) {
619                 flags = Context.BIND_AUTO_CREATE;
620             } else {
621                 flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
622             }
623 
624             mContext.bindServiceAsUser(mIntent, mServiceConnection, flags, mUserHandle);
625 
626             final long startMillis = SystemClock.uptimeMillis();
627             while (true) {
628                 if (mRemoteInstance != null) {
629                     break;
630                 }
631                 final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
632                 final long remainingMillis = BIND_SPOOLER_SERVICE_TIMEOUT - elapsedMillis;
633                 if (remainingMillis <= 0) {
634                     throw new TimeoutException("Cannot get spooler!");
635                 }
636                 mLock.wait(remainingMillis);
637             }
638 
639             mCanUnbind = true;
640         } finally {
641             mIsBinding = false;
642             mLock.notifyAll();
643         }
644     }
645 
unbindLocked()646     private void unbindLocked() {
647         if (mRemoteInstance == null) {
648             return;
649         }
650         while (true) {
651             if (mCanUnbind) {
652                 if (DEBUG) {
653                     Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()");
654                 }
655                 clearClientLocked();
656                 mRemoteInstance = null;
657                 mContext.unbindService(mServiceConnection);
658                 return;
659             }
660             try {
661                 mLock.wait();
662             } catch (InterruptedException ie) {
663                 /* ignore */
664             }
665         }
666 
667     }
668 
setClientLocked()669     private void setClientLocked() {
670         try {
671             mRemoteInstance.setClient(mClient);
672         } catch (RemoteException re) {
673             Slog.d(LOG_TAG, "Error setting print spooler client", re);
674         }
675     }
676 
clearClientLocked()677     private void clearClientLocked() {
678         try {
679             mRemoteInstance.setClient(null);
680         } catch (RemoteException re) {
681             Slog.d(LOG_TAG, "Error clearing print spooler client", re);
682         }
683 
684     }
685 
throwIfDestroyedLocked()686     private void throwIfDestroyedLocked() {
687         if (mDestroyed) {
688             throw new IllegalStateException("Cannot interact with a destroyed instance.");
689         }
690     }
691 
throwIfCalledOnMainThread()692     private void throwIfCalledOnMainThread() {
693         if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
694             throw new RuntimeException("Cannot invoke on the main thread");
695         }
696     }
697 
698     private final class MyServiceConnection implements ServiceConnection {
699         @Override
onServiceConnected(ComponentName name, IBinder service)700         public void onServiceConnected(ComponentName name, IBinder service) {
701             synchronized (mLock) {
702                 mRemoteInstance = IPrintSpooler.Stub.asInterface(service);
703                 setClientLocked();
704                 mLock.notifyAll();
705             }
706         }
707 
708         @Override
onServiceDisconnected(ComponentName name)709         public void onServiceDisconnected(ComponentName name) {
710             synchronized (mLock) {
711                 if (mRemoteInstance != null) {
712                     clearClientLocked();
713                     mRemoteInstance = null;
714                 }
715             }
716         }
717     }
718 
719     private static final class GetPrintJobInfosCaller
720             extends TimedRemoteCaller<List<PrintJobInfo>> {
721         private final IPrintSpoolerCallbacks mCallback;
722 
GetPrintJobInfosCaller()723         public GetPrintJobInfosCaller() {
724             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
725             mCallback = new BasePrintSpoolerServiceCallbacks() {
726                 @Override
727                 public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobs, int sequence) {
728                     onRemoteMethodResult(printJobs, sequence);
729                 }
730             };
731         }
732 
getPrintJobInfos(IPrintSpooler target, ComponentName componentName, int state, int appId)733         public List<PrintJobInfo> getPrintJobInfos(IPrintSpooler target,
734                 ComponentName componentName, int state, int appId)
735                         throws RemoteException, TimeoutException {
736             final int sequence = onBeforeRemoteCall();
737             target.getPrintJobInfos(mCallback, componentName, state, appId, sequence);
738             return getResultTimed(sequence);
739         }
740     }
741 
742     private static final class GetPrintJobInfoCaller extends TimedRemoteCaller<PrintJobInfo> {
743         private final IPrintSpoolerCallbacks mCallback;
744 
GetPrintJobInfoCaller()745         public GetPrintJobInfoCaller() {
746             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
747             mCallback = new BasePrintSpoolerServiceCallbacks() {
748                 @Override
749                 public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) {
750                     onRemoteMethodResult(printJob, sequence);
751                 }
752             };
753         }
754 
getPrintJobInfo(IPrintSpooler target, PrintJobId printJobId, int appId)755         public PrintJobInfo getPrintJobInfo(IPrintSpooler target, PrintJobId printJobId,
756                 int appId) throws RemoteException, TimeoutException {
757             final int sequence = onBeforeRemoteCall();
758             target.getPrintJobInfo(printJobId, mCallback, appId, sequence);
759             return getResultTimed(sequence);
760         }
761     }
762 
763     private static final class SetPrintJobStateCaller extends TimedRemoteCaller<Boolean> {
764         private final IPrintSpoolerCallbacks mCallback;
765 
SetPrintJobStateCaller()766         public SetPrintJobStateCaller() {
767             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
768             mCallback = new BasePrintSpoolerServiceCallbacks() {
769                 @Override
770                 public void onSetPrintJobStateResult(boolean success, int sequence) {
771                     onRemoteMethodResult(success, sequence);
772                 }
773             };
774         }
775 
setPrintJobState(IPrintSpooler target, PrintJobId printJobId, int status, String error)776         public boolean setPrintJobState(IPrintSpooler target, PrintJobId printJobId,
777                 int status, String error) throws RemoteException, TimeoutException {
778             final int sequence = onBeforeRemoteCall();
779             target.setPrintJobState(printJobId, status, error, mCallback, sequence);
780             return getResultTimed(sequence);
781         }
782     }
783 
784     private static final class SetPrintJobTagCaller extends TimedRemoteCaller<Boolean> {
785         private final IPrintSpoolerCallbacks mCallback;
786 
SetPrintJobTagCaller()787         public SetPrintJobTagCaller() {
788             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
789             mCallback = new BasePrintSpoolerServiceCallbacks() {
790                 @Override
791                 public void onSetPrintJobTagResult(boolean success, int sequence) {
792                     onRemoteMethodResult(success, sequence);
793                 }
794             };
795         }
796 
setPrintJobTag(IPrintSpooler target, PrintJobId printJobId, String tag)797         public boolean setPrintJobTag(IPrintSpooler target, PrintJobId printJobId,
798                 String tag) throws RemoteException, TimeoutException {
799             final int sequence = onBeforeRemoteCall();
800             target.setPrintJobTag(printJobId, tag, mCallback, sequence);
801             return getResultTimed(sequence);
802         }
803     }
804 
805     private static final class OnCustomPrinterIconLoadedCaller extends TimedRemoteCaller<Void> {
806         private final IPrintSpoolerCallbacks mCallback;
807 
OnCustomPrinterIconLoadedCaller()808         public OnCustomPrinterIconLoadedCaller() {
809             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
810             mCallback = new BasePrintSpoolerServiceCallbacks() {
811                 @Override
812                 public void onCustomPrinterIconCached(int sequence) {
813                     onRemoteMethodResult(null, sequence);
814                 }
815             };
816         }
817 
onCustomPrinterIconLoaded(IPrintSpooler target, PrinterId printerId, Icon icon)818         public Void onCustomPrinterIconLoaded(IPrintSpooler target, PrinterId printerId,
819                 Icon icon) throws RemoteException, TimeoutException {
820             final int sequence = onBeforeRemoteCall();
821             target.onCustomPrinterIconLoaded(printerId, icon, mCallback, sequence);
822             return getResultTimed(sequence);
823         }
824     }
825 
826     private static final class ClearCustomPrinterIconCacheCaller extends TimedRemoteCaller<Void> {
827         private final IPrintSpoolerCallbacks mCallback;
828 
ClearCustomPrinterIconCacheCaller()829         public ClearCustomPrinterIconCacheCaller() {
830             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
831             mCallback = new BasePrintSpoolerServiceCallbacks() {
832                 @Override
833                 public void customPrinterIconCacheCleared(int sequence) {
834                     onRemoteMethodResult(null, sequence);
835                 }
836             };
837         }
838 
clearCustomPrinterIconCache(IPrintSpooler target)839         public Void clearCustomPrinterIconCache(IPrintSpooler target)
840                 throws RemoteException, TimeoutException {
841             final int sequence = onBeforeRemoteCall();
842             target.clearCustomPrinterIconCache(mCallback, sequence);
843             return getResultTimed(sequence);
844         }
845     }
846 
847     private static final class GetCustomPrinterIconCaller extends TimedRemoteCaller<Icon> {
848         private final IPrintSpoolerCallbacks mCallback;
849 
GetCustomPrinterIconCaller()850         public GetCustomPrinterIconCaller() {
851             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
852             mCallback = new BasePrintSpoolerServiceCallbacks() {
853                 @Override
854                 public void onGetCustomPrinterIconResult(Icon icon, int sequence) {
855                     onRemoteMethodResult(icon, sequence);
856                 }
857             };
858         }
859 
getCustomPrinterIcon(IPrintSpooler target, PrinterId printerId)860         public Icon getCustomPrinterIcon(IPrintSpooler target, PrinterId printerId)
861                 throws RemoteException, TimeoutException {
862             final int sequence = onBeforeRemoteCall();
863             target.getCustomPrinterIcon(printerId, mCallback, sequence);
864             return getResultTimed(sequence);
865         }
866     }
867 
868     private static abstract class BasePrintSpoolerServiceCallbacks
869             extends IPrintSpoolerCallbacks.Stub {
870         @Override
onGetPrintJobInfosResult(List<PrintJobInfo> printJobIds, int sequence)871         public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobIds, int sequence) {
872             /* do nothing */
873         }
874 
875         @Override
onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence)876         public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) {
877             /* do nothing */
878         }
879 
880         @Override
onCancelPrintJobResult(boolean canceled, int sequence)881         public void onCancelPrintJobResult(boolean canceled, int sequence) {
882             /* do nothing */
883         }
884 
885         @Override
onSetPrintJobStateResult(boolean success, int sequece)886         public void onSetPrintJobStateResult(boolean success, int sequece) {
887             /* do nothing */
888         }
889 
890         @Override
onSetPrintJobTagResult(boolean success, int sequence)891         public void onSetPrintJobTagResult(boolean success, int sequence) {
892             /* do nothing */
893         }
894 
895         @Override
onCustomPrinterIconCached(int sequence)896         public void onCustomPrinterIconCached(int sequence) {
897             /* do nothing */
898         }
899 
900         @Override
onGetCustomPrinterIconResult(@ullable Icon icon, int sequence)901         public void onGetCustomPrinterIconResult(@Nullable Icon icon, int sequence) {
902             /* do nothing */
903         }
904 
905         @Override
customPrinterIconCacheCleared(int sequence)906         public void customPrinterIconCacheCleared(int sequence) {
907             /* do nothing */
908         }
909     }
910 
911     private static final class PrintSpoolerClient extends IPrintSpoolerClient.Stub {
912 
913         private final WeakReference<RemotePrintSpooler> mWeakSpooler;
914 
PrintSpoolerClient(RemotePrintSpooler spooler)915         public PrintSpoolerClient(RemotePrintSpooler spooler) {
916             mWeakSpooler = new WeakReference<RemotePrintSpooler>(spooler);
917         }
918 
919         @Override
onPrintJobQueued(PrintJobInfo printJob)920         public void onPrintJobQueued(PrintJobInfo printJob) {
921             RemotePrintSpooler spooler = mWeakSpooler.get();
922             if (spooler != null) {
923                 final long identity = Binder.clearCallingIdentity();
924                 try {
925                     spooler.mCallbacks.onPrintJobQueued(printJob);
926                 } finally {
927                     Binder.restoreCallingIdentity(identity);
928                 }
929             }
930         }
931 
932         @Override
onAllPrintJobsForServiceHandled(ComponentName printService)933         public void onAllPrintJobsForServiceHandled(ComponentName printService) {
934             RemotePrintSpooler spooler = mWeakSpooler.get();
935             if (spooler != null) {
936                 final long identity = Binder.clearCallingIdentity();
937                 try {
938                     spooler.mCallbacks.onAllPrintJobsForServiceHandled(printService);
939                 } finally {
940                     Binder.restoreCallingIdentity(identity);
941                 }
942             }
943         }
944 
945         @Override
onAllPrintJobsHandled()946         public void onAllPrintJobsHandled() {
947             RemotePrintSpooler spooler = mWeakSpooler.get();
948             if (spooler != null) {
949                 final long identity = Binder.clearCallingIdentity();
950                 try {
951                     spooler.onAllPrintJobsHandled();
952                 } finally {
953                     Binder.restoreCallingIdentity(identity);
954                 }
955             }
956         }
957 
958         @Override
onPrintJobStateChanged(PrintJobInfo printJob)959         public void onPrintJobStateChanged(PrintJobInfo printJob) {
960             RemotePrintSpooler spooler = mWeakSpooler.get();
961             if (spooler != null) {
962                 final long identity = Binder.clearCallingIdentity();
963                 try {
964                     spooler.onPrintJobStateChanged(printJob);
965                 } finally {
966                     Binder.restoreCallingIdentity(identity);
967                 }
968             }
969         }
970     }
971 }
972