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.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.os.Binder;
24import android.os.Build;
25import android.os.IBinder;
26import android.os.ParcelFileDescriptor;
27import android.os.RemoteException;
28import android.os.SystemClock;
29import android.os.UserHandle;
30import android.print.IPrintSpooler;
31import android.print.IPrintSpoolerCallbacks;
32import android.print.IPrintSpoolerClient;
33import android.print.PrintJobId;
34import android.print.PrintJobInfo;
35import android.util.Slog;
36import android.util.TimedRemoteCaller;
37
38import java.io.FileDescriptor;
39import java.io.PrintWriter;
40import java.lang.ref.WeakReference;
41import java.util.List;
42import java.util.concurrent.TimeoutException;
43
44import libcore.io.IoUtils;
45
46/**
47 * This represents the remote print spooler as a local object to the
48 * PrintManagerService. It is responsible to connecting to the remote
49 * spooler if needed, to make the timed remote calls, to handle
50 * remote exceptions, and to bind/unbind to the remote instance as
51 * needed.
52 */
53final class RemotePrintSpooler {
54
55    private static final String LOG_TAG = "RemotePrintSpooler";
56
57    private static final boolean DEBUG = false;
58
59    private static final long BIND_SPOOLER_SERVICE_TIMEOUT =
60            ("eng".equals(Build.TYPE)) ? 120000 : 10000;
61
62    private final Object mLock = new Object();
63
64    private final GetPrintJobInfosCaller mGetPrintJobInfosCaller = new GetPrintJobInfosCaller();
65
66    private final GetPrintJobInfoCaller mGetPrintJobInfoCaller = new GetPrintJobInfoCaller();
67
68    private final SetPrintJobStateCaller mSetPrintJobStatusCaller = new SetPrintJobStateCaller();
69
70    private final SetPrintJobTagCaller mSetPrintJobTagCaller = new SetPrintJobTagCaller();
71
72    private final ServiceConnection mServiceConnection = new MyServiceConnection();
73
74    private final Context mContext;
75
76    private final UserHandle mUserHandle;
77
78    private final PrintSpoolerClient mClient;
79
80    private final Intent mIntent;
81
82    private final PrintSpoolerCallbacks mCallbacks;
83
84    private IPrintSpooler mRemoteInstance;
85
86    private boolean mDestroyed;
87
88    private boolean mCanUnbind;
89
90    public static interface PrintSpoolerCallbacks {
91        public void onPrintJobQueued(PrintJobInfo printJob);
92        public void onAllPrintJobsForServiceHandled(ComponentName printService);
93        public void onPrintJobStateChanged(PrintJobInfo printJob);
94    }
95
96    public RemotePrintSpooler(Context context, int userId,
97            PrintSpoolerCallbacks callbacks) {
98        mContext = context;
99        mUserHandle = new UserHandle(userId);
100        mCallbacks = callbacks;
101        mClient = new PrintSpoolerClient(this);
102        mIntent = new Intent();
103        mIntent.setComponent(new ComponentName("com.android.printspooler",
104                "com.android.printspooler.model.PrintSpoolerService"));
105    }
106
107    public final List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state,
108            int appId) {
109        throwIfCalledOnMainThread();
110        synchronized (mLock) {
111            throwIfDestroyedLocked();
112            mCanUnbind = false;
113        }
114        try {
115            return mGetPrintJobInfosCaller.getPrintJobInfos(getRemoteInstanceLazy(),
116                    componentName, state, appId);
117        } catch (RemoteException re) {
118            Slog.e(LOG_TAG, "Error getting print jobs.", re);
119        } catch (TimeoutException te) {
120            Slog.e(LOG_TAG, "Error getting print jobs.", te);
121        } finally {
122            if (DEBUG) {
123                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfos()");
124            }
125            synchronized (mLock) {
126                mCanUnbind = true;
127                mLock.notifyAll();
128            }
129        }
130        return null;
131    }
132
133    public final void createPrintJob(PrintJobInfo printJob) {
134        throwIfCalledOnMainThread();
135        synchronized (mLock) {
136            throwIfDestroyedLocked();
137            mCanUnbind = false;
138        }
139        try {
140            getRemoteInstanceLazy().createPrintJob(printJob);
141        } catch (RemoteException re) {
142            Slog.e(LOG_TAG, "Error creating print job.", re);
143        } catch (TimeoutException te) {
144            Slog.e(LOG_TAG, "Error creating print job.", te);
145        } finally {
146            if (DEBUG) {
147                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] createPrintJob()");
148            }
149            synchronized (mLock) {
150                mCanUnbind = true;
151                mLock.notifyAll();
152            }
153        }
154    }
155
156    public final void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
157        throwIfCalledOnMainThread();
158        synchronized (mLock) {
159            throwIfDestroyedLocked();
160            mCanUnbind = false;
161        }
162        try {
163            getRemoteInstanceLazy().writePrintJobData(fd, printJobId);
164        } catch (RemoteException re) {
165            Slog.e(LOG_TAG, "Error writing print job data.", re);
166        } catch (TimeoutException te) {
167            Slog.e(LOG_TAG, "Error writing print job data.", te);
168        } finally {
169            if (DEBUG) {
170                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] writePrintJobData()");
171            }
172            // We passed the file descriptor across and now the other
173            // side is responsible to close it, so close the local copy.
174            IoUtils.closeQuietly(fd);
175            synchronized (mLock) {
176                mCanUnbind = true;
177                mLock.notifyAll();
178            }
179        }
180    }
181
182    public final PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
183        throwIfCalledOnMainThread();
184        synchronized (mLock) {
185            throwIfDestroyedLocked();
186            mCanUnbind = false;
187        }
188        try {
189            return mGetPrintJobInfoCaller.getPrintJobInfo(getRemoteInstanceLazy(),
190                    printJobId, appId);
191        } catch (RemoteException re) {
192            Slog.e(LOG_TAG, "Error getting print job info.", re);
193        } catch (TimeoutException te) {
194            Slog.e(LOG_TAG, "Error getting print job info.", te);
195        } finally {
196            if (DEBUG) {
197                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfo()");
198            }
199            synchronized (mLock) {
200                mCanUnbind = true;
201                mLock.notifyAll();
202            }
203        }
204        return null;
205    }
206
207    public final boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
208        throwIfCalledOnMainThread();
209        synchronized (mLock) {
210            throwIfDestroyedLocked();
211            mCanUnbind = false;
212        }
213        try {
214            return mSetPrintJobStatusCaller.setPrintJobState(getRemoteInstanceLazy(),
215                    printJobId, state, error);
216        } catch (RemoteException re) {
217            Slog.e(LOG_TAG, "Error setting print job state.", re);
218        } catch (TimeoutException te) {
219            Slog.e(LOG_TAG, "Error setting print job state.", te);
220        } finally {
221            if (DEBUG) {
222                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobState()");
223            }
224            synchronized (mLock) {
225                mCanUnbind = true;
226                mLock.notifyAll();
227            }
228        }
229        return false;
230    }
231
232    public final boolean setPrintJobTag(PrintJobId printJobId, String tag) {
233        throwIfCalledOnMainThread();
234        synchronized (mLock) {
235            throwIfDestroyedLocked();
236            mCanUnbind = false;
237        }
238        try {
239            return mSetPrintJobTagCaller.setPrintJobTag(getRemoteInstanceLazy(),
240                    printJobId, tag);
241        } catch (RemoteException re) {
242            Slog.e(LOG_TAG, "Error setting print job tag.", re);
243        } catch (TimeoutException te) {
244            Slog.e(LOG_TAG, "Error setting print job tag.", te);
245        } finally {
246            if (DEBUG) {
247                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobTag()");
248            }
249            synchronized (mLock) {
250                mCanUnbind = true;
251                mLock.notifyAll();
252            }
253        }
254        return false;
255    }
256
257    public final void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
258        throwIfCalledOnMainThread();
259        synchronized (mLock) {
260            throwIfDestroyedLocked();
261            mCanUnbind = false;
262        }
263        try {
264            getRemoteInstanceLazy().setPrintJobCancelling(printJobId,
265                    cancelling);
266        } catch (RemoteException re) {
267            Slog.e(LOG_TAG, "Error setting print job cancelling.", re);
268        } catch (TimeoutException te) {
269            Slog.e(LOG_TAG, "Error setting print job cancelling.", te);
270        } finally {
271            if (DEBUG) {
272                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
273                        + "] setPrintJobCancelling()");
274            }
275            synchronized (mLock) {
276                mCanUnbind = true;
277                mLock.notifyAll();
278            }
279        }
280    }
281
282    public final void removeObsoletePrintJobs() {
283        throwIfCalledOnMainThread();
284        synchronized (mLock) {
285            throwIfDestroyedLocked();
286            mCanUnbind = false;
287        }
288        try {
289            getRemoteInstanceLazy().removeObsoletePrintJobs();
290        } catch (RemoteException re) {
291            Slog.e(LOG_TAG, "Error removing obsolete print jobs .", re);
292        } catch (TimeoutException te) {
293            Slog.e(LOG_TAG, "Error removing obsolete print jobs .", te);
294        } finally {
295            if (DEBUG) {
296                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
297                        + "] removeObsoletePrintJobs()");
298            }
299            synchronized (mLock) {
300                mCanUnbind = true;
301                mLock.notifyAll();
302            }
303        }
304    }
305
306    public final void destroy() {
307        throwIfCalledOnMainThread();
308        if (DEBUG) {
309            Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] destroy()");
310        }
311        synchronized (mLock) {
312            throwIfDestroyedLocked();
313            unbindLocked();
314            mDestroyed = true;
315            mCanUnbind = false;
316        }
317    }
318
319    public void dump(FileDescriptor fd, PrintWriter pw, String prefix) {
320        synchronized (mLock) {
321            pw.append(prefix).append("destroyed=")
322                    .append(String.valueOf(mDestroyed)).println();
323            pw.append(prefix).append("bound=")
324                    .append((mRemoteInstance != null) ? "true" : "false").println();
325
326            pw.flush();
327
328            try {
329                getRemoteInstanceLazy().asBinder().dump(fd, new String[]{prefix});
330            } catch (TimeoutException te) {
331                /* ignore */
332            } catch (RemoteException re) {
333                /* ignore */
334            }
335        }
336    }
337
338    private void onAllPrintJobsHandled() {
339        synchronized (mLock) {
340            throwIfDestroyedLocked();
341            unbindLocked();
342        }
343    }
344
345    private void onPrintJobStateChanged(PrintJobInfo printJob) {
346        mCallbacks.onPrintJobStateChanged(printJob);
347    }
348
349    private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException {
350        synchronized (mLock) {
351            if (mRemoteInstance != null) {
352                return mRemoteInstance;
353            }
354            bindLocked();
355            return mRemoteInstance;
356        }
357    }
358
359    private void bindLocked() throws TimeoutException {
360        if (mRemoteInstance != null) {
361            return;
362        }
363        if (DEBUG) {
364            Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] bindLocked()");
365        }
366
367        mContext.bindServiceAsUser(mIntent, mServiceConnection,
368                Context.BIND_AUTO_CREATE, mUserHandle);
369
370        final long startMillis = SystemClock.uptimeMillis();
371        while (true) {
372            if (mRemoteInstance != null) {
373                break;
374            }
375            final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
376            final long remainingMillis = BIND_SPOOLER_SERVICE_TIMEOUT - elapsedMillis;
377            if (remainingMillis <= 0) {
378                throw new TimeoutException("Cannot get spooler!");
379            }
380            try {
381                mLock.wait(remainingMillis);
382            } catch (InterruptedException ie) {
383                /* ignore */
384            }
385        }
386
387        mCanUnbind = true;
388        mLock.notifyAll();
389    }
390
391    private void unbindLocked() {
392        if (mRemoteInstance == null) {
393            return;
394        }
395        while (true) {
396            if (mCanUnbind) {
397                if (DEBUG) {
398                    Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()");
399                }
400                clearClientLocked();
401                mRemoteInstance = null;
402                mContext.unbindService(mServiceConnection);
403                return;
404            }
405            try {
406                mLock.wait();
407            } catch (InterruptedException ie) {
408                /* ignore */
409            }
410        }
411
412    }
413
414    private void setClientLocked() {
415        try {
416            mRemoteInstance.setClient(mClient);
417        } catch (RemoteException re) {
418            Slog.d(LOG_TAG, "Error setting print spooler client", re);
419        }
420    }
421
422    private void clearClientLocked() {
423        try {
424            mRemoteInstance.setClient(null);
425        } catch (RemoteException re) {
426            Slog.d(LOG_TAG, "Error clearing print spooler client", re);
427        }
428
429    }
430
431    private void throwIfDestroyedLocked() {
432        if (mDestroyed) {
433            throw new IllegalStateException("Cannot interact with a destroyed instance.");
434        }
435    }
436
437    private void throwIfCalledOnMainThread() {
438        if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
439            throw new RuntimeException("Cannot invoke on the main thread");
440        }
441    }
442
443    private final class MyServiceConnection implements ServiceConnection {
444        @Override
445        public void onServiceConnected(ComponentName name, IBinder service) {
446            synchronized (mLock) {
447                mRemoteInstance = IPrintSpooler.Stub.asInterface(service);
448                setClientLocked();
449                mLock.notifyAll();
450            }
451        }
452
453        @Override
454        public void onServiceDisconnected(ComponentName name) {
455            synchronized (mLock) {
456                clearClientLocked();
457                mRemoteInstance = null;
458            }
459        }
460    }
461
462    private static final class GetPrintJobInfosCaller
463            extends TimedRemoteCaller<List<PrintJobInfo>> {
464        private final IPrintSpoolerCallbacks mCallback;
465
466        public GetPrintJobInfosCaller() {
467            super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
468            mCallback = new BasePrintSpoolerServiceCallbacks() {
469                @Override
470                public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobs, int sequence) {
471                    onRemoteMethodResult(printJobs, sequence);
472                }
473            };
474        }
475
476        public List<PrintJobInfo> getPrintJobInfos(IPrintSpooler target,
477                ComponentName componentName, int state, int appId)
478                        throws RemoteException, TimeoutException {
479            final int sequence = onBeforeRemoteCall();
480            target.getPrintJobInfos(mCallback, componentName, state, appId, sequence);
481            return getResultTimed(sequence);
482        }
483    }
484
485    private static final class GetPrintJobInfoCaller extends TimedRemoteCaller<PrintJobInfo> {
486        private final IPrintSpoolerCallbacks mCallback;
487
488        public GetPrintJobInfoCaller() {
489            super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
490            mCallback = new BasePrintSpoolerServiceCallbacks() {
491                @Override
492                public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) {
493                    onRemoteMethodResult(printJob, sequence);
494                }
495            };
496        }
497
498        public PrintJobInfo getPrintJobInfo(IPrintSpooler target, PrintJobId printJobId,
499                int appId) throws RemoteException, TimeoutException {
500            final int sequence = onBeforeRemoteCall();
501            target.getPrintJobInfo(printJobId, mCallback, appId, sequence);
502            return getResultTimed(sequence);
503        }
504    }
505
506    private static final class SetPrintJobStateCaller extends TimedRemoteCaller<Boolean> {
507        private final IPrintSpoolerCallbacks mCallback;
508
509        public SetPrintJobStateCaller() {
510            super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
511            mCallback = new BasePrintSpoolerServiceCallbacks() {
512                @Override
513                public void onSetPrintJobStateResult(boolean success, int sequence) {
514                    onRemoteMethodResult(success, sequence);
515                }
516            };
517        }
518
519        public boolean setPrintJobState(IPrintSpooler target, PrintJobId printJobId,
520                int status, String error) throws RemoteException, TimeoutException {
521            final int sequence = onBeforeRemoteCall();
522            target.setPrintJobState(printJobId, status, error, mCallback, sequence);
523            return getResultTimed(sequence);
524        }
525    }
526
527    private static final class SetPrintJobTagCaller extends TimedRemoteCaller<Boolean> {
528        private final IPrintSpoolerCallbacks mCallback;
529
530        public SetPrintJobTagCaller() {
531            super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
532            mCallback = new BasePrintSpoolerServiceCallbacks() {
533                @Override
534                public void onSetPrintJobTagResult(boolean success, int sequence) {
535                    onRemoteMethodResult(success, sequence);
536                }
537            };
538        }
539
540        public boolean setPrintJobTag(IPrintSpooler target, PrintJobId printJobId,
541                String tag) throws RemoteException, TimeoutException {
542            final int sequence = onBeforeRemoteCall();
543            target.setPrintJobTag(printJobId, tag, mCallback, sequence);
544            return getResultTimed(sequence);
545        }
546    }
547
548    private static abstract class BasePrintSpoolerServiceCallbacks
549            extends IPrintSpoolerCallbacks.Stub {
550        @Override
551        public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobIds, int sequence) {
552            /* do nothing */
553        }
554
555        @Override
556        public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) {
557            /* do nothing */
558        }
559
560        @Override
561        public void onCancelPrintJobResult(boolean canceled, int sequence) {
562            /* do nothing */
563        }
564
565        @Override
566        public void onSetPrintJobStateResult(boolean success, int sequece) {
567            /* do nothing */
568        }
569
570        @Override
571        public void onSetPrintJobTagResult(boolean success, int sequence) {
572            /* do nothing */
573        }
574    }
575
576    private static final class PrintSpoolerClient extends IPrintSpoolerClient.Stub {
577
578        private final WeakReference<RemotePrintSpooler> mWeakSpooler;
579
580        public PrintSpoolerClient(RemotePrintSpooler spooler) {
581            mWeakSpooler = new WeakReference<RemotePrintSpooler>(spooler);
582        }
583
584        @Override
585        public void onPrintJobQueued(PrintJobInfo printJob) {
586            RemotePrintSpooler spooler = mWeakSpooler.get();
587            if (spooler != null) {
588                final long identity = Binder.clearCallingIdentity();
589                try {
590                    spooler.mCallbacks.onPrintJobQueued(printJob);
591                } finally {
592                    Binder.restoreCallingIdentity(identity);
593                }
594            }
595        }
596
597        @Override
598        public void onAllPrintJobsForServiceHandled(ComponentName printService) {
599            RemotePrintSpooler spooler = mWeakSpooler.get();
600            if (spooler != null) {
601                final long identity = Binder.clearCallingIdentity();
602                try {
603                    spooler.mCallbacks.onAllPrintJobsForServiceHandled(printService);
604                } finally {
605                    Binder.restoreCallingIdentity(identity);
606                }
607            }
608        }
609
610        @Override
611        public void onAllPrintJobsHandled() {
612            RemotePrintSpooler spooler = mWeakSpooler.get();
613            if (spooler != null) {
614                final long identity = Binder.clearCallingIdentity();
615                try {
616                    spooler.onAllPrintJobsHandled();
617                } finally {
618                    Binder.restoreCallingIdentity(identity);
619                }
620            }
621        }
622
623        @Override
624        public void onPrintJobStateChanged(PrintJobInfo printJob) {
625            RemotePrintSpooler spooler = mWeakSpooler.get();
626            if (spooler != null) {
627                final long identity = Binder.clearCallingIdentity();
628                try {
629                    spooler.onPrintJobStateChanged(printJob);
630                } finally {
631                    Binder.restoreCallingIdentity(identity);
632                }
633            }
634        }
635    }
636}
637