BluetoothOppTransfer.java revision 1ac5507790a87810061a19dadec36eb328a222ea
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 javax.obex.ObexTransport;
36
37import android.bluetooth.BluetoothAdapter;
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 BluetoothOppBatch.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 BluetoothAdapter mAdapter;
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        mAdapter = (BluetoothAdapter) 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 (!((BluetoothDevice)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.v(TAG, "Batch " + mBatch.mId + " is done");
208                            }
209                            mSession.stop();
210                        }
211                    }
212                    break;
213                /*
214                 * Handle session completed status Set batch status to
215                 * finished
216                 */
217                case BluetoothOppObexSession.MSG_SESSION_COMPLETE:
218                    BluetoothOppShareInfo info1 = (BluetoothOppShareInfo)msg.obj;
219                    if (Constants.LOGVV) {
220                        Log.v(TAG, "receive MSG_SESSION_COMPLETE for batch " + mBatch.mId);
221                    }
222                    mBatch.mStatus = Constants.BATCH_STATUS_FINISHED;
223                    /*
224                     * trigger content provider again to know batch status change
225                     */
226                    tickShareStatus(info1);
227                    break;
228
229                /* Handle the error state of an Obex session */
230                case BluetoothOppObexSession.MSG_SESSION_ERROR:
231                    if (Constants.LOGVV) {
232                        Log.v(TAG, "receive MSG_SESSION_ERROR for batch " + mBatch.mId);
233                    }
234                    BluetoothOppShareInfo info2 = (BluetoothOppShareInfo)msg.obj;
235                    mSession.stop();
236                    mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
237                    markBatchFailed(info2.mStatus);
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                    BluetoothOppShareInfo info3 = (BluetoothOppShareInfo)msg.obj;
246                    if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
247                        try {
248                            if (mTransport == null) {
249                                Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null");
250                            } else {
251                                mTransport.close();
252                            }
253                        } catch (IOException e) {
254                            Log.e(TAG, "failed to close mTransport");
255                        }
256                        if (Constants.LOGVV) {
257                            Log.v(TAG, "mTransport closed ");
258                        }
259                    }
260                    mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
261                    if (info3 != null) {
262                        markBatchFailed(info3.mStatus);
263                    } else {
264                        markBatchFailed();
265                    }
266                    tickShareStatus(mCurrentShare);
267                    break;
268
269                case BluetoothOppObexSession.MSG_CONNECT_TIMEOUT:
270                    if (Constants.LOGVV) {
271                        Log.v(TAG, "receive MSG_CONNECT_TIMEOUT for batch " + mBatch.mId);
272                    }
273                    /* for outbound transfer, the block point is BluetoothSocket.write()
274                     * The only way to unblock is to tear down lower transport
275                     * */
276                    if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
277                        try {
278                            if (mTransport == null) {
279                                Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null");
280                            } else {
281                                mTransport.close();
282                            }
283                        } catch (IOException e) {
284                            Log.e(TAG, "failed to close mTransport");
285                        }
286                        if (Constants.LOGVV) {
287                            Log.v(TAG, "mTransport closed ");
288                        }
289                    } else {
290                        /* For inbound transfer, the block point is waiting for user confirmation
291                         * we can interrupt it nicely
292                         */
293                        markShareTimeout(mCurrentShare);
294                    }
295                    break;
296            }
297        }
298    }
299
300    private void markShareTimeout(BluetoothOppShareInfo share) {
301        Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId);
302        ContentValues updateValues = new ContentValues();
303        updateValues
304                .put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_TIMEOUT);
305        mContext.getContentResolver().update(contentUri, updateValues, null, null);
306    }
307
308    private void markBatchFailed(int failReason) {
309        synchronized (this) {
310            try {
311                wait(1000);
312            } catch (InterruptedException e) {
313                if (Constants.LOGVV) {
314                    Log.v(TAG, "Interrupted waiting for markBatchFailed");
315                }
316            }
317        }
318
319        if (Constants.LOGV) {
320            Log.v(TAG, "Mark all ShareInfo in the batch as failed");
321        }
322        if (mCurrentShare != null) {
323            if (Constants.LOGVV) {
324                Log.v(TAG, "Current share has status " + mCurrentShare.mStatus);
325            }
326            if (BluetoothShare.isStatusError(mCurrentShare.mStatus)) {
327                failReason = mCurrentShare.mStatus;
328            }
329            if (mCurrentShare.mDirection == BluetoothShare.DIRECTION_INBOUND
330                    && mCurrentShare.mFilename != null) {
331                new File(mCurrentShare.mFilename).delete();
332            }
333        }
334
335        BluetoothOppShareInfo info = mBatch.getPendingShare();
336        while (info != null) {
337            if (info.mStatus < 200) {
338                info.mStatus = failReason;
339                Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mId);
340                ContentValues updateValues = new ContentValues();
341                updateValues.put(BluetoothShare.STATUS, info.mStatus);
342                /* Update un-processed outbound transfer to show some info */
343                if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
344                    BluetoothOppSendFileInfo fileInfo = null;
345                    fileInfo = BluetoothOppSendFileInfo.generateFileInfo(mContext, info.mUri,
346                            info.mMimetype);
347                    if (fileInfo.mFileName != null) {
348                        updateValues.put(BluetoothShare.FILENAME_HINT, fileInfo.mFileName);
349                        updateValues.put(BluetoothShare.TOTAL_BYTES, fileInfo.mLength);
350                        updateValues.put(BluetoothShare.MIMETYPE, fileInfo.mMimetype);
351                    }
352                } else {
353                    if (info.mStatus < 200 && info.mFilename != null) {
354                        new File(info.mFilename).delete();
355                    }
356                }
357                mContext.getContentResolver().update(contentUri, updateValues, null, null);
358                Constants.sendIntentIfCompleted(mContext, contentUri, info.mStatus);
359            }
360            info = mBatch.getPendingShare();
361        }
362
363    }
364
365    private void markBatchFailed() {
366        markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR);
367    }
368
369    /*
370     * NOTE
371     * For outbound transfer
372     * 1) Check Bluetooth status
373     * 2) Start handler thread
374     * 3) new a thread to connect to target device
375     * 3.1) Try a few times to do SDP query for target device OPUSH channel
376     * 3.2) Try a few seconds to connect to target socket
377     * 4) After BluetoothSocket is connected,create an instance of RfcommTransport
378     * 5) Create an instance of BluetoothOppClientSession
379     * 6) Start the session and process the first share in batch
380     * For inbound transfer
381     * The transfer already has session and transport setup, just start it
382     * 1) Check Bluetooth status
383     * 2) Start handler thread
384     * 3) Start the session and process the first share in batch
385     */
386    /**
387     * Start the transfer
388     */
389    public void start() {
390        /* check Bluetooth enable status */
391        /*
392         * normally it's impossible to reach here if BT is disabled. Just check
393         * for safety
394         */
395        if (!mAdapter.isEnabled()) {
396            Log.e(TAG, "Can't start transfer when Bluetooth is disabled for " + mBatch.mId);
397            markBatchFailed();
398            mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
399            return;
400        }
401
402        if (mHandlerThread == null) {
403            if (Constants.LOGVV) {
404                Log.v(TAG, "Create handler thread for batch " + mBatch.mId);
405            }
406            mHandlerThread = new HandlerThread("BtOpp Transfer Handler",
407                    Process.THREAD_PRIORITY_BACKGROUND);
408            mHandlerThread.start();
409            mSessionHandler = new EventHandler(mHandlerThread.getLooper());
410
411            if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
412                /* for outbound transfer, we do connect first */
413                startConnectSession();
414            } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
415                /*
416                 * for inbound transfer, it's already connected, so we start
417                 * OBEX session directly
418                 */
419                startObexSession();
420            }
421        }
422    }
423
424    /**
425     * Stop the transfer
426     */
427    public void stop() {
428        if (Constants.LOGVV) {
429            Log.v(TAG, "stop");
430        }
431        if (mConnectThread != null) {
432            try {
433                mConnectThread.interrupt();
434                if (Constants.LOGVV) {
435                    Log.v(TAG, "waiting for connect thread to terminate");
436                }
437                mConnectThread.join();
438            } catch (InterruptedException e) {
439                if (Constants.LOGVV) {
440                    Log.v(TAG, "Interrupted waiting for connect thread to join");
441                }
442            }
443            mConnectThread = null;
444        }
445        if (mSession != null) {
446            if (Constants.LOGVV) {
447                Log.v(TAG, "Stop mSession");
448            }
449            mSession.stop();
450        }
451        if (mHandlerThread != null) {
452            mHandlerThread.getLooper().quit();
453            mHandlerThread.interrupt();
454            mHandlerThread = null;
455        }
456    }
457
458    private void startObexSession() {
459
460        mBatch.mStatus = Constants.BATCH_STATUS_RUNNING;
461
462        mCurrentShare = mBatch.getPendingShare();
463        if (mCurrentShare == null) {
464            /*
465             * TODO catch this error
466             */
467            Log.e(TAG, "Unexpected error happened !");
468            return;
469        }
470        if (Constants.LOGVV) {
471            Log.v(TAG, "Start session for info " + mCurrentShare.mId + " for batch " + mBatch.mId);
472        }
473
474        if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
475            if (Constants.LOGVV) {
476                Log.v(TAG, "Create Client session with transport " + mTransport.toString());
477            }
478            mSession = new BluetoothOppObexClientSession(mContext, mTransport);
479        } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
480            /*
481             * For inbounds transfer, a server session should already exists
482             * before BluetoothOppTransfer is initialized. We should pass in a
483             * mSession instance.
484             */
485            if (mSession == null) {
486                /** set current share as error */
487                Log.e(TAG, "Unexpected error happened !");
488                markBatchFailed();
489                mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
490                return;
491            }
492            if (Constants.LOGVV) {
493                Log.v(TAG, "Transfer has Server session" + mSession.toString());
494            }
495        }
496
497        mSession.start(mSessionHandler);
498        processCurrentShare();
499    }
500
501    private void processCurrentShare() {
502        /* This transfer need user confirm */
503        if (Constants.LOGVV) {
504            Log.v(TAG, "processCurrentShare" + mCurrentShare.mId);
505        }
506        mSession.addShare(mCurrentShare);
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        IBluetoothCallback.Stub mDeviceCallback = new IBluetoothCallback.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 (!mAdapter.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 = mBatch.mDestination.getUuids();
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        int channel = -1;
581        if (uuids != null) {
582            for (String uuid : uuids) {
583                UUID remoteUuid = UUID.fromString(uuid);
584                if (Constants.LOGVV) {
585                    Log.v(TAG, "SDP UUID: remoteUuid = " + remoteUuid);
586                }
587                if (remoteUuid.equals(OPUSH_UUID128)) {
588                    savedUuid = uuid;
589                    isOpush = true;
590                }
591
592            }
593            if (isOpush) {
594                channel = mBatch.mDestination.getServiceChannel(savedUuid);
595                if (Constants.LOGV) {
596                    Log.v(TAG, "Get OPUSH channel " + channel + " from SDP for "
597                            + mBatch.mDestination);
598                }
599                if (channel != -1) {
600                    mConnectThread = new SocketConnectThread(mBatch.mDestination, channel);
601                    mConnectThread.start();
602                    return;
603                }
604            }
605
606        }
607
608        Message msg = mSessionHandler.obtainMessage(SDP_RESULT, channel, -1, mBatch.mDestination);
609        mSessionHandler.sendMessageDelayed(msg, 2000);
610    }
611
612    private SocketConnectThread mConnectThread;
613
614    private class SocketConnectThread extends Thread {
615        private final String host;
616        private final BluetoothDevice device;
617        private final int channel;
618
619        private boolean isConnected;
620        private long timestamp;
621        private BluetoothSocket btSocket = null;
622
623        /* create a TCP socket */
624        public SocketConnectThread(String host, int port, int dummy) {
625            super("Socket Connect Thread");
626            this.host = host;
627            this.channel = port;
628            this.device = null;
629            isConnected = false;
630        }
631
632        /* create a Rfcomm Socket */
633        public SocketConnectThread(BluetoothDevice device, int channel) {
634            super("Socket Connect Thread");
635            this.device = device;
636            this.host = null;
637            this.channel = channel;
638            isConnected = false;
639        }
640
641        public void interrupt() {
642            if (!Constants.USE_TCP_DEBUG) {
643                if (btSocket != null) {
644                    try {
645                        btSocket.close();
646                    } catch (IOException e) {
647                        Log.v(TAG, "Error when close socket");
648                    }
649                }
650            }
651        }
652
653        @Override
654        public void run() {
655
656            timestamp = System.currentTimeMillis();
657
658            if (Constants.USE_TCP_DEBUG) {
659                /* Use TCP socket to connect */
660                Socket s = new Socket();
661
662                // Try to connect for 50 seconds
663                int result = 0;
664                for (int i = 0; i < CONNECT_RETRY_TIME && result == 0; i++) {
665                    try {
666                        s.connect(new InetSocketAddress(host, channel), CONNECT_WAIT_TIMEOUT);
667                    } catch (UnknownHostException e) {
668                        Log.e(TAG, "TCP socket connect unknown host ");
669                    } catch (IOException e) {
670                        Log.e(TAG, "TCP socket connect failed ");
671                    }
672                    if (s.isConnected()) {
673                        if (Constants.LOGV) {
674                            Log.v(TAG, "TCP socket connected ");
675                        }
676                        isConnected = true;
677                        break;
678                    }
679                    if (isInterrupted()) {
680                        Log.e(TAG, "TCP socket connect interrupted ");
681                        markConnectionFailed(s);
682                        return;
683                    }
684                }
685                if (!isConnected) {
686                    Log.e(TAG, "TCP socket connect failed after 20 seconds");
687                    markConnectionFailed(s);
688                    return;
689                }
690
691                if (Constants.LOGVV) {
692                    Log.v(TAG, "TCP Socket connection attempt took "
693                            + (System.currentTimeMillis() - timestamp) + " ms");
694                }
695
696                TestTcpTransport transport;
697                transport = new TestTcpTransport(s);
698
699                if (isInterrupted()) {
700                    isConnected = false;
701                    markConnectionFailed(s);
702                    transport = null;
703                    return;
704                }
705                if (!isConnected) {
706                    transport = null;
707                    Log.e(TAG, "TCP connect session error: ");
708                    markConnectionFailed(s);
709                    return;
710                } else {
711                    if (Constants.LOGV) {
712                        Log.v(TAG, "Send transport message " + transport.toString());
713                    }
714                    mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
715                }
716            } else {
717
718                /* Use BluetoothSocket to connect */
719
720                try {
721                    btSocket = device.createInsecureRfcommSocket(channel);
722                } catch (IOException e1) {
723                    Log.e(TAG, "Rfcomm socket create error");
724                    markConnectionFailed(btSocket);
725                    return;
726                }
727                try {
728                    btSocket.connect();
729                } catch (IOException e) {
730                    Log.e(TAG, "Rfcomm socket connect exception ");
731                    markConnectionFailed(btSocket);
732                    return;
733                }
734
735                if (Constants.LOGVV) {
736                    Log.v(TAG, "Rfcomm socket connection attempt took "
737                            + (System.currentTimeMillis() - timestamp) + " ms");
738                }
739                BluetoothOppRfcommTransport transport;
740                transport = new BluetoothOppRfcommTransport(btSocket);
741
742                BluetoothOppPreference.getInstance(mContext).setChannel(device, OPUSH_UUID16,
743                        channel);
744                BluetoothOppPreference.getInstance(mContext).setName(device, device.getName());
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            try {
765                s.close();
766            } catch (IOException e) {
767                if (Constants.LOGVV) {
768                    Log.e(TAG, "Error when close socket");
769                }
770            }
771            mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
772            return;
773        }
774    };
775
776    /* update a trivial field of a share to notify Provider the batch status change */
777    private void tickShareStatus(BluetoothOppShareInfo share) {
778        Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId);
779        ContentValues updateValues = new ContentValues();
780        updateValues.put(BluetoothShare.DIRECTION, share.mDirection);
781        mContext.getContentResolver().update(contentUri, updateValues, null, null);
782    }
783
784    /*
785     * Note: For outbound transfer We don't implement this method now. If later
786     * we want to support merging a later added share into an existing session,
787     * we could implement here For inbounds transfer add share means it's
788     * multiple receive in the same session, we should handle it to fill it into
789     * mSession
790     */
791    /**
792     * Process when a share is added to current transfer
793     */
794    public void onShareAdded(int id) {
795        BluetoothOppShareInfo info = mBatch.getPendingShare();
796        if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {
797            mCurrentShare = mBatch.getPendingShare();
798            /*
799             * TODO what if it's not auto confirmed?
800             */
801            if (mCurrentShare != null
802                    && mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED) {
803                /* have additional auto confirmed share to process */
804                if (Constants.LOGVV) {
805                    Log.v(TAG, "Transfer continue session for info " + mCurrentShare.mId
806                            + " from batch " + mBatch.mId);
807                }
808                processCurrentShare();
809                setConfirmed();
810            }
811        }
812    }
813
814    /*
815     * NOTE We don't implement this method now. Now delete a single share from
816     * the batch means the whole batch should be canceled. If later we want to
817     * support single cancel, we could implement here For outbound transfer, if
818     * the share is currently in transfer, cancel it For inbounds transfer,
819     * delete share means the current receiving file should be canceled.
820     */
821    /**
822     * Process when a share is deleted from current transfer
823     */
824    public void onShareDeleted(int id) {
825
826    }
827
828    /**
829     * Process when current transfer is canceled
830     */
831    public void onBatchCanceled() {
832        if (Constants.LOGVV) {
833            Log.v(TAG, "Transfer on Batch canceled");
834        }
835
836        this.stop();
837        mBatch.mStatus = Constants.BATCH_STATUS_FINISHED;
838    }
839}
840