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