• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
33 package com.android.bluetooth.opp;
34 
35 import javax.obex.ObexTransport;
36 
37 import android.app.NotificationManager;
38 import android.bluetooth.BluetoothAdapter;
39 import android.bluetooth.BluetoothDevice;
40 import android.bluetooth.BluetoothSocket;
41 import android.bluetooth.BluetoothUuid;
42 import android.os.ParcelUuid;
43 import android.content.BroadcastReceiver;
44 import android.content.ContentValues;
45 import android.content.Context;
46 import android.content.Intent;
47 import android.content.IntentFilter;
48 import android.net.Uri;
49 import android.os.Handler;
50 import android.os.HandlerThread;
51 import android.os.Looper;
52 import android.os.Message;
53 import android.os.Parcelable;
54 import android.os.PowerManager;
55 import android.os.Process;
56 import android.util.Log;
57 
58 import java.io.File;
59 import java.io.IOException;
60 import java.net.InetSocketAddress;
61 import java.net.Socket;
62 import java.net.UnknownHostException;
63 
64 /**
65  * This class run an actual Opp transfer session (from connect target device to
66  * disconnect)
67  */
68 public 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 
BluetoothOppTransfer(Context context, PowerManager powerManager, BluetoothOppBatch batch, BluetoothOppObexSession session)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 
BluetoothOppTransfer(Context context, PowerManager powerManager, BluetoothOppBatch batch)119     public BluetoothOppTransfer(Context context, PowerManager powerManager, BluetoothOppBatch batch) {
120         this(context, powerManager, batch, null);
121     }
122 
getBatchId()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 {
EventHandler(Looper looper)131         public EventHandler(Looper looper) {
132             super(looper);
133         }
134 
135         @Override
handleMessage(Message msg)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 
markShareTimeout(BluetoothOppShareInfo share)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 
markBatchFailed(int failReason)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 
markBatchFailed()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      */
start()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      */
stop()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 
startObexSession()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 
processCurrentShare()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      */
setConfirmed()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 
startConnectSession()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 */
SocketConnectThread(String host, int port, int dummy)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 */
SocketConnectThread(BluetoothDevice device, int channel, boolean retry)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 */
SocketConnectThread(BluetoothDevice device, boolean retry)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 
interrupt()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
run()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 
markConnectionFailed(Socket s)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 
markConnectionFailed(BluetoothSocket s)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 */
tickShareStatus(BluetoothOppShareInfo share)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      */
onShareAdded(int id)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      */
onShareDeleted(int id)729     public void onShareDeleted(int id) {
730 
731     }
732 
733     /**
734      * Process when current transfer is canceled
735      */
onBatchCanceled()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