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