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