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 android.app.NotificationManager;
     36 import android.bluetooth.BluetoothAdapter;
     37 import android.bluetooth.BluetoothDevice;
     38 import android.bluetooth.BluetoothSocket;
     39 import android.bluetooth.BluetoothUuid;
     40 import android.bluetooth.SdpOppOpsRecord;
     41 import android.content.BroadcastReceiver;
     42 import android.content.ContentValues;
     43 import android.content.Context;
     44 import android.content.Intent;
     45 import android.content.IntentFilter;
     46 import android.net.Uri;
     47 import android.os.Handler;
     48 import android.os.HandlerThread;
     49 import android.os.Looper;
     50 import android.os.Message;
     51 import android.os.ParcelUuid;
     52 import android.os.Process;
     53 import android.util.Log;
     54 
     55 import com.android.bluetooth.BluetoothObexTransport;
     56 
     57 import java.io.File;
     58 import java.io.IOException;
     59 
     60 import javax.obex.ObexTransport;
     61 
     62 /**
     63  * This class run an actual Opp transfer session (from connect target device to
     64  * disconnect)
     65  */
     66 public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatchListener {
     67     private static final String TAG = "BtOppTransfer";
     68 
     69     private static final boolean D = Constants.DEBUG;
     70 
     71     private static final boolean V = Constants.VERBOSE;
     72 
     73     private static final int TRANSPORT_ERROR = 10;
     74 
     75     private static final int TRANSPORT_CONNECTED = 11;
     76 
     77     private static final int SOCKET_ERROR_RETRY = 13;
     78 
     79     private static final int CONNECT_WAIT_TIMEOUT = 45000;
     80 
     81     private static final int CONNECT_RETRY_TIME = 100;
     82 
     83     private static final String SOCKET_LINK_KEY_ERROR = "Invalid exchange";
     84 
     85     private Context mContext;
     86 
     87     private BluetoothAdapter mAdapter;
     88 
     89     private BluetoothDevice mDevice;
     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     private long mTimestamp;
    104 
    105     private class OppConnectionReceiver extends BroadcastReceiver {
    106         @Override
    107         public void onReceive(Context context, Intent intent) {
    108             String action = intent.getAction();
    109             if (D) {
    110                 Log.d(TAG, " Action :" + action);
    111             }
    112             if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
    113                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    114                 if (device == null || mBatch == null || mCurrentShare == null) {
    115                     Log.e(TAG, "device : " + device + " mBatch :" + mBatch + " mCurrentShare :"
    116                             + mCurrentShare);
    117                     return;
    118                 }
    119                 try {
    120                     if (V) {
    121                         Log.v(TAG, "Device :" + device + "- OPP device: " + mBatch.mDestination
    122                                 + " \n mCurrentShare.mConfirm == " + mCurrentShare.mConfirm);
    123                     }
    124                     if ((device.equals(mBatch.mDestination)) && (mCurrentShare.mConfirm
    125                             == BluetoothShare.USER_CONFIRMATION_PENDING)) {
    126                         if (V) {
    127                             Log.v(TAG, "ACTION_ACL_DISCONNECTED to be processed for batch: "
    128                                     + mBatch.mId);
    129                         }
    130                         // Remove the timeout message triggered earlier during Obex Put
    131                         mSessionHandler.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
    132                         // Now reuse the same message to clean up the session.
    133                         mSessionHandler.sendMessage(mSessionHandler.obtainMessage(
    134                                 BluetoothOppObexSession.MSG_CONNECT_TIMEOUT));
    135                     }
    136                 } catch (Exception e) {
    137                     e.printStackTrace();
    138                 }
    139             } else if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) {
    140                 ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
    141                 if (D) {
    142                     Log.d(TAG, "Received UUID: " + uuid.toString());
    143                     Log.d(TAG, "expected UUID: " + BluetoothUuid.ObexObjectPush.toString());
    144                 }
    145                 if (uuid.equals(BluetoothUuid.ObexObjectPush)) {
    146                     int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1);
    147                     Log.d(TAG, " -> status: " + status);
    148                     BluetoothDevice device =
    149                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    150                     if (mDevice == null) {
    151                         Log.w(TAG, "OPP SDP search, target device is null, ignoring result");
    152                         return;
    153                     }
    154                     if (!device.getAddress().equalsIgnoreCase(mDevice.getAddress())) {
    155                         Log.w(TAG, " OPP SDP search for wrong device, ignoring!!");
    156                         return;
    157                     }
    158                     SdpOppOpsRecord record =
    159                             intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD);
    160                     if (record == null) {
    161                         Log.w(TAG, " Invalid SDP , ignoring !!");
    162                         markConnectionFailed(null);
    163                         return;
    164                     }
    165                     mConnectThread =
    166                             new SocketConnectThread(mDevice, false, true, record.getL2capPsm());
    167                     mConnectThread.start();
    168                     mDevice = null;
    169                 }
    170             }
    171         }
    172     }
    173 
    174     private OppConnectionReceiver mBluetoothReceiver;
    175 
    176     public BluetoothOppTransfer(Context context, BluetoothOppBatch batch,
    177             BluetoothOppObexSession session) {
    178 
    179         mContext = context;
    180         mBatch = batch;
    181         mSession = session;
    182 
    183         mBatch.registerListern(this);
    184         mAdapter = BluetoothAdapter.getDefaultAdapter();
    185 
    186     }
    187 
    188     public BluetoothOppTransfer(Context context, BluetoothOppBatch batch) {
    189         this(context, batch, null);
    190     }
    191 
    192     public int getBatchId() {
    193         return mBatch.mId;
    194     }
    195 
    196     /*
    197      * Receives events from mConnectThread & mSession back in the main thread.
    198      */
    199     private class EventHandler extends Handler {
    200         EventHandler(Looper looper) {
    201             super(looper);
    202         }
    203 
    204         @Override
    205         public void handleMessage(Message msg) {
    206             switch (msg.what) {
    207                 case SOCKET_ERROR_RETRY:
    208                     mConnectThread = new SocketConnectThread((BluetoothDevice) msg.obj, true);
    209 
    210                     mConnectThread.start();
    211                     break;
    212                 case TRANSPORT_ERROR:
    213                     /*
    214                     * RFCOMM connect fail is for outbound share only! Mark batch
    215                     * failed, and all shares in batch failed
    216                     */
    217                     if (V) {
    218                         Log.v(TAG, "receive TRANSPORT_ERROR msg");
    219                     }
    220                     mConnectThread = null;
    221                     markBatchFailed(BluetoothShare.STATUS_CONNECTION_ERROR);
    222                     mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
    223 
    224                     break;
    225                 case TRANSPORT_CONNECTED:
    226                     /*
    227                     * RFCOMM connected is for outbound share only! Create
    228                     * BluetoothOppObexClientSession and start it
    229                     */
    230                     if (V) {
    231                         Log.v(TAG, "Transfer receive TRANSPORT_CONNECTED msg");
    232                     }
    233                     mConnectThread = null;
    234                     mTransport = (ObexTransport) msg.obj;
    235                     startObexSession();
    236 
    237                     break;
    238                 case BluetoothOppObexSession.MSG_SHARE_COMPLETE:
    239                     /*
    240                     * Put next share if available,or finish the transfer.
    241                     * For outbound session, call session.addShare() to send next file,
    242                     * or call session.stop().
    243                     * For inbounds session, do nothing. If there is next file to receive,it
    244                     * will be notified through onShareAdded()
    245                     */
    246                     BluetoothOppShareInfo info = (BluetoothOppShareInfo) msg.obj;
    247                     if (V) {
    248                         Log.v(TAG, "receive MSG_SHARE_COMPLETE for info " + info.mId);
    249                     }
    250                     if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
    251                         mCurrentShare = mBatch.getPendingShare();
    252 
    253                         if (mCurrentShare != null) {
    254                             /* we have additional share to process */
    255                             if (V) {
    256                                 Log.v(TAG, "continue session for info " + mCurrentShare.mId
    257                                         + " from batch " + mBatch.mId);
    258                             }
    259                             processCurrentShare();
    260                         } else {
    261                             /* for outbound transfer, all shares are processed */
    262                             if (V) {
    263                                 Log.v(TAG, "Batch " + mBatch.mId + " is done");
    264                             }
    265                             mSession.stop();
    266                         }
    267                     }
    268                     break;
    269                 case BluetoothOppObexSession.MSG_SESSION_COMPLETE:
    270                     /*
    271                     * Handle session completed status Set batch status to
    272                     * finished
    273                     */
    274                     cleanUp();
    275                     BluetoothOppShareInfo info1 = (BluetoothOppShareInfo) msg.obj;
    276                     if (V) {
    277                         Log.v(TAG, "receive MSG_SESSION_COMPLETE for batch " + mBatch.mId);
    278                     }
    279                     mBatch.mStatus = Constants.BATCH_STATUS_FINISHED;
    280                     /*
    281                      * trigger content provider again to know batch status change
    282                      */
    283                     tickShareStatus(info1);
    284                     break;
    285 
    286                 case BluetoothOppObexSession.MSG_SESSION_ERROR:
    287                     /* Handle the error state of an Obex session */
    288                     if (V) {
    289                         Log.v(TAG, "receive MSG_SESSION_ERROR for batch " + mBatch.mId);
    290                     }
    291                     cleanUp();
    292                     try {
    293                         BluetoothOppShareInfo info2 = (BluetoothOppShareInfo) msg.obj;
    294                         if (mSession != null) {
    295                             mSession.stop();
    296                         }
    297                         mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
    298                         markBatchFailed(info2.mStatus);
    299                         tickShareStatus(mCurrentShare);
    300                     } catch (Exception e) {
    301                         Log.e(TAG, "Exception while handling MSG_SESSION_ERROR");
    302                         e.printStackTrace();
    303                     }
    304                     break;
    305 
    306                 case BluetoothOppObexSession.MSG_SHARE_INTERRUPTED:
    307                     if (V) {
    308                         Log.v(TAG, "receive MSG_SHARE_INTERRUPTED for batch " + mBatch.mId);
    309                     }
    310                     BluetoothOppShareInfo info3 = (BluetoothOppShareInfo) msg.obj;
    311                     if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
    312                         try {
    313                             if (mTransport == null) {
    314                                 Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null");
    315                             } else {
    316                                 mTransport.close();
    317                             }
    318                         } catch (IOException e) {
    319                             Log.e(TAG, "failed to close mTransport");
    320                         }
    321                         if (V) {
    322                             Log.v(TAG, "mTransport closed ");
    323                         }
    324                         mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
    325                         if (info3 != null) {
    326                             markBatchFailed(info3.mStatus);
    327                         } else {
    328                             markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR);
    329                         }
    330                         tickShareStatus(mCurrentShare);
    331                     }
    332                     break;
    333 
    334                 case BluetoothOppObexSession.MSG_CONNECT_TIMEOUT:
    335                     if (V) {
    336                         Log.v(TAG, "receive MSG_CONNECT_TIMEOUT for batch " + mBatch.mId);
    337                     }
    338                     /* for outbound transfer, the block point is BluetoothSocket.write()
    339                      * The only way to unblock is to tear down lower transport
    340                      * */
    341                     if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
    342                         try {
    343                             if (mTransport == null) {
    344                                 Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null");
    345                             } else {
    346                                 mTransport.close();
    347                             }
    348                         } catch (IOException e) {
    349                             Log.e(TAG, "failed to close mTransport");
    350                         }
    351                         if (V) {
    352                             Log.v(TAG, "mTransport closed ");
    353                         }
    354                     } else {
    355                         /*
    356                          * For inbound transfer, the block point is waiting for
    357                          * user confirmation we can interrupt it nicely
    358                          */
    359 
    360                         // Remove incoming file confirm notification
    361                         NotificationManager nm = (NotificationManager) mContext.getSystemService(
    362                                 Context.NOTIFICATION_SERVICE);
    363                         nm.cancel(mCurrentShare.mId);
    364                         // Send intent to UI for timeout handling
    365                         Intent in = new Intent(BluetoothShare.USER_CONFIRMATION_TIMEOUT_ACTION);
    366                         mContext.sendBroadcast(in);
    367 
    368                         markShareTimeout(mCurrentShare);
    369                     }
    370                     break;
    371             }
    372         }
    373     }
    374 
    375     private void markShareTimeout(BluetoothOppShareInfo share) {
    376         Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId);
    377         ContentValues updateValues = new ContentValues();
    378         updateValues.put(BluetoothShare.USER_CONFIRMATION,
    379                 BluetoothShare.USER_CONFIRMATION_TIMEOUT);
    380         mContext.getContentResolver().update(contentUri, updateValues, null, null);
    381     }
    382 
    383     private void markBatchFailed(int failReason) {
    384         synchronized (this) {
    385             try {
    386                 wait(1000);
    387             } catch (InterruptedException e) {
    388                 if (V) {
    389                     Log.v(TAG, "Interrupted waiting for markBatchFailed");
    390                 }
    391             }
    392         }
    393 
    394         if (D) {
    395             Log.d(TAG, "Mark all ShareInfo in the batch as failed");
    396         }
    397         if (mCurrentShare != null) {
    398             if (V) {
    399                 Log.v(TAG, "Current share has status " + mCurrentShare.mStatus);
    400             }
    401             if (BluetoothShare.isStatusError(mCurrentShare.mStatus)) {
    402                 failReason = mCurrentShare.mStatus;
    403             }
    404             if (mCurrentShare.mDirection == BluetoothShare.DIRECTION_INBOUND
    405                     && mCurrentShare.mFilename != null) {
    406                 new File(mCurrentShare.mFilename).delete();
    407             }
    408         }
    409 
    410         BluetoothOppShareInfo info = null;
    411         if (mBatch == null) {
    412             return;
    413         }
    414         info = mBatch.getPendingShare();
    415         while (info != null) {
    416             if (info.mStatus < 200) {
    417                 info.mStatus = failReason;
    418                 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mId);
    419                 ContentValues updateValues = new ContentValues();
    420                 updateValues.put(BluetoothShare.STATUS, info.mStatus);
    421                 /* Update un-processed outbound transfer to show some info */
    422                 if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
    423                     BluetoothOppSendFileInfo fileInfo =
    424                             BluetoothOppUtility.getSendFileInfo(info.mUri);
    425                     BluetoothOppUtility.closeSendFileInfo(info.mUri);
    426                     if (fileInfo.mFileName != null) {
    427                         updateValues.put(BluetoothShare.FILENAME_HINT, fileInfo.mFileName);
    428                         updateValues.put(BluetoothShare.TOTAL_BYTES, fileInfo.mLength);
    429                         updateValues.put(BluetoothShare.MIMETYPE, fileInfo.mMimetype);
    430                     }
    431                 } else {
    432                     if (info.mStatus < 200 && info.mFilename != null) {
    433                         new File(info.mFilename).delete();
    434                     }
    435                 }
    436                 mContext.getContentResolver().update(contentUri, updateValues, null, null);
    437                 Constants.sendIntentIfCompleted(mContext, contentUri, info.mStatus);
    438             }
    439             info = mBatch.getPendingShare();
    440         }
    441 
    442     }
    443 
    444     /*
    445      * NOTE
    446      * For outbound transfer
    447      * 1) Check Bluetooth status
    448      * 2) Start handler thread
    449      * 3) new a thread to connect to target device
    450      * 3.1) Try a few times to do SDP query for target device OPUSH channel
    451      * 3.2) Try a few seconds to connect to target socket
    452      * 4) After BluetoothSocket is connected,create an instance of RfcommTransport
    453      * 5) Create an instance of BluetoothOppClientSession
    454      * 6) Start the session and process the first share in batch
    455      * For inbound transfer
    456      * The transfer already has session and transport setup, just start it
    457      * 1) Check Bluetooth status
    458      * 2) Start handler thread
    459      * 3) Start the session and process the first share in batch
    460      */
    461 
    462     /**
    463      * Start the transfer
    464      */
    465     public void start() {
    466         /* check Bluetooth enable status */
    467         /*
    468          * normally it's impossible to reach here if BT is disabled. Just check
    469          * for safety
    470          */
    471         if (!mAdapter.isEnabled()) {
    472             Log.e(TAG, "Can't start transfer when Bluetooth is disabled for " + mBatch.mId);
    473             markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR);
    474             mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
    475             return;
    476         }
    477 
    478         if (mHandlerThread == null) {
    479             if (V) {
    480                 Log.v(TAG, "Create handler thread for batch " + mBatch.mId);
    481             }
    482             mHandlerThread =
    483                     new HandlerThread("BtOpp Transfer Handler", Process.THREAD_PRIORITY_BACKGROUND);
    484             mHandlerThread.start();
    485             mSessionHandler = new EventHandler(mHandlerThread.getLooper());
    486 
    487             if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
    488                 /* for outbound transfer, we do connect first */
    489                 startConnectSession();
    490             } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
    491                 /*
    492                  * for inbound transfer, it's already connected, so we start
    493                  * OBEX session directly
    494                  */
    495                 startObexSession();
    496             }
    497         }
    498         registerConnectionreceiver();
    499     }
    500 
    501     /**
    502      * Stop the transfer
    503      */
    504     public void stop() {
    505         if (V) {
    506             Log.v(TAG, "stop");
    507         }
    508         if (mSession != null) {
    509             if (V) {
    510                 Log.v(TAG, "Stop mSession");
    511             }
    512             mSession.stop();
    513         }
    514 
    515         cleanUp();
    516         if (mConnectThread != null) {
    517             try {
    518                 mConnectThread.interrupt();
    519                 if (V) {
    520                     Log.v(TAG, "waiting for connect thread to terminate");
    521                 }
    522                 mConnectThread.join();
    523             } catch (InterruptedException e) {
    524                 if (V) {
    525                     Log.v(TAG, "Interrupted waiting for connect thread to join");
    526                 }
    527             }
    528             mConnectThread = null;
    529         }
    530         // Prevent concurrent access
    531         synchronized (this) {
    532             if (mHandlerThread != null) {
    533                 mHandlerThread.quit();
    534                 mHandlerThread.interrupt();
    535                 mHandlerThread = null;
    536             }
    537         }
    538     }
    539 
    540     private void startObexSession() {
    541 
    542         mBatch.mStatus = Constants.BATCH_STATUS_RUNNING;
    543 
    544         mCurrentShare = mBatch.getPendingShare();
    545         if (mCurrentShare == null) {
    546             /*
    547              * TODO catch this error
    548              */
    549             Log.e(TAG, "Unexpected error happened !");
    550             return;
    551         }
    552         if (V) {
    553             Log.v(TAG, "Start session for info " + mCurrentShare.mId + " for batch " + mBatch.mId);
    554         }
    555 
    556         if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
    557             if (V) {
    558                 Log.v(TAG, "Create Client session with transport " + mTransport.toString());
    559             }
    560             mSession = new BluetoothOppObexClientSession(mContext, mTransport);
    561         } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
    562             /*
    563              * For inbounds transfer, a server session should already exists
    564              * before BluetoothOppTransfer is initialized. We should pass in a
    565              * mSession instance.
    566              */
    567             if (mSession == null) {
    568                 /** set current share as error */
    569                 Log.e(TAG, "Unexpected error happened !");
    570                 markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR);
    571                 mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
    572                 return;
    573             }
    574             if (V) {
    575                 Log.v(TAG, "Transfer has Server session" + mSession.toString());
    576             }
    577         }
    578 
    579         mSession.start(mSessionHandler, mBatch.getNumShares());
    580         processCurrentShare();
    581     }
    582 
    583     private void registerConnectionreceiver() {
    584         /*
    585          * OBEX channel need to be monitored for unexpected ACL disconnection
    586          * such as Remote Battery removal
    587          */
    588         synchronized (this) {
    589             try {
    590                 if (mBluetoothReceiver == null) {
    591                     mBluetoothReceiver = new OppConnectionReceiver();
    592                     IntentFilter filter = new IntentFilter();
    593                     filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
    594                     filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
    595                     mContext.registerReceiver(mBluetoothReceiver, filter);
    596                     if (V) {
    597                         Log.v(TAG, "Registered mBluetoothReceiver");
    598                     }
    599                 }
    600             } catch (IllegalArgumentException e) {
    601                 Log.e(TAG, "mBluetoothReceiver Registered already ", e);
    602             }
    603         }
    604     }
    605 
    606     private void processCurrentShare() {
    607         /* This transfer need user confirm */
    608         if (V) {
    609             Log.v(TAG, "processCurrentShare" + mCurrentShare.mId);
    610         }
    611         mSession.addShare(mCurrentShare);
    612         if (mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED) {
    613             confirmStatusChanged();
    614         }
    615     }
    616 
    617     /**
    618      * Set transfer confirmed status. It should only be called for inbound
    619      * transfer
    620      */
    621     public void confirmStatusChanged() {
    622         /* unblock server session */
    623         final Thread notifyThread = new Thread("Server Unblock thread") {
    624             @Override
    625             public void run() {
    626                 synchronized (mSession) {
    627                     mSession.unblock();
    628                     mSession.notify();
    629                 }
    630             }
    631         };
    632         if (V) {
    633             Log.v(TAG, "confirmStatusChanged to unblock mSession" + mSession.toString());
    634         }
    635         notifyThread.start();
    636     }
    637 
    638     private void startConnectSession() {
    639         mDevice = mBatch.mDestination;
    640         if (!mBatch.mDestination.sdpSearch(BluetoothUuid.ObexObjectPush)) {
    641             if (D) {
    642                 Log.d(TAG, "SDP failed, start rfcomm connect directly");
    643             }
    644             /* update bd address as sdp could not be started */
    645             mDevice = null;
    646             /* SDP failed, start rfcomm connect directly */
    647             mConnectThread = new SocketConnectThread(mBatch.mDestination, false, false, -1);
    648             mConnectThread.start();
    649         }
    650     }
    651 
    652     private SocketConnectThread mConnectThread;
    653 
    654     private class SocketConnectThread extends Thread {
    655         private final String mHost;
    656 
    657         private final BluetoothDevice mDevice;
    658 
    659         private final int mChannel;
    660 
    661         private int mL2cChannel = 0;
    662 
    663         private boolean mIsConnected;
    664 
    665         private long mTimestamp;
    666 
    667         private BluetoothSocket mBtSocket = null;
    668 
    669         private boolean mRetry = false;
    670 
    671         private boolean mSdpInitiated = false;
    672 
    673         private boolean mIsInterrupted = false;
    674 
    675         /* create a Rfcomm/L2CAP Socket */
    676         SocketConnectThread(BluetoothDevice device, boolean retry) {
    677             super("Socket Connect Thread");
    678             this.mDevice = device;
    679             this.mHost = null;
    680             this.mChannel = -1;
    681             mIsConnected = false;
    682             mRetry = retry;
    683             mSdpInitiated = false;
    684         }
    685 
    686         /* create a Rfcomm/L2CAP Socket */
    687         SocketConnectThread(BluetoothDevice device, boolean retry, boolean sdpInitiated,
    688                 int l2capChannel) {
    689             super("Socket Connect Thread");
    690             this.mDevice = device;
    691             this.mHost = null;
    692             this.mChannel = -1;
    693             mIsConnected = false;
    694             mRetry = retry;
    695             mSdpInitiated = sdpInitiated;
    696             mL2cChannel = l2capChannel;
    697         }
    698 
    699         @Override
    700         public void interrupt() {
    701             if (D) {
    702                 Log.d(TAG, "start interrupt :" + mBtSocket);
    703             }
    704             mIsInterrupted = true;
    705             if (mBtSocket != null) {
    706                 try {
    707                     mBtSocket.close();
    708                 } catch (IOException e) {
    709                     Log.v(TAG, "Error when close socket");
    710                 }
    711             }
    712         }
    713 
    714         private void connectRfcommSocket() {
    715             if (V) {
    716                 Log.v(TAG, "connectRfcommSocket");
    717             }
    718             try {
    719                 if (mIsInterrupted) {
    720                     Log.d(TAG, "connectRfcommSocket interrupted");
    721                     markConnectionFailed(mBtSocket);
    722                     return;
    723                 }
    724                 mBtSocket = mDevice.createInsecureRfcommSocketToServiceRecord(
    725                         BluetoothUuid.ObexObjectPush.getUuid());
    726             } catch (IOException e1) {
    727                 Log.e(TAG, "Rfcomm socket create error", e1);
    728                 markConnectionFailed(mBtSocket);
    729                 return;
    730             }
    731             try {
    732                 mBtSocket.connect();
    733 
    734                 if (V) {
    735                     Log.v(TAG,
    736                             "Rfcomm socket connection attempt took " + (System.currentTimeMillis()
    737                                     - mTimestamp) + " ms");
    738                 }
    739                 BluetoothObexTransport transport;
    740                 transport = new BluetoothObexTransport(mBtSocket);
    741 
    742                 BluetoothOppPreference.getInstance(mContext).setName(mDevice, mDevice.getName());
    743 
    744                 if (V) {
    745                     Log.v(TAG, "Send transport message " + transport.toString());
    746                 }
    747 
    748                 mSessionHandler.obtainMessage(TRANSPORT_CONNECTED, transport).sendToTarget();
    749             } catch (IOException e) {
    750                 Log.e(TAG, "Rfcomm socket connect exception", e);
    751                 // If the devices were paired before, but unpaired on the
    752                 // remote end, it will return an error for the auth request
    753                 // for the socket connection. Link keys will get exchanged
    754                 // again, but we need to retry. There is no good way to
    755                 // inform this socket asking it to retry apart from a blind
    756                 // delayed retry.
    757                 if (!mRetry && e.getMessage().equals(SOCKET_LINK_KEY_ERROR)) {
    758                     Message msg =
    759                             mSessionHandler.obtainMessage(SOCKET_ERROR_RETRY, -1, -1, mDevice);
    760                     mSessionHandler.sendMessageDelayed(msg, 1500);
    761                 } else {
    762                     markConnectionFailed(mBtSocket);
    763                 }
    764             }
    765         }
    766 
    767         @Override
    768         public void run() {
    769             mTimestamp = System.currentTimeMillis();
    770             if (D) {
    771                 Log.d(TAG, "sdp initiated = " + mSdpInitiated + " l2cChannel :" + mL2cChannel);
    772             }
    773             // check if sdp initiated successfully for l2cap or not. If not
    774             // connect
    775             // directly to rfcomm
    776             if (!mSdpInitiated || mL2cChannel < 0) {
    777                 /* sdp failed for some reason, connect on rfcomm */
    778                 Log.d(TAG, "sdp not initiated, connecting on rfcomm");
    779                 connectRfcommSocket();
    780                 return;
    781             }
    782 
    783             /* Reset the flag */
    784             mSdpInitiated = false;
    785 
    786             /* Use BluetoothSocket to connect */
    787             try {
    788                 if (mIsInterrupted) {
    789                     Log.e(TAG, "btSocket connect interrupted ");
    790                     markConnectionFailed(mBtSocket);
    791                     return;
    792                 } else {
    793                     mBtSocket = mDevice.createInsecureL2capSocket(mL2cChannel);
    794                 }
    795             } catch (IOException e1) {
    796                 Log.e(TAG, "L2cap socket create error", e1);
    797                 connectRfcommSocket();
    798                 return;
    799             }
    800             try {
    801                 mBtSocket.connect();
    802                 if (V) {
    803                     Log.v(TAG, "L2cap socket connection attempt took " + (System.currentTimeMillis()
    804                             - mTimestamp) + " ms");
    805                 }
    806                 BluetoothObexTransport transport;
    807                 transport = new BluetoothObexTransport(mBtSocket);
    808                 BluetoothOppPreference.getInstance(mContext).setName(mDevice, mDevice.getName());
    809                 if (V) {
    810                     Log.v(TAG, "Send transport message " + transport.toString());
    811                 }
    812                 mSessionHandler.obtainMessage(TRANSPORT_CONNECTED, transport).sendToTarget();
    813             } catch (IOException e) {
    814                 Log.e(TAG, "L2cap socket connect exception", e);
    815                 try {
    816                     mBtSocket.close();
    817                 } catch (IOException e3) {
    818                     Log.e(TAG, "Bluetooth socket close error ", e3);
    819                 }
    820                 connectRfcommSocket();
    821                 return;
    822             }
    823         }
    824     }
    825 
    826     private void markConnectionFailed(BluetoothSocket s) {
    827         if (V) {
    828             Log.v(TAG, "markConnectionFailed " + s);
    829         }
    830         try {
    831             if (s != null) {
    832                 s.close();
    833             }
    834         } catch (IOException e) {
    835             if (V) {
    836                 Log.e(TAG, "Error when close socket");
    837             }
    838         }
    839         mSessionHandler.obtainMessage(TRANSPORT_ERROR).sendToTarget();
    840         return;
    841     }
    842 
    843     /* update a trivial field of a share to notify Provider the batch status change */
    844     private void tickShareStatus(BluetoothOppShareInfo share) {
    845         if (share == null) {
    846             Log.d(TAG, "Share is null");
    847             return;
    848         }
    849         Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId);
    850         ContentValues updateValues = new ContentValues();
    851         updateValues.put(BluetoothShare.DIRECTION, share.mDirection);
    852         mContext.getContentResolver().update(contentUri, updateValues, null, null);
    853     }
    854 
    855     /*
    856      * Note: For outbound transfer We don't implement this method now. If later
    857      * we want to support merging a later added share into an existing session,
    858      * we could implement here For inbounds transfer add share means it's
    859      * multiple receive in the same session, we should handle it to fill it into
    860      * mSession
    861      */
    862 
    863     /**
    864      * Process when a share is added to current transfer
    865      */
    866     @Override
    867     public void onShareAdded(int id) {
    868         BluetoothOppShareInfo info = mBatch.getPendingShare();
    869         if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {
    870             mCurrentShare = mBatch.getPendingShare();
    871             /*
    872              * TODO what if it's not auto confirmed?
    873              */
    874             if (mCurrentShare != null && (
    875                     mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED
    876                             || mCurrentShare.mConfirm
    877                             == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED)) {
    878                 /* have additional auto confirmed share to process */
    879                 if (V) {
    880                     Log.v(TAG, "Transfer continue session for info " + mCurrentShare.mId
    881                             + " from batch " + mBatch.mId);
    882                 }
    883                 processCurrentShare();
    884                 confirmStatusChanged();
    885             }
    886         }
    887     }
    888 
    889     /*
    890      * NOTE We don't implement this method now. Now delete a single share from
    891      * the batch means the whole batch should be canceled. If later we want to
    892      * support single cancel, we could implement here For outbound transfer, if
    893      * the share is currently in transfer, cancel it For inbounds transfer,
    894      * delete share means the current receiving file should be canceled.
    895      */
    896 
    897     /**
    898      * Process when a share is deleted from current transfer
    899      */
    900     @Override
    901     public void onShareDeleted(int id) {
    902 
    903     }
    904 
    905     /**
    906      * Process when current transfer is canceled
    907      */
    908     @Override
    909     public void onBatchCanceled() {
    910         if (V) {
    911             Log.v(TAG, "Transfer on Batch canceled");
    912         }
    913 
    914         this.stop();
    915         mBatch.mStatus = Constants.BATCH_STATUS_FINISHED;
    916     }
    917 
    918     private void cleanUp() {
    919         synchronized (this) {
    920             try {
    921                 if (mBluetoothReceiver != null) {
    922                     mContext.unregisterReceiver(mBluetoothReceiver);
    923                     mBluetoothReceiver = null;
    924                 }
    925             } catch (Exception e) {
    926                 Log.e(TAG, "Exception:unregisterReceiver");
    927                 e.printStackTrace();
    928             }
    929         }
    930     }
    931 }
    932