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