BluetoothOppTransfer.java revision 68ea2a43de2e7cee20c03332ed9f5ff004e42884
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.bluetooth.BluetoothUuid;
41import android.bluetooth.ParcelUuid;
42import android.content.ContentValues;
43import android.content.Context;
44import android.net.Uri;
45import android.os.Handler;
46import android.os.HandlerThread;
47import android.os.Looper;
48import android.os.Message;
49import android.os.PowerManager;
50import android.os.Process;
51import android.util.Log;
52
53import java.io.File;
54import java.io.IOException;
55import java.net.InetSocketAddress;
56import java.net.Socket;
57import java.net.UnknownHostException;
58
59/**
60 * This class run an actual Opp transfer session (from connect target device to
61 * disconnect)
62 */
63public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatchListener {
64    private static final String TAG = "BtOppTransfer";
65
66    private static final boolean D = Constants.DEBUG;
67
68    private static final boolean V = Constants.VERBOSE;
69
70    public static final int RFCOMM_ERROR = 10;
71
72    public static final int RFCOMM_CONNECTED = 11;
73
74    public static final int SDP_RESULT = 12;
75
76    private static final int CONNECT_WAIT_TIMEOUT = 45000;
77
78    private static final int CONNECT_RETRY_TIME = 100;
79
80    private static final short OPUSH_UUID16 = 0x1105;
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, info.mDestination);
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        ParcelUuid[] uuids = mBatch.mDestination.getUuids();
524        if (V) Log.v(TAG, "After call getRemoteUuids for address " + mBatch.mDestination);
525        int channel = -1;
526        if (uuids != null) {
527            if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
528                channel = mBatch.mDestination.getServiceChannel(BluetoothUuid.ObexObjectPush);
529                if (D) Log.d(TAG, "Get OPUSH channel " + channel + " from SDP for " +
530                        mBatch.mDestination);
531                if (channel != -1) {
532                    mConnectThread = new SocketConnectThread(mBatch.mDestination, channel);
533                    mConnectThread.start();
534                    return;
535                }
536            } else {
537                if (D) Log.d(TAG, "Remote device doesn't support OPP");
538            }
539        }
540
541        Message msg = mSessionHandler.obtainMessage(SDP_RESULT, channel, -1, mBatch.mDestination);
542        mSessionHandler.sendMessageDelayed(msg, 2000);
543    }
544
545    private SocketConnectThread mConnectThread;
546
547    private class SocketConnectThread extends Thread {
548        private final String host;
549
550        private final BluetoothDevice device;
551
552        private final int channel;
553
554        private boolean isConnected;
555
556        private long timestamp;
557
558        private BluetoothSocket btSocket = null;
559
560        /* create a TCP socket */
561        public SocketConnectThread(String host, int port, int dummy) {
562            super("Socket Connect Thread");
563            this.host = host;
564            this.channel = port;
565            this.device = null;
566            isConnected = false;
567        }
568
569        /* create a Rfcomm Socket */
570        public SocketConnectThread(BluetoothDevice device, int channel) {
571            super("Socket Connect Thread");
572            this.device = device;
573            this.host = null;
574            this.channel = channel;
575            isConnected = false;
576        }
577
578        public void interrupt() {
579            if (!Constants.USE_TCP_DEBUG) {
580                if (btSocket != null) {
581                    try {
582                        btSocket.close();
583                    } catch (IOException e) {
584                        Log.v(TAG, "Error when close socket");
585                    }
586                }
587            }
588        }
589
590        @Override
591        public void run() {
592
593            timestamp = System.currentTimeMillis();
594
595            if (Constants.USE_TCP_DEBUG) {
596                /* Use TCP socket to connect */
597                Socket s = new Socket();
598
599                // Try to connect for 50 seconds
600                int result = 0;
601                for (int i = 0; i < CONNECT_RETRY_TIME && result == 0; i++) {
602                    try {
603                        s.connect(new InetSocketAddress(host, channel), CONNECT_WAIT_TIMEOUT);
604                    } catch (UnknownHostException e) {
605                        Log.e(TAG, "TCP socket connect unknown host ");
606                    } catch (IOException e) {
607                        Log.e(TAG, "TCP socket connect failed ");
608                    }
609                    if (s.isConnected()) {
610                        if (D) Log.d(TAG, "TCP socket connected ");
611                        isConnected = true;
612                        break;
613                    }
614                    if (isInterrupted()) {
615                        Log.e(TAG, "TCP socket connect interrupted ");
616                        markConnectionFailed(s);
617                        return;
618                    }
619                }
620                if (!isConnected) {
621                    Log.e(TAG, "TCP socket connect failed after 20 seconds");
622                    markConnectionFailed(s);
623                    return;
624                }
625
626                if (V) Log.v(TAG, "TCP Socket connection attempt took " +
627                        (System.currentTimeMillis() - timestamp) + " ms");
628
629                TestTcpTransport transport;
630                transport = new TestTcpTransport(s);
631
632                if (isInterrupted()) {
633                    isConnected = false;
634                    markConnectionFailed(s);
635                    transport = null;
636                    return;
637                }
638                if (!isConnected) {
639                    transport = null;
640                    Log.e(TAG, "TCP connect session error: ");
641                    markConnectionFailed(s);
642                    return;
643                } else {
644                    if (D) Log.d(TAG, "Send transport message " + transport.toString());
645                    mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
646                }
647            } else {
648
649                /* Use BluetoothSocket to connect */
650
651                try {
652                    btSocket = device.createInsecureRfcommSocket(channel);
653                } catch (IOException e1) {
654                    Log.e(TAG, "Rfcomm socket create error");
655                    markConnectionFailed(btSocket);
656                    return;
657                }
658                try {
659                    btSocket.connect();
660
661                    if (V) Log.v(TAG, "Rfcomm socket connection attempt took " +
662                            (System.currentTimeMillis() - timestamp) + " ms");
663                    BluetoothOppRfcommTransport transport;
664                    transport = new BluetoothOppRfcommTransport(btSocket);
665
666                    BluetoothOppPreference.getInstance(mContext).setChannel(device, OPUSH_UUID16,
667                            channel);
668                    BluetoothOppPreference.getInstance(mContext).setName(device, device.getName());
669
670                    if (V) Log.v(TAG, "Send transport message " + transport.toString());
671
672                    mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
673                } catch (IOException e) {
674                    Log.e(TAG, "Rfcomm socket connect exception ");
675                    BluetoothOppPreference.getInstance(mContext)
676                            .removeChannel(device, OPUSH_UUID16);
677                    markConnectionFailed(btSocket);
678                    return;
679                }
680            }
681        }
682
683        private void markConnectionFailed(Socket s) {
684            try {
685                s.close();
686            } catch (IOException e) {
687                Log.e(TAG, "TCP socket close error");
688            }
689            mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
690        }
691
692        private void markConnectionFailed(BluetoothSocket s) {
693            try {
694                s.close();
695            } catch (IOException e) {
696                if (V) Log.e(TAG, "Error when close socket");
697            }
698            mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
699            return;
700        }
701    };
702
703    /* update a trivial field of a share to notify Provider the batch status change */
704    private void tickShareStatus(BluetoothOppShareInfo share) {
705        Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId);
706        ContentValues updateValues = new ContentValues();
707        updateValues.put(BluetoothShare.DIRECTION, share.mDirection);
708        mContext.getContentResolver().update(contentUri, updateValues, null, null);
709    }
710
711    /*
712     * Note: For outbound transfer We don't implement this method now. If later
713     * we want to support merging a later added share into an existing session,
714     * we could implement here For inbounds transfer add share means it's
715     * multiple receive in the same session, we should handle it to fill it into
716     * mSession
717     */
718    /**
719     * Process when a share is added to current transfer
720     */
721    public void onShareAdded(int id) {
722        BluetoothOppShareInfo info = mBatch.getPendingShare();
723        if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {
724            mCurrentShare = mBatch.getPendingShare();
725            /*
726             * TODO what if it's not auto confirmed?
727             */
728            if (mCurrentShare != null
729                    && mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED) {
730                /* have additional auto confirmed share to process */
731                if (V) Log.v(TAG, "Transfer continue session for info " + mCurrentShare.mId +
732                        " from batch " + mBatch.mId);
733                processCurrentShare();
734                setConfirmed();
735            }
736        }
737    }
738
739    /*
740     * NOTE We don't implement this method now. Now delete a single share from
741     * the batch means the whole batch should be canceled. If later we want to
742     * support single cancel, we could implement here For outbound transfer, if
743     * the share is currently in transfer, cancel it For inbounds transfer,
744     * delete share means the current receiving file should be canceled.
745     */
746    /**
747     * Process when a share is deleted from current transfer
748     */
749    public void onShareDeleted(int id) {
750
751    }
752
753    /**
754     * Process when current transfer is canceled
755     */
756    public void onBatchCanceled() {
757        if (V) Log.v(TAG, "Transfer on Batch canceled");
758
759        this.stop();
760        mBatch.mStatus = Constants.BATCH_STATUS_FINISHED;
761    }
762}
763