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