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