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