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.annotation.FloatRange;
     20 import android.annotation.NonNull;
     21 import android.annotation.Nullable;
     22 import android.annotation.StringRes;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.ServiceConnection;
     27 import android.graphics.drawable.Icon;
     28 import android.os.Binder;
     29 import android.os.Build;
     30 import android.os.IBinder;
     31 import android.os.ParcelFileDescriptor;
     32 import android.os.RemoteException;
     33 import android.os.SystemClock;
     34 import android.os.UserHandle;
     35 import android.print.IPrintSpooler;
     36 import android.print.IPrintSpoolerCallbacks;
     37 import android.print.IPrintSpoolerClient;
     38 import android.print.PrintJobId;
     39 import android.print.PrintJobInfo;
     40 import android.print.PrintManager;
     41 import android.print.PrinterId;
     42 import android.printservice.PrintService;
     43 import android.service.print.PrintSpoolerStateProto;
     44 import android.util.Slog;
     45 import android.util.TimedRemoteCaller;
     46 
     47 import com.android.internal.annotations.GuardedBy;
     48 import com.android.internal.os.TransferPipe;
     49 import com.android.internal.util.dump.DualDumpOutputStream;
     50 
     51 import libcore.io.IoUtils;
     52 
     53 import java.io.IOException;
     54 import java.lang.ref.WeakReference;
     55 import java.util.List;
     56 import java.util.concurrent.TimeoutException;
     57 
     58 /**
     59  * This represents the remote print spooler as a local object to the
     60  * PrintManagerService. It is responsible to connecting to the remote
     61  * spooler if needed, to make the timed remote calls, to handle
     62  * remote exceptions, and to bind/unbind to the remote instance as
     63  * needed.
     64  *
     65  * The calls might be blocking and need the main thread of to be unblocked to finish. Hence do not
     66  * call this while holding any monitors that might need to be acquired the main thread.
     67  */
     68 final class RemotePrintSpooler {
     69 
     70     private static final String LOG_TAG = "RemotePrintSpooler";
     71 
     72     private static final boolean DEBUG = false;
     73 
     74     private static final long BIND_SPOOLER_SERVICE_TIMEOUT =
     75             (Build.IS_ENG) ? 120000 : 10000;
     76 
     77     private final Object mLock = new Object();
     78 
     79     private final GetPrintJobInfosCaller mGetPrintJobInfosCaller = new GetPrintJobInfosCaller();
     80 
     81     private final GetPrintJobInfoCaller mGetPrintJobInfoCaller = new GetPrintJobInfoCaller();
     82 
     83     private final SetPrintJobStateCaller mSetPrintJobStatusCaller = new SetPrintJobStateCaller();
     84 
     85     private final SetPrintJobTagCaller mSetPrintJobTagCaller = new SetPrintJobTagCaller();
     86 
     87     private final OnCustomPrinterIconLoadedCaller mCustomPrinterIconLoadedCaller =
     88             new OnCustomPrinterIconLoadedCaller();
     89 
     90     private final ClearCustomPrinterIconCacheCaller mClearCustomPrinterIconCache =
     91             new ClearCustomPrinterIconCacheCaller();
     92 
     93     private final GetCustomPrinterIconCaller mGetCustomPrinterIconCaller =
     94             new GetCustomPrinterIconCaller();
     95 
     96     private final ServiceConnection mServiceConnection = new MyServiceConnection();
     97 
     98     private final Context mContext;
     99 
    100     private final UserHandle mUserHandle;
    101 
    102     private final PrintSpoolerClient mClient;
    103 
    104     private final Intent mIntent;
    105 
    106     private final PrintSpoolerCallbacks mCallbacks;
    107 
    108     private boolean mIsLowPriority;
    109 
    110     private IPrintSpooler mRemoteInstance;
    111 
    112     private boolean mDestroyed;
    113 
    114     private boolean mCanUnbind;
    115 
    116     /** Whether a thread is currently trying to {@link #bindLocked() bind to the print service} */
    117     @GuardedBy("mLock")
    118     private boolean mIsBinding;
    119 
    120     public static interface PrintSpoolerCallbacks {
    121         public void onPrintJobQueued(PrintJobInfo printJob);
    122         public void onAllPrintJobsForServiceHandled(ComponentName printService);
    123         public void onPrintJobStateChanged(PrintJobInfo printJob);
    124     }
    125 
    126     public RemotePrintSpooler(Context context, int userId, boolean lowPriority,
    127             PrintSpoolerCallbacks callbacks) {
    128         mContext = context;
    129         mUserHandle = new UserHandle(userId);
    130         mCallbacks = callbacks;
    131         mIsLowPriority = lowPriority;
    132         mClient = new PrintSpoolerClient(this);
    133         mIntent = new Intent();
    134         mIntent.setComponent(new ComponentName(PrintManager.PRINT_SPOOLER_PACKAGE_NAME,
    135                 PrintManager.PRINT_SPOOLER_PACKAGE_NAME + ".model.PrintSpoolerService"));
    136     }
    137 
    138     public void increasePriority() {
    139         if (mIsLowPriority) {
    140             mIsLowPriority = false;
    141 
    142             synchronized (mLock) {
    143                 throwIfDestroyedLocked();
    144 
    145                 while (!mCanUnbind) {
    146                     try {
    147                         mLock.wait();
    148                     } catch (InterruptedException e) {
    149                         Slog.e(LOG_TAG, "Interrupted while waiting for operation to complete");
    150                     }
    151                 }
    152 
    153                 if (DEBUG) {
    154                     Slog.i(LOG_TAG, "Unbinding as previous binding was low priority");
    155                 }
    156 
    157                 unbindLocked();
    158             }
    159         }
    160     }
    161 
    162     public final List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state,
    163             int appId) {
    164         throwIfCalledOnMainThread();
    165         synchronized (mLock) {
    166             throwIfDestroyedLocked();
    167             mCanUnbind = false;
    168         }
    169         try {
    170             return mGetPrintJobInfosCaller.getPrintJobInfos(getRemoteInstanceLazy(),
    171                     componentName, state, appId);
    172         } catch (RemoteException | TimeoutException | InterruptedException e) {
    173             Slog.e(LOG_TAG, "Error getting print jobs.", e);
    174         } finally {
    175             if (DEBUG) {
    176                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfos()");
    177             }
    178             synchronized (mLock) {
    179                 mCanUnbind = true;
    180                 mLock.notifyAll();
    181             }
    182         }
    183         return null;
    184     }
    185 
    186     public final void createPrintJob(PrintJobInfo printJob) {
    187         throwIfCalledOnMainThread();
    188         synchronized (mLock) {
    189             throwIfDestroyedLocked();
    190             mCanUnbind = false;
    191         }
    192         try {
    193             getRemoteInstanceLazy().createPrintJob(printJob);
    194         } catch (RemoteException | TimeoutException | InterruptedException e) {
    195             Slog.e(LOG_TAG, "Error creating print job.", e);
    196         } finally {
    197             if (DEBUG) {
    198                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] createPrintJob()");
    199             }
    200             synchronized (mLock) {
    201                 mCanUnbind = true;
    202                 mLock.notifyAll();
    203             }
    204         }
    205     }
    206 
    207     public final void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
    208         throwIfCalledOnMainThread();
    209         synchronized (mLock) {
    210             throwIfDestroyedLocked();
    211             mCanUnbind = false;
    212         }
    213         try {
    214             getRemoteInstanceLazy().writePrintJobData(fd, printJobId);
    215         } catch (RemoteException | TimeoutException | InterruptedException e) {
    216             Slog.e(LOG_TAG, "Error writing print job data.", e);
    217         } finally {
    218             if (DEBUG) {
    219                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] writePrintJobData()");
    220             }
    221             // We passed the file descriptor across and now the other
    222             // side is responsible to close it, so close the local copy.
    223             IoUtils.closeQuietly(fd);
    224             synchronized (mLock) {
    225                 mCanUnbind = true;
    226                 mLock.notifyAll();
    227             }
    228         }
    229     }
    230 
    231     public final PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
    232         throwIfCalledOnMainThread();
    233         synchronized (mLock) {
    234             throwIfDestroyedLocked();
    235             mCanUnbind = false;
    236         }
    237         try {
    238             return mGetPrintJobInfoCaller.getPrintJobInfo(getRemoteInstanceLazy(),
    239                     printJobId, appId);
    240         } catch (RemoteException | TimeoutException | InterruptedException e) {
    241             Slog.e(LOG_TAG, "Error getting print job info.", e);
    242         } finally {
    243             if (DEBUG) {
    244                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfo()");
    245             }
    246             synchronized (mLock) {
    247                 mCanUnbind = true;
    248                 mLock.notifyAll();
    249             }
    250         }
    251         return null;
    252     }
    253 
    254     public final boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
    255         throwIfCalledOnMainThread();
    256         synchronized (mLock) {
    257             throwIfDestroyedLocked();
    258             mCanUnbind = false;
    259         }
    260         try {
    261             return mSetPrintJobStatusCaller.setPrintJobState(getRemoteInstanceLazy(),
    262                     printJobId, state, error);
    263         } catch (RemoteException | TimeoutException | InterruptedException e) {
    264             Slog.e(LOG_TAG, "Error setting print job state.", e);
    265         } finally {
    266             if (DEBUG) {
    267                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobState()");
    268             }
    269             synchronized (mLock) {
    270                 mCanUnbind = true;
    271                 mLock.notifyAll();
    272             }
    273         }
    274         return false;
    275     }
    276 
    277     /**
    278      * Set progress of a print job.
    279      *
    280      * @param printJobId The print job to update
    281      * @param progress The new progress
    282      */
    283     public final void setProgress(@NonNull PrintJobId printJobId,
    284             @FloatRange(from=0.0, to=1.0) float progress) {
    285         throwIfCalledOnMainThread();
    286         synchronized (mLock) {
    287             throwIfDestroyedLocked();
    288             mCanUnbind = false;
    289         }
    290         try {
    291             getRemoteInstanceLazy().setProgress(printJobId, progress);
    292         } catch (RemoteException | TimeoutException | InterruptedException re) {
    293             Slog.e(LOG_TAG, "Error setting progress.", re);
    294         } finally {
    295             if (DEBUG) {
    296                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setProgress()");
    297             }
    298             synchronized (mLock) {
    299                 mCanUnbind = true;
    300                 mLock.notifyAll();
    301             }
    302         }
    303     }
    304 
    305     /**
    306      * Set status of a print job.
    307      *
    308      * @param printJobId The print job to update
    309      * @param status The new status
    310      */
    311     public final void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
    312         throwIfCalledOnMainThread();
    313         synchronized (mLock) {
    314             throwIfDestroyedLocked();
    315             mCanUnbind = false;
    316         }
    317         try {
    318             getRemoteInstanceLazy().setStatus(printJobId, status);
    319         } catch (RemoteException | TimeoutException | InterruptedException e) {
    320             Slog.e(LOG_TAG, "Error setting status.", e);
    321         } finally {
    322             if (DEBUG) {
    323                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setStatus()");
    324             }
    325             synchronized (mLock) {
    326                 mCanUnbind = true;
    327                 mLock.notifyAll();
    328             }
    329         }
    330     }
    331 
    332     /**
    333      * Set status of a print job.
    334      *
    335      * @param printJobId The print job to update
    336      * @param status The new status as a string resource
    337      * @param appPackageName The app package name the string res belongs to
    338      */
    339     public final void setStatus(@NonNull PrintJobId printJobId, @StringRes int status,
    340             @NonNull CharSequence appPackageName) {
    341         throwIfCalledOnMainThread();
    342         synchronized (mLock) {
    343             throwIfDestroyedLocked();
    344             mCanUnbind = false;
    345         }
    346         try {
    347             getRemoteInstanceLazy().setStatusRes(printJobId, status, appPackageName);
    348         } catch (RemoteException | TimeoutException | InterruptedException e) {
    349             Slog.e(LOG_TAG, "Error setting status.", e);
    350         } finally {
    351             if (DEBUG) {
    352                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setStatus()");
    353             }
    354             synchronized (mLock) {
    355                 mCanUnbind = true;
    356                 mLock.notifyAll();
    357             }
    358         }
    359     }
    360 
    361     /**
    362      * Handle that a custom icon for a printer was loaded.
    363      *
    364      * @param printerId the id of the printer the icon belongs to
    365      * @param icon the icon that was loaded
    366      * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon
    367      */
    368     public final void onCustomPrinterIconLoaded(@NonNull PrinterId printerId,
    369             @Nullable Icon icon) {
    370         throwIfCalledOnMainThread();
    371         synchronized (mLock) {
    372             throwIfDestroyedLocked();
    373             mCanUnbind = false;
    374         }
    375         try {
    376             mCustomPrinterIconLoadedCaller.onCustomPrinterIconLoaded(getRemoteInstanceLazy(),
    377                     printerId, icon);
    378         } catch (RemoteException | TimeoutException | InterruptedException re) {
    379             Slog.e(LOG_TAG, "Error loading new custom printer icon.", re);
    380         } finally {
    381             if (DEBUG) {
    382                 Slog.i(LOG_TAG,
    383                         "[user: " + mUserHandle.getIdentifier() + "] onCustomPrinterIconLoaded()");
    384             }
    385             synchronized (mLock) {
    386                 mCanUnbind = true;
    387                 mLock.notifyAll();
    388             }
    389         }
    390     }
    391 
    392     /**
    393      * Get the custom icon for a printer. If the icon is not cached, the icon is
    394      * requested asynchronously. Once it is available the printer is updated.
    395      *
    396      * @param printerId the id of the printer the icon should be loaded for
    397      * @return the custom icon to be used for the printer or null if the icon is
    398      *         not yet available
    399      * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon
    400      */
    401     public final @Nullable Icon getCustomPrinterIcon(@NonNull PrinterId printerId) {
    402         throwIfCalledOnMainThread();
    403         synchronized (mLock) {
    404             throwIfDestroyedLocked();
    405             mCanUnbind = false;
    406         }
    407         try {
    408             return mGetCustomPrinterIconCaller.getCustomPrinterIcon(getRemoteInstanceLazy(),
    409                     printerId);
    410         } catch (RemoteException | TimeoutException | InterruptedException e) {
    411             Slog.e(LOG_TAG, "Error getting custom printer icon.", e);
    412             return null;
    413         } finally {
    414             if (DEBUG) {
    415                 Slog.i(LOG_TAG,
    416                         "[user: " + mUserHandle.getIdentifier() + "] getCustomPrinterIcon()");
    417             }
    418             synchronized (mLock) {
    419                 mCanUnbind = true;
    420                 mLock.notifyAll();
    421             }
    422         }
    423     }
    424 
    425     /**
    426      * Clear the custom printer icon cache
    427      */
    428     public void clearCustomPrinterIconCache() {
    429         throwIfCalledOnMainThread();
    430         synchronized (mLock) {
    431             throwIfDestroyedLocked();
    432             mCanUnbind = false;
    433         }
    434         try {
    435             mClearCustomPrinterIconCache.clearCustomPrinterIconCache(getRemoteInstanceLazy());
    436         } catch (RemoteException | TimeoutException | InterruptedException e) {
    437             Slog.e(LOG_TAG, "Error clearing custom printer icon cache.", e);
    438         } finally {
    439             if (DEBUG) {
    440                 Slog.i(LOG_TAG,
    441                         "[user: " + mUserHandle.getIdentifier()
    442                                 + "] clearCustomPrinterIconCache()");
    443             }
    444             synchronized (mLock) {
    445                 mCanUnbind = true;
    446                 mLock.notifyAll();
    447             }
    448         }
    449     }
    450 
    451     public final boolean setPrintJobTag(PrintJobId printJobId, String tag) {
    452         throwIfCalledOnMainThread();
    453         synchronized (mLock) {
    454             throwIfDestroyedLocked();
    455             mCanUnbind = false;
    456         }
    457         try {
    458             return mSetPrintJobTagCaller.setPrintJobTag(getRemoteInstanceLazy(),
    459                     printJobId, tag);
    460         } catch (RemoteException | TimeoutException | InterruptedException e) {
    461             Slog.e(LOG_TAG, "Error setting print job tag.", e);
    462         } finally {
    463             if (DEBUG) {
    464                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobTag()");
    465             }
    466             synchronized (mLock) {
    467                 mCanUnbind = true;
    468                 mLock.notifyAll();
    469             }
    470         }
    471         return false;
    472     }
    473 
    474     public final void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
    475         throwIfCalledOnMainThread();
    476         synchronized (mLock) {
    477             throwIfDestroyedLocked();
    478             mCanUnbind = false;
    479         }
    480         try {
    481             getRemoteInstanceLazy().setPrintJobCancelling(printJobId,
    482                     cancelling);
    483         } catch (RemoteException | TimeoutException | InterruptedException e) {
    484             Slog.e(LOG_TAG, "Error setting print job cancelling.", e);
    485         } finally {
    486             if (DEBUG) {
    487                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
    488                         + "] setPrintJobCancelling()");
    489             }
    490             synchronized (mLock) {
    491                 mCanUnbind = true;
    492                 mLock.notifyAll();
    493             }
    494         }
    495     }
    496 
    497     /**
    498      * Remove all approved {@link PrintService print services} that are not in the given set.
    499      *
    500      * @param servicesToKeep The {@link ComponentName names } of the services to keep
    501      */
    502     public final void pruneApprovedPrintServices(List<ComponentName> servicesToKeep) {
    503         throwIfCalledOnMainThread();
    504         synchronized (mLock) {
    505             throwIfDestroyedLocked();
    506             mCanUnbind = false;
    507         }
    508         try {
    509             getRemoteInstanceLazy().pruneApprovedPrintServices(servicesToKeep);
    510         } catch (RemoteException | TimeoutException | InterruptedException e) {
    511             Slog.e(LOG_TAG, "Error pruning approved print services.", e);
    512         } finally {
    513             if (DEBUG) {
    514                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
    515                         + "] pruneApprovedPrintServices()");
    516             }
    517             synchronized (mLock) {
    518                 mCanUnbind = true;
    519                 mLock.notifyAll();
    520             }
    521         }
    522     }
    523 
    524     public final void removeObsoletePrintJobs() {
    525         throwIfCalledOnMainThread();
    526         synchronized (mLock) {
    527             throwIfDestroyedLocked();
    528             mCanUnbind = false;
    529         }
    530         try {
    531             getRemoteInstanceLazy().removeObsoletePrintJobs();
    532         } catch (RemoteException | TimeoutException | InterruptedException te) {
    533             Slog.e(LOG_TAG, "Error removing obsolete print jobs .", te);
    534         } finally {
    535             if (DEBUG) {
    536                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
    537                         + "] removeObsoletePrintJobs()");
    538             }
    539             synchronized (mLock) {
    540                 mCanUnbind = true;
    541                 mLock.notifyAll();
    542             }
    543         }
    544     }
    545 
    546     public final void destroy() {
    547         throwIfCalledOnMainThread();
    548         if (DEBUG) {
    549             Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] destroy()");
    550         }
    551         synchronized (mLock) {
    552             throwIfDestroyedLocked();
    553             unbindLocked();
    554             mDestroyed = true;
    555             mCanUnbind = false;
    556         }
    557     }
    558 
    559     public void dump(@NonNull DualDumpOutputStream dumpStream) {
    560         synchronized (mLock) {
    561             dumpStream.write("is_destroyed", PrintSpoolerStateProto.IS_DESTROYED, mDestroyed);
    562             dumpStream.write("is_bound", PrintSpoolerStateProto.IS_BOUND, mRemoteInstance != null);
    563         }
    564 
    565         try {
    566             if (dumpStream.isProto()) {
    567                 dumpStream.write(null, PrintSpoolerStateProto.INTERNAL_STATE,
    568                         TransferPipe.dumpAsync(getRemoteInstanceLazy().asBinder(), "--proto"));
    569             } else {
    570                 dumpStream.writeNested("internal_state", TransferPipe.dumpAsync(
    571                         getRemoteInstanceLazy().asBinder()));
    572             }
    573         } catch (IOException | TimeoutException | RemoteException | InterruptedException e) {
    574             Slog.e(LOG_TAG, "Failed to dump remote instance", e);
    575         }
    576     }
    577 
    578     private void onAllPrintJobsHandled() {
    579         synchronized (mLock) {
    580             throwIfDestroyedLocked();
    581             unbindLocked();
    582         }
    583     }
    584 
    585     private void onPrintJobStateChanged(PrintJobInfo printJob) {
    586         mCallbacks.onPrintJobStateChanged(printJob);
    587     }
    588 
    589     private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException, InterruptedException {
    590         synchronized (mLock) {
    591             if (mRemoteInstance != null) {
    592                 return mRemoteInstance;
    593             }
    594             bindLocked();
    595             return mRemoteInstance;
    596         }
    597     }
    598 
    599     @GuardedBy("mLock")
    600     private void bindLocked() throws TimeoutException, InterruptedException {
    601         while (mIsBinding) {
    602             mLock.wait();
    603         }
    604 
    605         if (mRemoteInstance != null) {
    606             return;
    607         }
    608 
    609         mIsBinding = true;
    610 
    611         if (DEBUG) {
    612             Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] bindLocked() " +
    613                     (mIsLowPriority ? "low priority" : ""));
    614         }
    615 
    616         try {
    617             int flags;
    618             if (mIsLowPriority) {
    619                 flags = Context.BIND_AUTO_CREATE;
    620             } else {
    621                 flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
    622             }
    623 
    624             mContext.bindServiceAsUser(mIntent, mServiceConnection, flags, mUserHandle);
    625 
    626             final long startMillis = SystemClock.uptimeMillis();
    627             while (true) {
    628                 if (mRemoteInstance != null) {
    629                     break;
    630                 }
    631                 final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
    632                 final long remainingMillis = BIND_SPOOLER_SERVICE_TIMEOUT - elapsedMillis;
    633                 if (remainingMillis <= 0) {
    634                     throw new TimeoutException("Cannot get spooler!");
    635                 }
    636                 mLock.wait(remainingMillis);
    637             }
    638 
    639             mCanUnbind = true;
    640         } finally {
    641             mIsBinding = false;
    642             mLock.notifyAll();
    643         }
    644     }
    645 
    646     private void unbindLocked() {
    647         if (mRemoteInstance == null) {
    648             return;
    649         }
    650         while (true) {
    651             if (mCanUnbind) {
    652                 if (DEBUG) {
    653                     Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()");
    654                 }
    655                 clearClientLocked();
    656                 mRemoteInstance = null;
    657                 mContext.unbindService(mServiceConnection);
    658                 return;
    659             }
    660             try {
    661                 mLock.wait();
    662             } catch (InterruptedException ie) {
    663                 /* ignore */
    664             }
    665         }
    666 
    667     }
    668 
    669     private void setClientLocked() {
    670         try {
    671             mRemoteInstance.setClient(mClient);
    672         } catch (RemoteException re) {
    673             Slog.d(LOG_TAG, "Error setting print spooler client", re);
    674         }
    675     }
    676 
    677     private void clearClientLocked() {
    678         try {
    679             mRemoteInstance.setClient(null);
    680         } catch (RemoteException re) {
    681             Slog.d(LOG_TAG, "Error clearing print spooler client", re);
    682         }
    683 
    684     }
    685 
    686     private void throwIfDestroyedLocked() {
    687         if (mDestroyed) {
    688             throw new IllegalStateException("Cannot interact with a destroyed instance.");
    689         }
    690     }
    691 
    692     private void throwIfCalledOnMainThread() {
    693         if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
    694             throw new RuntimeException("Cannot invoke on the main thread");
    695         }
    696     }
    697 
    698     private final class MyServiceConnection implements ServiceConnection {
    699         @Override
    700         public void onServiceConnected(ComponentName name, IBinder service) {
    701             synchronized (mLock) {
    702                 mRemoteInstance = IPrintSpooler.Stub.asInterface(service);
    703                 setClientLocked();
    704                 mLock.notifyAll();
    705             }
    706         }
    707 
    708         @Override
    709         public void onServiceDisconnected(ComponentName name) {
    710             synchronized (mLock) {
    711                 clearClientLocked();
    712                 mRemoteInstance = null;
    713             }
    714         }
    715     }
    716 
    717     private static final class GetPrintJobInfosCaller
    718             extends TimedRemoteCaller<List<PrintJobInfo>> {
    719         private final IPrintSpoolerCallbacks mCallback;
    720 
    721         public GetPrintJobInfosCaller() {
    722             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    723             mCallback = new BasePrintSpoolerServiceCallbacks() {
    724                 @Override
    725                 public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobs, int sequence) {
    726                     onRemoteMethodResult(printJobs, sequence);
    727                 }
    728             };
    729         }
    730 
    731         public List<PrintJobInfo> getPrintJobInfos(IPrintSpooler target,
    732                 ComponentName componentName, int state, int appId)
    733                         throws RemoteException, TimeoutException {
    734             final int sequence = onBeforeRemoteCall();
    735             target.getPrintJobInfos(mCallback, componentName, state, appId, sequence);
    736             return getResultTimed(sequence);
    737         }
    738     }
    739 
    740     private static final class GetPrintJobInfoCaller extends TimedRemoteCaller<PrintJobInfo> {
    741         private final IPrintSpoolerCallbacks mCallback;
    742 
    743         public GetPrintJobInfoCaller() {
    744             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    745             mCallback = new BasePrintSpoolerServiceCallbacks() {
    746                 @Override
    747                 public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) {
    748                     onRemoteMethodResult(printJob, sequence);
    749                 }
    750             };
    751         }
    752 
    753         public PrintJobInfo getPrintJobInfo(IPrintSpooler target, PrintJobId printJobId,
    754                 int appId) throws RemoteException, TimeoutException {
    755             final int sequence = onBeforeRemoteCall();
    756             target.getPrintJobInfo(printJobId, mCallback, appId, sequence);
    757             return getResultTimed(sequence);
    758         }
    759     }
    760 
    761     private static final class SetPrintJobStateCaller extends TimedRemoteCaller<Boolean> {
    762         private final IPrintSpoolerCallbacks mCallback;
    763 
    764         public SetPrintJobStateCaller() {
    765             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    766             mCallback = new BasePrintSpoolerServiceCallbacks() {
    767                 @Override
    768                 public void onSetPrintJobStateResult(boolean success, int sequence) {
    769                     onRemoteMethodResult(success, sequence);
    770                 }
    771             };
    772         }
    773 
    774         public boolean setPrintJobState(IPrintSpooler target, PrintJobId printJobId,
    775                 int status, String error) throws RemoteException, TimeoutException {
    776             final int sequence = onBeforeRemoteCall();
    777             target.setPrintJobState(printJobId, status, error, mCallback, sequence);
    778             return getResultTimed(sequence);
    779         }
    780     }
    781 
    782     private static final class SetPrintJobTagCaller extends TimedRemoteCaller<Boolean> {
    783         private final IPrintSpoolerCallbacks mCallback;
    784 
    785         public SetPrintJobTagCaller() {
    786             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    787             mCallback = new BasePrintSpoolerServiceCallbacks() {
    788                 @Override
    789                 public void onSetPrintJobTagResult(boolean success, int sequence) {
    790                     onRemoteMethodResult(success, sequence);
    791                 }
    792             };
    793         }
    794 
    795         public boolean setPrintJobTag(IPrintSpooler target, PrintJobId printJobId,
    796                 String tag) throws RemoteException, TimeoutException {
    797             final int sequence = onBeforeRemoteCall();
    798             target.setPrintJobTag(printJobId, tag, mCallback, sequence);
    799             return getResultTimed(sequence);
    800         }
    801     }
    802 
    803     private static final class OnCustomPrinterIconLoadedCaller extends TimedRemoteCaller<Void> {
    804         private final IPrintSpoolerCallbacks mCallback;
    805 
    806         public OnCustomPrinterIconLoadedCaller() {
    807             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    808             mCallback = new BasePrintSpoolerServiceCallbacks() {
    809                 @Override
    810                 public void onCustomPrinterIconCached(int sequence) {
    811                     onRemoteMethodResult(null, sequence);
    812                 }
    813             };
    814         }
    815 
    816         public Void onCustomPrinterIconLoaded(IPrintSpooler target, PrinterId printerId,
    817                 Icon icon) throws RemoteException, TimeoutException {
    818             final int sequence = onBeforeRemoteCall();
    819             target.onCustomPrinterIconLoaded(printerId, icon, mCallback, sequence);
    820             return getResultTimed(sequence);
    821         }
    822     }
    823 
    824     private static final class ClearCustomPrinterIconCacheCaller extends TimedRemoteCaller<Void> {
    825         private final IPrintSpoolerCallbacks mCallback;
    826 
    827         public ClearCustomPrinterIconCacheCaller() {
    828             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    829             mCallback = new BasePrintSpoolerServiceCallbacks() {
    830                 @Override
    831                 public void customPrinterIconCacheCleared(int sequence) {
    832                     onRemoteMethodResult(null, sequence);
    833                 }
    834             };
    835         }
    836 
    837         public Void clearCustomPrinterIconCache(IPrintSpooler target)
    838                 throws RemoteException, TimeoutException {
    839             final int sequence = onBeforeRemoteCall();
    840             target.clearCustomPrinterIconCache(mCallback, sequence);
    841             return getResultTimed(sequence);
    842         }
    843     }
    844 
    845     private static final class GetCustomPrinterIconCaller extends TimedRemoteCaller<Icon> {
    846         private final IPrintSpoolerCallbacks mCallback;
    847 
    848         public GetCustomPrinterIconCaller() {
    849             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    850             mCallback = new BasePrintSpoolerServiceCallbacks() {
    851                 @Override
    852                 public void onGetCustomPrinterIconResult(Icon icon, int sequence) {
    853                     onRemoteMethodResult(icon, sequence);
    854                 }
    855             };
    856         }
    857 
    858         public Icon getCustomPrinterIcon(IPrintSpooler target, PrinterId printerId)
    859                 throws RemoteException, TimeoutException {
    860             final int sequence = onBeforeRemoteCall();
    861             target.getCustomPrinterIcon(printerId, mCallback, sequence);
    862             return getResultTimed(sequence);
    863         }
    864     }
    865 
    866     private static abstract class BasePrintSpoolerServiceCallbacks
    867             extends IPrintSpoolerCallbacks.Stub {
    868         @Override
    869         public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobIds, int sequence) {
    870             /* do nothing */
    871         }
    872 
    873         @Override
    874         public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) {
    875             /* do nothing */
    876         }
    877 
    878         @Override
    879         public void onCancelPrintJobResult(boolean canceled, int sequence) {
    880             /* do nothing */
    881         }
    882 
    883         @Override
    884         public void onSetPrintJobStateResult(boolean success, int sequece) {
    885             /* do nothing */
    886         }
    887 
    888         @Override
    889         public void onSetPrintJobTagResult(boolean success, int sequence) {
    890             /* do nothing */
    891         }
    892 
    893         @Override
    894         public void onCustomPrinterIconCached(int sequence) {
    895             /* do nothing */
    896         }
    897 
    898         @Override
    899         public void onGetCustomPrinterIconResult(@Nullable Icon icon, int sequence) {
    900             /* do nothing */
    901         }
    902 
    903         @Override
    904         public void customPrinterIconCacheCleared(int sequence) {
    905             /* do nothing */
    906         }
    907     }
    908 
    909     private static final class PrintSpoolerClient extends IPrintSpoolerClient.Stub {
    910 
    911         private final WeakReference<RemotePrintSpooler> mWeakSpooler;
    912 
    913         public PrintSpoolerClient(RemotePrintSpooler spooler) {
    914             mWeakSpooler = new WeakReference<RemotePrintSpooler>(spooler);
    915         }
    916 
    917         @Override
    918         public void onPrintJobQueued(PrintJobInfo printJob) {
    919             RemotePrintSpooler spooler = mWeakSpooler.get();
    920             if (spooler != null) {
    921                 final long identity = Binder.clearCallingIdentity();
    922                 try {
    923                     spooler.mCallbacks.onPrintJobQueued(printJob);
    924                 } finally {
    925                     Binder.restoreCallingIdentity(identity);
    926                 }
    927             }
    928         }
    929 
    930         @Override
    931         public void onAllPrintJobsForServiceHandled(ComponentName printService) {
    932             RemotePrintSpooler spooler = mWeakSpooler.get();
    933             if (spooler != null) {
    934                 final long identity = Binder.clearCallingIdentity();
    935                 try {
    936                     spooler.mCallbacks.onAllPrintJobsForServiceHandled(printService);
    937                 } finally {
    938                     Binder.restoreCallingIdentity(identity);
    939                 }
    940             }
    941         }
    942 
    943         @Override
    944         public void onAllPrintJobsHandled() {
    945             RemotePrintSpooler spooler = mWeakSpooler.get();
    946             if (spooler != null) {
    947                 final long identity = Binder.clearCallingIdentity();
    948                 try {
    949                     spooler.onAllPrintJobsHandled();
    950                 } finally {
    951                     Binder.restoreCallingIdentity(identity);
    952                 }
    953             }
    954         }
    955 
    956         @Override
    957         public void onPrintJobStateChanged(PrintJobInfo printJob) {
    958             RemotePrintSpooler spooler = mWeakSpooler.get();
    959             if (spooler != null) {
    960                 final long identity = Binder.clearCallingIdentity();
    961                 try {
    962                     spooler.onPrintJobStateChanged(printJob);
    963                 } finally {
    964                     Binder.restoreCallingIdentity(identity);
    965                 }
    966             }
    967         }
    968     }
    969 }
    970