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