• 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.bluetooth.BluetoothAdapter;
36 import android.bluetooth.BluetoothDevice;
37 import android.bluetooth.BluetoothDevicePicker;
38 import android.bluetooth.BluetoothSocket;
39 import android.content.BroadcastReceiver;
40 import android.content.ContentResolver;
41 import android.content.ContentValues;
42 import android.content.Context;
43 import android.content.Intent;
44 import android.content.IntentFilter;
45 import android.database.CharArrayBuffer;
46 import android.database.ContentObserver;
47 import android.database.Cursor;
48 import android.media.MediaScannerConnection;
49 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
50 import android.net.Uri;
51 import android.os.Binder;
52 import android.os.Handler;
53 import android.os.Message;
54 import android.os.Process;
55 import android.util.Log;
56 
57 import com.android.bluetooth.BluetoothObexTransport;
58 import com.android.bluetooth.IObexConnectionHandler;
59 import com.android.bluetooth.ObexServerSockets;
60 import com.android.bluetooth.btservice.ProfileService;
61 import com.android.bluetooth.sdp.SdpManager;
62 import com.android.internal.annotations.VisibleForTesting;
63 
64 import com.google.android.collect.Lists;
65 
66 import java.io.IOException;
67 import java.text.SimpleDateFormat;
68 import java.util.ArrayList;
69 import java.util.Date;
70 import java.util.Locale;
71 
72 import javax.obex.ObexTransport;
73 
74 /**
75  * Performs the background Bluetooth OPP transfer. It also starts thread to
76  * accept incoming OPP connection.
77  */
78 
79 public class BluetoothOppService extends ProfileService implements IObexConnectionHandler {
80     private static final boolean D = Constants.DEBUG;
81     private static final boolean V = Constants.VERBOSE;
82 
83     private static final byte[] SUPPORTED_OPP_FORMAT = {
84             0x01 /* vCard 2.1 */,
85             0x02 /* vCard 3.0 */,
86             0x03 /* vCal 1.0 */,
87             0x04 /* iCal 2.0 */,
88             (byte) 0xFF /* Any type of object */
89     };
90 
91     private class BluetoothShareContentObserver extends ContentObserver {
92 
BluetoothShareContentObserver()93         BluetoothShareContentObserver() {
94             super(new Handler());
95         }
96 
97         @Override
onChange(boolean selfChange)98         public void onChange(boolean selfChange) {
99             if (V) {
100                 Log.v(TAG, "ContentObserver received notification");
101             }
102             updateFromProvider();
103         }
104     }
105 
106     private static final String TAG = "BtOppService";
107 
108     /** Observer to get notified when the content observer's data changes */
109     private BluetoothShareContentObserver mObserver;
110 
111     /** Class to handle Notification Manager updates */
112     private BluetoothOppNotification mNotifier;
113 
114     private boolean mPendingUpdate;
115 
116     private UpdateThread mUpdateThread;
117 
118     private boolean mUpdateThreadRunning;
119 
120     private ArrayList<BluetoothOppShareInfo> mShares;
121 
122     private ArrayList<BluetoothOppBatch> mBatches;
123 
124     private BluetoothOppTransfer mTransfer;
125 
126     private BluetoothOppTransfer mServerTransfer;
127 
128     private int mBatchId;
129 
130     /**
131      * Array used when extracting strings from content provider
132      */
133     private CharArrayBuffer mOldChars;
134     /**
135      * Array used when extracting strings from content provider
136      */
137     private CharArrayBuffer mNewChars;
138 
139     private boolean mListenStarted;
140 
141     private boolean mMediaScanInProgress;
142 
143     private int mIncomingRetries;
144 
145     private ObexTransport mPendingConnection;
146 
147     private int mOppSdpHandle = -1;
148 
149     boolean mAcceptNewConnections;
150 
151     private BluetoothAdapter mAdapter;
152 
153     private static final String INVISIBLE =
154             BluetoothShare.VISIBILITY + "=" + BluetoothShare.VISIBILITY_HIDDEN;
155 
156     private static final String WHERE_INBOUND_SUCCESS =
157             BluetoothShare.DIRECTION + "=" + BluetoothShare.DIRECTION_INBOUND + " AND "
158                     + BluetoothShare.STATUS + "=" + BluetoothShare.STATUS_SUCCESS + " AND "
159                     + INVISIBLE;
160 
161     private static final String WHERE_CONFIRM_PENDING_INBOUND =
162             BluetoothShare.DIRECTION + "=" + BluetoothShare.DIRECTION_INBOUND + " AND "
163                     + BluetoothShare.USER_CONFIRMATION + "="
164                     + BluetoothShare.USER_CONFIRMATION_PENDING;
165 
166     private static final String WHERE_INVISIBLE_UNCONFIRMED =
167             "(" + BluetoothShare.STATUS + ">=" + BluetoothShare.STATUS_SUCCESS + " AND " + INVISIBLE
168                     + ") OR (" + WHERE_CONFIRM_PENDING_INBOUND + ")";
169 
170     private static BluetoothOppService sBluetoothOppService;
171 
172     /*
173      * TODO No support for queue incoming from multiple devices.
174      * Make an array list of server session to support receiving queue from
175      * multiple devices
176      */
177     private BluetoothOppObexServerSession mServerSession;
178 
179     @Override
initBinder()180     protected IProfileServiceBinder initBinder() {
181         return new OppBinder(this);
182     }
183 
184     private static class OppBinder extends Binder implements IProfileServiceBinder {
185 
OppBinder(BluetoothOppService service)186         OppBinder(BluetoothOppService service) {
187         }
188 
189         @Override
cleanup()190         public void cleanup() {
191         }
192     }
193 
194     @Override
create()195     protected void create() {
196         if (V) {
197             Log.v(TAG, "onCreate");
198         }
199         mShares = Lists.newArrayList();
200         mBatches = Lists.newArrayList();
201         mBatchId = 1;
202         final ContentResolver contentResolver = getContentResolver();
203         new Thread("trimDatabase") {
204             @Override
205             public void run() {
206                 trimDatabase(contentResolver);
207             }
208         }.start();
209 
210         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
211         registerReceiver(mBluetoothReceiver, filter);
212 
213         mAdapter = BluetoothAdapter.getDefaultAdapter();
214         synchronized (BluetoothOppService.this) {
215             if (mAdapter == null) {
216                 Log.w(TAG, "Local BT device is not enabled");
217             }
218         }
219         if (V) {
220             BluetoothOppPreference preference = BluetoothOppPreference.getInstance(this);
221             if (preference != null) {
222                 preference.dump();
223             } else {
224                 Log.w(TAG, "BluetoothOppPreference.getInstance returned null.");
225             }
226         }
227     }
228 
229     @Override
start()230     public boolean start() {
231         if (V) {
232             Log.v(TAG, "start()");
233         }
234         mObserver = new BluetoothShareContentObserver();
235         getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver);
236         mNotifier = new BluetoothOppNotification(this);
237         mNotifier.mNotificationMgr.cancelAll();
238         mNotifier.updateNotification();
239         updateFromProvider();
240         setBluetoothOppService(this);
241         return true;
242     }
243 
244     @Override
stop()245     public boolean stop() {
246         if (sBluetoothOppService == null) {
247             Log.w(TAG, "stop() called before start()");
248             return true;
249         }
250         setBluetoothOppService(null);
251         mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
252         return true;
253     }
254 
startListener()255     private void startListener() {
256         if (!mListenStarted) {
257             if (mAdapter.isEnabled()) {
258                 if (V) {
259                     Log.v(TAG, "Starting RfcommListener");
260                 }
261                 mHandler.sendMessage(mHandler.obtainMessage(START_LISTENER));
262                 mListenStarted = true;
263             }
264         }
265     }
266 
267     @Override
dump(StringBuilder sb)268     public void dump(StringBuilder sb) {
269         super.dump(sb);
270         if (mShares.size() > 0) {
271             println(sb, "Shares:");
272             for (BluetoothOppShareInfo info : mShares) {
273                 String dir = info.mDirection == BluetoothShare.DIRECTION_OUTBOUND ? " -> " : " <- ";
274                 SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss", Locale.US);
275                 Date date = new Date(info.mTimestamp);
276                 println(sb, "  " + format.format(date) + dir + info.mCurrentBytes + "/"
277                         + info.mTotalBytes);
278             }
279         }
280     }
281 
282     /**
283      * Get the current instance of {@link BluetoothOppService}
284      *
285      * @return current instance of {@link BluetoothOppService}
286      */
287     @VisibleForTesting
getBluetoothOppService()288     public static synchronized BluetoothOppService getBluetoothOppService() {
289         if (sBluetoothOppService == null) {
290             Log.w(TAG, "getBluetoothOppService(): service is null");
291             return null;
292         }
293         if (!sBluetoothOppService.isAvailable()) {
294             Log.w(TAG, "getBluetoothOppService(): service is not available");
295             return null;
296         }
297         return sBluetoothOppService;
298     }
299 
setBluetoothOppService(BluetoothOppService instance)300     private static synchronized void setBluetoothOppService(BluetoothOppService instance) {
301         if (D) {
302             Log.d(TAG, "setBluetoothOppService(): set to: " + instance);
303         }
304         sBluetoothOppService = instance;
305     }
306 
307     private static final int START_LISTENER = 1;
308 
309     private static final int MEDIA_SCANNED = 2;
310 
311     private static final int MEDIA_SCANNED_FAILED = 3;
312 
313     private static final int MSG_INCOMING_CONNECTION_RETRY = 4;
314 
315     private static final int MSG_INCOMING_BTOPP_CONNECTION = 100;
316 
317     private static final int STOP_LISTENER = 200;
318 
319     private Handler mHandler = new Handler() {
320         @Override
321         public void handleMessage(Message msg) {
322             switch (msg.what) {
323                 case STOP_LISTENER:
324                     stopListeners();
325                     mListenStarted = false;
326                     //Stop Active INBOUND Transfer
327                     if (mServerTransfer != null) {
328                         mServerTransfer.onBatchCanceled();
329                         mServerTransfer = null;
330                     }
331                     //Stop Active OUTBOUND Transfer
332                     if (mTransfer != null) {
333                         mTransfer.onBatchCanceled();
334                         mTransfer = null;
335                     }
336                     unregisterReceivers();
337                     synchronized (BluetoothOppService.this) {
338                         if (mUpdateThread != null) {
339                             mUpdateThread.interrupt();
340                         }
341                     }
342                     while (mUpdateThread != null && mUpdateThreadRunning) {
343                         try {
344                             Thread.sleep(50);
345                         } catch (Exception e) {
346                             Log.e(TAG, "Thread sleep", e);
347                         }
348                     }
349                     synchronized (BluetoothOppService.this) {
350                         if (mUpdateThread != null) {
351                             try {
352                                 mUpdateThread.join();
353                             } catch (InterruptedException e) {
354                                 Log.e(TAG, "Interrupted", e);
355                             }
356                             mUpdateThread = null;
357                         }
358                     }
359 
360                     mNotifier.cancelNotifications();
361                     break;
362                 case START_LISTENER:
363                     if (mAdapter.isEnabled()) {
364                         startSocketListener();
365                     }
366                     break;
367                 case MEDIA_SCANNED:
368                     if (V) {
369                         Log.v(TAG, "Update mInfo.id " + msg.arg1 + " for data uri= "
370                                 + msg.obj.toString());
371                     }
372                     ContentValues updateValues = new ContentValues();
373                     Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + msg.arg1);
374                     updateValues.put(Constants.MEDIA_SCANNED, Constants.MEDIA_SCANNED_SCANNED_OK);
375                     updateValues.put(BluetoothShare.URI, msg.obj.toString()); // update
376                     updateValues.put(BluetoothShare.MIMETYPE,
377                             getContentResolver().getType(Uri.parse(msg.obj.toString())));
378                     getContentResolver().update(contentUri, updateValues, null, null);
379                     synchronized (BluetoothOppService.this) {
380                         mMediaScanInProgress = false;
381                     }
382                     break;
383                 case MEDIA_SCANNED_FAILED:
384                     Log.v(TAG, "Update mInfo.id " + msg.arg1 + " for MEDIA_SCANNED_FAILED");
385                     ContentValues updateValues1 = new ContentValues();
386                     Uri contentUri1 = Uri.parse(BluetoothShare.CONTENT_URI + "/" + msg.arg1);
387                     updateValues1.put(Constants.MEDIA_SCANNED,
388                             Constants.MEDIA_SCANNED_SCANNED_FAILED);
389                     getContentResolver().update(contentUri1, updateValues1, null, null);
390                     synchronized (BluetoothOppService.this) {
391                         mMediaScanInProgress = false;
392                     }
393                     break;
394                 case MSG_INCOMING_BTOPP_CONNECTION:
395                     if (D) {
396                         Log.d(TAG, "Get incoming connection");
397                     }
398                     ObexTransport transport = (ObexTransport) msg.obj;
399 
400                     /*
401                      * Strategy for incoming connections:
402                      * 1. If there is no ongoing transfer, no on-hold connection, start it
403                      * 2. If there is ongoing transfer, hold it for 20 seconds(1 seconds * 20 times)
404                      * 3. If there is on-hold connection, reject directly
405                      */
406                     if (mBatches.size() == 0 && mPendingConnection == null) {
407                         Log.i(TAG, "Start Obex Server");
408                         createServerSession(transport);
409                     } else {
410                         if (mPendingConnection != null) {
411                             Log.w(TAG, "OPP busy! Reject connection");
412                             try {
413                                 transport.close();
414                             } catch (IOException e) {
415                                 Log.e(TAG, "close tranport error");
416                             }
417                         } else {
418                             Log.i(TAG, "OPP busy! Retry after 1 second");
419                             mIncomingRetries = mIncomingRetries + 1;
420                             mPendingConnection = transport;
421                             Message msg1 = Message.obtain(mHandler);
422                             msg1.what = MSG_INCOMING_CONNECTION_RETRY;
423                             mHandler.sendMessageDelayed(msg1, 1000);
424                         }
425                     }
426                     break;
427                 case MSG_INCOMING_CONNECTION_RETRY:
428                     if (mBatches.size() == 0) {
429                         Log.i(TAG, "Start Obex Server");
430                         createServerSession(mPendingConnection);
431                         mIncomingRetries = 0;
432                         mPendingConnection = null;
433                     } else {
434                         if (mIncomingRetries == 20) {
435                             Log.w(TAG, "Retried 20 seconds, reject connection");
436                             try {
437                                 mPendingConnection.close();
438                             } catch (IOException e) {
439                                 Log.e(TAG, "close tranport error");
440                             }
441                             if (mServerSocket != null) {
442                                 acceptNewConnections();
443                             }
444                             mIncomingRetries = 0;
445                             mPendingConnection = null;
446                         } else {
447                             Log.i(TAG, "OPP busy! Retry after 1 second");
448                             mIncomingRetries = mIncomingRetries + 1;
449                             Message msg2 = Message.obtain(mHandler);
450                             msg2.what = MSG_INCOMING_CONNECTION_RETRY;
451                             mHandler.sendMessageDelayed(msg2, 1000);
452                         }
453                     }
454                     break;
455             }
456         }
457     };
458 
459     private ObexServerSockets mServerSocket;
460 
startSocketListener()461     private void startSocketListener() {
462         if (D) {
463             Log.d(TAG, "start Socket Listeners");
464         }
465         stopListeners();
466         mServerSocket = ObexServerSockets.createInsecure(this);
467         acceptNewConnections();
468         SdpManager sdpManager = SdpManager.getDefaultManager();
469         if (sdpManager == null || mServerSocket == null) {
470             Log.e(TAG, "ERROR:serversocket object is NULL  sdp manager :" + sdpManager
471                     + " mServerSocket:" + mServerSocket);
472             return;
473         }
474         mOppSdpHandle =
475                 sdpManager.createOppOpsRecord("OBEX Object Push", mServerSocket.getRfcommChannel(),
476                         mServerSocket.getL2capPsm(), 0x0102, SUPPORTED_OPP_FORMAT);
477         if (D) {
478             Log.d(TAG, "mOppSdpHandle :" + mOppSdpHandle);
479         }
480     }
481 
482     @Override
cleanup()483     protected void cleanup() {
484         if (V) {
485             Log.v(TAG, "onDestroy");
486         }
487         stopListeners();
488         if (mBatches != null) {
489             mBatches.clear();
490         }
491         if (mShares != null) {
492             mShares.clear();
493         }
494         if (mHandler != null) {
495             mHandler.removeCallbacksAndMessages(null);
496         }
497     }
498 
unregisterReceivers()499     private void unregisterReceivers() {
500         try {
501             if (mObserver != null) {
502                 getContentResolver().unregisterContentObserver(mObserver);
503                 mObserver = null;
504             }
505             unregisterReceiver(mBluetoothReceiver);
506         } catch (IllegalArgumentException e) {
507             Log.w(TAG, "unregisterReceivers " + e.toString());
508         }
509     }
510 
511     /* suppose we auto accept an incoming OPUSH connection */
createServerSession(ObexTransport transport)512     private void createServerSession(ObexTransport transport) {
513         mServerSession = new BluetoothOppObexServerSession(this, transport, this);
514         mServerSession.preStart();
515         if (D) {
516             Log.d(TAG, "Get ServerSession " + mServerSession.toString() + " for incoming connection"
517                     + transport.toString());
518         }
519     }
520 
521     private final BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
522         @Override
523         public void onReceive(Context context, Intent intent) {
524             String action = intent.getAction();
525 
526             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
527                 switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
528                     case BluetoothAdapter.STATE_ON:
529                         if (V) {
530                             Log.v(TAG, "Bluetooth state changed: STATE_ON");
531                         }
532                         startListener();
533                         // If this is within a sending process, continue the handle
534                         // logic to display device picker dialog.
535                         synchronized (this) {
536                             if (BluetoothOppManager.getInstance(context).mSendingFlag) {
537                                 // reset the flags
538                                 BluetoothOppManager.getInstance(context).mSendingFlag = false;
539 
540                                 Intent in1 = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
541                                 in1.putExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
542                                 in1.putExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
543                                         BluetoothDevicePicker.FILTER_TYPE_TRANSFER);
544                                 in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE,
545                                         Constants.THIS_PACKAGE_NAME);
546                                 in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS,
547                                         BluetoothOppReceiver.class.getName());
548 
549                                 in1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
550                                 context.startActivity(in1);
551                             }
552                         }
553 
554                         break;
555                     case BluetoothAdapter.STATE_TURNING_OFF:
556                         if (V) {
557                             Log.v(TAG, "Bluetooth state changed: STATE_TURNING_OFF");
558                         }
559                         mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
560                         break;
561                 }
562             }
563         }
564     };
565 
updateFromProvider()566     private void updateFromProvider() {
567         synchronized (BluetoothOppService.this) {
568             mPendingUpdate = true;
569             if (mUpdateThread == null) {
570                 mUpdateThread = new UpdateThread();
571                 mUpdateThread.start();
572                 mUpdateThreadRunning = true;
573             }
574         }
575     }
576 
577     private class UpdateThread extends Thread {
578         private boolean mIsInterrupted;
579 
UpdateThread()580         UpdateThread() {
581             super("Bluetooth Share Service");
582             mIsInterrupted = false;
583         }
584 
585         @Override
interrupt()586         public void interrupt() {
587             mIsInterrupted = true;
588             if (D) {
589                 Log.d(TAG, "OPP UpdateThread interrupted ");
590             }
591             super.interrupt();
592         }
593 
594 
595         @Override
run()596         public void run() {
597             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
598 
599             while (!mIsInterrupted) {
600                 synchronized (BluetoothOppService.this) {
601                     if (mUpdateThread != this) {
602                         mUpdateThreadRunning = false;
603                         throw new IllegalStateException(
604                                 "multiple UpdateThreads in BluetoothOppService");
605                     }
606                     if (V) {
607                         Log.v(TAG, "pendingUpdate is " + mPendingUpdate + " sListenStarted is "
608                                 + mListenStarted + " isInterrupted :" + mIsInterrupted);
609                     }
610                     if (!mPendingUpdate) {
611                         mUpdateThread = null;
612                         mUpdateThreadRunning = false;
613                         return;
614                     }
615                     mPendingUpdate = false;
616                 }
617                 Cursor cursor =
618                         getContentResolver().query(BluetoothShare.CONTENT_URI, null, null, null,
619                                 BluetoothShare._ID);
620 
621                 if (cursor == null) {
622                     mUpdateThreadRunning = false;
623                     return;
624                 }
625 
626                 cursor.moveToFirst();
627 
628                 int arrayPos = 0;
629 
630                 boolean isAfterLast = cursor.isAfterLast();
631 
632                 int idColumn = cursor.getColumnIndexOrThrow(BluetoothShare._ID);
633                 /*
634                  * Walk the cursor and the local array to keep them in sync. The
635                  * key to the algorithm is that the ids are unique and sorted
636                  * both in the cursor and in the array, so that they can be
637                  * processed in order in both sources at the same time: at each
638                  * step, both sources point to the lowest id that hasn't been
639                  * processed from that source, and the algorithm processes the
640                  * lowest id from those two possibilities. At each step: -If the
641                  * array contains an entry that's not in the cursor, remove the
642                  * entry, move to next entry in the array. -If the array
643                  * contains an entry that's in the cursor, nothing to do, move
644                  * to next cursor row and next array entry. -If the cursor
645                  * contains an entry that's not in the array, insert a new entry
646                  * in the array, move to next cursor row and next array entry.
647                  */
648                 while (!isAfterLast || arrayPos < mShares.size() && mListenStarted) {
649                     if (isAfterLast) {
650                         // We're beyond the end of the cursor but there's still some
651                         // stuff in the local array, which can only be junk
652                         if (mShares.size() != 0) {
653                             if (V) {
654                                 Log.v(TAG, "Array update: trimming " + mShares.get(arrayPos).mId
655                                         + " @ " + arrayPos);
656                             }
657                         }
658 
659                         deleteShare(arrayPos); // this advances in the array
660                     } else {
661                         int id = cursor.getInt(idColumn);
662 
663                         if (arrayPos == mShares.size()) {
664                             insertShare(cursor, arrayPos);
665                             if (V) {
666                                 Log.v(TAG, "Array update: inserting " + id + " @ " + arrayPos);
667                             }
668                             ++arrayPos;
669                             cursor.moveToNext();
670                             isAfterLast = cursor.isAfterLast();
671                         } else {
672                             int arrayId = 0;
673                             if (mShares.size() != 0) {
674                                 arrayId = mShares.get(arrayPos).mId;
675                             }
676 
677                             if (arrayId < id) {
678                                 if (V) {
679                                     Log.v(TAG,
680                                             "Array update: removing " + arrayId + " @ " + arrayPos);
681                                 }
682                                 deleteShare(arrayPos);
683                             } else if (arrayId == id) {
684                                 // This cursor row already exists in the stored array.
685                                 updateShare(cursor, arrayPos);
686                                 scanFileIfNeeded(arrayPos);
687                                 ++arrayPos;
688                                 cursor.moveToNext();
689                                 isAfterLast = cursor.isAfterLast();
690                             } else {
691                                 // This cursor entry didn't exist in the stored
692                                 // array
693                                 if (V) {
694                                     Log.v(TAG, "Array update: appending " + id + " @ " + arrayPos);
695                                 }
696                                 insertShare(cursor, arrayPos);
697 
698                                 ++arrayPos;
699                                 cursor.moveToNext();
700                                 isAfterLast = cursor.isAfterLast();
701                             }
702                         }
703                     }
704                 }
705 
706                 mNotifier.updateNotification();
707 
708                 cursor.close();
709             }
710 
711             mUpdateThreadRunning = false;
712         }
713     }
714 
insertShare(Cursor cursor, int arrayPos)715     private void insertShare(Cursor cursor, int arrayPos) {
716         String uriString = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.URI));
717         Uri uri;
718         if (uriString != null) {
719             uri = Uri.parse(uriString);
720             Log.d(TAG, "insertShare parsed URI: " + uri);
721         } else {
722             uri = null;
723             Log.e(TAG, "insertShare found null URI at cursor!");
724         }
725         BluetoothOppShareInfo info = new BluetoothOppShareInfo(
726                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)), uri,
727                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT)),
728                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare._DATA)),
729                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.MIMETYPE)),
730                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION)),
731                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.DESTINATION)),
732                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.VISIBILITY)),
733                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION)),
734                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS)),
735                 cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES)),
736                 cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES)),
737                 cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)),
738                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED))
739                         != Constants.MEDIA_SCANNED_NOT_SCANNED);
740 
741         if (V) {
742             Log.v(TAG, "Service adding new entry");
743             Log.v(TAG, "ID      : " + info.mId);
744             // Log.v(TAG, "URI     : " + ((info.mUri != null) ? "yes" : "no"));
745             Log.v(TAG, "URI     : " + info.mUri);
746             Log.v(TAG, "HINT    : " + info.mHint);
747             Log.v(TAG, "FILENAME: " + info.mFilename);
748             Log.v(TAG, "MIMETYPE: " + info.mMimetype);
749             Log.v(TAG, "DIRECTION: " + info.mDirection);
750             Log.v(TAG, "DESTINAT: " + info.mDestination);
751             Log.v(TAG, "VISIBILI: " + info.mVisibility);
752             Log.v(TAG, "CONFIRM : " + info.mConfirm);
753             Log.v(TAG, "STATUS  : " + info.mStatus);
754             Log.v(TAG, "TOTAL   : " + info.mTotalBytes);
755             Log.v(TAG, "CURRENT : " + info.mCurrentBytes);
756             Log.v(TAG, "TIMESTAMP : " + info.mTimestamp);
757             Log.v(TAG, "SCANNED : " + info.mMediaScanned);
758         }
759 
760         mShares.add(arrayPos, info);
761 
762         /* Mark the info as failed if it's in invalid status */
763         if (info.isObsolete()) {
764             Constants.updateShareStatus(this, info.mId, BluetoothShare.STATUS_UNKNOWN_ERROR);
765         }
766         /*
767          * Add info into a batch. The logic is
768          * 1) Only add valid and readyToStart info
769          * 2) If there is no batch, create a batch and insert this transfer into batch,
770          * then run the batch
771          * 3) If there is existing batch and timestamp match, insert transfer into batch
772          * 4) If there is existing batch and timestamp does not match, create a new batch and
773          * put in queue
774          */
775 
776         if (info.isReadyToStart()) {
777             if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
778                 /* check if the file exists */
779                 BluetoothOppSendFileInfo sendFileInfo =
780                         BluetoothOppUtility.getSendFileInfo(info.mUri);
781                 if (sendFileInfo == null || sendFileInfo.mInputStream == null) {
782                     Log.e(TAG, "Can't open file for OUTBOUND info " + info.mId);
783                     Constants.updateShareStatus(this, info.mId, BluetoothShare.STATUS_BAD_REQUEST);
784                     BluetoothOppUtility.closeSendFileInfo(info.mUri);
785                     return;
786                 }
787             }
788             if (mBatches.size() == 0) {
789                 BluetoothOppBatch newBatch = new BluetoothOppBatch(this, info);
790                 newBatch.mId = mBatchId;
791                 mBatchId++;
792                 mBatches.add(newBatch);
793                 if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
794                     if (V) {
795                         Log.v(TAG,
796                                 "Service create new Batch " + newBatch.mId + " for OUTBOUND info "
797                                         + info.mId);
798                     }
799                     mTransfer = new BluetoothOppTransfer(this, newBatch);
800                 } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {
801                     if (V) {
802                         Log.v(TAG, "Service create new Batch " + newBatch.mId + " for INBOUND info "
803                                 + info.mId);
804                     }
805                     mServerTransfer = new BluetoothOppTransfer(this, newBatch, mServerSession);
806                 }
807 
808                 if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND && mTransfer != null) {
809                     if (V) {
810                         Log.v(TAG, "Service start transfer new Batch " + newBatch.mId + " for info "
811                                 + info.mId);
812                     }
813                     mTransfer.start();
814                 } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND
815                         && mServerTransfer != null) {
816                     if (V) {
817                         Log.v(TAG, "Service start server transfer new Batch " + newBatch.mId
818                                 + " for info " + info.mId);
819                     }
820                     mServerTransfer.start();
821                 }
822 
823             } else {
824                 int i = findBatchWithTimeStamp(info.mTimestamp);
825                 if (i != -1) {
826                     if (V) {
827                         Log.v(TAG, "Service add info " + info.mId + " to existing batch " + mBatches
828                                 .get(i).mId);
829                     }
830                     mBatches.get(i).addShare(info);
831                 } else {
832                     // There is ongoing batch
833                     BluetoothOppBatch newBatch = new BluetoothOppBatch(this, info);
834                     newBatch.mId = mBatchId;
835                     mBatchId++;
836                     mBatches.add(newBatch);
837                     if (V) {
838                         Log.v(TAG,
839                                 "Service add new Batch " + newBatch.mId + " for info " + info.mId);
840                     }
841                 }
842             }
843         }
844     }
845 
updateShare(Cursor cursor, int arrayPos)846     private void updateShare(Cursor cursor, int arrayPos) {
847         BluetoothOppShareInfo info = mShares.get(arrayPos);
848         int statusColumn = cursor.getColumnIndexOrThrow(BluetoothShare.STATUS);
849 
850         info.mId = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));
851         if (info.mUri != null) {
852             info.mUri =
853                     Uri.parse(stringFromCursor(info.mUri.toString(), cursor, BluetoothShare.URI));
854         } else {
855             Log.w(TAG, "updateShare() called for ID " + info.mId + " with null URI");
856         }
857         info.mHint = stringFromCursor(info.mHint, cursor, BluetoothShare.FILENAME_HINT);
858         info.mFilename = stringFromCursor(info.mFilename, cursor, BluetoothShare._DATA);
859         info.mMimetype = stringFromCursor(info.mMimetype, cursor, BluetoothShare.MIMETYPE);
860         info.mDirection = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION));
861         info.mDestination = stringFromCursor(info.mDestination, cursor, BluetoothShare.DESTINATION);
862         int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.VISIBILITY));
863 
864         boolean confirmUpdated = false;
865         int newConfirm =
866                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION));
867 
868         if (info.mVisibility == BluetoothShare.VISIBILITY_VISIBLE
869                 && newVisibility != BluetoothShare.VISIBILITY_VISIBLE && (
870                 BluetoothShare.isStatusCompleted(info.mStatus)
871                         || newConfirm == BluetoothShare.USER_CONFIRMATION_PENDING)) {
872             mNotifier.mNotificationMgr.cancel(info.mId);
873         }
874 
875         info.mVisibility = newVisibility;
876 
877         if (info.mConfirm == BluetoothShare.USER_CONFIRMATION_PENDING
878                 && newConfirm != BluetoothShare.USER_CONFIRMATION_PENDING) {
879             confirmUpdated = true;
880         }
881         info.mConfirm =
882                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION));
883         int newStatus = cursor.getInt(statusColumn);
884 
885         if (BluetoothShare.isStatusCompleted(info.mStatus)) {
886             mNotifier.mNotificationMgr.cancel(info.mId);
887         }
888 
889         info.mStatus = newStatus;
890         info.mTotalBytes = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES));
891         info.mCurrentBytes =
892                 cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES));
893         info.mTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
894         info.mMediaScanned = (cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED))
895                 != Constants.MEDIA_SCANNED_NOT_SCANNED);
896 
897         if (confirmUpdated) {
898             if (V) {
899                 Log.v(TAG, "Service handle info " + info.mId + " confirmation updated");
900             }
901             /* Inbounds transfer user confirmation status changed, update the session server */
902             int i = findBatchWithTimeStamp(info.mTimestamp);
903             if (i != -1) {
904                 BluetoothOppBatch batch = mBatches.get(i);
905                 if (mServerTransfer != null && batch.mId == mServerTransfer.getBatchId()) {
906                     mServerTransfer.confirmStatusChanged();
907                 } //TODO need to think about else
908             }
909         }
910         int i = findBatchWithTimeStamp(info.mTimestamp);
911         if (i != -1) {
912             BluetoothOppBatch batch = mBatches.get(i);
913             if (batch.mStatus == Constants.BATCH_STATUS_FINISHED
914                     || batch.mStatus == Constants.BATCH_STATUS_FAILED) {
915                 if (V) {
916                     Log.v(TAG, "Batch " + batch.mId + " is finished");
917                 }
918                 if (batch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
919                     if (mTransfer == null) {
920                         Log.e(TAG, "Unexpected error! mTransfer is null");
921                     } else if (batch.mId == mTransfer.getBatchId()) {
922                         mTransfer.stop();
923                     } else {
924                         Log.e(TAG, "Unexpected error! batch id " + batch.mId
925                                 + " doesn't match mTransfer id " + mTransfer.getBatchId());
926                     }
927                     mTransfer = null;
928                 } else {
929                     if (mServerTransfer == null) {
930                         Log.e(TAG, "Unexpected error! mServerTransfer is null");
931                     } else if (batch.mId == mServerTransfer.getBatchId()) {
932                         mServerTransfer.stop();
933                     } else {
934                         Log.e(TAG, "Unexpected error! batch id " + batch.mId
935                                 + " doesn't match mServerTransfer id "
936                                 + mServerTransfer.getBatchId());
937                     }
938                     mServerTransfer = null;
939                 }
940                 removeBatch(batch);
941             }
942         }
943     }
944 
945     /**
946      * Removes the local copy of the info about a share.
947      */
deleteShare(int arrayPos)948     private void deleteShare(int arrayPos) {
949         BluetoothOppShareInfo info = mShares.get(arrayPos);
950 
951         /*
952          * Delete arrayPos from a batch. The logic is
953          * 1) Search existing batch for the info
954          * 2) cancel the batch
955          * 3) If the batch become empty delete the batch
956          */
957         int i = findBatchWithTimeStamp(info.mTimestamp);
958         if (i != -1) {
959             BluetoothOppBatch batch = mBatches.get(i);
960             if (batch.hasShare(info)) {
961                 if (V) {
962                     Log.v(TAG, "Service cancel batch for share " + info.mId);
963                 }
964                 batch.cancelBatch();
965             }
966             if (batch.isEmpty()) {
967                 if (V) {
968                     Log.v(TAG, "Service remove batch  " + batch.mId);
969                 }
970                 removeBatch(batch);
971             }
972         }
973         mShares.remove(arrayPos);
974     }
975 
stringFromCursor(String old, Cursor cursor, String column)976     private String stringFromCursor(String old, Cursor cursor, String column) {
977         int index = cursor.getColumnIndexOrThrow(column);
978         if (old == null) {
979             return cursor.getString(index);
980         }
981         if (mNewChars == null) {
982             mNewChars = new CharArrayBuffer(128);
983         }
984         cursor.copyStringToBuffer(index, mNewChars);
985         int length = mNewChars.sizeCopied;
986         if (length != old.length()) {
987             return cursor.getString(index);
988         }
989         if (mOldChars == null || mOldChars.sizeCopied < length) {
990             mOldChars = new CharArrayBuffer(length);
991         }
992         char[] oldArray = mOldChars.data;
993         char[] newArray = mNewChars.data;
994         old.getChars(0, length, oldArray, 0);
995         for (int i = length - 1; i >= 0; --i) {
996             if (oldArray[i] != newArray[i]) {
997                 return new String(newArray, 0, length);
998             }
999         }
1000         return old;
1001     }
1002 
findBatchWithTimeStamp(long timestamp)1003     private int findBatchWithTimeStamp(long timestamp) {
1004         for (int i = mBatches.size() - 1; i >= 0; i--) {
1005             if (mBatches.get(i).mTimestamp == timestamp) {
1006                 return i;
1007             }
1008         }
1009         return -1;
1010     }
1011 
removeBatch(BluetoothOppBatch batch)1012     private void removeBatch(BluetoothOppBatch batch) {
1013         if (V) {
1014             Log.v(TAG, "Remove batch " + batch.mId);
1015         }
1016         mBatches.remove(batch);
1017         if (mBatches.size() > 0) {
1018             for (BluetoothOppBatch nextBatch : mBatches) {
1019                 // we have a running batch
1020                 if (nextBatch.mStatus == Constants.BATCH_STATUS_RUNNING) {
1021                     return;
1022                 } else {
1023                     // just finish a transfer, start pending outbound transfer
1024                     if (nextBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
1025                         if (V) {
1026                             Log.v(TAG, "Start pending outbound batch " + nextBatch.mId);
1027                         }
1028                         mTransfer = new BluetoothOppTransfer(this, nextBatch);
1029                         mTransfer.start();
1030                         return;
1031                     } else if (nextBatch.mDirection == BluetoothShare.DIRECTION_INBOUND
1032                             && mServerSession != null) {
1033                         // have to support pending inbound transfer
1034                         // if an outbound transfer and incoming socket happens together
1035                         if (V) {
1036                             Log.v(TAG, "Start pending inbound batch " + nextBatch.mId);
1037                         }
1038                         mServerTransfer = new BluetoothOppTransfer(this, nextBatch, mServerSession);
1039                         mServerTransfer.start();
1040                         if (nextBatch.getPendingShare() != null
1041                                 && nextBatch.getPendingShare().mConfirm
1042                                 == BluetoothShare.USER_CONFIRMATION_CONFIRMED) {
1043                             mServerTransfer.confirmStatusChanged();
1044                         }
1045                         return;
1046                     }
1047                 }
1048             }
1049         }
1050     }
1051 
scanFileIfNeeded(int arrayPos)1052     private void scanFileIfNeeded(int arrayPos) {
1053         BluetoothOppShareInfo info = mShares.get(arrayPos);
1054         boolean isFileReceived = BluetoothShare.isStatusSuccess(info.mStatus)
1055                 && info.mDirection == BluetoothShare.DIRECTION_INBOUND && !info.mMediaScanned
1056                 && info.mConfirm != BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED;
1057         if (!isFileReceived) {
1058             return;
1059         }
1060         synchronized (BluetoothOppService.this) {
1061             if (D) {
1062                 Log.d(TAG, "Scanning file " + info.mFilename);
1063             }
1064             if (!mMediaScanInProgress) {
1065                 mMediaScanInProgress = true;
1066                 new MediaScannerNotifier(this, info, mHandler);
1067             }
1068         }
1069     }
1070 
1071     // Run in a background thread at boot.
trimDatabase(ContentResolver contentResolver)1072     private static void trimDatabase(ContentResolver contentResolver) {
1073         // remove the invisible/unconfirmed inbound shares
1074         int delNum = contentResolver.delete(BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED,
1075                 null);
1076         if (V) {
1077             Log.v(TAG, "Deleted shares, number = " + delNum);
1078         }
1079 
1080         // Keep the latest inbound and successful shares.
1081         Cursor cursor =
1082                 contentResolver.query(BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID},
1083                         WHERE_INBOUND_SUCCESS, null, BluetoothShare._ID); // sort by id
1084         if (cursor == null) {
1085             return;
1086         }
1087         int recordNum = cursor.getCount();
1088         if (recordNum > Constants.MAX_RECORDS_IN_DATABASE) {
1089             int numToDelete = recordNum - Constants.MAX_RECORDS_IN_DATABASE;
1090 
1091             if (cursor.moveToPosition(numToDelete)) {
1092                 int columnId = cursor.getColumnIndexOrThrow(BluetoothShare._ID);
1093                 long id = cursor.getLong(columnId);
1094                 delNum = contentResolver.delete(BluetoothShare.CONTENT_URI,
1095                         BluetoothShare._ID + " < " + id, null);
1096                 if (V) {
1097                     Log.v(TAG, "Deleted old inbound success share: " + delNum);
1098                 }
1099             }
1100         }
1101         cursor.close();
1102     }
1103 
1104     private static class MediaScannerNotifier implements MediaScannerConnectionClient {
1105 
1106         private MediaScannerConnection mConnection;
1107 
1108         private BluetoothOppShareInfo mInfo;
1109 
1110         private Context mContext;
1111 
1112         private Handler mCallback;
1113 
MediaScannerNotifier(Context context, BluetoothOppShareInfo info, Handler handler)1114         MediaScannerNotifier(Context context, BluetoothOppShareInfo info, Handler handler) {
1115             mContext = context;
1116             mInfo = info;
1117             mCallback = handler;
1118             mConnection = new MediaScannerConnection(mContext, this);
1119             if (V) {
1120                 Log.v(TAG, "Connecting to MediaScannerConnection ");
1121             }
1122             mConnection.connect();
1123         }
1124 
1125         @Override
onMediaScannerConnected()1126         public void onMediaScannerConnected() {
1127             if (V) {
1128                 Log.v(TAG, "MediaScannerConnection onMediaScannerConnected");
1129             }
1130             mConnection.scanFile(mInfo.mFilename, mInfo.mMimetype);
1131         }
1132 
1133         @Override
onScanCompleted(String path, Uri uri)1134         public void onScanCompleted(String path, Uri uri) {
1135             try {
1136                 if (V) {
1137                     Log.v(TAG, "MediaScannerConnection onScanCompleted");
1138                     Log.v(TAG, "MediaScannerConnection path is " + path);
1139                     Log.v(TAG, "MediaScannerConnection Uri is " + uri);
1140                 }
1141                 if (uri != null) {
1142                     Message msg = Message.obtain();
1143                     msg.setTarget(mCallback);
1144                     msg.what = MEDIA_SCANNED;
1145                     msg.arg1 = mInfo.mId;
1146                     msg.obj = uri;
1147                     msg.sendToTarget();
1148                 } else {
1149                     Message msg = Message.obtain();
1150                     msg.setTarget(mCallback);
1151                     msg.what = MEDIA_SCANNED_FAILED;
1152                     msg.arg1 = mInfo.mId;
1153                     msg.sendToTarget();
1154                 }
1155             } catch (NullPointerException ex) {
1156                 Log.v(TAG, "!!!MediaScannerConnection exception: " + ex);
1157             } finally {
1158                 if (V) {
1159                     Log.v(TAG, "MediaScannerConnection disconnect");
1160                 }
1161                 mConnection.disconnect();
1162             }
1163         }
1164     }
1165 
stopListeners()1166     private void stopListeners() {
1167         if (mAdapter != null && mOppSdpHandle >= 0 && SdpManager.getDefaultManager() != null) {
1168             if (D) {
1169                 Log.d(TAG, "Removing SDP record mOppSdpHandle :" + mOppSdpHandle);
1170             }
1171             boolean status = SdpManager.getDefaultManager().removeSdpRecord(mOppSdpHandle);
1172             Log.d(TAG, "RemoveSDPrecord returns " + status);
1173             mOppSdpHandle = -1;
1174         }
1175         if (mServerSocket != null) {
1176             mServerSocket.shutdown(false);
1177             mServerSocket = null;
1178         }
1179         if (D) {
1180             Log.d(TAG, "stopListeners: mServerSocket is null");
1181         }
1182     }
1183 
1184     @Override
onConnect(BluetoothDevice device, BluetoothSocket socket)1185     public boolean onConnect(BluetoothDevice device, BluetoothSocket socket) {
1186 
1187         if (D) {
1188             Log.d(TAG, " onConnect BluetoothSocket :" + socket + " \n :device :" + device);
1189         }
1190         if (!mAcceptNewConnections) {
1191             Log.d(TAG, " onConnect BluetoothSocket :" + socket + " rejected");
1192             return false;
1193         }
1194         BluetoothObexTransport transport = new BluetoothObexTransport(socket);
1195         Message msg = mHandler.obtainMessage(MSG_INCOMING_BTOPP_CONNECTION);
1196         msg.obj = transport;
1197         msg.sendToTarget();
1198         mAcceptNewConnections = false;
1199         return true;
1200     }
1201 
1202     @Override
onAcceptFailed()1203     public void onAcceptFailed() {
1204         Log.d(TAG, " onAcceptFailed:");
1205         mHandler.sendMessage(mHandler.obtainMessage(START_LISTENER));
1206     }
1207 
1208     /**
1209      * Set mAcceptNewConnections to true to allow new connections.
1210      */
acceptNewConnections()1211     void acceptNewConnections() {
1212         mAcceptNewConnections = true;
1213     }
1214 }
1215