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