BluetoothOppTransfer.java revision 6769b59d715ea98bd72eafcfea9acd2714a887da
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 60 * disconnect) 61 */ 62public class BluetoothOppTransfer implements 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 BluetoothDevice mBluetooth; 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 mBluetooth = (BluetoothDevice)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 (!((String)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 208 .v(TAG, "Outbound transfer for batch " + mBatch.mId 209 + " is done"); 210 } 211 mSession.stop(); 212 } 213 } 214 break; 215 /* 216 * Handle session completed status Set batch status to 217 * finished 218 */ 219 case BluetoothOppObexSession.MSG_SESSION_COMPLETE: 220 BluetoothOppShareInfo info1 = (BluetoothOppShareInfo)msg.obj; 221 if (Constants.LOGVV) { 222 Log.v(TAG, "receive MSG_SESSION_COMPLETE for batch " + mBatch.mId); 223 } 224 mBatch.mStatus = Constants.BATCH_STATUS_FINISHED; 225 /* 226 * trigger content provider again to know batch status change 227 */ 228 tickShareStatus(info1); 229 break; 230 231 /* Handle the error state of an Obex session */ 232 case BluetoothOppObexSession.MSG_SESSION_ERROR: 233 if (Constants.LOGVV) { 234 Log.v(TAG, "receive MSG_SESSION_ERROR for batch " + mBatch.mId); 235 } 236 mSession.stop(); 237 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 238 markBatchFailed(); 239 tickShareStatus(mCurrentShare); 240 break; 241 242 case BluetoothOppObexSession.MSG_SHARE_INTERRUPTED: 243 if (Constants.LOGVV) { 244 Log.v(TAG, "receive MSG_SHARE_INTERRUPTED for batch " + mBatch.mId); 245 } 246 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 247 markBatchFailed(); 248 tickShareStatus(mCurrentShare); 249 250 try { 251 if (mTransport == null) { 252 Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null"); 253 } else { 254 mTransport.close(); 255 } 256 } catch (IOException e) { 257 Log.e(TAG, "failed to close mTransport"); 258 } 259 if (Constants.LOGVV) { 260 Log.v(TAG, "mTransport closed "); 261 } 262 break; 263 264 case BluetoothOppObexSession.MSG_CONNECT_TIMEOUT: 265 if (Constants.LOGVV) { 266 Log.v(TAG, "receive MSG_CONNECT_TIMEOUT for batch " + mBatch.mId); 267 } 268 /* for outbound transfer, the block point is BluetoothSocket.write() 269 * The only way to unblock is to tear down lower transport 270 * */ 271 if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 272 try { 273 if (mTransport == null) { 274 Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null"); 275 } else { 276 mTransport.close(); 277 } 278 } catch (IOException e) { 279 Log.e(TAG, "failed to close mTransport"); 280 } 281 if (Constants.LOGVV) { 282 Log.v(TAG, "mTransport closed "); 283 } 284 } else { 285 /* For inbound transfer, the block point is waiting for user confirmation 286 * we can interrupt it nicely 287 */ 288 markShareTimeout(mCurrentShare); 289 } 290 break; 291 } 292 } 293 } 294 295 private void markShareTimeout(BluetoothOppShareInfo share) { 296 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId); 297 ContentValues updateValues = new ContentValues(); 298 updateValues 299 .put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_TIMEOUT); 300 mContext.getContentResolver().update(contentUri, updateValues, null, null); 301 } 302 303 private void markBatchFailed(int failReason) { 304 305 synchronized (this) { 306 try { 307 wait(500); 308 } catch (InterruptedException e) { 309 if (Constants.LOGVV) { 310 Log.v(TAG, "Interrupted waiting for markBatchFailed"); 311 } 312 } 313 } 314 315 if (Constants.LOGV) { 316 Log.v(TAG, "Mark all ShareInfo in the batch as failed"); 317 } 318 if (mCurrentShare != null) { 319 if (Constants.LOGV) { 320 Log.v(TAG, "Current share has status " + mCurrentShare.mStatus); 321 } 322 if (BluetoothShare.isStatusError(mCurrentShare.mStatus)) { 323 failReason = mCurrentShare.mStatus; 324 } 325 if (mCurrentShare.mDirection == BluetoothShare.DIRECTION_INBOUND 326 && mCurrentShare.mFilename != null) { 327 new File(mCurrentShare.mFilename).delete(); 328 } 329 } 330 331 BluetoothOppShareInfo info = mBatch.getPendingShare(); 332 while (info != null) { 333 if (info.mStatus < 200) { 334 info.mStatus = failReason; 335 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mId); 336 ContentValues updateValues = new ContentValues(); 337 updateValues.put(BluetoothShare.STATUS, info.mStatus); 338 /* Update un-processed outbound transfer to show some info */ 339 if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 340 BluetoothOppSendFileInfo fileInfo = null; 341 fileInfo = BluetoothOppSendFileInfo.generateFileInfo(mContext, info.mUri); 342 if (fileInfo.mFileName != null) { 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 } else { 348 if (info.mStatus < 200 && info.mFilename != null) { 349 new File(info.mFilename).delete(); 350 } 351 } 352 mContext.getContentResolver().update(contentUri, updateValues, null, null); 353 Constants.sendIntentIfCompleted(mContext, contentUri, info.mStatus); 354 } 355 info = mBatch.getPendingShare(); 356 } 357 358 } 359 360 private void markBatchFailed() { 361 markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR); 362 } 363 364 /* 365 * NOTE 366 * For outbound transfer 367 * 1) Check Bluetooth status 368 * 2) Start handler thread 369 * 3) new a thread to connect to target device 370 * 3.1) Try a few times to do SDP query for target device OPUSH channel 371 * 3.2) Try a few seconds to connect to target socket 372 * 4) After BluetoothSocket is connected,create an instance of RfcommTransport 373 * 5) Create an instance of BluetoothOppClientSession 374 * 6) Start the session and process the first share in batch 375 * For inbound transfer 376 * The transfer already has session and transport setup, just start it 377 * 1) Check Bluetooth status 378 * 2) Start handler thread 379 * 3) Start the session and process the first share in batch 380 */ 381 /** 382 * Start the transfer 383 */ 384 public void start() { 385 /* check Bluetooth enable status */ 386 /* 387 * normally it's impossible to reach here if BT is disabled. Just check 388 * for safety 389 */ 390 if (!mBluetooth.isEnabled()) { 391 Log.e(TAG, "Can't start transfer when Bluetooth is disabled for " + mBatch.mId); 392 markBatchFailed(); 393 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 394 return; 395 } 396 397 if (mHandlerThread == null) { 398 if (Constants.LOGVV) { 399 Log.v(TAG, "Create handler thread for batch " + mBatch.mId); 400 } 401 mHandlerThread = new HandlerThread("BtOpp Transfer Handler", 402 Process.THREAD_PRIORITY_BACKGROUND); 403 mHandlerThread.start(); 404 mSessionHandler = new EventHandler(mHandlerThread.getLooper()); 405 406 if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 407 /* for outbound transfer, we do connect first */ 408 startConnectSession(); 409 } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) { 410 /* 411 * for inbound transfer, it's already connected, so we start 412 * OBEX session directly 413 */ 414 startObexSession(); 415 } 416 } 417 } 418 419 /** 420 * Stop the transfer 421 */ 422 public void stop() { 423 if (Constants.LOGVV) { 424 Log.v(TAG, "stop"); 425 } 426 if (mConnectThread != null) { 427 try { 428 mConnectThread.interrupt(); 429 if (Constants.LOGVV) { 430 Log.v(TAG, "waiting for connect thread to terminate"); 431 } 432 mConnectThread.join(); 433 } catch (InterruptedException e) { 434 if (Constants.LOGVV) { 435 Log.v(TAG, "Interrupted waiting for connect thread to join"); 436 } 437 } 438 mConnectThread = null; 439 } 440 if (mSession != null) { 441 if (Constants.LOGVV) { 442 Log.v(TAG, "Stop mSession"); 443 } 444 mSession.stop(); 445 } 446 if (mHandlerThread != null) { 447 mHandlerThread.getLooper().quit(); 448 mHandlerThread.interrupt(); 449 mHandlerThread = null; 450 } 451 } 452 453 private void startObexSession() { 454 455 mBatch.mStatus = Constants.BATCH_STATUS_RUNNING; 456 457 mCurrentShare = mBatch.getPendingShare(); 458 if (mCurrentShare == null) { 459 /* 460 * TODO catch this error 461 */ 462 Log.e(TAG, "Unexpected error happened !"); 463 return; 464 } 465 if (Constants.LOGVV) { 466 Log.v(TAG, "Transfer start session for info " + mCurrentShare.mId + " for batch " 467 + mBatch.mId); 468 } 469 470 if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 471 if (Constants.LOGVV) { 472 Log 473 .v(TAG, "Transfer create Client session with transport " 474 + mTransport.toString()); 475 } 476 477 mSession = new BluetoothOppObexClientSession(mContext, 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