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