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