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