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