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