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 33 package com.android.bluetooth.opp; 34 35 import android.app.NotificationManager; 36 import android.bluetooth.BluetoothAdapter; 37 import android.bluetooth.BluetoothDevice; 38 import android.bluetooth.BluetoothSocket; 39 import android.bluetooth.BluetoothUuid; 40 import android.bluetooth.SdpOppOpsRecord; 41 import android.content.BroadcastReceiver; 42 import android.content.ContentValues; 43 import android.content.Context; 44 import android.content.Intent; 45 import android.content.IntentFilter; 46 import android.net.Uri; 47 import android.os.Handler; 48 import android.os.HandlerThread; 49 import android.os.Looper; 50 import android.os.Message; 51 import android.os.ParcelUuid; 52 import android.os.Process; 53 import android.util.Log; 54 55 import com.android.bluetooth.BluetoothObexTransport; 56 57 import java.io.IOException; 58 59 import javax.obex.ObexTransport; 60 61 /** 62 * This class run an actual Opp transfer session (from connect target device to 63 * disconnect) 64 */ 65 public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatchListener { 66 private static final String TAG = "BtOppTransfer"; 67 68 private static final boolean D = Constants.DEBUG; 69 70 private static final boolean V = Constants.VERBOSE; 71 72 private static final int TRANSPORT_ERROR = 10; 73 74 private static final int TRANSPORT_CONNECTED = 11; 75 76 private static final int SOCKET_ERROR_RETRY = 13; 77 78 private static final int CONNECT_WAIT_TIMEOUT = 45000; 79 80 private static final int CONNECT_RETRY_TIME = 100; 81 82 private static final String SOCKET_LINK_KEY_ERROR = "Invalid exchange"; 83 84 private Context mContext; 85 86 private BluetoothAdapter mAdapter; 87 88 private BluetoothDevice mDevice; 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 private long mTimestamp; 103 104 private class OppConnectionReceiver extends BroadcastReceiver { 105 @Override onReceive(Context context, Intent intent)106 public void onReceive(Context context, Intent intent) { 107 String action = intent.getAction(); 108 if (D) { 109 Log.d(TAG, " Action :" + action); 110 } 111 if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { 112 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 113 if (device == null || mBatch == null || mCurrentShare == null) { 114 Log.e(TAG, "device : " + device + " mBatch :" + mBatch + " mCurrentShare :" 115 + mCurrentShare); 116 return; 117 } 118 try { 119 if (V) { 120 Log.v(TAG, "Device :" + device + "- OPP device: " + mBatch.mDestination 121 + " \n mCurrentShare.mConfirm == " + mCurrentShare.mConfirm); 122 } 123 if ((device.equals(mBatch.mDestination)) && (mCurrentShare.mConfirm 124 == BluetoothShare.USER_CONFIRMATION_PENDING)) { 125 if (V) { 126 Log.v(TAG, "ACTION_ACL_DISCONNECTED to be processed for batch: " 127 + mBatch.mId); 128 } 129 // Remove the timeout message triggered earlier during Obex Put 130 mSessionHandler.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT); 131 // Now reuse the same message to clean up the session. 132 mSessionHandler.sendMessage(mSessionHandler.obtainMessage( 133 BluetoothOppObexSession.MSG_CONNECT_TIMEOUT)); 134 } 135 } catch (Exception e) { 136 e.printStackTrace(); 137 } 138 } else if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) { 139 ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID); 140 if (D) { 141 Log.d(TAG, "Received UUID: " + uuid.toString()); 142 Log.d(TAG, "expected UUID: " + BluetoothUuid.OBEX_OBJECT_PUSH.toString()); 143 } 144 if (uuid.equals(BluetoothUuid.OBEX_OBJECT_PUSH)) { 145 int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1); 146 Log.d(TAG, " -> status: " + status); 147 BluetoothDevice device = 148 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 149 if (mDevice == null) { 150 Log.w(TAG, "OPP SDP search, target device is null, ignoring result"); 151 return; 152 } 153 if (!device.getAddress().equalsIgnoreCase(mDevice.getAddress())) { 154 Log.w(TAG, " OPP SDP search for wrong device, ignoring!!"); 155 return; 156 } 157 SdpOppOpsRecord record = 158 intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD); 159 if (record == null) { 160 Log.w(TAG, " Invalid SDP , ignoring !!"); 161 markConnectionFailed(null); 162 return; 163 } 164 mConnectThread = 165 new SocketConnectThread(mDevice, false, true, record.getL2capPsm()); 166 mConnectThread.start(); 167 mDevice = null; 168 } 169 } 170 } 171 } 172 173 private OppConnectionReceiver mBluetoothReceiver; 174 BluetoothOppTransfer(Context context, BluetoothOppBatch batch, BluetoothOppObexSession session)175 public BluetoothOppTransfer(Context context, BluetoothOppBatch batch, 176 BluetoothOppObexSession session) { 177 178 mContext = context; 179 mBatch = batch; 180 mSession = session; 181 182 mBatch.registerListern(this); 183 mAdapter = BluetoothAdapter.getDefaultAdapter(); 184 185 } 186 BluetoothOppTransfer(Context context, BluetoothOppBatch batch)187 public BluetoothOppTransfer(Context context, BluetoothOppBatch batch) { 188 this(context, batch, null); 189 } 190 getBatchId()191 public int getBatchId() { 192 return mBatch.mId; 193 } 194 195 /* 196 * Receives events from mConnectThread & mSession back in the main thread. 197 */ 198 private class EventHandler extends Handler { EventHandler(Looper looper)199 EventHandler(Looper looper) { 200 super(looper); 201 } 202 203 @Override handleMessage(Message msg)204 public void handleMessage(Message msg) { 205 switch (msg.what) { 206 case SOCKET_ERROR_RETRY: 207 mConnectThread = new SocketConnectThread((BluetoothDevice) msg.obj, true); 208 209 mConnectThread.start(); 210 break; 211 case TRANSPORT_ERROR: 212 /* 213 * RFCOMM connect fail is for outbound share only! Mark batch 214 * failed, and all shares in batch failed 215 */ 216 if (V) { 217 Log.v(TAG, "receive TRANSPORT_ERROR msg"); 218 } 219 mConnectThread = null; 220 markBatchFailed(BluetoothShare.STATUS_CONNECTION_ERROR); 221 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 222 223 break; 224 case TRANSPORT_CONNECTED: 225 /* 226 * RFCOMM connected is for outbound share only! Create 227 * BluetoothOppObexClientSession and start it 228 */ 229 if (V) { 230 Log.v(TAG, "Transfer receive TRANSPORT_CONNECTED msg"); 231 } 232 mConnectThread = null; 233 mTransport = (ObexTransport) msg.obj; 234 startObexSession(); 235 236 break; 237 case BluetoothOppObexSession.MSG_SHARE_COMPLETE: 238 /* 239 * Put next share if available,or finish the transfer. 240 * For outbound session, call session.addShare() to send next file, 241 * or call session.stop(). 242 * For inbounds session, do nothing. If there is next file to receive,it 243 * will be notified through onShareAdded() 244 */ 245 BluetoothOppShareInfo info = (BluetoothOppShareInfo) msg.obj; 246 if (V) { 247 Log.v(TAG, "receive MSG_SHARE_COMPLETE for info " + info.mId); 248 } 249 if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 250 mCurrentShare = mBatch.getPendingShare(); 251 252 if (mCurrentShare != null) { 253 /* we have additional share to process */ 254 if (V) { 255 Log.v(TAG, "continue session for info " + mCurrentShare.mId 256 + " from batch " + mBatch.mId); 257 } 258 processCurrentShare(); 259 } else { 260 /* for outbound transfer, all shares are processed */ 261 if (V) { 262 Log.v(TAG, "Batch " + mBatch.mId + " is done"); 263 } 264 mSession.stop(); 265 } 266 } 267 break; 268 case BluetoothOppObexSession.MSG_SESSION_COMPLETE: 269 /* 270 * Handle session completed status Set batch status to 271 * finished 272 */ 273 cleanUp(); 274 BluetoothOppShareInfo info1 = (BluetoothOppShareInfo) msg.obj; 275 if (V) { 276 Log.v(TAG, "receive MSG_SESSION_COMPLETE for batch " + mBatch.mId); 277 } 278 mBatch.mStatus = Constants.BATCH_STATUS_FINISHED; 279 /* 280 * trigger content provider again to know batch status change 281 */ 282 tickShareStatus(info1); 283 break; 284 285 case BluetoothOppObexSession.MSG_SESSION_ERROR: 286 /* Handle the error state of an Obex session */ 287 if (V) { 288 Log.v(TAG, "receive MSG_SESSION_ERROR for batch " + mBatch.mId); 289 } 290 cleanUp(); 291 try { 292 BluetoothOppShareInfo info2 = (BluetoothOppShareInfo) msg.obj; 293 if (mSession != null) { 294 mSession.stop(); 295 } 296 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 297 markBatchFailed(info2.mStatus); 298 tickShareStatus(mCurrentShare); 299 } catch (Exception e) { 300 Log.e(TAG, "Exception while handling MSG_SESSION_ERROR"); 301 e.printStackTrace(); 302 } 303 break; 304 305 case BluetoothOppObexSession.MSG_SHARE_INTERRUPTED: 306 if (V) { 307 Log.v(TAG, "receive MSG_SHARE_INTERRUPTED for batch " + mBatch.mId); 308 } 309 BluetoothOppShareInfo info3 = (BluetoothOppShareInfo) msg.obj; 310 if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 311 try { 312 if (mTransport == null) { 313 Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null"); 314 } else { 315 mTransport.close(); 316 } 317 } catch (IOException e) { 318 Log.e(TAG, "failed to close mTransport"); 319 } 320 if (V) { 321 Log.v(TAG, "mTransport closed "); 322 } 323 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 324 if (info3 != null) { 325 markBatchFailed(info3.mStatus); 326 } else { 327 markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR); 328 } 329 tickShareStatus(mCurrentShare); 330 } 331 break; 332 333 case BluetoothOppObexSession.MSG_CONNECT_TIMEOUT: 334 if (V) { 335 Log.v(TAG, "receive MSG_CONNECT_TIMEOUT for batch " + mBatch.mId); 336 } 337 /* for outbound transfer, the block point is BluetoothSocket.write() 338 * The only way to unblock is to tear down lower transport 339 * */ 340 if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 341 try { 342 if (mTransport == null) { 343 Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null"); 344 } else { 345 mTransport.close(); 346 } 347 } catch (IOException e) { 348 Log.e(TAG, "failed to close mTransport"); 349 } 350 if (V) { 351 Log.v(TAG, "mTransport closed "); 352 } 353 } else { 354 /* 355 * For inbound transfer, the block point is waiting for 356 * user confirmation we can interrupt it nicely 357 */ 358 359 // Remove incoming file confirm notification 360 NotificationManager nm = (NotificationManager) mContext.getSystemService( 361 Context.NOTIFICATION_SERVICE); 362 nm.cancel(mCurrentShare.mId); 363 // Send intent to UI for timeout handling 364 Intent in = new Intent(BluetoothShare.USER_CONFIRMATION_TIMEOUT_ACTION); 365 mContext.sendBroadcast(in); 366 367 markShareTimeout(mCurrentShare); 368 } 369 break; 370 } 371 } 372 } 373 markShareTimeout(BluetoothOppShareInfo share)374 private void markShareTimeout(BluetoothOppShareInfo share) { 375 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId); 376 ContentValues updateValues = new ContentValues(); 377 updateValues.put(BluetoothShare.USER_CONFIRMATION, 378 BluetoothShare.USER_CONFIRMATION_TIMEOUT); 379 mContext.getContentResolver().update(contentUri, updateValues, null, null); 380 } 381 markBatchFailed(int failReason)382 private void markBatchFailed(int failReason) { 383 synchronized (this) { 384 try { 385 wait(1000); 386 } catch (InterruptedException e) { 387 if (V) { 388 Log.v(TAG, "Interrupted waiting for markBatchFailed"); 389 } 390 } 391 } 392 393 if (D) { 394 Log.d(TAG, "Mark all ShareInfo in the batch as failed"); 395 } 396 if (mCurrentShare != null) { 397 if (V) { 398 Log.v(TAG, "Current share has status " + mCurrentShare.mStatus); 399 } 400 if (BluetoothShare.isStatusError(mCurrentShare.mStatus)) { 401 failReason = mCurrentShare.mStatus; 402 } 403 if (mCurrentShare.mDirection == BluetoothShare.DIRECTION_INBOUND 404 && mCurrentShare.mUri != null) { 405 mContext.getContentResolver().delete(mCurrentShare.mUri, null, null); 406 } 407 } 408 409 BluetoothOppShareInfo info = null; 410 if (mBatch == null) { 411 return; 412 } 413 info = mBatch.getPendingShare(); 414 while (info != null) { 415 if (info.mStatus < 200) { 416 info.mStatus = failReason; 417 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mId); 418 ContentValues updateValues = new ContentValues(); 419 updateValues.put(BluetoothShare.STATUS, info.mStatus); 420 /* Update un-processed outbound transfer to show some info */ 421 if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 422 BluetoothOppSendFileInfo fileInfo = 423 BluetoothOppUtility.getSendFileInfo(info.mUri); 424 BluetoothOppUtility.closeSendFileInfo(info.mUri); 425 if (fileInfo.mFileName != null) { 426 updateValues.put(BluetoothShare.FILENAME_HINT, fileInfo.mFileName); 427 updateValues.put(BluetoothShare.TOTAL_BYTES, fileInfo.mLength); 428 updateValues.put(BluetoothShare.MIMETYPE, fileInfo.mMimetype); 429 } 430 } else { 431 if (info.mStatus < 200 && info.mUri != null) { 432 mContext.getContentResolver().delete(info.mUri, null, null); 433 } 434 } 435 mContext.getContentResolver().update(contentUri, updateValues, null, null); 436 Constants.sendIntentIfCompleted(mContext, contentUri, info.mStatus); 437 } 438 info = mBatch.getPendingShare(); 439 } 440 441 } 442 443 /* 444 * NOTE 445 * For outbound transfer 446 * 1) Check Bluetooth status 447 * 2) Start handler thread 448 * 3) new a thread to connect to target device 449 * 3.1) Try a few times to do SDP query for target device OPUSH channel 450 * 3.2) Try a few seconds to connect to target socket 451 * 4) After BluetoothSocket is connected,create an instance of RfcommTransport 452 * 5) Create an instance of BluetoothOppClientSession 453 * 6) Start the session and process the first share in batch 454 * For inbound transfer 455 * The transfer already has session and transport setup, just start it 456 * 1) Check Bluetooth status 457 * 2) Start handler thread 458 * 3) Start the session and process the first share in batch 459 */ 460 461 /** 462 * Start the transfer 463 */ start()464 public void start() { 465 /* check Bluetooth enable status */ 466 /* 467 * normally it's impossible to reach here if BT is disabled. Just check 468 * for safety 469 */ 470 if (!mAdapter.isEnabled()) { 471 Log.e(TAG, "Can't start transfer when Bluetooth is disabled for " + mBatch.mId); 472 markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR); 473 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 474 return; 475 } 476 registerConnectionreceiver(); 477 if (mHandlerThread == null) { 478 if (V) { 479 Log.v(TAG, "Create handler thread for batch " + mBatch.mId); 480 } 481 mHandlerThread = 482 new HandlerThread("BtOpp Transfer Handler", Process.THREAD_PRIORITY_BACKGROUND); 483 mHandlerThread.start(); 484 mSessionHandler = new EventHandler(mHandlerThread.getLooper()); 485 486 if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 487 /* for outbound transfer, we do connect first */ 488 startConnectSession(); 489 } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) { 490 /* 491 * for inbound transfer, it's already connected, so we start 492 * OBEX session directly 493 */ 494 startObexSession(); 495 } 496 } 497 498 } 499 500 /** 501 * Stop the transfer 502 */ stop()503 public void stop() { 504 if (V) { 505 Log.v(TAG, "stop"); 506 } 507 if (mSession != null) { 508 if (V) { 509 Log.v(TAG, "Stop mSession"); 510 } 511 mSession.stop(); 512 } 513 514 cleanUp(); 515 if (mConnectThread != null) { 516 try { 517 mConnectThread.interrupt(); 518 if (V) { 519 Log.v(TAG, "waiting for connect thread to terminate"); 520 } 521 mConnectThread.join(); 522 } catch (InterruptedException e) { 523 if (V) { 524 Log.v(TAG, "Interrupted waiting for connect thread to join"); 525 } 526 } 527 mConnectThread = null; 528 } 529 // Prevent concurrent access 530 synchronized (this) { 531 if (mHandlerThread != null) { 532 mHandlerThread.quit(); 533 mHandlerThread.interrupt(); 534 mHandlerThread = null; 535 } 536 } 537 } 538 startObexSession()539 private void startObexSession() { 540 541 mBatch.mStatus = Constants.BATCH_STATUS_RUNNING; 542 543 mCurrentShare = mBatch.getPendingShare(); 544 if (mCurrentShare == null) { 545 /* 546 * TODO catch this error 547 */ 548 Log.e(TAG, "Unexpected error happened !"); 549 return; 550 } 551 if (V) { 552 Log.v(TAG, "Start session for info " + mCurrentShare.mId + " for batch " + mBatch.mId); 553 } 554 555 if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { 556 if (V) { 557 Log.v(TAG, "Create Client session with transport " + mTransport.toString()); 558 } 559 mSession = new BluetoothOppObexClientSession(mContext, mTransport); 560 } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) { 561 /* 562 * For inbounds transfer, a server session should already exists 563 * before BluetoothOppTransfer is initialized. We should pass in a 564 * mSession instance. 565 */ 566 if (mSession == null) { 567 /** set current share as error */ 568 Log.e(TAG, "Unexpected error happened !"); 569 markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR); 570 mBatch.mStatus = Constants.BATCH_STATUS_FAILED; 571 return; 572 } 573 if (V) { 574 Log.v(TAG, "Transfer has Server session" + mSession.toString()); 575 } 576 } 577 578 mSession.start(mSessionHandler, mBatch.getNumShares()); 579 processCurrentShare(); 580 } 581 registerConnectionreceiver()582 private void registerConnectionreceiver() { 583 /* 584 * OBEX channel need to be monitored for unexpected ACL disconnection 585 * such as Remote Battery removal 586 */ 587 synchronized (this) { 588 try { 589 if (mBluetoothReceiver == null) { 590 mBluetoothReceiver = new OppConnectionReceiver(); 591 IntentFilter filter = new IntentFilter(); 592 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 593 filter.addAction(BluetoothDevice.ACTION_SDP_RECORD); 594 mContext.registerReceiver(mBluetoothReceiver, filter); 595 if (V) { 596 Log.v(TAG, "Registered mBluetoothReceiver"); 597 } 598 } 599 } catch (IllegalArgumentException e) { 600 Log.e(TAG, "mBluetoothReceiver Registered already ", e); 601 } 602 } 603 } 604 processCurrentShare()605 private void processCurrentShare() { 606 /* This transfer need user confirm */ 607 if (V) { 608 Log.v(TAG, "processCurrentShare" + mCurrentShare.mId); 609 } 610 mSession.addShare(mCurrentShare); 611 if (mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED) { 612 confirmStatusChanged(); 613 } 614 } 615 616 /** 617 * Set transfer confirmed status. It should only be called for inbound 618 * transfer 619 */ confirmStatusChanged()620 public void confirmStatusChanged() { 621 /* unblock server session */ 622 final Thread notifyThread = new Thread("Server Unblock thread") { 623 @Override 624 public void run() { 625 synchronized (mSession) { 626 mSession.unblock(); 627 mSession.notify(); 628 } 629 } 630 }; 631 if (V) { 632 Log.v(TAG, "confirmStatusChanged to unblock mSession" + mSession.toString()); 633 } 634 notifyThread.start(); 635 } 636 startConnectSession()637 private void startConnectSession() { 638 mDevice = mBatch.mDestination; 639 if (!mBatch.mDestination.sdpSearch(BluetoothUuid.OBEX_OBJECT_PUSH)) { 640 if (D) { 641 Log.d(TAG, "SDP failed, start rfcomm connect directly"); 642 } 643 /* update bd address as sdp could not be started */ 644 mDevice = null; 645 /* SDP failed, start rfcomm connect directly */ 646 mConnectThread = new SocketConnectThread(mBatch.mDestination, false, false, -1); 647 mConnectThread.start(); 648 } 649 } 650 651 private SocketConnectThread mConnectThread; 652 653 private class SocketConnectThread extends Thread { 654 private final String mHost; 655 656 private final BluetoothDevice mDevice; 657 658 private final int mChannel; 659 660 private int mL2cChannel = 0; 661 662 private boolean mIsConnected; 663 664 private long mTimestamp; 665 666 private BluetoothSocket mBtSocket = null; 667 668 private boolean mRetry = false; 669 670 private boolean mSdpInitiated = false; 671 672 private boolean mIsInterrupted = false; 673 674 /* create a Rfcomm/L2CAP Socket */ SocketConnectThread(BluetoothDevice device, boolean retry)675 SocketConnectThread(BluetoothDevice device, boolean retry) { 676 super("Socket Connect Thread"); 677 this.mDevice = device; 678 this.mHost = null; 679 this.mChannel = -1; 680 mIsConnected = false; 681 mRetry = retry; 682 mSdpInitiated = false; 683 } 684 685 /* create a Rfcomm/L2CAP Socket */ SocketConnectThread(BluetoothDevice device, boolean retry, boolean sdpInitiated, int l2capChannel)686 SocketConnectThread(BluetoothDevice device, boolean retry, boolean sdpInitiated, 687 int l2capChannel) { 688 super("Socket Connect Thread"); 689 this.mDevice = device; 690 this.mHost = null; 691 this.mChannel = -1; 692 mIsConnected = false; 693 mRetry = retry; 694 mSdpInitiated = sdpInitiated; 695 mL2cChannel = l2capChannel; 696 } 697 698 @Override interrupt()699 public void interrupt() { 700 if (D) { 701 Log.d(TAG, "start interrupt :" + mBtSocket); 702 } 703 mIsInterrupted = true; 704 if (mBtSocket != null) { 705 try { 706 mBtSocket.close(); 707 } catch (IOException e) { 708 Log.v(TAG, "Error when close socket"); 709 } 710 } 711 } 712 connectRfcommSocket()713 private void connectRfcommSocket() { 714 if (V) { 715 Log.v(TAG, "connectRfcommSocket"); 716 } 717 try { 718 if (mIsInterrupted) { 719 Log.d(TAG, "connectRfcommSocket interrupted"); 720 markConnectionFailed(mBtSocket); 721 return; 722 } 723 mBtSocket = mDevice.createInsecureRfcommSocketToServiceRecord( 724 BluetoothUuid.OBEX_OBJECT_PUSH.getUuid()); 725 } catch (IOException e1) { 726 Log.e(TAG, "Rfcomm socket create error", e1); 727 markConnectionFailed(mBtSocket); 728 return; 729 } 730 try { 731 mBtSocket.connect(); 732 733 if (V) { 734 Log.v(TAG, 735 "Rfcomm socket connection attempt took " + (System.currentTimeMillis() 736 - mTimestamp) + " ms"); 737 } 738 BluetoothObexTransport transport; 739 transport = new BluetoothObexTransport(mBtSocket); 740 741 BluetoothOppPreference.getInstance(mContext).setName(mDevice, mDevice.getName()); 742 743 if (V) { 744 Log.v(TAG, "Send transport message " + transport.toString()); 745 } 746 747 mSessionHandler.obtainMessage(TRANSPORT_CONNECTED, transport).sendToTarget(); 748 } catch (IOException e) { 749 Log.e(TAG, "Rfcomm socket connect exception", e); 750 // If the devices were paired before, but unpaired on the 751 // remote end, it will return an error for the auth request 752 // for the socket connection. Link keys will get exchanged 753 // again, but we need to retry. There is no good way to 754 // inform this socket asking it to retry apart from a blind 755 // delayed retry. 756 if (!mRetry && e.getMessage().equals(SOCKET_LINK_KEY_ERROR)) { 757 Message msg = 758 mSessionHandler.obtainMessage(SOCKET_ERROR_RETRY, -1, -1, mDevice); 759 mSessionHandler.sendMessageDelayed(msg, 1500); 760 } else { 761 markConnectionFailed(mBtSocket); 762 } 763 } 764 } 765 766 @Override run()767 public void run() { 768 mTimestamp = System.currentTimeMillis(); 769 if (D) { 770 Log.d(TAG, "sdp initiated = " + mSdpInitiated + " l2cChannel :" + mL2cChannel); 771 } 772 // check if sdp initiated successfully for l2cap or not. If not 773 // connect 774 // directly to rfcomm 775 if (!mSdpInitiated || mL2cChannel < 0) { 776 /* sdp failed for some reason, connect on rfcomm */ 777 Log.d(TAG, "sdp not initiated, connecting on rfcomm"); 778 connectRfcommSocket(); 779 return; 780 } 781 782 /* Reset the flag */ 783 mSdpInitiated = false; 784 785 /* Use BluetoothSocket to connect */ 786 try { 787 if (mIsInterrupted) { 788 Log.e(TAG, "btSocket connect interrupted "); 789 markConnectionFailed(mBtSocket); 790 return; 791 } else { 792 mBtSocket = mDevice.createInsecureL2capSocket(mL2cChannel); 793 } 794 } catch (IOException e1) { 795 Log.e(TAG, "L2cap socket create error", e1); 796 connectRfcommSocket(); 797 return; 798 } 799 try { 800 mBtSocket.connect(); 801 if (V) { 802 Log.v(TAG, "L2cap socket connection attempt took " + (System.currentTimeMillis() 803 - mTimestamp) + " ms"); 804 } 805 BluetoothObexTransport transport; 806 transport = new BluetoothObexTransport(mBtSocket); 807 BluetoothOppPreference.getInstance(mContext).setName(mDevice, mDevice.getName()); 808 if (V) { 809 Log.v(TAG, "Send transport message " + transport.toString()); 810 } 811 mSessionHandler.obtainMessage(TRANSPORT_CONNECTED, transport).sendToTarget(); 812 } catch (IOException e) { 813 Log.e(TAG, "L2cap socket connect exception", e); 814 try { 815 mBtSocket.close(); 816 } catch (IOException e3) { 817 Log.e(TAG, "Bluetooth socket close error ", e3); 818 } 819 connectRfcommSocket(); 820 return; 821 } 822 } 823 } 824 markConnectionFailed(BluetoothSocket s)825 private void markConnectionFailed(BluetoothSocket s) { 826 if (V) { 827 Log.v(TAG, "markConnectionFailed " + s); 828 } 829 try { 830 if (s != null) { 831 s.close(); 832 } 833 } catch (IOException e) { 834 if (V) { 835 Log.e(TAG, "Error when close socket"); 836 } 837 } 838 mSessionHandler.obtainMessage(TRANSPORT_ERROR).sendToTarget(); 839 return; 840 } 841 842 /* update a trivial field of a share to notify Provider the batch status change */ tickShareStatus(BluetoothOppShareInfo share)843 private void tickShareStatus(BluetoothOppShareInfo share) { 844 if (share == null) { 845 Log.d(TAG, "Share is null"); 846 return; 847 } 848 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId); 849 ContentValues updateValues = new ContentValues(); 850 updateValues.put(BluetoothShare.DIRECTION, share.mDirection); 851 mContext.getContentResolver().update(contentUri, updateValues, null, null); 852 } 853 854 /* 855 * Note: For outbound transfer We don't implement this method now. If later 856 * we want to support merging a later added share into an existing session, 857 * we could implement here For inbounds transfer add share means it's 858 * multiple receive in the same session, we should handle it to fill it into 859 * mSession 860 */ 861 862 /** 863 * Process when a share is added to current transfer 864 */ 865 @Override onShareAdded(int id)866 public void onShareAdded(int id) { 867 BluetoothOppShareInfo info = mBatch.getPendingShare(); 868 if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) { 869 mCurrentShare = mBatch.getPendingShare(); 870 /* 871 * TODO what if it's not auto confirmed? 872 */ 873 if (mCurrentShare != null && ( 874 mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED 875 || mCurrentShare.mConfirm 876 == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED)) { 877 /* have additional auto confirmed share to process */ 878 if (V) { 879 Log.v(TAG, "Transfer continue session for info " + mCurrentShare.mId 880 + " from batch " + mBatch.mId); 881 } 882 processCurrentShare(); 883 confirmStatusChanged(); 884 } 885 } 886 } 887 888 /* 889 * NOTE We don't implement this method now. Now delete a single share from 890 * the batch means the whole batch should be canceled. If later we want to 891 * support single cancel, we could implement here For outbound transfer, if 892 * the share is currently in transfer, cancel it For inbounds transfer, 893 * delete share means the current receiving file should be canceled. 894 */ 895 896 /** 897 * Process when a share is deleted from current transfer 898 */ 899 @Override onShareDeleted(int id)900 public void onShareDeleted(int id) { 901 902 } 903 904 /** 905 * Process when current transfer is canceled 906 */ 907 @Override onBatchCanceled()908 public void onBatchCanceled() { 909 if (V) { 910 Log.v(TAG, "Transfer on Batch canceled"); 911 } 912 913 this.stop(); 914 mBatch.mStatus = Constants.BATCH_STATUS_FINISHED; 915 } 916 cleanUp()917 private void cleanUp() { 918 synchronized (this) { 919 try { 920 if (mBluetoothReceiver != null) { 921 mContext.unregisterReceiver(mBluetoothReceiver); 922 mBluetoothReceiver = null; 923 } 924 } catch (Exception e) { 925 Log.e(TAG, "Exception:unregisterReceiver"); 926 e.printStackTrace(); 927 } 928 } 929 } 930 } 931