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