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