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