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