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