Home | History | Annotate | Download | only in opp
      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 
     93         BluetoothShareContentObserver() {
     94             super(new Handler());
     95         }
     96 
     97         @Override
     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
    178     protected IProfileServiceBinder initBinder() {
    179         return new OppBinder(this);
    180     }
    181 
    182     private static class OppBinder extends Binder implements IProfileServiceBinder {
    183 
    184         OppBinder(BluetoothOppService service) {
    185         }
    186 
    187         @Override
    188         public void cleanup() {
    189         }
    190     }
    191 
    192     @Override
    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
    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
    243     public boolean stop() {
    244         setBluetoothOppService(null);
    245         mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
    246         return true;
    247     }
    248 
    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
    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
    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 
    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 
    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
    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 
    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 */
    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 
    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 
    561         UpdateThread() {
    562             super("Bluetooth Share Service");
    563             mIsInterrupted = false;
    564         }
    565 
    566         @Override
    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
    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 
    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 
    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      */
    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 
    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 
    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 
    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 
   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 
   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.
   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 
   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
   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
   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 
   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
   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
   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      */
   1197     void acceptNewConnections() {
   1198         mAcceptNewConnections = true;
   1199     }
   1200 }
   1201