RemotePrintService.java revision 66160bb881470a691005c8ad4e9c31c41fd5f810
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 17package com.android.server.print; 18 19import android.content.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.content.ServiceConnection; 23import android.os.Binder; 24import android.os.Build; 25import android.os.Handler; 26import android.os.IBinder; 27import android.os.IBinder.DeathRecipient; 28import android.os.Looper; 29import android.os.Message; 30import android.os.ParcelFileDescriptor; 31import android.os.RemoteException; 32import android.os.UserHandle; 33import android.print.IPrinterDiscoverySessionController; 34import android.print.IPrinterDiscoverySessionObserver; 35import android.print.PrintJobInfo; 36import android.print.PrintManager; 37import android.print.PrinterId; 38import android.print.PrinterInfo; 39import android.printservice.IPrintService; 40import android.printservice.IPrintServiceClient; 41import android.util.Slog; 42 43import java.lang.ref.WeakReference; 44import java.util.ArrayList; 45import java.util.List; 46 47/** 48 * This class represents a remote print service. It abstracts away the binding 49 * and unbinding from the remote implementation. Clients can call methods of 50 * this class without worrying about when and how to bind/unbind. 51 */ 52final class RemotePrintService implements DeathRecipient { 53 54 private static final String LOG_TAG = "RemotePrintService"; 55 56 private static final boolean DEBUG = true && Build.IS_DEBUGGABLE; 57 58 private final Context mContext; 59 60 private final ComponentName mComponentName; 61 62 private final Intent mIntent; 63 64 private final RemotePrintSpooler mSpooler; 65 66 private final int mUserId; 67 68 private final List<Runnable> mPendingCommands = new ArrayList<Runnable>(); 69 70 private final ServiceConnection mServiceConnection = new RemoteServiceConneciton(); 71 72 private final RemotePrintServiceClient mPrintServiceClient; 73 74 private final Handler mHandler; 75 76 private IPrintService mPrintService; 77 78 private boolean mBinding; 79 80 private boolean mDestroyed; 81 82 public RemotePrintService(Context context, ComponentName componentName, int userId, 83 RemotePrintSpooler spooler) { 84 mContext = context; 85 mComponentName = componentName; 86 mIntent = new Intent().setComponent(mComponentName); 87 mUserId = userId; 88 mSpooler = spooler; 89 mHandler = new MyHandler(context.getMainLooper()); 90 mPrintServiceClient = new RemotePrintServiceClient(this); 91 } 92 93 public void destroy() { 94 mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY); 95 } 96 97 private void handleDestroy() { 98 throwIfDestroyed(); 99 ensureUnbound(); 100 mDestroyed = true; 101 } 102 103 public void onAllPrintJobsHandled() { 104 mHandler.sendEmptyMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED); 105 } 106 107 @Override 108 public void binderDied() { 109 mHandler.sendEmptyMessage(MyHandler.MSG_BINDER_DIED); 110 } 111 112 private void handleBinderDied() { 113 mPendingCommands.clear(); 114 ensureUnbound(); 115 } 116 117 private void handleOnAllPrintJobsHandled() { 118 throwIfDestroyed(); 119 if (isBound()) { 120 if (DEBUG) { 121 Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnAllPrintJobsHandled()"); 122 } 123 // If bound and all the work is completed, then unbind. 124 ensureUnbound(); 125 } 126 } 127 128 public void onRequestCancelPrintJob(PrintJobInfo printJob) { 129 mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_CANCEL_PRINT_JOB, 130 printJob).sendToTarget(); 131 } 132 133 private void handleRequestCancelPrintJob(final PrintJobInfo printJob) { 134 throwIfDestroyed(); 135 // If we are not bound, then we have no print jobs to handle 136 // which means that there are no print jobs to be cancelled. 137 if (isBound()) { 138 if (DEBUG) { 139 Slog.i(LOG_TAG, "[user: " + mUserId + "] handleRequestCancelPrintJob()"); 140 } 141 try { 142 mPrintService.requestCancelPrintJob(printJob); 143 } catch (RemoteException re) { 144 Slog.e(LOG_TAG, "Error canceling a pring job.", re); 145 } 146 } 147 } 148 149 public void onPrintJobQueued(PrintJobInfo printJob) { 150 mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED, 151 printJob).sendToTarget(); 152 } 153 154 private void handleOnPrintJobQueued(final PrintJobInfo printJob) { 155 throwIfDestroyed(); 156 if (!isBound()) { 157 ensureBound(); 158 mPendingCommands.add(new Runnable() { 159 @Override 160 public void run() { 161 handleOnPrintJobQueued(printJob); 162 } 163 }); 164 } else { 165 if (DEBUG) { 166 Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnPrintJobQueued()"); 167 } 168 try { 169 mPrintService.onPrintJobQueued(printJob); 170 } catch (RemoteException re) { 171 Slog.e(LOG_TAG, "Error announcing queued pring job.", re); 172 } 173 } 174 } 175 176 public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) { 177 mHandler.obtainMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION, 178 observer).sendToTarget(); 179 } 180 181 private void handleCreatePrinterDiscoverySession( 182 final IPrinterDiscoverySessionObserver observer) { 183 throwIfDestroyed(); 184 if (!isBound()) { 185 ensureBound(); 186 mPendingCommands.add(new Runnable() { 187 @Override 188 public void run() { 189 handleCreatePrinterDiscoverySession(observer); 190 } 191 }); 192 } else { 193 if (DEBUG) { 194 Slog.i(LOG_TAG, "[user: " + mUserId + "] createPrinterDiscoverySession()"); 195 } 196 try { 197 mPrintService.createPrinterDiscoverySession(observer); 198 } catch (RemoteException re) { 199 Slog.e(LOG_TAG, "Error announcing start printer dicovery.", re); 200 } 201 } 202 } 203 204 private boolean isBound() { 205 return mPrintService != null; 206 } 207 208 private void ensureBound() { 209 if (isBound() || mBinding) { 210 return; 211 } 212 if (DEBUG) { 213 Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureBound()"); 214 } 215 mBinding = true; 216 mContext.bindServiceAsUser(mIntent, mServiceConnection, 217 Context.BIND_AUTO_CREATE, new UserHandle(mUserId)); 218 } 219 220 private void ensureUnbound() { 221 if (!isBound() && !mBinding) { 222 return; 223 } 224 if (DEBUG) { 225 Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureUnbound()"); 226 } 227 mBinding = false; 228 mPendingCommands.clear(); 229 if (isBound()) { 230 try { 231 mPrintService.setClient(null); 232 } catch (RemoteException re) { 233 /* ignore */ 234 } 235 mPrintService.asBinder().unlinkToDeath(this, 0); 236 mPrintService = null; 237 mContext.unbindService(mServiceConnection); 238 } 239 } 240 241 private void throwIfDestroyed() { 242 if (mDestroyed) { 243 throw new IllegalStateException("Cannot interact with a destroyed service"); 244 } 245 } 246 247 private class RemoteServiceConneciton implements ServiceConnection { 248 @Override 249 public void onServiceConnected(ComponentName name, IBinder service) { 250 if (mDestroyed || !mBinding) { 251 return; 252 } 253 mBinding = false; 254 mPrintService = IPrintService.Stub.asInterface(service); 255 try { 256 service.linkToDeath(RemotePrintService.this, 0); 257 } catch (RemoteException re) { 258 handleBinderDied(); 259 return; 260 } 261 try { 262 mPrintService.setClient(mPrintServiceClient); 263 } catch (RemoteException re) { 264 Slog.e(LOG_TAG, "Error setting client for: " + service, re); 265 handleBinderDied(); 266 return; 267 } 268 final int pendingCommandCount = mPendingCommands.size(); 269 for (int i = 0; i < pendingCommandCount; i++) { 270 Runnable pendingCommand = mPendingCommands.get(i); 271 pendingCommand.run(); 272 } 273 } 274 275 @Override 276 public void onServiceDisconnected(ComponentName name) { 277 mBinding = true; 278 } 279 } 280 281 private final class MyHandler extends Handler { 282 public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 1; 283 public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 2; 284 public static final int MSG_ON_PRINT_JOB_QUEUED = 3; 285 public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 4; 286 public static final int MSG_DESTROY = 6; 287 public static final int MSG_BINDER_DIED = 7; 288 289 public MyHandler(Looper looper) { 290 super(looper, null, false); 291 } 292 293 @Override 294 public void handleMessage(Message message) { 295 switch (message.what) { 296 case MSG_ON_ALL_PRINT_JOBS_HANDLED: { 297 handleOnAllPrintJobsHandled(); 298 } break; 299 300 case MSG_ON_REQUEST_CANCEL_PRINT_JOB: { 301 PrintJobInfo printJob = (PrintJobInfo) message.obj; 302 handleRequestCancelPrintJob(printJob); 303 } break; 304 305 case MSG_ON_PRINT_JOB_QUEUED: { 306 PrintJobInfo printJob = (PrintJobInfo) message.obj; 307 handleOnPrintJobQueued(printJob); 308 } break; 309 310 case MSG_CREATE_PRINTER_DISCOVERY_SESSION: { 311 IPrinterDiscoverySessionObserver observer = 312 (IPrinterDiscoverySessionObserver) message.obj; 313 handleCreatePrinterDiscoverySession(new SecurePrinterDiscoverySessionObserver( 314 mComponentName, observer)); 315 } break; 316 317 case MSG_DESTROY: { 318 handleDestroy(); 319 } break; 320 321 case MSG_BINDER_DIED: { 322 handleBinderDied(); 323 } break; 324 } 325 } 326 } 327 328 private static final class RemotePrintServiceClient extends IPrintServiceClient.Stub { 329 private final WeakReference<RemotePrintService> mWeakService; 330 331 public RemotePrintServiceClient(RemotePrintService service) { 332 mWeakService = new WeakReference<RemotePrintService>(service); 333 } 334 335 @Override 336 public List<PrintJobInfo> getPrintJobInfos() { 337 RemotePrintService service = mWeakService.get(); 338 if (service != null) { 339 final long identity = Binder.clearCallingIdentity(); 340 try { 341 return service.mSpooler.getPrintJobInfos(service.mComponentName, 342 PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS, PrintManager.APP_ID_ANY); 343 } finally { 344 Binder.restoreCallingIdentity(identity); 345 } 346 } 347 return null; 348 } 349 350 @Override 351 public PrintJobInfo getPrintJobInfo(int printJobId) { 352 RemotePrintService service = mWeakService.get(); 353 if (service != null) { 354 final long identity = Binder.clearCallingIdentity(); 355 try { 356 return service.mSpooler.getPrintJobInfo(printJobId, 357 PrintManager.APP_ID_ANY); 358 } finally { 359 Binder.restoreCallingIdentity(identity); 360 } 361 } 362 return null; 363 } 364 365 @Override 366 public boolean setPrintJobState(int printJobId, int state, CharSequence error) { 367 RemotePrintService service = mWeakService.get(); 368 if (service != null) { 369 final long identity = Binder.clearCallingIdentity(); 370 try { 371 return service.mSpooler.setPrintJobState(printJobId, state, error); 372 } finally { 373 Binder.restoreCallingIdentity(identity); 374 } 375 } 376 return false; 377 } 378 379 @Override 380 public boolean setPrintJobTag(int printJobId, String tag) { 381 RemotePrintService service = mWeakService.get(); 382 if (service != null) { 383 final long identity = Binder.clearCallingIdentity(); 384 try { 385 return service.mSpooler.setPrintJobTag(printJobId, tag); 386 } finally { 387 Binder.restoreCallingIdentity(identity); 388 } 389 } 390 return false; 391 } 392 393 @Override 394 public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) { 395 RemotePrintService service = mWeakService.get(); 396 if (service != null) { 397 final long identity = Binder.clearCallingIdentity(); 398 try { 399 service.mSpooler.writePrintJobData(fd, printJobId); 400 } finally { 401 Binder.restoreCallingIdentity(identity); 402 } 403 } 404 } 405 } 406 407 private static final class SecurePrinterDiscoverySessionObserver 408 extends IPrinterDiscoverySessionObserver.Stub { 409 private final ComponentName mComponentName; 410 411 private final IPrinterDiscoverySessionObserver mDecoratedObsever; 412 413 public SecurePrinterDiscoverySessionObserver(ComponentName componentName, 414 IPrinterDiscoverySessionObserver observer) { 415 mComponentName = componentName; 416 mDecoratedObsever = observer; 417 } 418 419 @Override 420 public void onPrintersAdded(List<PrinterInfo> printers) { 421 throwIfPrinterIdsForPrinterInfoTampered(printers); 422 try { 423 mDecoratedObsever.onPrintersAdded(printers); 424 } catch (RemoteException re) { 425 Slog.e(LOG_TAG, "Error delegating to onPrintersAdded", re); 426 } 427 } 428 429 @Override 430 public void onPrintersUpdated(List<PrinterInfo> printers) { 431 throwIfPrinterIdsForPrinterInfoTampered(printers); 432 try { 433 mDecoratedObsever.onPrintersUpdated(printers); 434 } catch (RemoteException re) { 435 Slog.e(LOG_TAG, "Error delegating to onPrintersUpdated.", re); 436 } 437 } 438 439 @Override 440 public void onPrintersRemoved(List<PrinterId> printerIds) { 441 throwIfPrinterIdsTampered(printerIds); 442 try { 443 mDecoratedObsever.onPrintersRemoved(printerIds); 444 } catch (RemoteException re) { 445 Slog.e(LOG_TAG, "Error delegating to onPrintersRemoved", re); 446 } 447 } 448 449 @Override 450 public void setController(IPrinterDiscoverySessionController controller) { 451 try { 452 mDecoratedObsever.setController(controller); 453 } catch (RemoteException re) { 454 Slog.e(LOG_TAG, "Error setting controller", re); 455 } 456 } 457 458 private void throwIfPrinterIdsForPrinterInfoTampered( 459 List<PrinterInfo> printerInfos) { 460 final int printerInfoCount = printerInfos.size(); 461 for (int i = 0; i < printerInfoCount; i++) { 462 PrinterId printerId = printerInfos.get(i).getId(); 463 throwIfPrinterIdTampered(printerId); 464 } 465 } 466 467 private void throwIfPrinterIdsTampered(List<PrinterId> printerIds) { 468 final int printerIdCount = printerIds.size(); 469 for (int i = 0; i < printerIdCount; i++) { 470 PrinterId printerId = printerIds.get(i); 471 throwIfPrinterIdTampered(printerId); 472 } 473 } 474 475 private void throwIfPrinterIdTampered(PrinterId printerId) { 476 if (printerId == null || printerId.getServiceName() == null 477 || !printerId.getServiceName().equals(mComponentName)) { 478 throw new IllegalArgumentException("Invalid printer id: " + printerId); 479 } 480 } 481 } 482} 483