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