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