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 static com.android.internal.print.DumpUtils.writePrinterId;
     20 import static com.android.internal.util.dump.DumpUtils.writeComponentName;
     21 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
     22 
     23 import android.annotation.FloatRange;
     24 import android.annotation.NonNull;
     25 import android.annotation.Nullable;
     26 import android.annotation.StringRes;
     27 import android.content.ComponentName;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.content.ServiceConnection;
     31 import android.content.pm.ParceledListSlice;
     32 import android.graphics.drawable.Icon;
     33 import android.os.Binder;
     34 import android.os.Handler;
     35 import android.os.IBinder;
     36 import android.os.IBinder.DeathRecipient;
     37 import android.os.ParcelFileDescriptor;
     38 import android.os.RemoteException;
     39 import android.os.UserHandle;
     40 import android.print.PrintJobId;
     41 import android.print.PrintJobInfo;
     42 import android.print.PrintManager;
     43 import android.print.PrinterId;
     44 import android.print.PrinterInfo;
     45 import android.printservice.IPrintService;
     46 import android.printservice.IPrintServiceClient;
     47 import android.service.print.ActivePrintServiceProto;
     48 import android.util.Slog;
     49 
     50 import com.android.internal.annotations.GuardedBy;
     51 import com.android.internal.util.dump.DualDumpOutputStream;
     52 
     53 import java.lang.ref.WeakReference;
     54 import java.util.ArrayList;
     55 import java.util.List;
     56 
     57 /**
     58  * This class represents a remote print service. It abstracts away the binding
     59  * and unbinding from the remote implementation. Clients can call methods of
     60  * this class without worrying about when and how to bind/unbind.
     61  */
     62 final class RemotePrintService implements DeathRecipient {
     63 
     64     private static final String LOG_TAG = "RemotePrintService";
     65 
     66     private static final boolean DEBUG = false;
     67 
     68     private final Object mLock = new Object();
     69 
     70     private final Context mContext;
     71 
     72     private final ComponentName mComponentName;
     73 
     74     private final Intent mIntent;
     75 
     76     private final RemotePrintSpooler mSpooler;
     77 
     78     private final PrintServiceCallbacks mCallbacks;
     79 
     80     private final int mUserId;
     81 
     82     private final List<Runnable> mPendingCommands = new ArrayList<Runnable>();
     83 
     84     private final ServiceConnection mServiceConnection = new RemoteServiceConneciton();
     85 
     86     private final RemotePrintServiceClient mPrintServiceClient;
     87 
     88     private IPrintService mPrintService;
     89 
     90     private boolean mBinding;
     91 
     92     private boolean mDestroyed;
     93 
     94     private boolean mHasActivePrintJobs;
     95 
     96     private boolean mHasPrinterDiscoverySession;
     97 
     98     private boolean mServiceDied;
     99 
    100     private List<PrinterId> mDiscoveryPriorityList;
    101 
    102     @GuardedBy("mLock")
    103     private List<PrinterId> mTrackedPrinterList;
    104 
    105     public static interface PrintServiceCallbacks {
    106         public void onPrintersAdded(List<PrinterInfo> printers);
    107         public void onPrintersRemoved(List<PrinterId> printerIds);
    108         public void onServiceDied(RemotePrintService service);
    109 
    110         /**
    111          * Handle that a custom icon for a printer was loaded.
    112          *
    113          * @param printerId the id of the printer the icon belongs to
    114          * @param icon the icon that was loaded
    115          * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
    116          */
    117         public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon);
    118     }
    119 
    120     public RemotePrintService(Context context, ComponentName componentName, int userId,
    121             RemotePrintSpooler spooler, PrintServiceCallbacks callbacks) {
    122         mContext = context;
    123         mCallbacks = callbacks;
    124         mComponentName = componentName;
    125         mIntent = new Intent().setComponent(mComponentName);
    126         mUserId = userId;
    127         mSpooler = spooler;
    128         mPrintServiceClient = new RemotePrintServiceClient(this);
    129     }
    130 
    131     public ComponentName getComponentName() {
    132         return mComponentName;
    133     }
    134 
    135     public void destroy() {
    136         Handler.getMain().sendMessage(obtainMessage(
    137                 RemotePrintService::handleDestroy, this));
    138     }
    139 
    140     private void handleDestroy() {
    141         // Stop tracking printers.
    142         stopTrackingAllPrinters();
    143 
    144         // Stop printer discovery.
    145         if (mDiscoveryPriorityList != null) {
    146             handleStopPrinterDiscovery();
    147         }
    148 
    149         // Destroy the discovery session.
    150         if (mHasPrinterDiscoverySession) {
    151             handleDestroyPrinterDiscoverySession();
    152         }
    153 
    154         // Unbind.
    155         ensureUnbound();
    156 
    157         // Done
    158         mDestroyed = true;
    159     }
    160 
    161     @Override
    162     public void binderDied() {
    163         Handler.getMain().sendMessage(obtainMessage(
    164                 RemotePrintService::handleBinderDied, this));
    165     }
    166 
    167     private void handleBinderDied() {
    168         if (mPrintService != null) {
    169             mPrintService.asBinder().unlinkToDeath(this, 0);
    170         }
    171 
    172         mPrintService = null;
    173         mServiceDied = true;
    174         mCallbacks.onServiceDied(this);
    175     }
    176 
    177     public void onAllPrintJobsHandled() {
    178         Handler.getMain().sendMessage(obtainMessage(
    179                 RemotePrintService::handleOnAllPrintJobsHandled, this));
    180     }
    181 
    182     private void handleOnAllPrintJobsHandled() {
    183         mHasActivePrintJobs = false;
    184         if (!isBound()) {
    185             // The service is dead and neither has active jobs nor discovery
    186             // session, so ensure we are unbound since the service has no work.
    187             if (mServiceDied && !mHasPrinterDiscoverySession) {
    188                 ensureUnbound();
    189                 return;
    190             }
    191             ensureBound();
    192             mPendingCommands.add(new Runnable() {
    193                 @Override
    194                 public void run() {
    195                     handleOnAllPrintJobsHandled();
    196                 }
    197             });
    198         } else {
    199             if (DEBUG) {
    200                 Slog.i(LOG_TAG, "[user: " + mUserId + "] onAllPrintJobsHandled()");
    201             }
    202             // If the service has a printer discovery session
    203             // created we should not disconnect from it just yet.
    204             if (!mHasPrinterDiscoverySession) {
    205                 ensureUnbound();
    206             }
    207         }
    208     }
    209 
    210     public void onRequestCancelPrintJob(PrintJobInfo printJob) {
    211         Handler.getMain().sendMessage(obtainMessage(
    212                 RemotePrintService::handleRequestCancelPrintJob, this, printJob));
    213     }
    214 
    215     private void handleRequestCancelPrintJob(final PrintJobInfo printJob) {
    216         if (!isBound()) {
    217             ensureBound();
    218             mPendingCommands.add(new Runnable() {
    219                 @Override
    220                 public void run() {
    221                     handleRequestCancelPrintJob(printJob);
    222                 }
    223             });
    224         } else {
    225             if (DEBUG) {
    226                 Slog.i(LOG_TAG, "[user: " + mUserId + "] requestCancelPrintJob()");
    227             }
    228             try {
    229                 mPrintService.requestCancelPrintJob(printJob);
    230             } catch (RemoteException re) {
    231                 Slog.e(LOG_TAG, "Error canceling a pring job.", re);
    232             }
    233         }
    234     }
    235 
    236     public void onPrintJobQueued(PrintJobInfo printJob) {
    237         Handler.getMain().sendMessage(obtainMessage(
    238                 RemotePrintService::handleOnPrintJobQueued, this, printJob));
    239     }
    240 
    241     private void handleOnPrintJobQueued(final PrintJobInfo printJob) {
    242         mHasActivePrintJobs = true;
    243         if (!isBound()) {
    244             ensureBound();
    245             mPendingCommands.add(new Runnable() {
    246                 @Override
    247                 public void run() {
    248                     handleOnPrintJobQueued(printJob);
    249                 }
    250             });
    251         } else {
    252             if (DEBUG) {
    253                 Slog.i(LOG_TAG, "[user: " + mUserId + "] onPrintJobQueued()");
    254             }
    255             try {
    256                 mPrintService.onPrintJobQueued(printJob);
    257             } catch (RemoteException re) {
    258                 Slog.e(LOG_TAG, "Error announcing queued pring job.", re);
    259             }
    260         }
    261     }
    262 
    263     public void createPrinterDiscoverySession() {
    264         Handler.getMain().sendMessage(obtainMessage(
    265                 RemotePrintService::handleCreatePrinterDiscoverySession, this));
    266     }
    267 
    268     private void handleCreatePrinterDiscoverySession() {
    269         mHasPrinterDiscoverySession = true;
    270         if (!isBound()) {
    271             ensureBound();
    272             mPendingCommands.add(new Runnable() {
    273                 @Override
    274                 public void run() {
    275                     handleCreatePrinterDiscoverySession();
    276                 }
    277             });
    278         } else {
    279             if (DEBUG) {
    280                 Slog.i(LOG_TAG, "[user: " + mUserId + "] createPrinterDiscoverySession()");
    281             }
    282             try {
    283                 mPrintService.createPrinterDiscoverySession();
    284             } catch (RemoteException re) {
    285                 Slog.e(LOG_TAG, "Error creating printer discovery session.", re);
    286             }
    287         }
    288     }
    289 
    290     public void destroyPrinterDiscoverySession() {
    291         Handler.getMain().sendMessage(obtainMessage(
    292                 RemotePrintService::handleDestroyPrinterDiscoverySession, this));
    293     }
    294 
    295     private void handleDestroyPrinterDiscoverySession() {
    296         mHasPrinterDiscoverySession = false;
    297         if (!isBound()) {
    298             // The service is dead and neither has active jobs nor discovery
    299             // session, so ensure we are unbound since the service has no work.
    300             if (mServiceDied && !mHasActivePrintJobs) {
    301                 ensureUnbound();
    302                 return;
    303             }
    304             ensureBound();
    305             mPendingCommands.add(new Runnable() {
    306                 @Override
    307                 public void run() {
    308                     handleDestroyPrinterDiscoverySession();
    309                 }
    310             });
    311         } else {
    312             if (DEBUG) {
    313                 Slog.i(LOG_TAG, "[user: " + mUserId + "] destroyPrinterDiscoverySession()");
    314             }
    315             try {
    316                 mPrintService.destroyPrinterDiscoverySession();
    317             } catch (RemoteException re) {
    318                 Slog.e(LOG_TAG, "Error destroying printer dicovery session.", re);
    319             }
    320             // If the service has no print jobs and no active discovery
    321             // session anymore we should disconnect from it.
    322             if (!mHasActivePrintJobs) {
    323                 ensureUnbound();
    324             }
    325         }
    326     }
    327 
    328     public void startPrinterDiscovery(List<PrinterId> priorityList) {
    329         Handler.getMain().sendMessage(obtainMessage(
    330                 RemotePrintService::handleStartPrinterDiscovery, this, priorityList));
    331     }
    332 
    333     private void handleStartPrinterDiscovery(final List<PrinterId> priorityList) {
    334         // Take a note that we are doing discovery.
    335         mDiscoveryPriorityList = new ArrayList<PrinterId>();
    336         if (priorityList != null) {
    337             mDiscoveryPriorityList.addAll(priorityList);
    338         }
    339         if (!isBound()) {
    340             ensureBound();
    341             mPendingCommands.add(new Runnable() {
    342                 @Override
    343                 public void run() {
    344                     handleStartPrinterDiscovery(priorityList);
    345                 }
    346             });
    347         } else {
    348             if (DEBUG) {
    349                 Slog.i(LOG_TAG, "[user: " + mUserId + "] startPrinterDiscovery()");
    350             }
    351             try {
    352                 mPrintService.startPrinterDiscovery(priorityList);
    353             } catch (RemoteException re) {
    354                 Slog.e(LOG_TAG, "Error starting printer dicovery.", re);
    355             }
    356         }
    357     }
    358 
    359     public void stopPrinterDiscovery() {
    360         Handler.getMain().sendMessage(obtainMessage(
    361                 RemotePrintService::handleStopPrinterDiscovery, this));
    362     }
    363 
    364     private void handleStopPrinterDiscovery() {
    365         // We are not doing discovery anymore.
    366         mDiscoveryPriorityList = null;
    367         if (!isBound()) {
    368             ensureBound();
    369             mPendingCommands.add(new Runnable() {
    370                 @Override
    371                 public void run() {
    372                     handleStopPrinterDiscovery();
    373                 }
    374             });
    375         } else {
    376             if (DEBUG) {
    377                 Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterDiscovery()");
    378             }
    379 
    380             // Stop tracking printers.
    381             stopTrackingAllPrinters();
    382 
    383             try {
    384                 mPrintService.stopPrinterDiscovery();
    385             } catch (RemoteException re) {
    386                 Slog.e(LOG_TAG, "Error stopping printer discovery.", re);
    387             }
    388         }
    389     }
    390 
    391     public void validatePrinters(List<PrinterId> printerIds) {
    392         Handler.getMain().sendMessage(obtainMessage(
    393                 RemotePrintService::handleValidatePrinters, this, printerIds));
    394     }
    395 
    396     private void handleValidatePrinters(final List<PrinterId> printerIds) {
    397         if (!isBound()) {
    398             ensureBound();
    399             mPendingCommands.add(new Runnable() {
    400                 @Override
    401                 public void run() {
    402                     handleValidatePrinters(printerIds);
    403                 }
    404             });
    405         } else {
    406             if (DEBUG) {
    407                 Slog.i(LOG_TAG, "[user: " + mUserId + "] validatePrinters()");
    408             }
    409             try {
    410                 mPrintService.validatePrinters(printerIds);
    411             } catch (RemoteException re) {
    412                 Slog.e(LOG_TAG, "Error requesting printers validation.", re);
    413             }
    414         }
    415     }
    416 
    417     public void startPrinterStateTracking(@NonNull PrinterId printerId) {
    418         Handler.getMain().sendMessage(obtainMessage(
    419                 RemotePrintService::handleStartPrinterStateTracking, this, printerId));
    420     }
    421 
    422     /**
    423      * Queue a request for a custom printer icon for a printer.
    424      *
    425      * @param printerId the id of the printer the icon should be loaded for
    426      * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon
    427      */
    428     public void requestCustomPrinterIcon(@NonNull PrinterId printerId) {
    429         Handler.getMain().sendMessage(obtainMessage(
    430                 RemotePrintService::handleRequestCustomPrinterIcon, this, printerId));
    431     }
    432 
    433     /**
    434      * Request a custom printer icon for a printer.
    435      *
    436      * @param printerId the id of the printer the icon should be loaded for
    437      * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon
    438      */
    439     private void handleRequestCustomPrinterIcon(@NonNull PrinterId printerId) {
    440         if (!isBound()) {
    441             ensureBound();
    442             mPendingCommands.add(() -> handleRequestCustomPrinterIcon(printerId));
    443         } else {
    444             if (DEBUG) {
    445                 Slog.i(LOG_TAG, "[user: " + mUserId + "] requestCustomPrinterIcon()");
    446             }
    447 
    448             try {
    449                 mPrintService.requestCustomPrinterIcon(printerId);
    450             } catch (RemoteException re) {
    451                 Slog.e(LOG_TAG, "Error requesting icon for " + printerId, re);
    452             }
    453         }
    454     }
    455 
    456     private void handleStartPrinterStateTracking(final @NonNull PrinterId printerId) {
    457         synchronized (mLock) {
    458             // Take a note we are tracking the printer.
    459             if (mTrackedPrinterList == null) {
    460                 mTrackedPrinterList = new ArrayList<PrinterId>();
    461             }
    462             mTrackedPrinterList.add(printerId);
    463         }
    464 
    465         if (!isBound()) {
    466             ensureBound();
    467             mPendingCommands.add(new Runnable() {
    468                 @Override
    469                 public void run() {
    470                     handleStartPrinterStateTracking(printerId);
    471                 }
    472             });
    473         } else {
    474             if (DEBUG) {
    475                 Slog.i(LOG_TAG, "[user: " + mUserId + "] startPrinterTracking()");
    476             }
    477             try {
    478                 mPrintService.startPrinterStateTracking(printerId);
    479             } catch (RemoteException re) {
    480                 Slog.e(LOG_TAG, "Error requesting start printer tracking.", re);
    481             }
    482         }
    483     }
    484 
    485     public void stopPrinterStateTracking(PrinterId printerId) {
    486         Handler.getMain().sendMessage(obtainMessage(
    487                 RemotePrintService::handleStopPrinterStateTracking, this, printerId));
    488     }
    489 
    490     private void handleStopPrinterStateTracking(final PrinterId printerId) {
    491         synchronized (mLock) {
    492             // We are no longer tracking the printer.
    493             if (mTrackedPrinterList == null || !mTrackedPrinterList.remove(printerId)) {
    494                 return;
    495             }
    496             if (mTrackedPrinterList.isEmpty()) {
    497                 mTrackedPrinterList = null;
    498             }
    499         }
    500 
    501         if (!isBound()) {
    502             ensureBound();
    503             mPendingCommands.add(new Runnable() {
    504                 @Override
    505                 public void run() {
    506                     handleStopPrinterStateTracking(printerId);
    507                 }
    508             });
    509         } else {
    510             if (DEBUG) {
    511                 Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterTracking()");
    512             }
    513             try {
    514                 mPrintService.stopPrinterStateTracking(printerId);
    515             } catch (RemoteException re) {
    516                 Slog.e(LOG_TAG, "Error requesting stop printer tracking.", re);
    517             }
    518         }
    519     }
    520 
    521     private void stopTrackingAllPrinters() {
    522         synchronized (mLock) {
    523             if (mTrackedPrinterList == null) {
    524                 return;
    525             }
    526             final int trackedPrinterCount = mTrackedPrinterList.size();
    527             for (int i = trackedPrinterCount - 1; i >= 0; i--) {
    528                 PrinterId printerId = mTrackedPrinterList.get(i);
    529                 if (printerId.getServiceName().equals(mComponentName)) {
    530                     handleStopPrinterStateTracking(printerId);
    531                 }
    532             }
    533         }
    534     }
    535 
    536     public void dump(@NonNull DualDumpOutputStream proto) {
    537         writeComponentName(proto, "component_name", ActivePrintServiceProto.COMPONENT_NAME,
    538                 mComponentName);
    539 
    540         proto.write("is_destroyed", ActivePrintServiceProto.IS_DESTROYED, mDestroyed);
    541         proto.write("is_bound", ActivePrintServiceProto.IS_BOUND, isBound());
    542         proto.write("has_discovery_session", ActivePrintServiceProto.HAS_DISCOVERY_SESSION,
    543                 mHasPrinterDiscoverySession);
    544         proto.write("has_active_print_jobs", ActivePrintServiceProto.HAS_ACTIVE_PRINT_JOBS,
    545                 mHasActivePrintJobs);
    546         proto.write("is_discovering_printers", ActivePrintServiceProto.IS_DISCOVERING_PRINTERS,
    547                 mDiscoveryPriorityList != null);
    548 
    549         synchronized (mLock) {
    550             if (mTrackedPrinterList != null) {
    551                 int numTrackedPrinters = mTrackedPrinterList.size();
    552                 for (int i = 0; i < numTrackedPrinters; i++) {
    553                     writePrinterId(proto, "tracked_printers",
    554                             ActivePrintServiceProto.TRACKED_PRINTERS, mTrackedPrinterList.get(i));
    555                 }
    556             }
    557         }
    558     }
    559 
    560     private boolean isBound() {
    561         return mPrintService != null;
    562     }
    563 
    564     private void ensureBound() {
    565         if (isBound() || mBinding) {
    566             return;
    567         }
    568         if (DEBUG) {
    569             Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureBound()");
    570         }
    571         mBinding = true;
    572 
    573         boolean wasBound = mContext.bindServiceAsUser(mIntent, mServiceConnection,
    574                 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
    575                         | Context.BIND_ALLOW_INSTANT, new UserHandle(mUserId));
    576 
    577         if (!wasBound) {
    578             if (DEBUG) {
    579                 Slog.i(LOG_TAG, "[user: " + mUserId + "] could not bind to " + mIntent);
    580             }
    581             mBinding = false;
    582 
    583             if (!mServiceDied) {
    584                 handleBinderDied();
    585             }
    586         }
    587     }
    588 
    589     private void ensureUnbound() {
    590         if (!isBound() && !mBinding) {
    591             return;
    592         }
    593         if (DEBUG) {
    594             Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureUnbound()");
    595         }
    596         mBinding = false;
    597         mPendingCommands.clear();
    598         mHasActivePrintJobs = false;
    599         mHasPrinterDiscoverySession = false;
    600         mDiscoveryPriorityList = null;
    601 
    602         synchronized (mLock) {
    603             mTrackedPrinterList = null;
    604         }
    605 
    606         if (isBound()) {
    607             try {
    608                 mPrintService.setClient(null);
    609             } catch (RemoteException re) {
    610                 /* ignore */
    611             }
    612             mPrintService.asBinder().unlinkToDeath(this, 0);
    613             mPrintService = null;
    614             mContext.unbindService(mServiceConnection);
    615         }
    616     }
    617 
    618     private class RemoteServiceConneciton implements ServiceConnection {
    619         @Override
    620         public void onServiceConnected(ComponentName name, IBinder service) {
    621             if (mDestroyed || !mBinding) {
    622                 mContext.unbindService(mServiceConnection);
    623                 return;
    624             }
    625             mBinding = false;
    626             mPrintService = IPrintService.Stub.asInterface(service);
    627             try {
    628                 service.linkToDeath(RemotePrintService.this, 0);
    629             } catch (RemoteException re) {
    630                 handleBinderDied();
    631                 return;
    632             }
    633             try {
    634                 mPrintService.setClient(mPrintServiceClient);
    635             } catch (RemoteException re) {
    636                 Slog.e(LOG_TAG, "Error setting client for: " + service, re);
    637                 handleBinderDied();
    638                 return;
    639             }
    640             // If the service died and there is a discovery session, recreate it.
    641             if (mServiceDied && mHasPrinterDiscoverySession) {
    642                 handleCreatePrinterDiscoverySession();
    643             }
    644             // If the service died and there is discovery started, restart it.
    645             if (mServiceDied && mDiscoveryPriorityList != null) {
    646                 handleStartPrinterDiscovery(mDiscoveryPriorityList);
    647             }
    648             synchronized (mLock) {
    649                 // If the service died and printers were tracked, start tracking.
    650                 if (mServiceDied && mTrackedPrinterList != null) {
    651                     final int trackedPrinterCount = mTrackedPrinterList.size();
    652                     for (int i = 0; i < trackedPrinterCount; i++) {
    653                         handleStartPrinterStateTracking(mTrackedPrinterList.get(i));
    654                     }
    655                 }
    656             }
    657             // Finally, do all the pending work.
    658             while (!mPendingCommands.isEmpty()) {
    659                 Runnable pendingCommand = mPendingCommands.remove(0);
    660                 pendingCommand.run();
    661             }
    662             // We did a best effort to get to the last state if we crashed.
    663             // If we do not have print jobs and no discovery is in progress,
    664             // then no need to be bound.
    665             if (!mHasPrinterDiscoverySession && !mHasActivePrintJobs) {
    666                 ensureUnbound();
    667             }
    668             mServiceDied = false;
    669         }
    670 
    671         @Override
    672         public void onServiceDisconnected(ComponentName name) {
    673             mBinding = true;
    674         }
    675     }
    676 
    677     private static final class RemotePrintServiceClient extends IPrintServiceClient.Stub {
    678         private final WeakReference<RemotePrintService> mWeakService;
    679 
    680         public RemotePrintServiceClient(RemotePrintService service) {
    681             mWeakService = new WeakReference<RemotePrintService>(service);
    682         }
    683 
    684         @Override
    685         public List<PrintJobInfo> getPrintJobInfos() {
    686             RemotePrintService service = mWeakService.get();
    687             if (service != null) {
    688                 final long identity = Binder.clearCallingIdentity();
    689                 try {
    690                     return service.mSpooler.getPrintJobInfos(service.mComponentName,
    691                             PrintJobInfo.STATE_ANY_SCHEDULED, PrintManager.APP_ID_ANY);
    692                 } finally {
    693                     Binder.restoreCallingIdentity(identity);
    694                 }
    695             }
    696             return null;
    697         }
    698 
    699         @Override
    700         public PrintJobInfo getPrintJobInfo(PrintJobId printJobId) {
    701             RemotePrintService service = mWeakService.get();
    702             if (service != null) {
    703                 final long identity = Binder.clearCallingIdentity();
    704                 try {
    705                     return service.mSpooler.getPrintJobInfo(printJobId,
    706                             PrintManager.APP_ID_ANY);
    707                 } finally {
    708                     Binder.restoreCallingIdentity(identity);
    709                 }
    710             }
    711             return null;
    712         }
    713 
    714         @Override
    715         public boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
    716             RemotePrintService service = mWeakService.get();
    717             if (service != null) {
    718                 final long identity = Binder.clearCallingIdentity();
    719                 try {
    720                     return service.mSpooler.setPrintJobState(printJobId, state, error);
    721                 } finally {
    722                     Binder.restoreCallingIdentity(identity);
    723                 }
    724             }
    725             return false;
    726         }
    727 
    728         @Override
    729         public boolean setPrintJobTag(PrintJobId printJobId, String tag) {
    730             RemotePrintService service = mWeakService.get();
    731             if (service != null) {
    732                 final long identity = Binder.clearCallingIdentity();
    733                 try {
    734                     return service.mSpooler.setPrintJobTag(printJobId, tag);
    735                 } finally {
    736                     Binder.restoreCallingIdentity(identity);
    737                 }
    738             }
    739             return false;
    740         }
    741 
    742         @Override
    743         public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
    744             RemotePrintService service = mWeakService.get();
    745             if (service != null) {
    746                 final long identity = Binder.clearCallingIdentity();
    747                 try {
    748                     service.mSpooler.writePrintJobData(fd, printJobId);
    749                 } finally {
    750                     Binder.restoreCallingIdentity(identity);
    751                 }
    752             }
    753         }
    754 
    755         @Override
    756         public void setProgress(@NonNull PrintJobId printJobId,
    757                 @FloatRange(from=0.0, to=1.0) float progress) {
    758             RemotePrintService service = mWeakService.get();
    759             if (service != null) {
    760                 final long identity = Binder.clearCallingIdentity();
    761                 try {
    762                     service.mSpooler.setProgress(printJobId, progress);
    763                 } finally {
    764                     Binder.restoreCallingIdentity(identity);
    765                 }
    766             }
    767         }
    768 
    769         @Override
    770         public void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
    771             RemotePrintService service = mWeakService.get();
    772             if (service != null) {
    773                 final long identity = Binder.clearCallingIdentity();
    774                 try {
    775                     service.mSpooler.setStatus(printJobId, status);
    776                 } finally {
    777                     Binder.restoreCallingIdentity(identity);
    778                 }
    779             }
    780         }
    781 
    782         @Override
    783         public void setStatusRes(@NonNull PrintJobId printJobId, @StringRes int status,
    784                 @NonNull CharSequence appPackageName) {
    785             RemotePrintService service = mWeakService.get();
    786             if (service != null) {
    787                 final long identity = Binder.clearCallingIdentity();
    788                 try {
    789                     service.mSpooler.setStatus(printJobId, status, appPackageName);
    790                 } finally {
    791                     Binder.restoreCallingIdentity(identity);
    792                 }
    793             }
    794         }
    795 
    796         @Override
    797         @SuppressWarnings({"rawtypes", "unchecked"})
    798         public void onPrintersAdded(ParceledListSlice printers) {
    799             RemotePrintService service = mWeakService.get();
    800             if (service != null) {
    801                 List<PrinterInfo> addedPrinters = (List<PrinterInfo>) printers.getList();
    802                 throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, addedPrinters);
    803                 final long identity = Binder.clearCallingIdentity();
    804                 try {
    805                     service.mCallbacks.onPrintersAdded(addedPrinters);
    806                 } finally {
    807                     Binder.restoreCallingIdentity(identity);
    808                 }
    809             }
    810         }
    811 
    812         @Override
    813         @SuppressWarnings({"rawtypes", "unchecked"})
    814         public void onPrintersRemoved(ParceledListSlice printerIds) {
    815             RemotePrintService service = mWeakService.get();
    816             if (service != null) {
    817                 List<PrinterId> removedPrinterIds = (List<PrinterId>) printerIds.getList();
    818                 throwIfPrinterIdsTampered(service.mComponentName, removedPrinterIds);
    819                 final long identity = Binder.clearCallingIdentity();
    820                 try {
    821                     service.mCallbacks.onPrintersRemoved(removedPrinterIds);
    822                 } finally {
    823                     Binder.restoreCallingIdentity(identity);
    824                 }
    825             }
    826         }
    827 
    828         private void throwIfPrinterIdsForPrinterInfoTampered(ComponentName serviceName,
    829                 List<PrinterInfo> printerInfos) {
    830             final int printerInfoCount = printerInfos.size();
    831             for (int i = 0; i < printerInfoCount; i++) {
    832                 PrinterId printerId = printerInfos.get(i).getId();
    833                 throwIfPrinterIdTampered(serviceName, printerId);
    834             }
    835         }
    836 
    837         private void throwIfPrinterIdsTampered(ComponentName serviceName,
    838                 List<PrinterId> printerIds) {
    839             final int printerIdCount = printerIds.size();
    840             for (int i = 0; i < printerIdCount; i++) {
    841                 PrinterId printerId = printerIds.get(i);
    842                 throwIfPrinterIdTampered(serviceName, printerId);
    843             }
    844         }
    845 
    846         private void throwIfPrinterIdTampered(ComponentName serviceName, PrinterId printerId) {
    847             if (printerId == null || !printerId.getServiceName().equals(serviceName)) {
    848                 throw new IllegalArgumentException("Invalid printer id: " + printerId);
    849             }
    850         }
    851 
    852         @Override
    853         public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon)
    854                 throws RemoteException {
    855             RemotePrintService service = mWeakService.get();
    856             if (service != null) {
    857                 final long identity = Binder.clearCallingIdentity();
    858                 try {
    859                     service.mCallbacks.onCustomPrinterIconLoaded(printerId, icon);
    860                 } finally {
    861                     Binder.restoreCallingIdentity(identity);
    862                 }
    863             }
    864         }
    865     }
    866 }
    867