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