• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2008-2009, Motorola, Inc.
3  *
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * - Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * - Redistributions in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * - Neither the name of the Motorola, Inc. nor the names of its contributors
17  * may be used to endorse or promote products derived from this software
18  * without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 package com.android.bluetooth.opp;
34 
35 import javax.obex.ObexTransport;
36 
37 import android.app.NotificationManager;
38 import android.bluetooth.BluetoothAdapter;
39 import android.bluetooth.BluetoothDevice;
40 import android.bluetooth.BluetoothSocket;
41 import android.bluetooth.BluetoothUuid;
42 import android.os.ParcelUuid;
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.Parcelable;
54 import android.os.PowerManager;
55 import android.os.Process;
56 import android.util.Log;
57 
58 import java.io.File;
59 import java.io.IOException;
60 import java.net.InetSocketAddress;
61 import java.net.Socket;
62 import java.net.UnknownHostException;
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     public static final int RFCOMM_ERROR = 10;
76 
77     public static final int RFCOMM_CONNECTED = 11;
78 
79     public static final int SDP_RESULT = 12;
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 short OPUSH_UUID16 = 0x1105;
86 
87     private Context mContext;
88 
89     private BluetoothAdapter mAdapter;
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     /*
104      * TODO check if we need PowerManager here
105      */
106     private PowerManager mPowerManager;
107 
108     private long mTimestamp;
109 
BluetoothOppTransfer(Context context, PowerManager powerManager, BluetoothOppBatch batch, BluetoothOppObexSession session)110     public BluetoothOppTransfer(Context context, PowerManager powerManager,
111             BluetoothOppBatch batch, BluetoothOppObexSession session) {
112 
113         mContext = context;
114         mPowerManager = powerManager;
115         mBatch = batch;
116         mSession = session;
117 
118         mBatch.registerListern(this);
119         mAdapter = BluetoothAdapter.getDefaultAdapter();
120 
121     }
122 
BluetoothOppTransfer(Context context, PowerManager powerManager, BluetoothOppBatch batch)123     public BluetoothOppTransfer(Context context, PowerManager powerManager, BluetoothOppBatch batch) {
124         this(context, powerManager, batch, null);
125     }
126 
getBatchId()127     public int getBatchId() {
128         return mBatch.mId;
129     }
130 
131     /*
132      * Receives events from mConnectThread & mSession back in the main thread.
133      */
134     private class EventHandler extends Handler {
EventHandler(Looper looper)135         public EventHandler(Looper looper) {
136             super(looper);
137         }
138 
139         @Override
handleMessage(Message msg)140         public void handleMessage(Message msg) {
141             switch (msg.what) {
142                 case SDP_RESULT:
143                     if (V) Log.v(TAG, "SDP request returned " + msg.arg1 + " (" +
144                             (System.currentTimeMillis() - mTimestamp + " ms)"));
145                     if (!((BluetoothDevice)msg.obj).equals(mBatch.mDestination)) {
146                         return;
147                     }
148                     try {
149                         mContext.unregisterReceiver(mReceiver);
150                     } catch (IllegalArgumentException e) {
151                         // ignore
152                     }
153                     if (msg.arg1 > 0) {
154                         mConnectThread = new SocketConnectThread(mBatch.mDestination, msg.arg1);
155                         mConnectThread.start();
156                     } else {
157                         /* SDP query fail case */
158                         Log.e(TAG, "SDP query failed!");
159                         markBatchFailed(BluetoothShare.STATUS_CONNECTION_ERROR);
160                         mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
161                     }
162 
163                     break;
164 
165                 /*
166                  * RFCOMM connect fail is for outbound share only! Mark batch
167                  * failed, and all shares in batch failed
168                  */
169                 case RFCOMM_ERROR:
170                     if (V) Log.v(TAG, "receive RFCOMM_ERROR msg");
171                     mConnectThread = null;
172                     markBatchFailed(BluetoothShare.STATUS_CONNECTION_ERROR);
173                     mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
174 
175                     break;
176                 /*
177                  * RFCOMM connected is for outbound share only! Create
178                  * BluetoothOppObexClientSession and start it
179                  */
180                 case RFCOMM_CONNECTED:
181                     if (V) Log.v(TAG, "Transfer receive RFCOMM_CONNECTED msg");
182                     mConnectThread = null;
183                     mTransport = (ObexTransport)msg.obj;
184                     startObexSession();
185 
186                     break;
187                 /*
188                  * Put next share if available,or finish the transfer.
189                  * For outbound session, call session.addShare() to send next file,
190                  * or call session.stop().
191                  * For inbounds session, do nothing. If there is next file to receive,it
192                  * will be notified through onShareAdded()
193                  */
194                 case BluetoothOppObexSession.MSG_SHARE_COMPLETE:
195                     BluetoothOppShareInfo info = (BluetoothOppShareInfo)msg.obj;
196                     if (V) Log.v(TAG, "receive MSG_SHARE_COMPLETE for info " + info.mId);
197                     if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
198                         mCurrentShare = mBatch.getPendingShare();
199 
200                         if (mCurrentShare != null) {
201                             /* we have additional share to process */
202                             if (V) Log.v(TAG, "continue session for info " + mCurrentShare.mId +
203                                     " from batch " + mBatch.mId);
204                             processCurrentShare();
205                         } else {
206                             /* for outbound transfer, all shares are processed */
207                             if (V) Log.v(TAG, "Batch " + mBatch.mId + " is done");
208                             mSession.stop();
209                         }
210                     }
211                     break;
212                 /*
213                  * Handle session completed status Set batch status to
214                  * finished
215                  */
216                 case BluetoothOppObexSession.MSG_SESSION_COMPLETE:
217                     BluetoothOppShareInfo info1 = (BluetoothOppShareInfo)msg.obj;
218                     if (V) Log.v(TAG, "receive MSG_SESSION_COMPLETE for batch " + mBatch.mId);
219                     mBatch.mStatus = Constants.BATCH_STATUS_FINISHED;
220                     /*
221                      * trigger content provider again to know batch status change
222                      */
223                     tickShareStatus(info1);
224                     break;
225 
226                 /* Handle the error state of an Obex session */
227                 case BluetoothOppObexSession.MSG_SESSION_ERROR:
228                     if (V) Log.v(TAG, "receive MSG_SESSION_ERROR for batch " + mBatch.mId);
229                     BluetoothOppShareInfo info2 = (BluetoothOppShareInfo)msg.obj;
230                     mSession.stop();
231                     mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
232                     markBatchFailed(info2.mStatus);
233                     tickShareStatus(mCurrentShare);
234                     break;
235 
236                 case BluetoothOppObexSession.MSG_SHARE_INTERRUPTED:
237                     if (V) Log.v(TAG, "receive MSG_SHARE_INTERRUPTED for batch " + mBatch.mId);
238                     BluetoothOppShareInfo info3 = (BluetoothOppShareInfo)msg.obj;
239                     if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
240                         try {
241                             if (mTransport == null) {
242                                 Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null");
243                             } else {
244                                 mTransport.close();
245                             }
246                         } catch (IOException e) {
247                             Log.e(TAG, "failed to close mTransport");
248                         }
249                         if (V) Log.v(TAG, "mTransport closed ");
250                         mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
251                         if (info3 != null) {
252                             markBatchFailed(info3.mStatus);
253                         } else {
254                             markBatchFailed();
255                         }
256                         tickShareStatus(mCurrentShare);
257                     }
258                     break;
259 
260                 case BluetoothOppObexSession.MSG_CONNECT_TIMEOUT:
261                     if (V) Log.v(TAG, "receive MSG_CONNECT_TIMEOUT for batch " + mBatch.mId);
262                     /* for outbound transfer, the block point is BluetoothSocket.write()
263                      * The only way to unblock is to tear down lower transport
264                      * */
265                     if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
266                         try {
267                             if (mTransport == null) {
268                                 Log.v(TAG, "receive MSG_SHARE_INTERRUPTED but mTransport = null");
269                             } else {
270                                 mTransport.close();
271                             }
272                         } catch (IOException e) {
273                             Log.e(TAG, "failed to close mTransport");
274                         }
275                         if (V) Log.v(TAG, "mTransport closed ");
276                     } else {
277                         /*
278                          * For inbound transfer, the block point is waiting for
279                          * user confirmation we can interrupt it nicely
280                          */
281 
282                         // Remove incoming file confirm notification
283                         NotificationManager nm = (NotificationManager)mContext
284                                 .getSystemService(Context.NOTIFICATION_SERVICE);
285                         nm.cancel(mCurrentShare.mId);
286                         // Send intent to UI for timeout handling
287                         Intent in = new Intent(BluetoothShare.USER_CONFIRMATION_TIMEOUT_ACTION);
288                         mContext.sendBroadcast(in);
289 
290                         markShareTimeout(mCurrentShare);
291                     }
292                     break;
293             }
294         }
295     }
296 
markShareTimeout(BluetoothOppShareInfo share)297     private void markShareTimeout(BluetoothOppShareInfo share) {
298         Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId);
299         ContentValues updateValues = new ContentValues();
300         updateValues
301                 .put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_TIMEOUT);
302         mContext.getContentResolver().update(contentUri, updateValues, null, null);
303     }
304 
markBatchFailed(int failReason)305     private void markBatchFailed(int failReason) {
306         synchronized (this) {
307             try {
308                 wait(1000);
309             } catch (InterruptedException e) {
310                 if (V) Log.v(TAG, "Interrupted waiting for markBatchFailed");
311             }
312         }
313 
314         if (D) Log.d(TAG, "Mark all ShareInfo in the batch as failed");
315         if (mCurrentShare != null) {
316             if (V) Log.v(TAG, "Current share has status " + mCurrentShare.mStatus);
317             if (BluetoothShare.isStatusError(mCurrentShare.mStatus)) {
318                 failReason = mCurrentShare.mStatus;
319             }
320             if (mCurrentShare.mDirection == BluetoothShare.DIRECTION_INBOUND
321                     && mCurrentShare.mFilename != null) {
322                 new File(mCurrentShare.mFilename).delete();
323             }
324         }
325 
326         BluetoothOppShareInfo info = mBatch.getPendingShare();
327         while (info != null) {
328             if (info.mStatus < 200) {
329                 info.mStatus = failReason;
330                 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mId);
331                 ContentValues updateValues = new ContentValues();
332                 updateValues.put(BluetoothShare.STATUS, info.mStatus);
333                 /* Update un-processed outbound transfer to show some info */
334                 if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
335                     BluetoothOppSendFileInfo fileInfo = null;
336                     fileInfo = BluetoothOppSendFileInfo.generateFileInfo(mContext, info.mUri,
337                             info.mMimetype, info.mDestination);
338                     if (fileInfo.mFileName != null) {
339                         updateValues.put(BluetoothShare.FILENAME_HINT, fileInfo.mFileName);
340                         updateValues.put(BluetoothShare.TOTAL_BYTES, fileInfo.mLength);
341                         updateValues.put(BluetoothShare.MIMETYPE, fileInfo.mMimetype);
342                     }
343                 } else {
344                     if (info.mStatus < 200 && info.mFilename != null) {
345                         new File(info.mFilename).delete();
346                     }
347                 }
348                 mContext.getContentResolver().update(contentUri, updateValues, null, null);
349                 Constants.sendIntentIfCompleted(mContext, contentUri, info.mStatus);
350             }
351             info = mBatch.getPendingShare();
352         }
353 
354     }
355 
markBatchFailed()356     private void markBatchFailed() {
357         markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR);
358     }
359 
360     /*
361      * NOTE
362      * For outbound transfer
363      * 1) Check Bluetooth status
364      * 2) Start handler thread
365      * 3) new a thread to connect to target device
366      * 3.1) Try a few times to do SDP query for target device OPUSH channel
367      * 3.2) Try a few seconds to connect to target socket
368      * 4) After BluetoothSocket is connected,create an instance of RfcommTransport
369      * 5) Create an instance of BluetoothOppClientSession
370      * 6) Start the session and process the first share in batch
371      * For inbound transfer
372      * The transfer already has session and transport setup, just start it
373      * 1) Check Bluetooth status
374      * 2) Start handler thread
375      * 3) Start the session and process the first share in batch
376      */
377     /**
378      * Start the transfer
379      */
start()380     public void start() {
381         /* check Bluetooth enable status */
382         /*
383          * normally it's impossible to reach here if BT is disabled. Just check
384          * for safety
385          */
386         if (!mAdapter.isEnabled()) {
387             Log.e(TAG, "Can't start transfer when Bluetooth is disabled for " + mBatch.mId);
388             markBatchFailed();
389             mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
390             return;
391         }
392 
393         if (mHandlerThread == null) {
394             if (V) Log.v(TAG, "Create handler thread for batch " + mBatch.mId);
395             mHandlerThread = new HandlerThread("BtOpp Transfer Handler",
396                     Process.THREAD_PRIORITY_BACKGROUND);
397             mHandlerThread.start();
398             mSessionHandler = new EventHandler(mHandlerThread.getLooper());
399 
400             if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
401                 /* for outbound transfer, we do connect first */
402                 startConnectSession();
403             } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
404                 /*
405                  * for inbound transfer, it's already connected, so we start
406                  * OBEX session directly
407                  */
408                 startObexSession();
409             }
410         }
411     }
412 
413     /**
414      * Stop the transfer
415      */
stop()416     public void stop() {
417         if (V) Log.v(TAG, "stop");
418         if (mConnectThread != null) {
419             try {
420                 mConnectThread.interrupt();
421                 if (V) Log.v(TAG, "waiting for connect thread to terminate");
422                 mConnectThread.join();
423             } catch (InterruptedException e) {
424                 if (V) Log.v(TAG, "Interrupted waiting for connect thread to join");
425             }
426             mConnectThread = null;
427         }
428         if (mSession != null) {
429             if (V) Log.v(TAG, "Stop mSession");
430             mSession.stop();
431         }
432         if (mHandlerThread != null) {
433             mHandlerThread.getLooper().quit();
434             mHandlerThread.interrupt();
435             mHandlerThread = null;
436         }
437     }
438 
startObexSession()439     private void startObexSession() {
440 
441         mBatch.mStatus = Constants.BATCH_STATUS_RUNNING;
442 
443         mCurrentShare = mBatch.getPendingShare();
444         if (mCurrentShare == null) {
445             /*
446              * TODO catch this error
447              */
448             Log.e(TAG, "Unexpected error happened !");
449             return;
450         }
451         if (V) Log.v(TAG, "Start session for info " + mCurrentShare.mId + " for batch " +
452                 mBatch.mId);
453 
454         if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
455             if (V) Log.v(TAG, "Create Client session with transport " + mTransport.toString());
456             mSession = new BluetoothOppObexClientSession(mContext, mTransport);
457         } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
458             /*
459              * For inbounds transfer, a server session should already exists
460              * before BluetoothOppTransfer is initialized. We should pass in a
461              * mSession instance.
462              */
463             if (mSession == null) {
464                 /** set current share as error */
465                 Log.e(TAG, "Unexpected error happened !");
466                 markBatchFailed();
467                 mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
468                 return;
469             }
470             if (V) Log.v(TAG, "Transfer has Server session" + mSession.toString());
471         }
472 
473         mSession.start(mSessionHandler);
474         processCurrentShare();
475     }
476 
processCurrentShare()477     private void processCurrentShare() {
478         /* This transfer need user confirm */
479         if (V) Log.v(TAG, "processCurrentShare" + mCurrentShare.mId);
480         mSession.addShare(mCurrentShare);
481     }
482 
483     /**
484      * Set transfer confirmed status. It should only be called for inbound
485      * transfer
486      */
setConfirmed()487     public void setConfirmed() {
488         /* unblock server session */
489         final Thread notifyThread = new Thread("Server Unblock thread") {
490             public void run() {
491                 synchronized (mSession) {
492                     mSession.unblock();
493                     mSession.notify();
494                 }
495             }
496         };
497         if (V) Log.v(TAG, "setConfirmed to unblock mSession" + mSession.toString());
498         notifyThread.start();
499     }
500 
startConnectSession()501     private void startConnectSession() {
502 
503         if (Constants.USE_TCP_DEBUG) {
504             mConnectThread = new SocketConnectThread("localhost", Constants.TCP_DEBUG_PORT, 0);
505             mConnectThread.start();
506         } else {
507             int channel = BluetoothOppPreference.getInstance(mContext).getChannel(
508                     mBatch.mDestination, OPUSH_UUID16);
509             if (channel != -1) {
510                 if (D) Log.d(TAG, "Get OPUSH channel " + channel + " from cache for " +
511                         mBatch.mDestination);
512                 mTimestamp = System.currentTimeMillis();
513                 mSessionHandler.obtainMessage(SDP_RESULT, channel, -1, mBatch.mDestination)
514                         .sendToTarget();
515             } else {
516                 doOpushSdp();
517             }
518         }
519     }
520 
doOpushSdp()521     private void doOpushSdp() {
522         if (V) Log.v(TAG, "Do Opush SDP request for address " + mBatch.mDestination);
523 
524         mTimestamp = System.currentTimeMillis();
525 
526         int channel;
527         channel = mBatch.mDestination.getServiceChannel(BluetoothUuid.ObexObjectPush);
528         if (channel != -1) {
529             if (D) Log.d(TAG, "Get OPUSH channel " + channel + " from SDP for "
530                     + mBatch.mDestination);
531 
532             mSessionHandler.obtainMessage(SDP_RESULT, channel, -1, mBatch.mDestination)
533                     .sendToTarget();
534             return;
535 
536         } else {
537             if (V) Log.v(TAG, "Remote Service channel not in cache");
538 
539             if (!mBatch.mDestination.fetchUuidsWithSdp()) {
540                 Log.e(TAG, "Start SDP query failed");
541             } else {
542                 // we expect framework send us Intent ACTION_UUID. otherwise we will fail
543                 if (V) Log.v(TAG, "Start new SDP, wait for result");
544                 IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_UUID);
545                 mContext.registerReceiver(mReceiver, intentFilter);
546                 return;
547             }
548         }
549         Message msg = mSessionHandler.obtainMessage(SDP_RESULT, channel, -1, mBatch.mDestination);
550         mSessionHandler.sendMessageDelayed(msg, 2000);
551     }
552 
553     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
554         @Override
555         public void onReceive(Context context, Intent intent) {
556             if (intent.getAction().equals(BluetoothDevice.ACTION_UUID)) {
557                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
558                 if (V) Log.v(TAG, "ACTION_UUID for device " + device);
559                 if (device.equals(mBatch.mDestination)) {
560                     int channel = -1;
561                     Parcelable[] uuid = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
562                     if (uuid != null) {
563                         ParcelUuid[] uuids = new ParcelUuid[uuid.length];
564                         for (int i = 0; i < uuid.length; i++) {
565                             uuids[i] = (ParcelUuid)uuid[i];
566                         }
567                         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
568                             if (V) Log.v(TAG, "SDP get OPP result for device " + device);
569                             channel = mBatch.mDestination
570                                     .getServiceChannel(BluetoothUuid.ObexObjectPush);
571                         }
572                     }
573                     mSessionHandler.obtainMessage(SDP_RESULT, channel, -1, mBatch.mDestination)
574                             .sendToTarget();
575                 }
576             }
577         }
578     };
579 
580     private SocketConnectThread mConnectThread;
581 
582     private class SocketConnectThread extends Thread {
583         private final String host;
584 
585         private final BluetoothDevice device;
586 
587         private final int channel;
588 
589         private boolean isConnected;
590 
591         private long timestamp;
592 
593         private BluetoothSocket btSocket = null;
594 
595         /* create a TCP socket */
SocketConnectThread(String host, int port, int dummy)596         public SocketConnectThread(String host, int port, int dummy) {
597             super("Socket Connect Thread");
598             this.host = host;
599             this.channel = port;
600             this.device = null;
601             isConnected = false;
602         }
603 
604         /* create a Rfcomm Socket */
SocketConnectThread(BluetoothDevice device, int channel)605         public SocketConnectThread(BluetoothDevice device, int channel) {
606             super("Socket Connect Thread");
607             this.device = device;
608             this.host = null;
609             this.channel = channel;
610             isConnected = false;
611         }
612 
interrupt()613         public void interrupt() {
614             if (!Constants.USE_TCP_DEBUG) {
615                 if (btSocket != null) {
616                     try {
617                         btSocket.close();
618                     } catch (IOException e) {
619                         Log.v(TAG, "Error when close socket");
620                     }
621                 }
622             }
623         }
624 
625         @Override
run()626         public void run() {
627 
628             timestamp = System.currentTimeMillis();
629 
630             if (Constants.USE_TCP_DEBUG) {
631                 /* Use TCP socket to connect */
632                 Socket s = new Socket();
633 
634                 // Try to connect for 50 seconds
635                 int result = 0;
636                 for (int i = 0; i < CONNECT_RETRY_TIME && result == 0; i++) {
637                     try {
638                         s.connect(new InetSocketAddress(host, channel), CONNECT_WAIT_TIMEOUT);
639                     } catch (UnknownHostException e) {
640                         Log.e(TAG, "TCP socket connect unknown host ");
641                     } catch (IOException e) {
642                         Log.e(TAG, "TCP socket connect failed ");
643                     }
644                     if (s.isConnected()) {
645                         if (D) Log.d(TAG, "TCP socket connected ");
646                         isConnected = true;
647                         break;
648                     }
649                     if (isInterrupted()) {
650                         Log.e(TAG, "TCP socket connect interrupted ");
651                         markConnectionFailed(s);
652                         return;
653                     }
654                 }
655                 if (!isConnected) {
656                     Log.e(TAG, "TCP socket connect failed after 20 seconds");
657                     markConnectionFailed(s);
658                     return;
659                 }
660 
661                 if (V) Log.v(TAG, "TCP Socket connection attempt took " +
662                         (System.currentTimeMillis() - timestamp) + " ms");
663 
664                 TestTcpTransport transport;
665                 transport = new TestTcpTransport(s);
666 
667                 if (isInterrupted()) {
668                     isConnected = false;
669                     markConnectionFailed(s);
670                     transport = null;
671                     return;
672                 }
673                 if (!isConnected) {
674                     transport = null;
675                     Log.e(TAG, "TCP connect session error: ");
676                     markConnectionFailed(s);
677                     return;
678                 } else {
679                     if (D) Log.d(TAG, "Send transport message " + transport.toString());
680                     mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
681                 }
682             } else {
683 
684                 /* Use BluetoothSocket to connect */
685 
686                 try {
687                     btSocket = device.createInsecureRfcommSocket(channel);
688                 } catch (IOException e1) {
689                     Log.e(TAG, "Rfcomm socket create error");
690                     markConnectionFailed(btSocket);
691                     return;
692                 }
693                 try {
694                     btSocket.connect();
695 
696                     if (V) Log.v(TAG, "Rfcomm socket connection attempt took " +
697                             (System.currentTimeMillis() - timestamp) + " ms");
698                     BluetoothOppRfcommTransport transport;
699                     transport = new BluetoothOppRfcommTransport(btSocket);
700 
701                     BluetoothOppPreference.getInstance(mContext).setChannel(device, OPUSH_UUID16,
702                             channel);
703                     BluetoothOppPreference.getInstance(mContext).setName(device, device.getName());
704 
705                     if (V) Log.v(TAG, "Send transport message " + transport.toString());
706 
707                     mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
708                 } catch (IOException e) {
709                     Log.e(TAG, "Rfcomm socket connect exception ");
710                     BluetoothOppPreference.getInstance(mContext)
711                             .removeChannel(device, OPUSH_UUID16);
712                     markConnectionFailed(btSocket);
713                     return;
714                 }
715             }
716         }
717 
markConnectionFailed(Socket s)718         private void markConnectionFailed(Socket s) {
719             try {
720                 s.close();
721             } catch (IOException e) {
722                 Log.e(TAG, "TCP socket close error");
723             }
724             mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
725         }
726 
markConnectionFailed(BluetoothSocket s)727         private void markConnectionFailed(BluetoothSocket s) {
728             try {
729                 s.close();
730             } catch (IOException e) {
731                 if (V) Log.e(TAG, "Error when close socket");
732             }
733             mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
734             return;
735         }
736     };
737 
738     /* update a trivial field of a share to notify Provider the batch status change */
tickShareStatus(BluetoothOppShareInfo share)739     private void tickShareStatus(BluetoothOppShareInfo share) {
740         Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId);
741         ContentValues updateValues = new ContentValues();
742         updateValues.put(BluetoothShare.DIRECTION, share.mDirection);
743         mContext.getContentResolver().update(contentUri, updateValues, null, null);
744     }
745 
746     /*
747      * Note: For outbound transfer We don't implement this method now. If later
748      * we want to support merging a later added share into an existing session,
749      * we could implement here For inbounds transfer add share means it's
750      * multiple receive in the same session, we should handle it to fill it into
751      * mSession
752      */
753     /**
754      * Process when a share is added to current transfer
755      */
onShareAdded(int id)756     public void onShareAdded(int id) {
757         BluetoothOppShareInfo info = mBatch.getPendingShare();
758         if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {
759             mCurrentShare = mBatch.getPendingShare();
760             /*
761              * TODO what if it's not auto confirmed?
762              */
763             if (mCurrentShare != null
764                     && mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED) {
765                 /* have additional auto confirmed share to process */
766                 if (V) Log.v(TAG, "Transfer continue session for info " + mCurrentShare.mId +
767                         " from batch " + mBatch.mId);
768                 processCurrentShare();
769                 setConfirmed();
770             }
771         }
772     }
773 
774     /*
775      * NOTE We don't implement this method now. Now delete a single share from
776      * the batch means the whole batch should be canceled. If later we want to
777      * support single cancel, we could implement here For outbound transfer, if
778      * the share is currently in transfer, cancel it For inbounds transfer,
779      * delete share means the current receiving file should be canceled.
780      */
781     /**
782      * Process when a share is deleted from current transfer
783      */
onShareDeleted(int id)784     public void onShareDeleted(int id) {
785 
786     }
787 
788     /**
789      * Process when current transfer is canceled
790      */
onBatchCanceled()791     public void onBatchCanceled() {
792         if (V) Log.v(TAG, "Transfer on Batch canceled");
793 
794         this.stop();
795         mBatch.mStatus = Constants.BATCH_STATUS_FINISHED;
796     }
797 }
798