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