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