BluetoothOppObexClientSession.java revision 1ac5507790a87810061a19dadec36eb328a222ea
1/* 2 * Copyright (c) 2008-2009, Motorola, Inc. 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * - Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * - Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * - Neither the name of the Motorola, Inc. nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33package com.android.bluetooth.opp; 34 35import javax.obex.ClientOperation; 36import javax.obex.ClientSession; 37import javax.obex.HeaderSet; 38import javax.obex.ObexTransport; 39import javax.obex.ResponseCodes; 40 41import android.content.ContentValues; 42import android.content.Context; 43import android.net.Uri; 44import android.os.Handler; 45import android.os.Message; 46import android.os.PowerManager; 47import android.os.PowerManager.WakeLock; 48import android.os.Process; 49import android.util.Log; 50 51import java.io.BufferedInputStream; 52import java.io.IOException; 53import java.io.InputStream; 54import java.io.OutputStream; 55import java.lang.Thread; 56 57/** 58 * This class runs as an OBEX client 59 */ 60public class BluetoothOppObexClientSession implements BluetoothOppObexSession { 61 62 private static final String TAG = "BtOpp ObexClient"; 63 64 private ClientThread mThread; 65 66 private ObexTransport mTransport; 67 68 private Context mContext; 69 70 private volatile boolean mInterrupted; 71 72 private volatile boolean mWaitingForRemote; 73 74 private Handler mCallback; 75 76 public BluetoothOppObexClientSession(Context context, ObexTransport transport) { 77 if (transport == null) { 78 throw new NullPointerException("transport is null"); 79 } 80 mContext = context; 81 mTransport = transport; 82 } 83 84 public void start(Handler handler) { 85 if (Constants.LOGV) { 86 Log.v(TAG, "Start!"); 87 } 88 mCallback = handler; 89 mThread = new ClientThread(mContext, mTransport); 90 mThread.start(); 91 } 92 93 public void stop() { 94 if (Constants.LOGV) { 95 Log.v(TAG, "Stop!"); 96 } 97 if (mThread != null) { 98 mInterrupted = true; 99 try { 100 mThread.interrupt(); 101 if (Constants.LOGVV) { 102 Log.v(TAG, "waiting for thread to terminate"); 103 } 104 mThread.join(); 105 mThread = null; 106 } catch (InterruptedException e) { 107 if (Constants.LOGVV) { 108 Log.v(TAG, "Interrupted waiting for thread to join"); 109 } 110 } 111 } 112 mCallback = null; 113 } 114 115 public void addShare(BluetoothOppShareInfo share) { 116 mThread.addShare(share); 117 } 118 119 private class ClientThread extends Thread { 120 121 private static final int sSleepTime = 500; 122 123 private Context mContext1; 124 125 private BluetoothOppShareInfo mInfo; 126 127 private volatile boolean waitingForShare; 128 129 private ObexTransport mTransport1; 130 131 private ClientSession mCs; 132 133 private WakeLock wakeLock; 134 135 private BluetoothOppSendFileInfo mFileInfo = null; 136 137 private boolean mConnected = false; 138 139 public ClientThread(Context context, ObexTransport transport) { 140 super("BtOpp ClientThread"); 141 mContext1 = context; 142 mTransport1 = transport; 143 waitingForShare = true; 144 mWaitingForRemote = false; 145 146 PowerManager pm = (PowerManager)mContext1.getSystemService(Context.POWER_SERVICE); 147 wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 148 } 149 150 public void addShare(BluetoothOppShareInfo info) { 151 mInfo = info; 152 mFileInfo = processShareInfo(); 153 waitingForShare = false; 154 } 155 156 @Override 157 public void run() { 158 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 159 160 if (Constants.LOGVV) { 161 Log.v(TAG, "acquire partial WakeLock"); 162 } 163 wakeLock.acquire(); 164 165 try { 166 Thread.sleep(100); 167 } catch (InterruptedException e1) { 168 if (Constants.LOGVV) { 169 Log.v(TAG, "Client thread was interrupted (1), exiting"); 170 } 171 mInterrupted = true; 172 } 173 if (!mInterrupted) { 174 connect(); 175 } 176 177 while (!mInterrupted) { 178 if (!waitingForShare) { 179 doSend(); 180 } else { 181 try { 182 if (Constants.LOGV) { 183 Log.v(TAG, "Client thread waiting for next share, sleep for " 184 + sSleepTime); 185 } 186 Thread.sleep(sSleepTime); 187 } catch (InterruptedException e) { 188 189 } 190 } 191 } 192 disconnect(); 193 194 if (wakeLock.isHeld()) { 195 if (Constants.LOGVV) { 196 Log.v(TAG, "release partial WakeLock"); 197 } 198 wakeLock.release(); 199 } 200 Message msg = Message.obtain(mCallback); 201 msg.what = BluetoothOppObexSession.MSG_SESSION_COMPLETE; 202 msg.obj = mInfo; 203 msg.sendToTarget(); 204 205 } 206 207 private void disconnect() { 208 try { 209 if (mCs != null) { 210 mCs.disconnect(null); 211 } 212 mCs = null; 213 if (Constants.LOGV) { 214 Log.v(TAG, "OBEX session disconnected"); 215 } 216 } catch (IOException e) { 217 Log.w(TAG, "OBEX session disconnect error" + e); 218 } 219 try { 220 if (mCs != null) { 221 if (Constants.LOGV) { 222 Log.v(TAG, "OBEX session close mCs"); 223 } 224 mCs.close(); 225 if (Constants.LOGV) { 226 Log.v(TAG, "OBEX session closed"); 227 } 228 } 229 } catch (IOException e) { 230 Log.w(TAG, "OBEX session close error" + e); 231 } 232 if (mTransport1 != null) { 233 try { 234 mTransport1.close(); 235 } catch (IOException e) { 236 Log.e(TAG, "mTransport.close error"); 237 } 238 239 } 240 } 241 242 private void connect() { 243 if (Constants.LOGV) { 244 Log.v(TAG, "Create ClientSession with transport " + mTransport1.toString()); 245 } 246 try { 247 mCs = new ClientSession(mTransport1); 248 mConnected = true; 249 } catch (IOException e1) { 250 Log.e(TAG, "OBEX session create error"); 251 } 252 if (mConnected) { 253 mConnected = false; 254 HeaderSet hs = new HeaderSet(); 255 synchronized (this) { 256 mWaitingForRemote = true; 257 } 258 try { 259 mCs.connect(hs); 260 if (Constants.LOGV) { 261 Log.v(TAG, "OBEX session created"); 262 } 263 mConnected = true; 264 } catch (IOException e) { 265 Log.e(TAG, "OBEX session connect error"); 266 } 267 } 268 synchronized (this) { 269 mWaitingForRemote = false; 270 } 271 } 272 273 private void doSend() { 274 275 int status = BluetoothShare.STATUS_SUCCESS; 276 277 /* connection is established too fast to get first mInfo */ 278 while (mFileInfo == null) { 279 try { 280 Thread.sleep(50); 281 } catch (InterruptedException e) { 282 status = BluetoothShare.STATUS_CANCELED; 283 } 284 } 285 if (!mConnected) { 286 // Obex connection error 287 status = BluetoothShare.STATUS_CONNECTION_ERROR; 288 } 289 if (status == BluetoothShare.STATUS_SUCCESS) { 290 /* do real send */ 291 if (mFileInfo.mFileName != null) { 292 status = sendFile(mFileInfo); 293 } else { 294 /* this is invalid request */ 295 status = mFileInfo.mStatus; 296 } 297 waitingForShare = true; 298 } else { 299 Constants.updateShareStatus(mContext1, mInfo.mId, status); 300 } 301 302 if (status == BluetoothShare.STATUS_SUCCESS) { 303 Message msg = Message.obtain(mCallback); 304 msg.what = BluetoothOppObexSession.MSG_SHARE_COMPLETE; 305 msg.obj = mInfo; 306 msg.sendToTarget(); 307 } else { 308 Message msg = Message.obtain(mCallback); 309 msg.what = BluetoothOppObexSession.MSG_SESSION_ERROR; 310 mInfo.mStatus = status; 311 msg.obj = mInfo; 312 msg.sendToTarget(); 313 } 314 } 315 316 /* 317 * Validate this ShareInfo 318 */ 319 private BluetoothOppSendFileInfo processShareInfo() { 320 if (Constants.LOGVV) { 321 Log.v(TAG, "Client thread processShareInfo() " + mInfo.mId); 322 } 323 324 BluetoothOppSendFileInfo fileInfo = BluetoothOppSendFileInfo.generateFileInfo( 325 mContext1, mInfo.mUri, mInfo.mMimetype); 326 if (fileInfo.mFileName == null || fileInfo.mLength == 0) { 327 if (Constants.LOGVV) { 328 Log.v(TAG, "BluetoothOppSendFileInfo get invalid file"); 329 Constants.updateShareStatus(mContext1, mInfo.mId, fileInfo.mStatus); 330 } 331 332 } else { 333 if (Constants.LOGVV) { 334 Log.v(TAG, "Generate BluetoothOppSendFileInfo:"); 335 Log.v(TAG, "filename :" + fileInfo.mFileName); 336 Log.v(TAG, "length :" + fileInfo.mLength); 337 Log.v(TAG, "mimetype :" + fileInfo.mMimetype); 338 } 339 340 ContentValues updateValues = new ContentValues(); 341 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId); 342 343 updateValues.put(BluetoothShare.FILENAME_HINT, fileInfo.mFileName); 344 updateValues.put(BluetoothShare.TOTAL_BYTES, fileInfo.mLength); 345 updateValues.put(BluetoothShare.MIMETYPE, fileInfo.mMimetype); 346 347 mContext1.getContentResolver().update(contentUri, updateValues, null, null); 348 349 } 350 return fileInfo; 351 } 352 353 private int sendFile(BluetoothOppSendFileInfo fileInfo) { 354 boolean error = false; 355 int responseCode = -1; 356 int status = BluetoothShare.STATUS_SUCCESS; 357 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId); 358 ContentValues updateValues; 359 HeaderSet request; 360 request = new HeaderSet(); 361 request.setHeader(HeaderSet.NAME, fileInfo.mFileName); 362 request.setHeader(HeaderSet.TYPE, fileInfo.mMimetype); 363 364 Constants.updateShareStatus(mContext1, mInfo.mId, BluetoothShare.STATUS_RUNNING); 365 366 request.setHeader(HeaderSet.LENGTH, fileInfo.mLength); 367 ClientOperation putOperation = null; 368 OutputStream outputStream = null; 369 InputStream inputStream = null; 370 try { 371 synchronized (this) { 372 mWaitingForRemote = true; 373 } 374 try { 375 if (Constants.LOGVV) { 376 Log.v(TAG, "put headerset for " + fileInfo.mFileName); 377 } 378 putOperation = (ClientOperation)mCs.put(request); 379 } catch (IOException e) { 380 status = BluetoothShare.STATUS_OBEX_DATA_ERROR; 381 Constants.updateShareStatus(mContext1, mInfo.mId, status); 382 383 Log.e(TAG, "Error when put HeaderSet "); 384 error = true; 385 } 386 synchronized (this) { 387 mWaitingForRemote = false; 388 } 389 390 if (!error) { 391 try { 392 if (Constants.LOGVV) { 393 Log.v(TAG, "openOutputStream " + fileInfo.mFileName); 394 } 395 outputStream = putOperation.openOutputStream(); 396 inputStream = putOperation.openInputStream(); 397 } catch (IOException e) { 398 status = BluetoothShare.STATUS_OBEX_DATA_ERROR; 399 Constants.updateShareStatus(mContext1, mInfo.mId, status); 400 Log.e(TAG, "Error when openOutputStream"); 401 error = true; 402 } 403 } 404 if (!error) { 405 updateValues = new ContentValues(); 406 updateValues.put(BluetoothShare.CURRENT_BYTES, 0); 407 updateValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_RUNNING); 408 mContext1.getContentResolver().update(contentUri, updateValues, null, null); 409 } 410 411 if (!error) { 412 int position = 0; 413 int readLength = 0; 414 boolean okToProceed = false; 415 long timestamp = 0; 416 int outputBufferSize = putOperation.getMaxPacketSize(); 417 byte[] buffer = new byte[outputBufferSize]; 418 BufferedInputStream a = new BufferedInputStream(fileInfo.mInputStream, 0x4000); 419 420 if (!mInterrupted && (position != fileInfo.mLength)) { 421 readLength = a.read(buffer, 0, outputBufferSize); 422 423 mCallback.sendMessageDelayed(mCallback 424 .obtainMessage(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT), 425 BluetoothOppObexSession.SESSION_TIMEOUT); 426 synchronized (this) { 427 mWaitingForRemote = true; 428 } 429 430 // first packet will block here 431 outputStream.write(buffer, 0, readLength); 432 433 position += readLength; 434 435 if (position != fileInfo.mLength) { 436 mCallback.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT); 437 synchronized (this) { 438 mWaitingForRemote = false; 439 } 440 } else { 441 // if file length is smaller than buffer size, only one packet 442 // so block point is here 443 outputStream.close(); 444 mCallback.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT); 445 synchronized (this) { 446 mWaitingForRemote = false; 447 } 448 } 449 /* check remote accept or reject */ 450 responseCode = putOperation.getResponseCode(); 451 452 if (responseCode == ResponseCodes.OBEX_HTTP_CONTINUE 453 || responseCode == ResponseCodes.OBEX_HTTP_OK) { 454 if (Constants.LOGVV) { 455 Log.v(TAG, "Remote accept"); 456 } 457 okToProceed = true; 458 updateValues = new ContentValues(); 459 updateValues.put(BluetoothShare.CURRENT_BYTES, position); 460 mContext1.getContentResolver().update(contentUri, updateValues, null, 461 null); 462 } else { 463 Log.i(TAG, "Remote reject, Response code is " + responseCode); 464 } 465 } 466 467 while (!mInterrupted && okToProceed && (position != fileInfo.mLength)) { 468 { 469 if (Constants.LOGVV) { 470 timestamp = System.currentTimeMillis(); 471 } 472 473 readLength = a.read(buffer, 0, outputBufferSize); 474 outputStream.write(buffer, 0, readLength); 475 476 /* check remote abort */ 477 responseCode = putOperation.getResponseCode(); 478 if (Constants.LOGVV) { 479 Log.v(TAG, "Response code is " + responseCode); 480 } 481 if (responseCode != ResponseCodes.OBEX_HTTP_CONTINUE 482 && responseCode != ResponseCodes.OBEX_HTTP_OK) { 483 /* abort happens */ 484 okToProceed = false; 485 } else { 486 position += readLength; 487 if (Constants.LOGVV) { 488 Log.v(TAG, "Sending file position = " + position 489 + " readLength " + readLength + " bytes took " 490 + (System.currentTimeMillis() - timestamp) + " ms"); 491 } 492 updateValues = new ContentValues(); 493 updateValues.put(BluetoothShare.CURRENT_BYTES, position); 494 mContext1.getContentResolver().update(contentUri, updateValues, 495 null, null); 496 } 497 } 498 } 499 500 if (responseCode == ResponseCodes.OBEX_HTTP_FORBIDDEN 501 || responseCode == ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE) { 502 Log.i(TAG, "Remote reject file " + fileInfo.mFileName + " length " 503 + fileInfo.mLength); 504 status = BluetoothShare.STATUS_FORBIDDEN; 505 } else if (responseCode == ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE) { 506 Log.i(TAG, "Remote reject file type " + fileInfo.mMimetype); 507 status = BluetoothShare.STATUS_NOT_ACCEPTABLE; 508 } else if (!mInterrupted && position == fileInfo.mLength) { 509 Log.i(TAG, "SendFile finished send out file " + fileInfo.mFileName 510 + " length " + fileInfo.mLength); 511 outputStream.close(); 512 } else { 513 error = true; 514 status = BluetoothShare.STATUS_CANCELED; 515 putOperation.abort(); 516 /* interrupted */ 517 Log.i(TAG, "SendFile interrupted when send out file " + fileInfo.mFileName 518 + " at " + position + " of " + fileInfo.mLength); 519 } 520 } 521 } catch (IOException e) { 522 status = BluetoothShare.STATUS_OBEX_DATA_ERROR; 523 Log.e(TAG, "Error when sending file"); 524 Constants.updateShareStatus(mContext1, mInfo.mId, status); 525 mCallback.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT); 526 } finally { 527 try { 528 fileInfo.mInputStream.close(); 529 if (!error) { 530 responseCode = putOperation.getResponseCode(); 531 if (responseCode != -1) { 532 if (Constants.LOGVV) { 533 Log.v(TAG, "Get response code " + responseCode); 534 } 535 if (responseCode != ResponseCodes.OBEX_HTTP_OK) { 536 Log.i(TAG, "Response error code is " + responseCode); 537 status = BluetoothShare.STATUS_UNHANDLED_OBEX_CODE; 538 if (responseCode == ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE) { 539 status = BluetoothShare.STATUS_NOT_ACCEPTABLE; 540 } 541 if (responseCode == ResponseCodes.OBEX_HTTP_FORBIDDEN 542 || responseCode == ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE) { 543 status = BluetoothShare.STATUS_FORBIDDEN; 544 } 545 } 546 } else { 547 // responseCode is -1, which means connection error 548 status = BluetoothShare.STATUS_CONNECTION_ERROR; 549 } 550 } 551 552 Constants.updateShareStatus(mContext1, mInfo.mId, status); 553 554 if (inputStream != null) { 555 inputStream.close(); 556 } 557 if (putOperation != null) { 558 putOperation.close(); 559 } 560 } catch (IOException e) { 561 Log.e(TAG, "Error when closing stream after send"); 562 } 563 } 564 return status; 565 } 566 567 @Override 568 public void interrupt() { 569 super.interrupt(); 570 synchronized (this) { 571 if (mWaitingForRemote) { 572 if (Constants.LOGVV) { 573 Log.v(TAG, "Interrupted when waitingForRemote"); 574 } 575 try { 576 mTransport1.close(); 577 } catch (IOException e) { 578 Log.e(TAG, "mTransport.close error"); 579 } 580 Message msg = Message.obtain(mCallback); 581 msg.what = BluetoothOppObexSession.MSG_SHARE_INTERRUPTED; 582 if (mInfo != null) { 583 msg.obj = mInfo; 584 } 585 msg.sendToTarget(); 586 } 587 } 588 } 589 } 590 591 public void unblock() { 592 // Not used for client case 593 } 594 595} 596