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