Home | History | Annotate | Download | only in print
      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 
     17 package com.android.server.print;
     18 
     19 import android.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.ServiceConnection;
     23 import android.os.Binder;
     24 import android.os.Build;
     25 import android.os.IBinder;
     26 import android.os.ParcelFileDescriptor;
     27 import android.os.RemoteException;
     28 import android.os.SystemClock;
     29 import android.os.UserHandle;
     30 import android.print.IPrintSpooler;
     31 import android.print.IPrintSpoolerCallbacks;
     32 import android.print.IPrintSpoolerClient;
     33 import android.print.PrintJobId;
     34 import android.print.PrintJobInfo;
     35 import android.util.Slog;
     36 import android.util.TimedRemoteCaller;
     37 
     38 import java.io.FileDescriptor;
     39 import java.io.PrintWriter;
     40 import java.lang.ref.WeakReference;
     41 import java.util.List;
     42 import java.util.concurrent.TimeoutException;
     43 
     44 import 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  */
     53 final 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