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