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