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