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 com.android.internal.annotations.GuardedBy;
47import com.android.internal.os.TransferPipe;
48
49import libcore.io.IoUtils;
50
51import java.io.FileDescriptor;
52import java.io.IOException;
53import java.io.PrintWriter;
54import java.lang.ref.WeakReference;
55import java.util.List;
56import 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 */
68final 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            ("eng".equals(Build.TYPE)) ? 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 {
121        public void onPrintJobQueued(PrintJobInfo printJob);
122        public void onAllPrintJobsForServiceHandled(ComponentName printService);
123        public void onPrintJobStateChanged(PrintJobInfo printJob);
124    }
125
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
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
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
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
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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
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
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     */
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
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
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
559    public void dump(FileDescriptor fd, PrintWriter pw, String prefix) {
560        synchronized (mLock) {
561            pw.append(prefix).append("destroyed=")
562                    .append(String.valueOf(mDestroyed)).println();
563            pw.append(prefix).append("bound=")
564                    .append((mRemoteInstance != null) ? "true" : "false").println();
565
566            pw.flush();
567            try {
568                TransferPipe.dumpAsync(getRemoteInstanceLazy().asBinder(), fd,
569                        new String[] { prefix });
570            } catch (IOException | TimeoutException | RemoteException | InterruptedException e) {
571                pw.println("Failed to dump remote instance: " + e);
572            }
573        }
574    }
575
576    private void onAllPrintJobsHandled() {
577        synchronized (mLock) {
578            throwIfDestroyedLocked();
579            unbindLocked();
580        }
581    }
582
583    private void onPrintJobStateChanged(PrintJobInfo printJob) {
584        mCallbacks.onPrintJobStateChanged(printJob);
585    }
586
587    private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException, InterruptedException {
588        synchronized (mLock) {
589            if (mRemoteInstance != null) {
590                return mRemoteInstance;
591            }
592            bindLocked();
593            return mRemoteInstance;
594        }
595    }
596
597    private void bindLocked() throws TimeoutException, InterruptedException {
598        while (mIsBinding) {
599            mLock.wait();
600        }
601
602        if (mRemoteInstance != null) {
603            return;
604        }
605
606        mIsBinding = true;
607
608        if (DEBUG) {
609            Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] bindLocked() " +
610                    (mIsLowPriority ? "low priority" : ""));
611        }
612
613        try {
614            int flags;
615            if (mIsLowPriority) {
616                flags = Context.BIND_AUTO_CREATE;
617            } else {
618                flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
619            }
620
621            mContext.bindServiceAsUser(mIntent, mServiceConnection, flags, mUserHandle);
622
623            final long startMillis = SystemClock.uptimeMillis();
624            while (true) {
625                if (mRemoteInstance != null) {
626                    break;
627                }
628                final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
629                final long remainingMillis = BIND_SPOOLER_SERVICE_TIMEOUT - elapsedMillis;
630                if (remainingMillis <= 0) {
631                    throw new TimeoutException("Cannot get spooler!");
632                }
633                mLock.wait(remainingMillis);
634            }
635
636            mCanUnbind = true;
637        } finally {
638            mIsBinding = false;
639            mLock.notifyAll();
640        }
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