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