RemotePrintService.java revision 66160bb881470a691005c8ad4e9c31c41fd5f810
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.Handler;
26import android.os.IBinder;
27import android.os.IBinder.DeathRecipient;
28import android.os.Looper;
29import android.os.Message;
30import android.os.ParcelFileDescriptor;
31import android.os.RemoteException;
32import android.os.UserHandle;
33import android.print.IPrinterDiscoverySessionController;
34import android.print.IPrinterDiscoverySessionObserver;
35import android.print.PrintJobInfo;
36import android.print.PrintManager;
37import android.print.PrinterId;
38import android.print.PrinterInfo;
39import android.printservice.IPrintService;
40import android.printservice.IPrintServiceClient;
41import android.util.Slog;
42
43import java.lang.ref.WeakReference;
44import java.util.ArrayList;
45import java.util.List;
46
47/**
48 * This class represents a remote print service. It abstracts away the binding
49 * and unbinding from the remote implementation. Clients can call methods of
50 * this class without worrying about when and how to bind/unbind.
51 */
52final class RemotePrintService implements DeathRecipient {
53
54    private static final String LOG_TAG = "RemotePrintService";
55
56    private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
57
58    private final Context mContext;
59
60    private final ComponentName mComponentName;
61
62    private final Intent mIntent;
63
64    private final RemotePrintSpooler mSpooler;
65
66    private final int mUserId;
67
68    private final List<Runnable> mPendingCommands = new ArrayList<Runnable>();
69
70    private final ServiceConnection mServiceConnection = new RemoteServiceConneciton();
71
72    private final RemotePrintServiceClient mPrintServiceClient;
73
74    private final Handler mHandler;
75
76    private IPrintService mPrintService;
77
78    private boolean mBinding;
79
80    private boolean mDestroyed;
81
82    public RemotePrintService(Context context, ComponentName componentName, int userId,
83            RemotePrintSpooler spooler) {
84        mContext = context;
85        mComponentName = componentName;
86        mIntent = new Intent().setComponent(mComponentName);
87        mUserId = userId;
88        mSpooler = spooler;
89        mHandler = new MyHandler(context.getMainLooper());
90        mPrintServiceClient = new RemotePrintServiceClient(this);
91    }
92
93    public void destroy() {
94        mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY);
95    }
96
97    private void handleDestroy() {
98        throwIfDestroyed();
99        ensureUnbound();
100        mDestroyed = true;
101    }
102
103    public void onAllPrintJobsHandled() {
104        mHandler.sendEmptyMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED);
105    }
106
107    @Override
108    public void binderDied() {
109        mHandler.sendEmptyMessage(MyHandler.MSG_BINDER_DIED);
110    }
111
112    private void handleBinderDied() {
113        mPendingCommands.clear();
114        ensureUnbound();
115    }
116
117    private void handleOnAllPrintJobsHandled() {
118        throwIfDestroyed();
119        if (isBound()) {
120            if (DEBUG) {
121                Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnAllPrintJobsHandled()");
122            }
123            // If bound and all the work is completed, then unbind.
124            ensureUnbound();
125        }
126    }
127
128    public void onRequestCancelPrintJob(PrintJobInfo printJob) {
129        mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_CANCEL_PRINT_JOB,
130                printJob).sendToTarget();
131    }
132
133    private void handleRequestCancelPrintJob(final PrintJobInfo printJob) {
134        throwIfDestroyed();
135        // If we are not bound, then we have no print jobs to handle
136        // which means that there are no print jobs to be cancelled.
137        if (isBound()) {
138            if (DEBUG) {
139                Slog.i(LOG_TAG, "[user: " + mUserId + "] handleRequestCancelPrintJob()");
140            }
141            try {
142                mPrintService.requestCancelPrintJob(printJob);
143            } catch (RemoteException re) {
144                Slog.e(LOG_TAG, "Error canceling a pring job.", re);
145            }
146        }
147    }
148
149    public void onPrintJobQueued(PrintJobInfo printJob) {
150        mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED,
151                printJob).sendToTarget();
152    }
153
154    private void handleOnPrintJobQueued(final PrintJobInfo printJob) {
155        throwIfDestroyed();
156        if (!isBound()) {
157            ensureBound();
158            mPendingCommands.add(new Runnable() {
159                @Override
160                 public void run() {
161                    handleOnPrintJobQueued(printJob);
162                }
163            });
164        } else {
165            if (DEBUG) {
166                Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnPrintJobQueued()");
167            }
168            try {
169                mPrintService.onPrintJobQueued(printJob);
170            } catch (RemoteException re) {
171                Slog.e(LOG_TAG, "Error announcing queued pring job.", re);
172            }
173        }
174    }
175
176    public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
177        mHandler.obtainMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION,
178                observer).sendToTarget();
179    }
180
181    private void handleCreatePrinterDiscoverySession(
182            final IPrinterDiscoverySessionObserver observer) {
183        throwIfDestroyed();
184        if (!isBound()) {
185            ensureBound();
186            mPendingCommands.add(new Runnable() {
187                @Override
188                public void run() {
189                    handleCreatePrinterDiscoverySession(observer);
190                }
191            });
192        } else {
193            if (DEBUG) {
194                Slog.i(LOG_TAG, "[user: " + mUserId + "] createPrinterDiscoverySession()");
195            }
196            try {
197                mPrintService.createPrinterDiscoverySession(observer);
198            } catch (RemoteException re) {
199                Slog.e(LOG_TAG, "Error announcing start printer dicovery.", re);
200            }
201        }
202    }
203
204    private boolean isBound() {
205        return mPrintService != null;
206    }
207
208    private void ensureBound() {
209        if (isBound() || mBinding) {
210            return;
211        }
212        if (DEBUG) {
213            Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureBound()");
214        }
215        mBinding = true;
216        mContext.bindServiceAsUser(mIntent, mServiceConnection,
217                Context.BIND_AUTO_CREATE, new UserHandle(mUserId));
218    }
219
220    private void ensureUnbound() {
221        if (!isBound() && !mBinding) {
222            return;
223        }
224        if (DEBUG) {
225            Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureUnbound()");
226        }
227        mBinding = false;
228        mPendingCommands.clear();
229        if (isBound()) {
230            try {
231                mPrintService.setClient(null);
232            } catch (RemoteException re) {
233                /* ignore */
234            }
235            mPrintService.asBinder().unlinkToDeath(this, 0);
236            mPrintService = null;
237            mContext.unbindService(mServiceConnection);
238        }
239    }
240
241    private void throwIfDestroyed() {
242        if (mDestroyed) {
243            throw new IllegalStateException("Cannot interact with a destroyed service");
244        }
245    }
246
247    private class RemoteServiceConneciton implements ServiceConnection {
248        @Override
249        public void onServiceConnected(ComponentName name, IBinder service) {
250            if (mDestroyed || !mBinding) {
251                return;
252            }
253            mBinding = false;
254            mPrintService = IPrintService.Stub.asInterface(service);
255            try {
256                service.linkToDeath(RemotePrintService.this, 0);
257            } catch (RemoteException re) {
258                handleBinderDied();
259                return;
260            }
261            try {
262                mPrintService.setClient(mPrintServiceClient);
263            } catch (RemoteException re) {
264                Slog.e(LOG_TAG, "Error setting client for: " + service, re);
265                handleBinderDied();
266                return;
267            }
268            final int pendingCommandCount = mPendingCommands.size();
269            for (int i = 0; i < pendingCommandCount; i++) {
270                Runnable pendingCommand = mPendingCommands.get(i);
271                pendingCommand.run();
272            }
273        }
274
275        @Override
276        public void onServiceDisconnected(ComponentName name) {
277            mBinding = true;
278        }
279    }
280
281    private final class MyHandler extends Handler {
282        public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 1;
283        public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 2;
284        public static final int MSG_ON_PRINT_JOB_QUEUED = 3;
285        public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 4;
286        public static final int MSG_DESTROY = 6;
287        public static final int MSG_BINDER_DIED = 7;
288
289        public MyHandler(Looper looper) {
290            super(looper, null, false);
291        }
292
293        @Override
294        public void handleMessage(Message message) {
295            switch (message.what) {
296                case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
297                    handleOnAllPrintJobsHandled();
298                } break;
299
300                case MSG_ON_REQUEST_CANCEL_PRINT_JOB: {
301                    PrintJobInfo printJob = (PrintJobInfo) message.obj;
302                    handleRequestCancelPrintJob(printJob);
303                } break;
304
305                case MSG_ON_PRINT_JOB_QUEUED: {
306                    PrintJobInfo printJob = (PrintJobInfo) message.obj;
307                    handleOnPrintJobQueued(printJob);
308                } break;
309
310                case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
311                    IPrinterDiscoverySessionObserver observer =
312                            (IPrinterDiscoverySessionObserver) message.obj;
313                    handleCreatePrinterDiscoverySession(new SecurePrinterDiscoverySessionObserver(
314                            mComponentName, observer));
315                } break;
316
317                case MSG_DESTROY: {
318                    handleDestroy();
319                } break;
320
321                case MSG_BINDER_DIED: {
322                    handleBinderDied();
323                } break;
324            }
325        }
326    }
327
328    private static final class RemotePrintServiceClient extends IPrintServiceClient.Stub {
329        private final WeakReference<RemotePrintService> mWeakService;
330
331        public RemotePrintServiceClient(RemotePrintService service) {
332            mWeakService = new WeakReference<RemotePrintService>(service);
333        }
334
335        @Override
336        public List<PrintJobInfo> getPrintJobInfos() {
337            RemotePrintService service = mWeakService.get();
338            if (service != null) {
339                final long identity = Binder.clearCallingIdentity();
340                try {
341                    return service.mSpooler.getPrintJobInfos(service.mComponentName,
342                            PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS, PrintManager.APP_ID_ANY);
343                } finally {
344                    Binder.restoreCallingIdentity(identity);
345                }
346            }
347            return null;
348        }
349
350        @Override
351        public PrintJobInfo getPrintJobInfo(int printJobId) {
352            RemotePrintService service = mWeakService.get();
353            if (service != null) {
354                final long identity = Binder.clearCallingIdentity();
355                try {
356                    return service.mSpooler.getPrintJobInfo(printJobId,
357                            PrintManager.APP_ID_ANY);
358                } finally {
359                    Binder.restoreCallingIdentity(identity);
360                }
361            }
362            return null;
363        }
364
365        @Override
366        public boolean setPrintJobState(int printJobId, int state, CharSequence error) {
367            RemotePrintService service = mWeakService.get();
368            if (service != null) {
369                final long identity = Binder.clearCallingIdentity();
370                try {
371                    return service.mSpooler.setPrintJobState(printJobId, state, error);
372                } finally {
373                    Binder.restoreCallingIdentity(identity);
374                }
375            }
376            return false;
377        }
378
379        @Override
380        public boolean setPrintJobTag(int printJobId, String tag) {
381            RemotePrintService service = mWeakService.get();
382            if (service != null) {
383                final long identity = Binder.clearCallingIdentity();
384                try {
385                    return service.mSpooler.setPrintJobTag(printJobId, tag);
386                } finally {
387                    Binder.restoreCallingIdentity(identity);
388                }
389            }
390            return false;
391        }
392
393        @Override
394        public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
395            RemotePrintService service = mWeakService.get();
396            if (service != null) {
397                final long identity = Binder.clearCallingIdentity();
398                try {
399                    service.mSpooler.writePrintJobData(fd, printJobId);
400                } finally {
401                    Binder.restoreCallingIdentity(identity);
402                }
403            }
404        }
405    }
406
407    private static final class SecurePrinterDiscoverySessionObserver
408            extends IPrinterDiscoverySessionObserver.Stub {
409        private final ComponentName mComponentName;
410
411        private final IPrinterDiscoverySessionObserver mDecoratedObsever;
412
413        public SecurePrinterDiscoverySessionObserver(ComponentName componentName,
414                IPrinterDiscoverySessionObserver observer) {
415            mComponentName = componentName;
416            mDecoratedObsever = observer;
417        }
418
419        @Override
420        public void onPrintersAdded(List<PrinterInfo> printers) {
421            throwIfPrinterIdsForPrinterInfoTampered(printers);
422            try {
423                mDecoratedObsever.onPrintersAdded(printers);
424            } catch (RemoteException re) {
425                Slog.e(LOG_TAG, "Error delegating to onPrintersAdded", re);
426            }
427        }
428
429        @Override
430        public void onPrintersUpdated(List<PrinterInfo> printers) {
431            throwIfPrinterIdsForPrinterInfoTampered(printers);
432            try {
433                mDecoratedObsever.onPrintersUpdated(printers);
434            } catch (RemoteException re) {
435                Slog.e(LOG_TAG, "Error delegating to onPrintersUpdated.", re);
436            }
437        }
438
439        @Override
440        public void onPrintersRemoved(List<PrinterId> printerIds) {
441            throwIfPrinterIdsTampered(printerIds);
442            try {
443                mDecoratedObsever.onPrintersRemoved(printerIds);
444            } catch (RemoteException re) {
445                Slog.e(LOG_TAG, "Error delegating to onPrintersRemoved", re);
446            }
447        }
448
449        @Override
450        public void setController(IPrinterDiscoverySessionController controller) {
451            try {
452                mDecoratedObsever.setController(controller);
453            } catch (RemoteException re) {
454                Slog.e(LOG_TAG, "Error setting controller", re);
455            }
456        }
457
458        private void throwIfPrinterIdsForPrinterInfoTampered(
459                List<PrinterInfo> printerInfos) {
460            final int printerInfoCount = printerInfos.size();
461            for (int i = 0; i < printerInfoCount; i++) {
462                PrinterId printerId = printerInfos.get(i).getId();
463                throwIfPrinterIdTampered(printerId);
464            }
465        }
466
467        private void throwIfPrinterIdsTampered(List<PrinterId> printerIds) {
468            final int printerIdCount = printerIds.size();
469            for (int i = 0; i < printerIdCount; i++) {
470                PrinterId printerId = printerIds.get(i);
471                throwIfPrinterIdTampered(printerId);
472            }
473        }
474
475        private void throwIfPrinterIdTampered(PrinterId printerId) {
476            if (printerId == null || printerId.getServiceName() == null
477                    || !printerId.getServiceName().equals(mComponentName)) {
478                throw new IllegalArgumentException("Invalid printer id: " + printerId);
479            }
480        }
481    }
482}
483