BluetoothOppTransfer.java revision eb7b90f5b93db1230a5b64caa3d8d05a642e33a6
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 com.android.bluetooth.BluetoothObexTransport;
38
39import android.app.NotificationManager;
40import android.bluetooth.BluetoothAdapter;
41import android.bluetooth.BluetoothDevice;
42import android.bluetooth.BluetoothSocket;
43import android.bluetooth.BluetoothUuid;
44import android.content.ContentValues;
45import android.content.Context;
46import android.content.Intent;
47import android.net.Uri;
48import android.os.Handler;
49import android.os.HandlerThread;
50import android.os.Looper;
51import android.os.Message;
52import android.os.PowerManager;
53import android.os.Process;
54import android.util.Log;
55
56import java.io.File;
57import java.io.IOException;
58import java.net.InetSocketAddress;
59import java.net.Socket;
60import java.net.UnknownHostException;
61
62/**
63 * This class run an actual Opp transfer session (from connect target device to
64 * disconnect)
65 */
66public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatchListener {
67    private static final String TAG = "BtOppTransfer";
68
69    private static final boolean D = Constants.DEBUG;
70
71    private static final boolean V = Constants.VERBOSE;
72
73    private static final int RFCOMM_ERROR = 10;
74
75    private static final int RFCOMM_CONNECTED = 11;
76
77    private static final int SOCKET_ERROR_RETRY = 13;
78
79    private static final int CONNECT_WAIT_TIMEOUT = 45000;
80
81    private static final int CONNECT_RETRY_TIME = 100;
82
83    private static final short OPUSH_UUID16 = 0x1105;
84
85    private static final String SOCKET_LINK_KEY_ERROR = "Invalid exchange";
86
87    private Context mContext;
88
89    private BluetoothAdapter mAdapter;
90
91    private BluetoothOppBatch mBatch;
92
93    private BluetoothOppObexSession mSession;
94
95    private BluetoothOppShareInfo mCurrentShare;
96
97    private ObexTransport mTransport;
98
99    private HandlerThread mHandlerThread;
100
101    private EventHandler mSessionHandler;
102
103    private long mTimestamp;
104
105    public BluetoothOppTransfer(Context context, PowerManager powerManager,
106            BluetoothOppBatch batch, BluetoothOppObexSession session) {
107
108        mContext = context;
109        mBatch = batch;
110        mSession = session;
111
112        mBatch.registerListern(this);
113        mAdapter = BluetoothAdapter.getDefaultAdapter();
114
115    }
116
117    public BluetoothOppTransfer(Context context, PowerManager powerManager, BluetoothOppBatch batch) {
118        this(context, powerManager, batch, null);
119    }
120
121    public int getBatchId() {
122        return mBatch.mId;
123    }
124
125    /*
126     * Receives events from mConnectThread & mSession back in the main thread.
127     */
128    private class EventHandler extends Handler {
129        public EventHandler(Looper looper) {
130            super(looper);
131        }
132
133        @Override
134        public void handleMessage(Message msg) {
135            switch (msg.what) {
136                case SOCKET_ERROR_RETRY:
137                    mConnectThread = new
138                        SocketConnectThread((BluetoothDevice)msg.obj, true);
139
140                    mConnectThread.start();
141                    break;
142                case RFCOMM_ERROR:
143                    /*
144                    * RFCOMM connect fail is for outbound share only! Mark batch
145                    * failed, and all shares in batch failed
146                    */
147                    if (V) Log.v(TAG, "receive RFCOMM_ERROR msg");
148                    mConnectThread = null;
149                    markBatchFailed(BluetoothShare.STATUS_CONNECTION_ERROR);
150                    mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
151
152                    break;
153                case RFCOMM_CONNECTED:
154                    /*
155                    * RFCOMM connected is for outbound share only! Create
156                    * BluetoothOppObexClientSession and start it
157                    */
158                    if (V) Log.v(TAG, "Transfer receive RFCOMM_CONNECTED msg");
159                    mConnectThread = null;
160                    mTransport = (ObexTransport)msg.obj;
161                    startObexSession();
162
163                    break;
164                case BluetoothOppObexSession.MSG_SHARE_COMPLETE:
165                    /*
166                    * Put next share if available,or finish the transfer.
167                    * For outbound session, call session.addShare() to send next file,
168                    * or call session.stop().
169                    * For inbounds session, do nothing. If there is next file to receive,it
170                    * will be notified through onShareAdded()
171                    */
172                    BluetoothOppShareInfo info = (BluetoothOppShareInfo)msg.obj;
173                    if (V) Log.v(TAG, "receive MSG_SHARE_COMPLETE for info " + info.mId);
174                    if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
175                        mCurrentShare = mBatch.getPendingShare();
176
177                        if (mCurrentShare != null) {
178                            /* we have additional share to process */
179                            if (V) Log.v(TAG, "continue session for info " + mCurrentShare.mId +
180                                    " from batch " + mBatch.mId);
181                            processCurrentShare();
182                        } else {
183                            /* for outbound transfer, all shares are processed */
184                            if (V) Log.v(TAG, "Batch " + mBatch.mId + " is done");
185                            mSession.stop();
186                        }
187                    }
188                    break;
189                case BluetoothOppObexSession.MSG_SESSION_COMPLETE:
190                    /*
191                    * Handle session completed status Set batch status to
192                    * finished
193                    */
194                    BluetoothOppShareInfo info1 = (BluetoothOppShareInfo)msg.obj;
195                    if (V) Log.v(TAG, "receive MSG_SESSION_COMPLETE for batch " + mBatch.mId);
196                    mBatch.mStatus = Constants.BATCH_STATUS_FINISHED;
197                    /*
198                     * trigger content provider again to know batch status change
199                     */
200                    tickShareStatus(info1);
201                    break;
202
203                case BluetoothOppObexSession.MSG_SESSION_ERROR:
204                    /* Handle the error state of an Obex session */
205                    if (V) Log.v(TAG, "receive MSG_SESSION_ERROR for batch " + mBatch.mId);
206                    BluetoothOppShareInfo info2 = (BluetoothOppShareInfo)msg.obj;
207                    mSession.stop();
208                    mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
209                    markBatchFailed(info2.mStatus);
210                    tickShareStatus(mCurrentShare);
211                    break;
212
213                case BluetoothOppObexSession.MSG_SHARE_INTERRUPTED:
214                    if (V) Log.v(TAG, "receive MSG_SHARE_INTERRUPTED for batch " + mBatch.mId);
215                    BluetoothOppShareInfo info3 = (BluetoothOppShareInfo)msg.obj;
216                    if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
217                        try {
218                            if (mTransport == null) {
219                                Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null");
220                            } else {
221                                mTransport.close();
222                            }
223                        } catch (IOException e) {
224                            Log.e(TAG, "failed to close mTransport");
225                        }
226                        if (V) Log.v(TAG, "mTransport closed ");
227                        mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
228                        if (info3 != null) {
229                            markBatchFailed(info3.mStatus);
230                        } else {
231                            markBatchFailed();
232                        }
233                        tickShareStatus(mCurrentShare);
234                    }
235                    break;
236
237                case BluetoothOppObexSession.MSG_CONNECT_TIMEOUT:
238                    if (V) Log.v(TAG, "receive MSG_CONNECT_TIMEOUT for batch " + mBatch.mId);
239                    /* for outbound transfer, the block point is BluetoothSocket.write()
240                     * The only way to unblock is to tear down lower transport
241                     * */
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                    } else {
254                        /*
255                         * For inbound transfer, the block point is waiting for
256                         * user confirmation we can interrupt it nicely
257                         */
258
259                        // Remove incoming file confirm notification
260                        NotificationManager nm = (NotificationManager)mContext
261                                .getSystemService(Context.NOTIFICATION_SERVICE);
262                        nm.cancel(mCurrentShare.mId);
263                        // Send intent to UI for timeout handling
264                        Intent in = new Intent(BluetoothShare.USER_CONFIRMATION_TIMEOUT_ACTION);
265                        mContext.sendBroadcast(in);
266
267                        markShareTimeout(mCurrentShare);
268                    }
269                    break;
270            }
271        }
272    }
273
274    private void markShareTimeout(BluetoothOppShareInfo share) {
275        Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId);
276        ContentValues updateValues = new ContentValues();
277        updateValues
278                .put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_TIMEOUT);
279        mContext.getContentResolver().update(contentUri, updateValues, null, null);
280    }
281
282    private void markBatchFailed(int failReason) {
283        synchronized (this) {
284            try {
285                wait(1000);
286            } catch (InterruptedException e) {
287                if (V) Log.v(TAG, "Interrupted waiting for markBatchFailed");
288            }
289        }
290
291        if (D) Log.d(TAG, "Mark all ShareInfo in the batch as failed");
292        if (mCurrentShare != null) {
293            if (V) Log.v(TAG, "Current share has status " + mCurrentShare.mStatus);
294            if (BluetoothShare.isStatusError(mCurrentShare.mStatus)) {
295                failReason = mCurrentShare.mStatus;
296            }
297            if (mCurrentShare.mDirection == BluetoothShare.DIRECTION_INBOUND
298                    && mCurrentShare.mFilename != null) {
299                new File(mCurrentShare.mFilename).delete();
300            }
301        }
302
303        BluetoothOppShareInfo info = null;
304        if (mBatch == null) {
305            return;
306        }
307        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
317                            = BluetoothOppUtility.getSendFileInfo(info.mUri);
318                    BluetoothOppUtility.closeSendFileInfo(info.mUri);
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, mBatch.getNumShares());
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        if (mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED) {
463            confirmStatusChanged();
464        }
465    }
466
467    /**
468     * Set transfer confirmed status. It should only be called for inbound
469     * transfer
470     */
471    public void confirmStatusChanged() {
472        /* unblock server session */
473        final Thread notifyThread = new Thread("Server Unblock thread") {
474            public void run() {
475                synchronized (mSession) {
476                    mSession.unblock();
477                    mSession.notify();
478                }
479            }
480        };
481        if (V) Log.v(TAG, "confirmStatusChanged to unblock mSession" + mSession.toString());
482        notifyThread.start();
483    }
484
485    private void startConnectSession() {
486
487        if (Constants.USE_TCP_DEBUG) {
488            mConnectThread = new SocketConnectThread("localhost", Constants.TCP_DEBUG_PORT, 0);
489            mConnectThread.start();
490        } else {
491            mConnectThread = new SocketConnectThread(mBatch.mDestination,false);
492            mConnectThread.start();
493        }
494    }
495
496    private SocketConnectThread mConnectThread;
497
498    private class SocketConnectThread extends Thread {
499        private final String host;
500
501        private final BluetoothDevice device;
502
503        private final int channel;
504
505        private boolean isConnected;
506
507        private long timestamp;
508
509        private BluetoothSocket btSocket = null;
510
511        private boolean mRetry = false;
512
513        /* create a TCP socket */
514        public SocketConnectThread(String host, int port, int dummy) {
515            super("Socket Connect Thread");
516            this.host = host;
517            this.channel = port;
518            this.device = null;
519            isConnected = false;
520        }
521
522        /* create a Rfcomm Socket */
523        public SocketConnectThread(BluetoothDevice device, int channel, boolean
524                retry) {
525            super("Socket Connect Thread");
526            this.device = device;
527            this.host = null;
528            this.channel = channel;
529            isConnected = false;
530            mRetry = retry;
531        }
532
533        /* create a Rfcomm Socket */
534        public SocketConnectThread(BluetoothDevice device, boolean
535                retry) {
536            super("Socket Connect Thread");
537            this.device = device;
538            this.host = null;
539            this.channel = -1;
540            isConnected = false;
541            mRetry = retry;
542        }
543
544        public void interrupt() {
545            if (!Constants.USE_TCP_DEBUG) {
546                if (btSocket != null) {
547                    try {
548                        btSocket.close();
549                    } catch (IOException e) {
550                        Log.v(TAG, "Error when close socket");
551                    }
552                }
553            }
554        }
555
556        @Override
557        public void run() {
558
559            timestamp = System.currentTimeMillis();
560
561            if (Constants.USE_TCP_DEBUG) {
562                /* Use TCP socket to connect */
563                Socket s = new Socket();
564
565                // Try to connect for 50 seconds
566                int result = 0;
567                for (int i = 0; i < CONNECT_RETRY_TIME && result == 0; i++) {
568                    try {
569                        s.connect(new InetSocketAddress(host, channel), CONNECT_WAIT_TIMEOUT);
570                    } catch (UnknownHostException e) {
571                        Log.e(TAG, "TCP socket connect unknown host ");
572                    } catch (IOException e) {
573                        Log.e(TAG, "TCP socket connect failed ");
574                    }
575                    if (s.isConnected()) {
576                        if (D) Log.d(TAG, "TCP socket connected ");
577                        isConnected = true;
578                        break;
579                    }
580                    if (isInterrupted()) {
581                        Log.e(TAG, "TCP socket connect interrupted ");
582                        markConnectionFailed(s);
583                        return;
584                    }
585                }
586                if (!isConnected) {
587                    Log.e(TAG, "TCP socket connect failed after 20 seconds");
588                    markConnectionFailed(s);
589                    return;
590                }
591
592                if (V) Log.v(TAG, "TCP Socket connection attempt took " +
593                        (System.currentTimeMillis() - timestamp) + " ms");
594
595                TestTcpTransport transport;
596                transport = new TestTcpTransport(s);
597
598                if (isInterrupted()) {
599                    isConnected = false;
600                    markConnectionFailed(s);
601                    transport = null;
602                    return;
603                }
604                if (!isConnected) {
605                    transport = null;
606                    Log.e(TAG, "TCP connect session error: ");
607                    markConnectionFailed(s);
608                    return;
609                } else {
610                    if (D) Log.d(TAG, "Send transport message " + transport.toString());
611                    mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
612                }
613            } else {
614
615                /* Use BluetoothSocket to connect */
616
617                try {
618                    btSocket = device.createInsecureRfcommSocketToServiceRecord(BluetoothUuid.ObexObjectPush.getUuid());
619                } catch (IOException e1) {
620                    Log.e(TAG, "Rfcomm socket create error",e1);
621                    markConnectionFailed(btSocket);
622                    return;
623                }
624                try {
625                    btSocket.connect();
626
627                    if (V) Log.v(TAG, "Rfcomm socket connection attempt took " +
628                            (System.currentTimeMillis() - timestamp) + " ms");
629                    BluetoothObexTransport transport;
630                    transport = new BluetoothObexTransport(btSocket);
631
632                    BluetoothOppPreference.getInstance(mContext).setName(device, device.getName());
633
634                    if (V) Log.v(TAG, "Send transport message " + transport.toString());
635
636                    mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
637                } catch (IOException e) {
638                    Log.e(TAG, "Rfcomm socket connect exception",e);
639                    // If the devices were paired before, but unpaired on the
640                    // remote end, it will return an error for the auth request
641                    // for the socket connection. Link keys will get exchanged
642                    // again, but we need to retry. There is no good way to
643                    // inform this socket asking it to retry apart from a blind
644                    // delayed retry.
645                    if (!mRetry && e.getMessage().equals(SOCKET_LINK_KEY_ERROR)) {
646                        Message msg = mSessionHandler.obtainMessage(SOCKET_ERROR_RETRY,-1,-1,device);
647                        mSessionHandler.sendMessageDelayed(msg, 1500);
648                    } else {
649                        markConnectionFailed(btSocket);
650                    }
651                }
652            }
653        }
654
655        private void markConnectionFailed(Socket s) {
656            try {
657                s.close();
658            } catch (IOException e) {
659                Log.e(TAG, "TCP socket close error");
660            }
661            mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
662        }
663
664        private void markConnectionFailed(BluetoothSocket s) {
665            try {
666                s.close();
667            } catch (IOException e) {
668                if (V) Log.e(TAG, "Error when close socket");
669            }
670            mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
671            return;
672        }
673    };
674
675    /* update a trivial field of a share to notify Provider the batch status change */
676    private void tickShareStatus(BluetoothOppShareInfo share) {
677        if (share == null) {
678            Log.d(TAG,"Share is null");
679            return;
680        }
681        Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId);
682        ContentValues updateValues = new ContentValues();
683        updateValues.put(BluetoothShare.DIRECTION, share.mDirection);
684        mContext.getContentResolver().update(contentUri, updateValues, null, null);
685    }
686
687    /*
688     * Note: For outbound transfer We don't implement this method now. If later
689     * we want to support merging a later added share into an existing session,
690     * we could implement here For inbounds transfer add share means it's
691     * multiple receive in the same session, we should handle it to fill it into
692     * mSession
693     */
694    /**
695     * Process when a share is added to current transfer
696     */
697    public void onShareAdded(int id) {
698        BluetoothOppShareInfo info = mBatch.getPendingShare();
699        if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {
700            mCurrentShare = mBatch.getPendingShare();
701            /*
702             * TODO what if it's not auto confirmed?
703             */
704            if (mCurrentShare != null &&
705                    (mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED ||
706                     mCurrentShare.mConfirm ==
707                     BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED)) {
708                /* have additional auto confirmed share to process */
709                if (V) Log.v(TAG, "Transfer continue session for info " + mCurrentShare.mId +
710                        " from batch " + mBatch.mId);
711                processCurrentShare();
712                confirmStatusChanged();
713            }
714        }
715    }
716
717    /*
718     * NOTE We don't implement this method now. Now delete a single share from
719     * the batch means the whole batch should be canceled. If later we want to
720     * support single cancel, we could implement here For outbound transfer, if
721     * the share is currently in transfer, cancel it For inbounds transfer,
722     * delete share means the current receiving file should be canceled.
723     */
724    /**
725     * Process when a share is deleted from current transfer
726     */
727    public void onShareDeleted(int id) {
728
729    }
730
731    /**
732     * Process when current transfer is canceled
733     */
734    public void onBatchCanceled() {
735        if (V) Log.v(TAG, "Transfer on Batch canceled");
736
737        this.stop();
738        mBatch.mStatus = Constants.BATCH_STATUS_FINISHED;
739    }
740}
741