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