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