BluetoothOppTransfer.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.ObexTransport; 36 37import android.bluetooth.BluetoothAdapter; 38import android.bluetooth.BluetoothDevice; 39import android.bluetooth.BluetoothSocket; 40import android.content.ContentValues; 41import android.content.Context; 42import android.net.Uri; 43import android.os.Handler; 44import android.os.HandlerThread; 45import android.os.Looper; 46import android.os.Message; 47import android.os.PowerManager; 48import android.os.Process; 49import android.util.Log; 50 51import java.io.File; 52import java.io.IOException; 53import java.net.InetSocketAddress; 54import java.net.Socket; 55import java.net.UnknownHostException; 56import java.util.UUID; 57 58/** 59 * This class run an actual Opp transfer session (from connect target device to 60 * disconnect) 61 */ 62public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatchListener { 63 private static final String TAG = "BtOpp Transfer"; 64 65 public static final int RFCOMM_ERROR = 10; 66 67 public static final int RFCOMM_CONNECTED = 11; 68 69 public static final int SDP_RESULT = 12; 70 71 private static final int CONNECT_WAIT_TIMEOUT = 45000; 72 73 private static final int CONNECT_RETRY_TIME = 100; 74 75 private static final short OPUSH_UUID16 = 0x1105; 76 77 public static final UUID OPUSH_UUID128 = UUID 78 .fromString("00001105-0000-1000-8000-00805f9b34fb"); 79 80 private Context mContext; 81 82 private BluetoothAdapter mAdapter; 83 84 private BluetoothOppBatch mBatch; 85 86 private BluetoothOppObexSession mSession; 87 88 private BluetoothOppShareInfo mCurrentShare; 89 90 private ObexTransport mTransport; 91 92 private HandlerThread mHandlerThread; 93 94 private EventHandler mSessionHandler; 95 96 /* 97 * TODO check if we need PowerManager here 98 */ 99 private PowerManager mPowerManager; 100 101 private long mTimestamp; 102 103 public BluetoothOppTransfer(Context context, PowerManager powerManager, 104 BluetoothOppBatch batch, BluetoothOppObexSession session) { 105 106 mContext = context; 107 mPowerManager = powerManager; 108 mBatch = batch; 109 mSession = session; 110 111 mBatch.registerListern(this); 112 mAdapter = (BluetoothAdapter) mContext.getSystemService(Context.BLUETOOTH_SERVICE); 113 114 } 115 116 public BluetoothOppTransfer(Context context, PowerManager powerManager, BluetoothOppBatch batch) { 117 this(context, powerManager, batch, null); 118 } 119 120 public int getBatchId() { 121 return mBatch.mId; 122 } 123 124 /* 125 * Receives events from mConnectThread & mSession back in the main thread. 126 */ 127 private class EventHandler extends Handler { 128 public EventHandler(Looper looper) { 129 super(looper); 130 } 131 132 @Override 133 public void handleMessage(Message msg) { 134 switch (msg.what) { 135 case SDP_RESULT: 136 if (Constants.LOGVV) { 137 Log.v(TAG, "SDP request returned " + msg.arg1 + " (" 138 + (System.currentTimeMillis() - mTimestamp + " ms)")); 139 } 140 if (!((BluetoothDevice)msg.obj).equals(mBatch.mDestination)) { 141 return; 142 } 143 144 if (msg.arg1 > 0) { 145 mConnectThread = new SocketConnectThread(mBatch.mDestination, msg.arg1); 146 mConnectThread.start(); 147 } else { 148 /* SDP query fail case */ 149 Log.e(TAG, "SDP query failed!"); 150 markBatchFailed(BluetoothShare.STATUS_CONNECTION_ERROR); 151 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 152 } 153 154 break; 155 156 /* 157 * RFCOMM connect fail is for outbound share only! Mark batch 158 * failed, and all shares in batch failed 159 */ 160 case RFCOMM_ERROR: 161 if (Constants.LOGVV) { 162 Log.v(TAG, "receive RFCOMM_ERROR msg"); 163 } 164 mConnectThread = null; 165 markBatchFailed(BluetoothShare.STATUS_CONNECTION_ERROR); 166 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 167 168 break; 169 /* 170 * RFCOMM connected is for outbound share only! Create 171 * BluetoothOppObexClientSession and start it 172 */ 173 case RFCOMM_CONNECTED: 174 if (Constants.LOGVV) { 175 Log.v(TAG, "Transfer receive RFCOMM_CONNECTED msg"); 176 } 177 mConnectThread = null; 178 mTransport = (ObexTransport)msg.obj; 179 startObexSession(); 180 181 break; 182 /* 183 * Put next share if available,or finish the transfer. 184 * For outbound session, call session.addShare() to send next file, 185 * or call session.stop(). 186 * For inbounds session, do nothing. If there is next file to receive,it 187 * will be notified through onShareAdded() 188 */ 189 case BluetoothOppObexSession.MSG_SHARE_COMPLETE: 190 BluetoothOppShareInfo info = (BluetoothOppShareInfo)msg.obj; 191 if (Constants.LOGVV) { 192 Log.v(TAG, "receive MSG_SHARE_COMPLETE for info " + info.mId); 193 } 194 if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 195 mCurrentShare = mBatch.getPendingShare(); 196 197 if (mCurrentShare != null) { 198 /* we have additional share to process */ 199 if (Constants.LOGVV) { 200 Log.v(TAG, "continue session for info " + mCurrentShare.mId 201 + " from batch " + mBatch.mId); 202 } 203 processCurrentShare(); 204 } else { 205 /* for outbound transfer, all shares are processed */ 206 if (Constants.LOGVV) { 207 Log.v(TAG, "Batch " + mBatch.mId + " is done"); 208 } 209 mSession.stop(); 210 } 211 } 212 break; 213 /* 214 * Handle session completed status Set batch status to 215 * finished 216 */ 217 case BluetoothOppObexSession.MSG_SESSION_COMPLETE: 218 BluetoothOppShareInfo info1 = (BluetoothOppShareInfo)msg.obj; 219 if (Constants.LOGVV) { 220 Log.v(TAG, "receive MSG_SESSION_COMPLETE for batch " + mBatch.mId); 221 } 222 mBatch.mStatus = Constants.BATCH_STATUS_FINISHED; 223 /* 224 * trigger content provider again to know batch status change 225 */ 226 tickShareStatus(info1); 227 break; 228 229 /* Handle the error state of an Obex session */ 230 case BluetoothOppObexSession.MSG_SESSION_ERROR: 231 if (Constants.LOGVV) { 232 Log.v(TAG, "receive MSG_SESSION_ERROR for batch " + mBatch.mId); 233 } 234 BluetoothOppShareInfo info2 = (BluetoothOppShareInfo)msg.obj; 235 mSession.stop(); 236 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 237 markBatchFailed(info2.mStatus); 238 tickShareStatus(mCurrentShare); 239 break; 240 241 case BluetoothOppObexSession.MSG_SHARE_INTERRUPTED: 242 if (Constants.LOGVV) { 243 Log.v(TAG, "receive MSG_SHARE_INTERRUPTED for batch " + mBatch.mId); 244 } 245 BluetoothOppShareInfo info3 = (BluetoothOppShareInfo)msg.obj; 246 if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 247 try { 248 if (mTransport == null) { 249 Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null"); 250 } else { 251 mTransport.close(); 252 } 253 } catch (IOException e) { 254 Log.e(TAG, "failed to close mTransport"); 255 } 256 if (Constants.LOGVV) { 257 Log.v(TAG, "mTransport closed "); 258 } 259 } 260 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 261 if (info3 != null) { 262 markBatchFailed(info3.mStatus); 263 } else { 264 markBatchFailed(); 265 } 266 tickShareStatus(mCurrentShare); 267 break; 268 269 case BluetoothOppObexSession.MSG_CONNECT_TIMEOUT: 270 if (Constants.LOGVV) { 271 Log.v(TAG, "receive MSG_CONNECT_TIMEOUT for batch " + mBatch.mId); 272 } 273 /* for outbound transfer, the block point is BluetoothSocket.write() 274 * The only way to unblock is to tear down lower transport 275 * */ 276 if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 277 try { 278 if (mTransport == null) { 279 Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null"); 280 } else { 281 mTransport.close(); 282 } 283 } catch (IOException e) { 284 Log.e(TAG, "failed to close mTransport"); 285 } 286 if (Constants.LOGVV) { 287 Log.v(TAG, "mTransport closed "); 288 } 289 } else { 290 /* For inbound transfer, the block point is waiting for user confirmation 291 * we can interrupt it nicely 292 */ 293 markShareTimeout(mCurrentShare); 294 } 295 break; 296 } 297 } 298 } 299 300 private void markShareTimeout(BluetoothOppShareInfo share) { 301 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId); 302 ContentValues updateValues = new ContentValues(); 303 updateValues 304 .put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_TIMEOUT); 305 mContext.getContentResolver().update(contentUri, updateValues, null, null); 306 } 307 308 private void markBatchFailed(int failReason) { 309 synchronized (this) { 310 try { 311 wait(1000); 312 } catch (InterruptedException e) { 313 if (Constants.LOGVV) { 314 Log.v(TAG, "Interrupted waiting for markBatchFailed"); 315 } 316 } 317 } 318 319 if (Constants.LOGV) { 320 Log.v(TAG, "Mark all ShareInfo in the batch as failed"); 321 } 322 if (mCurrentShare != null) { 323 if (Constants.LOGVV) { 324 Log.v(TAG, "Current share has status " + mCurrentShare.mStatus); 325 } 326 if (BluetoothShare.isStatusError(mCurrentShare.mStatus)) { 327 failReason = mCurrentShare.mStatus; 328 } 329 if (mCurrentShare.mDirection == BluetoothShare.DIRECTION_INBOUND 330 && mCurrentShare.mFilename != null) { 331 new File(mCurrentShare.mFilename).delete(); 332 } 333 } 334 335 BluetoothOppShareInfo info = mBatch.getPendingShare(); 336 while (info != null) { 337 if (info.mStatus < 200) { 338 info.mStatus = failReason; 339 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mId); 340 ContentValues updateValues = new ContentValues(); 341 updateValues.put(BluetoothShare.STATUS, info.mStatus); 342 /* Update un-processed outbound transfer to show some info */ 343 if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 344 BluetoothOppSendFileInfo fileInfo = null; 345 fileInfo = BluetoothOppSendFileInfo.generateFileInfo(mContext, info.mUri, 346 info.mMimetype); 347 if (fileInfo.mFileName != null) { 348 updateValues.put(BluetoothShare.FILENAME_HINT, fileInfo.mFileName); 349 updateValues.put(BluetoothShare.TOTAL_BYTES, fileInfo.mLength); 350 updateValues.put(BluetoothShare.MIMETYPE, fileInfo.mMimetype); 351 } 352 } else { 353 if (info.mStatus < 200 && info.mFilename != null) { 354 new File(info.mFilename).delete(); 355 } 356 } 357 mContext.getContentResolver().update(contentUri, updateValues, null, null); 358 Constants.sendIntentIfCompleted(mContext, contentUri, info.mStatus); 359 } 360 info = mBatch.getPendingShare(); 361 } 362 363 } 364 365 private void markBatchFailed() { 366 markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR); 367 } 368 369 /* 370 * NOTE 371 * For outbound transfer 372 * 1) Check Bluetooth status 373 * 2) Start handler thread 374 * 3) new a thread to connect to target device 375 * 3.1) Try a few times to do SDP query for target device OPUSH channel 376 * 3.2) Try a few seconds to connect to target socket 377 * 4) After BluetoothSocket is connected,create an instance of RfcommTransport 378 * 5) Create an instance of BluetoothOppClientSession 379 * 6) Start the session and process the first share in batch 380 * For inbound transfer 381 * The transfer already has session and transport setup, just start it 382 * 1) Check Bluetooth status 383 * 2) Start handler thread 384 * 3) Start the session and process the first share in batch 385 */ 386 /** 387 * Start the transfer 388 */ 389 public void start() { 390 /* check Bluetooth enable status */ 391 /* 392 * normally it's impossible to reach here if BT is disabled. Just check 393 * for safety 394 */ 395 if (!mAdapter.isEnabled()) { 396 Log.e(TAG, "Can't start transfer when Bluetooth is disabled for " + mBatch.mId); 397 markBatchFailed(); 398 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 399 return; 400 } 401 402 if (mHandlerThread == null) { 403 if (Constants.LOGVV) { 404 Log.v(TAG, "Create handler thread for batch " + mBatch.mId); 405 } 406 mHandlerThread = new HandlerThread("BtOpp Transfer Handler", 407 Process.THREAD_PRIORITY_BACKGROUND); 408 mHandlerThread.start(); 409 mSessionHandler = new EventHandler(mHandlerThread.getLooper()); 410 411 if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 412 /* for outbound transfer, we do connect first */ 413 startConnectSession(); 414 } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) { 415 /* 416 * for inbound transfer, it's already connected, so we start 417 * OBEX session directly 418 */ 419 startObexSession(); 420 } 421 } 422 } 423 424 /** 425 * Stop the transfer 426 */ 427 public void stop() { 428 if (Constants.LOGVV) { 429 Log.v(TAG, "stop"); 430 } 431 if (mConnectThread != null) { 432 try { 433 mConnectThread.interrupt(); 434 if (Constants.LOGVV) { 435 Log.v(TAG, "waiting for connect thread to terminate"); 436 } 437 mConnectThread.join(); 438 } catch (InterruptedException e) { 439 if (Constants.LOGVV) { 440 Log.v(TAG, "Interrupted waiting for connect thread to join"); 441 } 442 } 443 mConnectThread = null; 444 } 445 if (mSession != null) { 446 if (Constants.LOGVV) { 447 Log.v(TAG, "Stop mSession"); 448 } 449 mSession.stop(); 450 } 451 if (mHandlerThread != null) { 452 mHandlerThread.getLooper().quit(); 453 mHandlerThread.interrupt(); 454 mHandlerThread = null; 455 } 456 } 457 458 private void startObexSession() { 459 460 mBatch.mStatus = Constants.BATCH_STATUS_RUNNING; 461 462 mCurrentShare = mBatch.getPendingShare(); 463 if (mCurrentShare == null) { 464 /* 465 * TODO catch this error 466 */ 467 Log.e(TAG, "Unexpected error happened !"); 468 return; 469 } 470 if (Constants.LOGVV) { 471 Log.v(TAG, "Start session for info " + mCurrentShare.mId + " for batch " + mBatch.mId); 472 } 473 474 if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 475 if (Constants.LOGVV) { 476 Log.v(TAG, "Create Client session with transport " + mTransport.toString()); 477 } 478 mSession = new BluetoothOppObexClientSession(mContext, mTransport); 479 } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) { 480 /* 481 * For inbounds transfer, a server session should already exists 482 * before BluetoothOppTransfer is initialized. We should pass in a 483 * mSession instance. 484 */ 485 if (mSession == null) { 486 /** set current share as error */ 487 Log.e(TAG, "Unexpected error happened !"); 488 markBatchFailed(); 489 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 490 return; 491 } 492 if (Constants.LOGVV) { 493 Log.v(TAG, "Transfer has Server session" + mSession.toString()); 494 } 495 } 496 497 mSession.start(mSessionHandler); 498 processCurrentShare(); 499 } 500 501 private void processCurrentShare() { 502 /* This transfer need user confirm */ 503 if (Constants.LOGVV) { 504 Log.v(TAG, "processCurrentShare" + mCurrentShare.mId); 505 } 506 mSession.addShare(mCurrentShare); 507 } 508 509 /** 510 * Set transfer confirmed status. It should only be called for inbound 511 * transfer 512 */ 513 public void setConfirmed() { 514 /* unblock server session */ 515 final Thread notifyThread = new Thread("Server Unblock thread") { 516 public void run() { 517 synchronized (mSession) { 518 mSession.unblock(); 519 mSession.notify(); 520 } 521 } 522 }; 523 if (Constants.LOGVV) { 524 Log.v(TAG, "setConfirmed to unblock mSession" + mSession.toString()); 525 } 526 notifyThread.start(); 527 } 528 529 private void startConnectSession() { 530 531 if (Constants.USE_TCP_DEBUG) { 532 mConnectThread = new SocketConnectThread("localhost", Constants.TCP_DEBUG_PORT, 0); 533 mConnectThread.start(); 534 } else { 535 int channel = BluetoothOppPreference.getInstance(mContext).getChannel( 536 mBatch.mDestination, OPUSH_UUID16); 537 if (channel != -1) { 538 if (Constants.LOGV) { 539 Log.v(TAG, "Get OPUSH channel " + channel + " from cache for " 540 + mBatch.mDestination); 541 } 542 mTimestamp = System.currentTimeMillis(); 543 mSessionHandler.obtainMessage(SDP_RESULT, channel, -1, mBatch.mDestination) 544 .sendToTarget(); 545 } else { 546 doOpushSdp(); 547 } 548 } 549 } 550 551 //TODO this commented code is necessary after bluez4 has good SDP API 552 /* 553 IBluetoothCallback.Stub mDeviceCallback = new IBluetoothCallback.Stub() { 554 public void onGetRemoteServiceChannelResult(String address, int channel) { 555 mSessionHandler.obtainMessage(SDP_RESULT, channel, -1, address).sendToTarget(); 556 } 557 }; 558 */ 559 private void doOpushSdp() { 560 if (Constants.LOGVV) { 561 Log.v(TAG, "Do Opush SDP request for address " + mBatch.mDestination); 562 } 563 564 mTimestamp = System.currentTimeMillis(); 565 //TODO this commented code is necessary after bluez4 has good SDP API 566 /* 567 if (!mAdapter.getRemoteServiceChannel(mBatch.mDestination, OPUSH_UUID16, mDeviceCallback)) { 568 Log.e(TAG, "Could not start OPUSH SDP query"); 569 570 markBatchFailed(); 571 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 572 } 573 */ 574 String[] uuids = mBatch.mDestination.getUuids(); 575 if (Constants.LOGVV) { 576 Log.v(TAG, "After call getRemoteUuids for address " + mBatch.mDestination); 577 } 578 String savedUuid = null; 579 boolean isOpush = false; 580 int channel = -1; 581 if (uuids != null) { 582 for (String uuid : uuids) { 583 UUID remoteUuid = UUID.fromString(uuid); 584 if (Constants.LOGVV) { 585 Log.v(TAG, "SDP UUID: remoteUuid = " + remoteUuid); 586 } 587 if (remoteUuid.equals(OPUSH_UUID128)) { 588 savedUuid = uuid; 589 isOpush = true; 590 } 591 592 } 593 if (isOpush) { 594 channel = mBatch.mDestination.getServiceChannel(savedUuid); 595 if (Constants.LOGV) { 596 Log.v(TAG, "Get OPUSH channel " + channel + " from SDP for " 597 + mBatch.mDestination); 598 } 599 if (channel != -1) { 600 mConnectThread = new SocketConnectThread(mBatch.mDestination, channel); 601 mConnectThread.start(); 602 return; 603 } 604 } 605 606 } 607 608 Message msg = mSessionHandler.obtainMessage(SDP_RESULT, channel, -1, mBatch.mDestination); 609 mSessionHandler.sendMessageDelayed(msg, 2000); 610 } 611 612 private SocketConnectThread mConnectThread; 613 614 private class SocketConnectThread extends Thread { 615 private final String host; 616 private final BluetoothDevice device; 617 private final int channel; 618 619 private boolean isConnected; 620 private long timestamp; 621 private BluetoothSocket btSocket = null; 622 623 /* create a TCP socket */ 624 public SocketConnectThread(String host, int port, int dummy) { 625 super("Socket Connect Thread"); 626 this.host = host; 627 this.channel = port; 628 this.device = null; 629 isConnected = false; 630 } 631 632 /* create a Rfcomm Socket */ 633 public SocketConnectThread(BluetoothDevice device, int channel) { 634 super("Socket Connect Thread"); 635 this.device = device; 636 this.host = null; 637 this.channel = channel; 638 isConnected = false; 639 } 640 641 public void interrupt() { 642 if (!Constants.USE_TCP_DEBUG) { 643 if (btSocket != null) { 644 try { 645 btSocket.close(); 646 } catch (IOException e) { 647 Log.v(TAG, "Error when close socket"); 648 } 649 } 650 } 651 } 652 653 @Override 654 public void run() { 655 656 timestamp = System.currentTimeMillis(); 657 658 if (Constants.USE_TCP_DEBUG) { 659 /* Use TCP socket to connect */ 660 Socket s = new Socket(); 661 662 // Try to connect for 50 seconds 663 int result = 0; 664 for (int i = 0; i < CONNECT_RETRY_TIME && result == 0; i++) { 665 try { 666 s.connect(new InetSocketAddress(host, channel), CONNECT_WAIT_TIMEOUT); 667 } catch (UnknownHostException e) { 668 Log.e(TAG, "TCP socket connect unknown host "); 669 } catch (IOException e) { 670 Log.e(TAG, "TCP socket connect failed "); 671 } 672 if (s.isConnected()) { 673 if (Constants.LOGV) { 674 Log.v(TAG, "TCP socket connected "); 675 } 676 isConnected = true; 677 break; 678 } 679 if (isInterrupted()) { 680 Log.e(TAG, "TCP socket connect interrupted "); 681 markConnectionFailed(s); 682 return; 683 } 684 } 685 if (!isConnected) { 686 Log.e(TAG, "TCP socket connect failed after 20 seconds"); 687 markConnectionFailed(s); 688 return; 689 } 690 691 if (Constants.LOGVV) { 692 Log.v(TAG, "TCP Socket connection attempt took " 693 + (System.currentTimeMillis() - timestamp) + " ms"); 694 } 695 696 TestTcpTransport transport; 697 transport = new TestTcpTransport(s); 698 699 if (isInterrupted()) { 700 isConnected = false; 701 markConnectionFailed(s); 702 transport = null; 703 return; 704 } 705 if (!isConnected) { 706 transport = null; 707 Log.e(TAG, "TCP connect session error: "); 708 markConnectionFailed(s); 709 return; 710 } else { 711 if (Constants.LOGV) { 712 Log.v(TAG, "Send transport message " + transport.toString()); 713 } 714 mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget(); 715 } 716 } else { 717 718 /* Use BluetoothSocket to connect */ 719 720 try { 721 btSocket = device.createInsecureRfcommSocket(channel); 722 } catch (IOException e1) { 723 Log.e(TAG, "Rfcomm socket create error"); 724 markConnectionFailed(btSocket); 725 return; 726 } 727 try { 728 btSocket.connect(); 729 } catch (IOException e) { 730 Log.e(TAG, "Rfcomm socket connect exception "); 731 markConnectionFailed(btSocket); 732 return; 733 } 734 735 if (Constants.LOGVV) { 736 Log.v(TAG, "Rfcomm socket connection attempt took " 737 + (System.currentTimeMillis() - timestamp) + " ms"); 738 } 739 BluetoothOppRfcommTransport transport; 740 transport = new BluetoothOppRfcommTransport(btSocket); 741 742 BluetoothOppPreference.getInstance(mContext).setChannel(device, OPUSH_UUID16, 743 channel); 744 BluetoothOppPreference.getInstance(mContext).setName(device, device.getName()); 745 746 if (Constants.LOGVV) { 747 Log.v(TAG, "Send transport message " + transport.toString()); 748 } 749 mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget(); 750 } 751 752 } 753 754 private void markConnectionFailed(Socket s) { 755 try { 756 s.close(); 757 } catch (IOException e) { 758 Log.e(TAG, "TCP socket close error"); 759 } 760 mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget(); 761 } 762 763 private void markConnectionFailed(BluetoothSocket s) { 764 try { 765 s.close(); 766 } catch (IOException e) { 767 if (Constants.LOGVV) { 768 Log.e(TAG, "Error when close socket"); 769 } 770 } 771 mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget(); 772 return; 773 } 774 }; 775 776 /* update a trivial field of a share to notify Provider the batch status change */ 777 private void tickShareStatus(BluetoothOppShareInfo share) { 778 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId); 779 ContentValues updateValues = new ContentValues(); 780 updateValues.put(BluetoothShare.DIRECTION, share.mDirection); 781 mContext.getContentResolver().update(contentUri, updateValues, null, null); 782 } 783 784 /* 785 * Note: For outbound transfer We don't implement this method now. If later 786 * we want to support merging a later added share into an existing session, 787 * we could implement here For inbounds transfer add share means it's 788 * multiple receive in the same session, we should handle it to fill it into 789 * mSession 790 */ 791 /** 792 * Process when a share is added to current transfer 793 */ 794 public void onShareAdded(int id) { 795 BluetoothOppShareInfo info = mBatch.getPendingShare(); 796 if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) { 797 mCurrentShare = mBatch.getPendingShare(); 798 /* 799 * TODO what if it's not auto confirmed? 800 */ 801 if (mCurrentShare != null 802 && mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED) { 803 /* have additional auto confirmed share to process */ 804 if (Constants.LOGVV) { 805 Log.v(TAG, "Transfer continue session for info " + mCurrentShare.mId 806 + " from batch " + mBatch.mId); 807 } 808 processCurrentShare(); 809 setConfirmed(); 810 } 811 } 812 } 813 814 /* 815 * NOTE We don't implement this method now. Now delete a single share from 816 * the batch means the whole batch should be canceled. If later we want to 817 * support single cancel, we could implement here For outbound transfer, if 818 * the share is currently in transfer, cancel it For inbounds transfer, 819 * delete share means the current receiving file should be canceled. 820 */ 821 /** 822 * Process when a share is deleted from current transfer 823 */ 824 public void onShareDeleted(int id) { 825 826 } 827 828 /** 829 * Process when current transfer is canceled 830 */ 831 public void onBatchCanceled() { 832 if (Constants.LOGVV) { 833 Log.v(TAG, "Transfer on Batch canceled"); 834 } 835 836 this.stop(); 837 mBatch.mStatus = Constants.BATCH_STATUS_FINISHED; 838 } 839} 840