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