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