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