BluetoothOppTransfer.java revision 09e9cba205af60b3f42e7a4d891a7d1392e1f2a5
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 disconnect)
60 */
61public class BluetoothOppTransfer implements BluetoothOppBatchListener {
62    private static final String TAG = "BtOpp Transfer";
63
64    public static final int RFCOMM_ERROR = 10;
65
66    public static final int RFCOMM_CONNECTED = 11;
67
68    public static final int SDP_RESULT = 12;
69
70    private static final int CONNECT_WAIT_TIMEOUT = 45000;
71
72    private static final int CONNECT_RETRY_TIME = 100;
73
74    private static final short OPUSH_UUID16 = 0x1105;
75
76    public static final UUID OPUSH_UUID128 = UUID
77            .fromString("00001105-0000-1000-8000-00805f9b34fb");
78
79    private Context mContext;
80
81    private BluetoothDevice mBluetooth;
82
83    private BluetoothOppBatch mBatch;
84
85    private BluetoothOppObexSession mSession;
86
87    private BluetoothOppShareInfo mCurrentShare;
88
89    private ObexTransport mTransport;
90
91    private HandlerThread mHandlerThread;
92
93    private EventHandler mSessionHandler;
94
95    /*
96     * TODO check if we need PowerManager here
97     */
98    private PowerManager mPowerManager;
99
100    private long mTimestamp;
101
102    public BluetoothOppTransfer(Context context, PowerManager powerManager,
103            BluetoothOppBatch batch, BluetoothOppObexSession session) {
104
105        mContext = context;
106        mPowerManager = powerManager;
107        mBatch = batch;
108        mSession = session;
109
110        mBatch.registerListern(this);
111        mBluetooth = (BluetoothDevice)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
112
113    }
114
115    public BluetoothOppTransfer(Context context, PowerManager powerManager, BluetoothOppBatch batch) {
116        this(context, powerManager, batch, null);
117    }
118
119    public int getBatchId() {
120        return mBatch.mId;
121    }
122
123    /*
124     * Receives events from mConnectThread & mSession back in the main thread.
125     */
126    private class EventHandler extends Handler {
127        public EventHandler(Looper looper) {
128            super(looper);
129        }
130
131        @Override
132        public void handleMessage(Message msg) {
133            switch (msg.what) {
134                case SDP_RESULT:
135                    if (Constants.LOGVV) {
136                        Log.v(TAG, "SDP request returned " + msg.arg1 + " ("
137                                + (System.currentTimeMillis() - mTimestamp + " ms)"));
138                    }
139                    if (!((String)msg.obj).equals(mBatch.mDestination)) {
140                        return;
141                    }
142
143                    if (msg.arg1 > 0) {
144                        mConnectThread = new SocketConnectThread(mBatch.mDestination, msg.arg1);
145                        mConnectThread.start();
146                    } else {
147                        /* SDP query fail case */
148                        Log.e(TAG, "SDP query failed!");
149                        markBatchFailed(BluetoothShare.STATUS_CONNECTION_ERROR);
150                        mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
151                    }
152
153                    break;
154
155                /*
156                 * RFCOMM connect fail is for outbound share only! Mark batch
157                 * failed, and all shares in batch failed
158                 */
159                case RFCOMM_ERROR:
160                    if (Constants.LOGVV) {
161                        Log.v(TAG, "receive RFCOMM_ERROR msg");
162                    }
163                    mConnectThread = null;
164                    markBatchFailed(BluetoothShare.STATUS_CONNECTION_ERROR);
165                    mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
166
167                    break;
168                /*
169                 * RFCOMM connected is for outbound share only! Create
170                 * BluetoothOppObexClientSession and start it
171                 */
172                case RFCOMM_CONNECTED:
173                    if (Constants.LOGVV) {
174                        Log.v(TAG, "Transfer receive RFCOMM_CONNECTED msg");
175                    }
176                    mConnectThread = null;
177                    mTransport = (ObexTransport)msg.obj;
178                    startObexSession();
179
180                    break;
181                /*
182                 * Put next share if available,or finish the transfer.
183                 * For outbound session, call session.addShare() to send next file,
184                 * or call session.stop().
185                 * For inbounds session, do nothing. If there is next file to receive,it
186                 * will be notified through onShareAdded()
187                 */
188                case BluetoothOppObexSession.MSG_SHARE_COMPLETE:
189                    BluetoothOppShareInfo info = (BluetoothOppShareInfo)msg.obj;
190                    if (Constants.LOGVV) {
191                        Log.v(TAG, "receive MSG_SHARE_COMPLETE for info " + info.mId);
192                    }
193                    if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
194                        mCurrentShare = mBatch.getPendingShare();
195
196                        if (mCurrentShare != null) {
197                            /* we have additional share to process */
198                            if (Constants.LOGVV) {
199                                Log.v(TAG, "continue session for info " + mCurrentShare.mId
200                                        + " from batch " + mBatch.mId);
201                            }
202                            processCurrentShare();
203                        } else {
204                            /* for outbound transfer, all shares are processed */
205                            if (Constants.LOGVV) {
206                                Log
207                                        .v(TAG, "Outbound transfer for batch " + mBatch.mId
208                                                + " is done");
209                            }
210                            mSession.stop();
211                        }
212                    }
213                    break;
214                /*
215                 * Handle session completed status Set batch status to
216                 * finished
217                 */
218                case BluetoothOppObexSession.MSG_SESSION_COMPLETE:
219                    BluetoothOppShareInfo info1 = (BluetoothOppShareInfo)msg.obj;
220                    if (Constants.LOGVV) {
221                        Log.v(TAG, "receive MSG_SESSION_COMPLETE for batch " + mBatch.mId);
222                    }
223                    mBatch.mStatus = Constants.BATCH_STATUS_FINISHED;
224                    /*
225                     * trigger content provider again to know batch status change
226                     */
227                    tickShareStatus(info1);
228                    break;
229
230                /* Handle the error state of an Obex session */
231                case BluetoothOppObexSession.MSG_SESSION_ERROR:
232                    if (Constants.LOGVV) {
233                        Log.v(TAG, "receive MSG_SESSION_ERROR for batch " + mBatch.mId);
234                    }
235                    mSession.stop();
236                    mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
237                    markBatchFailed();
238                    tickShareStatus(mCurrentShare);
239                    break;
240
241                case BluetoothOppObexSession.MSG_SHARE_INTERRUPTED:
242                    if (Constants.LOGVV) {
243                        Log.v(TAG, "receive MSG_SHARE_INTERRUPTED for batch " + mBatch.mId);
244                    }
245                    mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
246                    markBatchFailed();
247                    tickShareStatus(mCurrentShare);
248
249                    try {
250                        if (mTransport == null) {
251                            Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null");
252                        } else {
253                            mTransport.close();
254                        }
255                    } catch (IOException e) {
256                        Log.e(TAG, "failed to close mTransport");
257                    }
258                    if (Constants.LOGVV) {
259                        Log.v(TAG, "mTransport closed ");
260                    }
261                    break;
262
263                case BluetoothOppObexSession.MSG_CONNECT_TIMEOUT:
264                    if (Constants.LOGVV) {
265                        Log.v(TAG, "receive MSG_CONNECT_TIMEOUT for batch " + mBatch.mId);
266                    }
267                    /* for outbound transfer, the block point is BluetoothSocket.write()
268                     * The only way to unblock is to tear down lower transport
269                     * */
270                    if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
271                        try {
272                            if (mTransport == null) {
273                                Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null");
274                            } else {
275                                mTransport.close();
276                            }
277                        } catch (IOException e) {
278                            Log.e(TAG, "failed to close mTransport");
279                        }
280                        if (Constants.LOGVV) {
281                            Log.v(TAG, "mTransport closed ");
282                        }
283                    } else {
284                        /* For inbound transfer, the block point is waiting for user confirmation
285                         * we can interrupt it nicely
286                         */
287                        markShareTimeout(mCurrentShare);
288                    }
289                    break;
290            }
291        }
292    }
293
294    private void markShareTimeout(BluetoothOppShareInfo share) {
295        Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId);
296        ContentValues updateValues = new ContentValues();
297        updateValues
298                .put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_TIMEOUT);
299        mContext.getContentResolver().update(contentUri, updateValues, null, null);
300    }
301
302    private void markBatchFailed(int failReason) {
303
304        synchronized (this) {
305            try {
306                wait(500);
307            } catch (InterruptedException e) {
308                if (Constants.LOGVV) {
309                    Log.v(TAG, "Interrupted waiting for markBatchFailed");
310                }
311            }
312        }
313
314        if (Constants.LOGV) {
315            Log.v(TAG, "Mark all ShareInfo in the batch as failed");
316        }
317        if (mCurrentShare != null) {
318            if (Constants.LOGV) {
319                Log.v(TAG, "Current share has status " + mCurrentShare.mStatus);
320            }
321            if (BluetoothShare.isStatusError(mCurrentShare.mStatus)) {
322                failReason = mCurrentShare.mStatus;
323            }
324            if (mCurrentShare.mDirection == BluetoothShare.DIRECTION_INBOUND
325                    && mCurrentShare.mFilename != null) {
326                new File(mCurrentShare.mFilename).delete();
327            }
328        }
329
330        BluetoothOppShareInfo info = mBatch.getPendingShare();
331        while (info != null) {
332            if (info.mStatus < 200) {
333                info.mStatus = failReason;
334                Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mId);
335                ContentValues updateValues = new ContentValues();
336                updateValues.put(BluetoothShare.STATUS, info.mStatus);
337                /* Update un-processed outbound transfer to show some info */
338                if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
339                    BluetoothOppSendFileInfo fileInfo = null;
340                    fileInfo = BluetoothOppSendFileInfo.generateFileInfo(mContext, info.mUri);
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 (!mBluetooth.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 (Constants.LOGVV) {
398                Log.v(TAG, "Create handler thread for batch " + mBatch.mId);
399            }
400            mHandlerThread = new HandlerThread("BtOpp Transfer Handler",
401                    Process.THREAD_PRIORITY_BACKGROUND);
402            mHandlerThread.start();
403            mSessionHandler = new EventHandler(mHandlerThread.getLooper());
404
405            if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
406                /* for outbound transfer, we do connect first */
407                startConnectSession();
408            } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
409                /*
410                 * for inbound transfer, it's already connected, so we start
411                 * OBEX session directly
412                 */
413                startObexSession();
414            }
415        }
416    }
417
418    /**
419     * Stop the transfer
420     */
421    public void stop() {
422        if (Constants.LOGVV) {
423            Log.v(TAG, "stop");
424        }
425        if (mConnectThread != null) {
426            try {
427                mConnectThread.interrupt();
428                if (Constants.LOGVV) {
429                    Log.v(TAG, "waiting for connect thread to terminate");
430                }
431                mConnectThread.join();
432            } catch (InterruptedException e) {
433                if (Constants.LOGVV) {
434                    Log.v(TAG, "Interrupted waiting for connect thread to join");
435                }
436            }
437            mConnectThread = null;
438        }
439        if (mSession != null) {
440            if (Constants.LOGVV) {
441                Log.v(TAG, "Stop mSession");
442            }
443            mSession.stop();
444        }
445        if (mHandlerThread != null) {
446            mHandlerThread.getLooper().quit();
447            mHandlerThread.interrupt();
448            mHandlerThread = null;
449        }
450    }
451
452    private void startObexSession() {
453
454        mBatch.mStatus = Constants.BATCH_STATUS_RUNNING;
455
456        mCurrentShare = mBatch.getPendingShare();
457        if (mCurrentShare == null) {
458            /*
459             * TODO catch this error
460             */
461            Log.e(TAG, "Unexpected error happened !");
462            return;
463        }
464        if (Constants.LOGVV) {
465            Log.v(TAG, "Transfer start session for info " + mCurrentShare.mId + " for batch "
466                    + mBatch.mId);
467        }
468
469        if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
470            if (Constants.LOGVV) {
471                Log
472                        .v(TAG, "Transfer create Client session with transport "
473                                + mTransport.toString());
474            }
475
476            mSession = (BluetoothOppObexSession)new BluetoothOppObexClientSession(mContext,
477                    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