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.util.Slog;
     44 import android.util.TimedRemoteCaller;
     45 
     46 import com.android.internal.annotations.GuardedBy;
     47 import com.android.internal.os.TransferPipe;
     48 
     49 import libcore.io.IoUtils;
     50 
     51 import java.io.FileDescriptor;
     52 import java.io.IOException;
     53 import java.io.PrintWriter;
     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(FileDescriptor fd, PrintWriter pw, String prefix) {
    560         synchronized (mLock) {
    561             pw.append(prefix).append("destroyed=")
    562                     .append(String.valueOf(mDestroyed)).println();
    563             pw.append(prefix).append("bound=")
    564                     .append((mRemoteInstance != null) ? "true" : "false").println();
    565 
    566             pw.flush();
    567             try {
    568                 TransferPipe.dumpAsync(getRemoteInstanceLazy().asBinder(), fd,
    569                         new String[] { prefix });
    570             } catch (IOException | TimeoutException | RemoteException | InterruptedException e) {
    571                 pw.println("Failed to dump remote instance: " + e);
    572             }
    573         }
    574     }
    575 
    576     private void onAllPrintJobsHandled() {
    577         synchronized (mLock) {
    578             throwIfDestroyedLocked();
    579             unbindLocked();
    580         }
    581     }
    582 
    583     private void onPrintJobStateChanged(PrintJobInfo printJob) {
    584         mCallbacks.onPrintJobStateChanged(printJob);
    585     }
    586 
    587     private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException, InterruptedException {
    588         synchronized (mLock) {
    589             if (mRemoteInstance != null) {
    590                 return mRemoteInstance;
    591             }
    592             bindLocked();
    593             return mRemoteInstance;
    594         }
    595     }
    596 
    597     private void bindLocked() throws TimeoutException, InterruptedException {
    598         while (mIsBinding) {
    599             mLock.wait();
    600         }
    601 
    602         if (mRemoteInstance != null) {
    603             return;
    604         }
    605 
    606         mIsBinding = true;
    607 
    608         if (DEBUG) {
    609             Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] bindLocked() " +
    610                     (mIsLowPriority ? "low priority" : ""));
    611         }
    612 
    613         try {
    614             int flags;
    615             if (mIsLowPriority) {
    616                 flags = Context.BIND_AUTO_CREATE;
    617             } else {
    618                 flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
    619             }
    620 
    621             mContext.bindServiceAsUser(mIntent, mServiceConnection, flags, mUserHandle);
    622 
    623             final long startMillis = SystemClock.uptimeMillis();
    624             while (true) {
    625                 if (mRemoteInstance != null) {
    626                     break;
    627                 }
    628                 final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
    629                 final long remainingMillis = BIND_SPOOLER_SERVICE_TIMEOUT - elapsedMillis;
    630                 if (remainingMillis <= 0) {
    631                     throw new TimeoutException("Cannot get spooler!");
    632                 }
    633                 mLock.wait(remainingMillis);
    634             }
    635 
    636             mCanUnbind = true;
    637         } finally {
    638             mIsBinding = false;
    639             mLock.notifyAll();
    640         }
    641     }
    642 
    643     private void unbindLocked() {
    644         if (mRemoteInstance == null) {
    645             return;
    646         }
    647         while (true) {
    648             if (mCanUnbind) {
    649                 if (DEBUG) {
    650                     Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()");
    651                 }
    652                 clearClientLocked();
    653                 mRemoteInstance = null;
    654                 mContext.unbindService(mServiceConnection);
    655                 return;
    656             }
    657             try {
    658                 mLock.wait();
    659             } catch (InterruptedException ie) {
    660                 /* ignore */
    661             }
    662         }
    663 
    664     }
    665 
    666     private void setClientLocked() {
    667         try {
    668             mRemoteInstance.setClient(mClient);
    669         } catch (RemoteException re) {
    670             Slog.d(LOG_TAG, "Error setting print spooler client", re);
    671         }
    672     }
    673 
    674     private void clearClientLocked() {
    675         try {
    676             mRemoteInstance.setClient(null);
    677         } catch (RemoteException re) {
    678             Slog.d(LOG_TAG, "Error clearing print spooler client", re);
    679         }
    680 
    681     }
    682 
    683     private void throwIfDestroyedLocked() {
    684         if (mDestroyed) {
    685             throw new IllegalStateException("Cannot interact with a destroyed instance.");
    686         }
    687     }
    688 
    689     private void throwIfCalledOnMainThread() {
    690         if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
    691             throw new RuntimeException("Cannot invoke on the main thread");
    692         }
    693     }
    694 
    695     private final class MyServiceConnection implements ServiceConnection {
    696         @Override
    697         public void onServiceConnected(ComponentName name, IBinder service) {
    698             synchronized (mLock) {
    699                 mRemoteInstance = IPrintSpooler.Stub.asInterface(service);
    700                 setClientLocked();
    701                 mLock.notifyAll();
    702             }
    703         }
    704 
    705         @Override
    706         public void onServiceDisconnected(ComponentName name) {
    707             synchronized (mLock) {
    708                 clearClientLocked();
    709                 mRemoteInstance = null;
    710             }
    711         }
    712     }
    713 
    714     private static final class GetPrintJobInfosCaller
    715             extends TimedRemoteCaller<List<PrintJobInfo>> {
    716         private final IPrintSpoolerCallbacks mCallback;
    717 
    718         public GetPrintJobInfosCaller() {
    719             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    720             mCallback = new BasePrintSpoolerServiceCallbacks() {
    721                 @Override
    722                 public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobs, int sequence) {
    723                     onRemoteMethodResult(printJobs, sequence);
    724                 }
    725             };
    726         }
    727 
    728         public List<PrintJobInfo> getPrintJobInfos(IPrintSpooler target,
    729                 ComponentName componentName, int state, int appId)
    730                         throws RemoteException, TimeoutException {
    731             final int sequence = onBeforeRemoteCall();
    732             target.getPrintJobInfos(mCallback, componentName, state, appId, sequence);
    733             return getResultTimed(sequence);
    734         }
    735     }
    736 
    737     private static final class GetPrintJobInfoCaller extends TimedRemoteCaller<PrintJobInfo> {
    738         private final IPrintSpoolerCallbacks mCallback;
    739 
    740         public GetPrintJobInfoCaller() {
    741             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    742             mCallback = new BasePrintSpoolerServiceCallbacks() {
    743                 @Override
    744                 public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) {
    745                     onRemoteMethodResult(printJob, sequence);
    746                 }
    747             };
    748         }
    749 
    750         public PrintJobInfo getPrintJobInfo(IPrintSpooler target, PrintJobId printJobId,
    751                 int appId) throws RemoteException, TimeoutException {
    752             final int sequence = onBeforeRemoteCall();
    753             target.getPrintJobInfo(printJobId, mCallback, appId, sequence);
    754             return getResultTimed(sequence);
    755         }
    756     }
    757 
    758     private static final class SetPrintJobStateCaller extends TimedRemoteCaller<Boolean> {
    759         private final IPrintSpoolerCallbacks mCallback;
    760 
    761         public SetPrintJobStateCaller() {
    762             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    763             mCallback = new BasePrintSpoolerServiceCallbacks() {
    764                 @Override
    765                 public void onSetPrintJobStateResult(boolean success, int sequence) {
    766                     onRemoteMethodResult(success, sequence);
    767                 }
    768             };
    769         }
    770 
    771         public boolean setPrintJobState(IPrintSpooler target, PrintJobId printJobId,
    772                 int status, String error) throws RemoteException, TimeoutException {
    773             final int sequence = onBeforeRemoteCall();
    774             target.setPrintJobState(printJobId, status, error, mCallback, sequence);
    775             return getResultTimed(sequence);
    776         }
    777     }
    778 
    779     private static final class SetPrintJobTagCaller extends TimedRemoteCaller<Boolean> {
    780         private final IPrintSpoolerCallbacks mCallback;
    781 
    782         public SetPrintJobTagCaller() {
    783             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    784             mCallback = new BasePrintSpoolerServiceCallbacks() {
    785                 @Override
    786                 public void onSetPrintJobTagResult(boolean success, int sequence) {
    787                     onRemoteMethodResult(success, sequence);
    788                 }
    789             };
    790         }
    791 
    792         public boolean setPrintJobTag(IPrintSpooler target, PrintJobId printJobId,
    793                 String tag) throws RemoteException, TimeoutException {
    794             final int sequence = onBeforeRemoteCall();
    795             target.setPrintJobTag(printJobId, tag, mCallback, sequence);
    796             return getResultTimed(sequence);
    797         }
    798     }
    799 
    800     private static final class OnCustomPrinterIconLoadedCaller extends TimedRemoteCaller<Void> {
    801         private final IPrintSpoolerCallbacks mCallback;
    802 
    803         public OnCustomPrinterIconLoadedCaller() {
    804             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    805             mCallback = new BasePrintSpoolerServiceCallbacks() {
    806                 @Override
    807                 public void onCustomPrinterIconCached(int sequence) {
    808                     onRemoteMethodResult(null, sequence);
    809                 }
    810             };
    811         }
    812 
    813         public Void onCustomPrinterIconLoaded(IPrintSpooler target, PrinterId printerId,
    814                 Icon icon) throws RemoteException, TimeoutException {
    815             final int sequence = onBeforeRemoteCall();
    816             target.onCustomPrinterIconLoaded(printerId, icon, mCallback, sequence);
    817             return getResultTimed(sequence);
    818         }
    819     }
    820 
    821     private static final class ClearCustomPrinterIconCacheCaller extends TimedRemoteCaller<Void> {
    822         private final IPrintSpoolerCallbacks mCallback;
    823 
    824         public ClearCustomPrinterIconCacheCaller() {
    825             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    826             mCallback = new BasePrintSpoolerServiceCallbacks() {
    827                 @Override
    828                 public void customPrinterIconCacheCleared(int sequence) {
    829                     onRemoteMethodResult(null, sequence);
    830                 }
    831             };
    832         }
    833 
    834         public Void clearCustomPrinterIconCache(IPrintSpooler target)
    835                 throws RemoteException, TimeoutException {
    836             final int sequence = onBeforeRemoteCall();
    837             target.clearCustomPrinterIconCache(mCallback, sequence);
    838             return getResultTimed(sequence);
    839         }
    840     }
    841 
    842     private static final class GetCustomPrinterIconCaller extends TimedRemoteCaller<Icon> {
    843         private final IPrintSpoolerCallbacks mCallback;
    844 
    845         public GetCustomPrinterIconCaller() {
    846             super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    847             mCallback = new BasePrintSpoolerServiceCallbacks() {
    848                 @Override
    849                 public void onGetCustomPrinterIconResult(Icon icon, int sequence) {
    850                     onRemoteMethodResult(icon, sequence);
    851                 }
    852             };
    853         }
    854 
    855         public Icon getCustomPrinterIcon(IPrintSpooler target, PrinterId printerId)
    856                 throws RemoteException, TimeoutException {
    857             final int sequence = onBeforeRemoteCall();
    858             target.getCustomPrinterIcon(printerId, mCallback, sequence);
    859             return getResultTimed(sequence);
    860         }
    861     }
    862 
    863     private static abstract class BasePrintSpoolerServiceCallbacks
    864             extends IPrintSpoolerCallbacks.Stub {
    865         @Override
    866         public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobIds, int sequence) {
    867             /* do nothing */
    868         }
    869 
    870         @Override
    871         public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) {
    872             /* do nothing */
    873         }
    874 
    875         @Override
    876         public void onCancelPrintJobResult(boolean canceled, int sequence) {
    877             /* do nothing */
    878         }
    879 
    880         @Override
    881         public void onSetPrintJobStateResult(boolean success, int sequece) {
    882             /* do nothing */
    883         }
    884 
    885         @Override
    886         public void onSetPrintJobTagResult(boolean success, int sequence) {
    887             /* do nothing */
    888         }
    889 
    890         @Override
    891         public void onCustomPrinterIconCached(int sequence) {
    892             /* do nothing */
    893         }
    894 
    895         @Override
    896         public void onGetCustomPrinterIconResult(@Nullable Icon icon, int sequence) {
    897             /* do nothing */
    898         }
    899 
    900         @Override
    901         public void customPrinterIconCacheCleared(int sequence) {
    902             /* do nothing */
    903         }
    904     }
    905 
    906     private static final class PrintSpoolerClient extends IPrintSpoolerClient.Stub {
    907 
    908         private final WeakReference<RemotePrintSpooler> mWeakSpooler;
    909 
    910         public PrintSpoolerClient(RemotePrintSpooler spooler) {
    911             mWeakSpooler = new WeakReference<RemotePrintSpooler>(spooler);
    912         }
    913 
    914         @Override
    915         public void onPrintJobQueued(PrintJobInfo printJob) {
    916             RemotePrintSpooler spooler = mWeakSpooler.get();
    917             if (spooler != null) {
    918                 final long identity = Binder.clearCallingIdentity();
    919                 try {
    920                     spooler.mCallbacks.onPrintJobQueued(printJob);
    921                 } finally {
    922                     Binder.restoreCallingIdentity(identity);
    923                 }
    924             }
    925         }
    926 
    927         @Override
    928         public void onAllPrintJobsForServiceHandled(ComponentName printService) {
    929             RemotePrintSpooler spooler = mWeakSpooler.get();
    930             if (spooler != null) {
    931                 final long identity = Binder.clearCallingIdentity();
    932                 try {
    933                     spooler.mCallbacks.onAllPrintJobsForServiceHandled(printService);
    934                 } finally {
    935                     Binder.restoreCallingIdentity(identity);
    936                 }
    937             }
    938         }
    939 
    940         @Override
    941         public void onAllPrintJobsHandled() {
    942             RemotePrintSpooler spooler = mWeakSpooler.get();
    943             if (spooler != null) {
    944                 final long identity = Binder.clearCallingIdentity();
    945                 try {
    946                     spooler.onAllPrintJobsHandled();
    947                 } finally {
    948                     Binder.restoreCallingIdentity(identity);
    949                 }
    950             }
    951         }
    952 
    953         @Override
    954         public void onPrintJobStateChanged(PrintJobInfo printJob) {
    955             RemotePrintSpooler spooler = mWeakSpooler.get();
    956             if (spooler != null) {
    957                 final long identity = Binder.clearCallingIdentity();
    958                 try {
    959                     spooler.onPrintJobStateChanged(printJob);
    960                 } finally {
    961                     Binder.restoreCallingIdentity(identity);
    962                 }
    963             }
    964         }
    965     }
    966 }
    967