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