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