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