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