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