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