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