Home | History | Annotate | Download | only in opp
      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 javax.obex.ObexTransport;
     36 
     37 import com.android.bluetooth.BluetoothObexTransport;
     38 
     39 import android.app.NotificationManager;
     40 import android.bluetooth.BluetoothAdapter;
     41 import android.bluetooth.BluetoothDevice;
     42 import android.bluetooth.BluetoothSocket;
     43 import android.bluetooth.BluetoothUuid;
     44 import android.os.ParcelUuid;
     45 import android.content.BroadcastReceiver;
     46 import android.content.ContentValues;
     47 import android.content.Context;
     48 import android.content.Intent;
     49 import android.content.IntentFilter;
     50 import android.net.Uri;
     51 import android.os.Handler;
     52 import android.os.HandlerThread;
     53 import android.os.Looper;
     54 import android.os.Message;
     55 import android.os.Parcelable;
     56 import android.os.PowerManager;
     57 import android.os.Process;
     58 import android.util.Log;
     59 
     60 import java.io.File;
     61 import java.io.IOException;
     62 import java.net.InetSocketAddress;
     63 import java.net.Socket;
     64 import java.net.UnknownHostException;
     65 
     66 /**
     67  * This class run an actual Opp transfer session (from connect target device to
     68  * disconnect)
     69  */
     70 public 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