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