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 = null;
306        if (mBatch == null) {
307            return;
308        }
309        info = mBatch.getPendingShare();
310        while (info != null) {
311            if (info.mStatus < 200) {
312                info.mStatus = failReason;
313                Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mId);
314                ContentValues updateValues = new ContentValues();
315                updateValues.put(BluetoothShare.STATUS, info.mStatus);
316                /* Update un-processed outbound transfer to show some info */
317                if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
318                    BluetoothOppSendFileInfo fileInfo
319                            = BluetoothOppUtility.getSendFileInfo(info.mUri);
320                    BluetoothOppUtility.closeSendFileInfo(info.mUri);
321                    if (fileInfo.mFileName != null) {
322                        updateValues.put(BluetoothShare.FILENAME_HINT, fileInfo.mFileName);
323                        updateValues.put(BluetoothShare.TOTAL_BYTES, fileInfo.mLength);
324                        updateValues.put(BluetoothShare.MIMETYPE, fileInfo.mMimetype);
325                    }
326                } else {
327                    if (info.mStatus < 200 && info.mFilename != null) {
328                        new File(info.mFilename).delete();
329                    }
330                }
331                mContext.getContentResolver().update(contentUri, updateValues, null, null);
332                Constants.sendIntentIfCompleted(mContext, contentUri, info.mStatus);
333            }
334            info = mBatch.getPendingShare();
335        }
336
337    }
338
339    private void markBatchFailed() {
340        markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR);
341    }
342
343    /*
344     * NOTE
345     * For outbound transfer
346     * 1) Check Bluetooth status
347     * 2) Start handler thread
348     * 3) new a thread to connect to target device
349     * 3.1) Try a few times to do SDP query for target device OPUSH channel
350     * 3.2) Try a few seconds to connect to target socket
351     * 4) After BluetoothSocket is connected,create an instance of RfcommTransport
352     * 5) Create an instance of BluetoothOppClientSession
353     * 6) Start the session and process the first share in batch
354     * For inbound transfer
355     * The transfer already has session and transport setup, just start it
356     * 1) Check Bluetooth status
357     * 2) Start handler thread
358     * 3) Start the session and process the first share in batch
359     */
360    /**
361     * Start the transfer
362     */
363    public void start() {
364        /* check Bluetooth enable status */
365        /*
366         * normally it's impossible to reach here if BT is disabled. Just check
367         * for safety
368         */
369        if (!mAdapter.isEnabled()) {
370            Log.e(TAG, "Can't start transfer when Bluetooth is disabled for " + mBatch.mId);
371            markBatchFailed();
372            mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
373            return;
374        }
375
376        if (mHandlerThread == null) {
377            if (V) Log.v(TAG, "Create handler thread for batch " + mBatch.mId);
378            mHandlerThread = new HandlerThread("BtOpp Transfer Handler",
379                    Process.THREAD_PRIORITY_BACKGROUND);
380            mHandlerThread.start();
381            mSessionHandler = new EventHandler(mHandlerThread.getLooper());
382
383            if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
384                /* for outbound transfer, we do connect first */
385                startConnectSession();
386            } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
387                /*
388                 * for inbound transfer, it's already connected, so we start
389                 * OBEX session directly
390                 */
391                startObexSession();
392            }
393        }
394    }
395
396    /**
397     * Stop the transfer
398     */
399    public void stop() {
400        if (V) Log.v(TAG, "stop");
401        if (mConnectThread != null) {
402            try {
403                mConnectThread.interrupt();
404                if (V) Log.v(TAG, "waiting for connect thread to terminate");
405                mConnectThread.join();
406            } catch (InterruptedException e) {
407                if (V) Log.v(TAG, "Interrupted waiting for connect thread to join");
408            }
409            mConnectThread = null;
410        }
411        if (mSession != null) {
412            if (V) Log.v(TAG, "Stop mSession");
413            mSession.stop();
414        }
415        if (mHandlerThread != null) {
416            mHandlerThread.getLooper().quit();
417            mHandlerThread.interrupt();
418            mHandlerThread = null;
419        }
420    }
421
422    private void startObexSession() {
423
424        mBatch.mStatus = Constants.BATCH_STATUS_RUNNING;
425
426        mCurrentShare = mBatch.getPendingShare();
427        if (mCurrentShare == null) {
428            /*
429             * TODO catch this error
430             */
431            Log.e(TAG, "Unexpected error happened !");
432            return;
433        }
434        if (V) Log.v(TAG, "Start session for info " + mCurrentShare.mId + " for batch " +
435                mBatch.mId);
436
437        if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
438            if (V) Log.v(TAG, "Create Client session with transport " + mTransport.toString());
439            mSession = new BluetoothOppObexClientSession(mContext, mTransport);
440        } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
441            /*
442             * For inbounds transfer, a server session should already exists
443             * before BluetoothOppTransfer is initialized. We should pass in a
444             * mSession instance.
445             */
446            if (mSession == null) {
447                /** set current share as error */
448                Log.e(TAG, "Unexpected error happened !");
449                markBatchFailed();
450                mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
451                return;
452            }
453            if (V) Log.v(TAG, "Transfer has Server session" + mSession.toString());
454        }
455
456        mSession.start(mSessionHandler, mBatch.getNumShares());
457        processCurrentShare();
458    }
459
460    private void processCurrentShare() {
461        /* This transfer need user confirm */
462        if (V) Log.v(TAG, "processCurrentShare" + mCurrentShare.mId);
463        mSession.addShare(mCurrentShare);
464        if (mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED) {
465            setConfirmed();
466        }
467    }
468
469    /**
470     * Set transfer confirmed status. It should only be called for inbound
471     * transfer
472     */
473    public void setConfirmed() {
474        /* unblock server session */
475        final Thread notifyThread = new Thread("Server Unblock thread") {
476            public void run() {
477                synchronized (mSession) {
478                    mSession.unblock();
479                    mSession.notify();
480                }
481            }
482        };
483        if (V) Log.v(TAG, "setConfirmed to unblock mSession" + mSession.toString());
484        notifyThread.start();
485    }
486
487    private void startConnectSession() {
488
489        if (Constants.USE_TCP_DEBUG) {
490            mConnectThread = new SocketConnectThread("localhost", Constants.TCP_DEBUG_PORT, 0);
491            mConnectThread.start();
492        } else {
493            mConnectThread = new SocketConnectThread(mBatch.mDestination,false);
494            mConnectThread.start();
495        }
496    }
497
498    private SocketConnectThread mConnectThread;
499
500    private class SocketConnectThread extends Thread {
501        private final String host;
502
503        private final BluetoothDevice device;
504
505        private final int channel;
506
507        private boolean isConnected;
508
509        private long timestamp;
510
511        private BluetoothSocket btSocket = null;
512
513        private boolean mRetry = false;
514
515        /* create a TCP socket */
516        public SocketConnectThread(String host, int port, int dummy) {
517            super("Socket Connect Thread");
518            this.host = host;
519            this.channel = port;
520            this.device = null;
521            isConnected = false;
522        }
523
524        /* create a Rfcomm Socket */
525        public SocketConnectThread(BluetoothDevice device, int channel, boolean
526                retry) {
527            super("Socket Connect Thread");
528            this.device = device;
529            this.host = null;
530            this.channel = channel;
531            isConnected = false;
532            mRetry = retry;
533        }
534
535        /* create a Rfcomm Socket */
536        public SocketConnectThread(BluetoothDevice device, boolean
537                retry) {
538            super("Socket Connect Thread");
539            this.device = device;
540            this.host = null;
541            this.channel = -1;
542            isConnected = false;
543            mRetry = retry;
544        }
545
546        public void interrupt() {
547            if (!Constants.USE_TCP_DEBUG) {
548                if (btSocket != null) {
549                    try {
550                        btSocket.close();
551                    } catch (IOException e) {
552                        Log.v(TAG, "Error when close socket");
553                    }
554                }
555            }
556        }
557
558        @Override
559        public void run() {
560
561            timestamp = System.currentTimeMillis();
562
563            if (Constants.USE_TCP_DEBUG) {
564                /* Use TCP socket to connect */
565                Socket s = new Socket();
566
567                // Try to connect for 50 seconds
568                int result = 0;
569                for (int i = 0; i < CONNECT_RETRY_TIME && result == 0; i++) {
570                    try {
571                        s.connect(new InetSocketAddress(host, channel), CONNECT_WAIT_TIMEOUT);
572                    } catch (UnknownHostException e) {
573                        Log.e(TAG, "TCP socket connect unknown host ");
574                    } catch (IOException e) {
575                        Log.e(TAG, "TCP socket connect failed ");
576                    }
577                    if (s.isConnected()) {
578                        if (D) Log.d(TAG, "TCP socket connected ");
579                        isConnected = true;
580                        break;
581                    }
582                    if (isInterrupted()) {
583                        Log.e(TAG, "TCP socket connect interrupted ");
584                        markConnectionFailed(s);
585                        return;
586                    }
587                }
588                if (!isConnected) {
589                    Log.e(TAG, "TCP socket connect failed after 20 seconds");
590                    markConnectionFailed(s);
591                    return;
592                }
593
594                if (V) Log.v(TAG, "TCP Socket connection attempt took " +
595                        (System.currentTimeMillis() - timestamp) + " ms");
596
597                TestTcpTransport transport;
598                transport = new TestTcpTransport(s);
599
600                if (isInterrupted()) {
601                    isConnected = false;
602                    markConnectionFailed(s);
603                    transport = null;
604                    return;
605                }
606                if (!isConnected) {
607                    transport = null;
608                    Log.e(TAG, "TCP connect session error: ");
609                    markConnectionFailed(s);
610                    return;
611                } else {
612                    if (D) Log.d(TAG, "Send transport message " + transport.toString());
613                    mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
614                }
615            } else {
616
617                /* Use BluetoothSocket to connect */
618
619                try {
620                    btSocket = device.createInsecureRfcommSocketToServiceRecord(BluetoothUuid.ObexObjectPush.getUuid());
621                } catch (IOException e1) {
622                    Log.e(TAG, "Rfcomm socket create error",e1);
623                    markConnectionFailed(btSocket);
624                    return;
625                }
626                try {
627                    btSocket.connect();
628
629                    if (V) Log.v(TAG, "Rfcomm socket connection attempt took " +
630                            (System.currentTimeMillis() - timestamp) + " ms");
631                    BluetoothOppRfcommTransport transport;
632                    transport = new BluetoothOppRfcommTransport(btSocket);
633
634                    BluetoothOppPreference.getInstance(mContext).setName(device, device.getName());
635
636                    if (V) Log.v(TAG, "Send transport message " + transport.toString());
637
638                    mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
639                } catch (IOException e) {
640                    Log.e(TAG, "Rfcomm socket connect exception",e);
641                    // If the devices were paired before, but unpaired on the
642                    // remote end, it will return an error for the auth request
643                    // for the socket connection. Link keys will get exchanged
644                    // again, but we need to retry. There is no good way to
645                    // inform this socket asking it to retry apart from a blind
646                    // delayed retry.
647                    if (!mRetry && e.getMessage().equals(SOCKET_LINK_KEY_ERROR)) {
648                        Message msg = mSessionHandler.obtainMessage(SOCKET_ERROR_RETRY,-1,-1,device);
649                        mSessionHandler.sendMessageDelayed(msg, 1500);
650                    } else {
651                        markConnectionFailed(btSocket);
652                    }
653                }
654            }
655        }
656
657        private void markConnectionFailed(Socket s) {
658            try {
659                s.close();
660            } catch (IOException e) {
661                Log.e(TAG, "TCP socket close error");
662            }
663            mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
664        }
665
666        private void markConnectionFailed(BluetoothSocket s) {
667            try {
668                s.close();
669            } catch (IOException e) {
670                if (V) Log.e(TAG, "Error when close socket");
671            }
672            mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
673            return;
674        }
675    };
676
677    /* update a trivial field of a share to notify Provider the batch status change */
678    private void tickShareStatus(BluetoothOppShareInfo share) {
679        if (share == null) {
680            Log.d(TAG,"Share is null");
681            return;
682        }
683        Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId);
684        ContentValues updateValues = new ContentValues();
685        updateValues.put(BluetoothShare.DIRECTION, share.mDirection);
686        mContext.getContentResolver().update(contentUri, updateValues, null, null);
687    }
688
689    /*
690     * Note: For outbound transfer We don't implement this method now. If later
691     * we want to support merging a later added share into an existing session,
692     * we could implement here For inbounds transfer add share means it's
693     * multiple receive in the same session, we should handle it to fill it into
694     * mSession
695     */
696    /**
697     * Process when a share is added to current transfer
698     */
699    public void onShareAdded(int id) {
700        BluetoothOppShareInfo info = mBatch.getPendingShare();
701        if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {
702            mCurrentShare = mBatch.getPendingShare();
703            /*
704             * TODO what if it's not auto confirmed?
705             */
706            if (mCurrentShare != null &&
707                    (mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED ||
708                     mCurrentShare.mConfirm ==
709                     BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED)) {
710                /* have additional auto confirmed share to process */
711                if (V) Log.v(TAG, "Transfer continue session for info " + mCurrentShare.mId +
712                        " from batch " + mBatch.mId);
713                processCurrentShare();
714                setConfirmed();
715            }
716        }
717    }
718
719    /*
720     * NOTE We don't implement this method now. Now delete a single share from
721     * the batch means the whole batch should be canceled. If later we want to
722     * support single cancel, we could implement here For outbound transfer, if
723     * the share is currently in transfer, cancel it For inbounds transfer,
724     * delete share means the current receiving file should be canceled.
725     */
726    /**
727     * Process when a share is deleted from current transfer
728     */
729    public void onShareDeleted(int id) {
730
731    }
732
733    /**
734     * Process when current transfer is canceled
735     */
736    public void onBatchCanceled() {
737        if (V) Log.v(TAG, "Transfer on Batch canceled");
738
739        this.stop();
740        mBatch.mStatus = Constants.BATCH_STATUS_FINISHED;
741    }
742}
743