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