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