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