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