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.BluetoothProfile; 36 import android.bluetooth.BluetoothProtoEnums; 37 import android.content.ContentValues; 38 import android.content.Context; 39 import android.net.Uri; 40 import android.os.Handler; 41 import android.os.Message; 42 import android.os.PowerManager; 43 import android.os.PowerManager.WakeLock; 44 import android.os.Process; 45 import android.os.SystemClock; 46 import android.util.Log; 47 48 import com.android.bluetooth.BluetoothMethodProxy; 49 import com.android.bluetooth.BluetoothMetricsProto; 50 import com.android.bluetooth.BluetoothStatsLog; 51 import com.android.bluetooth.btservice.MetricsLogger; 52 import com.android.bluetooth.content_profiles.ContentProfileErrorReportUtils; 53 import com.android.obex.ClientOperation; 54 import com.android.obex.ClientSession; 55 import com.android.obex.HeaderSet; 56 import com.android.obex.ObexTransport; 57 import com.android.obex.ResponseCodes; 58 59 import com.google.common.annotations.VisibleForTesting; 60 61 import java.io.BufferedInputStream; 62 import java.io.IOException; 63 import java.io.InputStream; 64 import java.io.OutputStream; 65 66 /** This class runs as an OBEX client */ 67 // Next tag value for ContentProfileErrorReportUtils.report(): 17 68 public class BluetoothOppObexClientSession implements BluetoothOppObexSession { 69 70 private static final String TAG = "BtOppObexClient"; 71 72 private ClientThread mThread; 73 74 private ObexTransport mTransport; 75 76 private Context mContext; 77 78 private volatile boolean mInterrupted; 79 80 @VisibleForTesting volatile boolean mWaitingForRemote; 81 82 private int mNumFilesAttemptedToSend; 83 BluetoothOppObexClientSession(Context context, ObexTransport transport)84 public BluetoothOppObexClientSession(Context context, ObexTransport transport) { 85 if (transport == null) { 86 throw new NullPointerException("transport is null"); 87 } 88 mContext = context; 89 mTransport = transport; 90 } 91 92 @Override start(Handler handler, int numShares)93 public void start(Handler handler, int numShares) { 94 Log.d(TAG, "Start!"); 95 mThread = new ClientThread(mContext, mTransport, numShares, handler); 96 mThread.start(); 97 } 98 99 @Override stop()100 public void stop() { 101 Log.d(TAG, "Stop!"); 102 if (mThread != null) { 103 mInterrupted = true; 104 Log.v(TAG, "Interrupt thread to terminate it"); 105 mThread.interrupt(); 106 mThread = null; 107 } 108 BluetoothOppUtility.cancelNotification(mContext); 109 } 110 111 @Override addShare(BluetoothOppShareInfo share)112 public void addShare(BluetoothOppShareInfo share) { 113 mThread.addShare(share); 114 } 115 116 @VisibleForTesting readFully(InputStream is, byte[] buffer, int size)117 static int readFully(InputStream is, byte[] buffer, int size) throws IOException { 118 int done = 0; 119 while (done < size) { 120 int got = is.read(buffer, done, size - done); 121 if (got <= 0) { 122 break; 123 } 124 done += got; 125 } 126 return done; 127 } 128 129 @VisibleForTesting 130 class ClientThread extends Thread { 131 132 private static final int SLEEP_TIME = 500; 133 134 private Context mContext1; 135 136 private BluetoothOppShareInfo mInfo; 137 138 private volatile boolean mWaitingForShare; 139 140 private ObexTransport mTransport1; 141 142 @VisibleForTesting ClientSession mCs; 143 144 private WakeLock mWakeLock; 145 146 private BluetoothOppSendFileInfo mFileInfo = null; 147 148 private boolean mConnected = false; 149 150 private int mNumShares; 151 private final Handler mCallbackHandler; 152 ClientThread( Context context, ObexTransport transport, int initialNumShares, Handler callback)153 ClientThread( 154 Context context, ObexTransport transport, int initialNumShares, Handler callback) { 155 super("BtOpp ClientThread"); 156 mContext1 = context; 157 mTransport1 = transport; 158 mWaitingForShare = true; 159 mWaitingForRemote = false; 160 mNumShares = initialNumShares; 161 PowerManager pm = mContext.getSystemService(PowerManager.class); 162 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 163 mCallbackHandler = callback; 164 } 165 addShare(BluetoothOppShareInfo info)166 public void addShare(BluetoothOppShareInfo info) { 167 mInfo = info; 168 mFileInfo = processShareInfo(); 169 mWaitingForShare = false; 170 } 171 172 @Override run()173 public void run() { 174 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 175 176 Log.v(TAG, "acquire partial WakeLock"); 177 mWakeLock.acquire(); 178 179 try { 180 Thread.sleep(100); 181 } catch (InterruptedException e1) { 182 ContentProfileErrorReportUtils.report( 183 BluetoothProfile.OPP, 184 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 185 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 186 0); 187 Log.v(TAG, "Client thread was interrupted (1), exiting"); 188 mInterrupted = true; 189 } 190 if (!mInterrupted) { 191 connect(mNumShares); 192 } 193 194 mNumFilesAttemptedToSend = 0; 195 while (!mInterrupted) { 196 if (!mWaitingForShare) { 197 doSend(); 198 } else { 199 try { 200 Log.d(TAG, "Client thread waiting for next share, sleep for " + SLEEP_TIME); 201 Thread.sleep(SLEEP_TIME); 202 } catch (InterruptedException e) { 203 ContentProfileErrorReportUtils.report( 204 BluetoothProfile.OPP, 205 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 206 BluetoothStatsLog 207 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 208 1); 209 } 210 } 211 } 212 disconnect(); 213 214 if (mWakeLock.isHeld()) { 215 Log.v(TAG, "release partial WakeLock"); 216 mWakeLock.release(); 217 } 218 219 if (mNumFilesAttemptedToSend > 0) { 220 // Log outgoing OPP transfer if more than one file is accepted by remote 221 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.OPP); 222 } 223 Message msg = Message.obtain(mCallbackHandler); 224 msg.what = BluetoothOppObexSession.MSG_SESSION_COMPLETE; 225 msg.obj = mInfo; 226 msg.sendToTarget(); 227 } 228 disconnect()229 private void disconnect() { 230 try { 231 if (mCs != null) { 232 mCs.disconnect(null); 233 } 234 mCs = null; 235 Log.d(TAG, "OBEX session disconnected"); 236 } catch (IOException e) { 237 ContentProfileErrorReportUtils.report( 238 BluetoothProfile.OPP, 239 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 240 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 241 2); 242 Log.w(TAG, "OBEX session disconnect error" + e); 243 } 244 try { 245 if (mCs != null) { 246 Log.d(TAG, "OBEX session close mCs"); 247 mCs.close(); 248 Log.d(TAG, "OBEX session closed"); 249 } 250 } catch (IOException e) { 251 ContentProfileErrorReportUtils.report( 252 BluetoothProfile.OPP, 253 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 254 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 255 3); 256 Log.w(TAG, "OBEX session close error" + e); 257 } 258 if (mTransport1 != null) { 259 try { 260 mTransport1.close(); 261 } catch (IOException e) { 262 ContentProfileErrorReportUtils.report( 263 BluetoothProfile.OPP, 264 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 265 BluetoothStatsLog 266 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 267 4); 268 Log.e(TAG, "mTransport.close error"); 269 } 270 } 271 } 272 connect(int numShares)273 private void connect(int numShares) { 274 Log.d(TAG, "Create ClientSession with transport " + mTransport1.toString()); 275 try { 276 mCs = new ClientSession(mTransport1); 277 mConnected = true; 278 } catch (IOException e1) { 279 ContentProfileErrorReportUtils.report( 280 BluetoothProfile.OPP, 281 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 282 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 283 5); 284 Log.e(TAG, "OBEX session create error"); 285 } 286 if (mConnected) { 287 mConnected = false; 288 HeaderSet hs = new HeaderSet(); 289 hs.setHeader(HeaderSet.COUNT, (long) numShares); 290 synchronized (this) { 291 mWaitingForRemote = true; 292 } 293 try { 294 mCs.connect(hs); 295 Log.d(TAG, "OBEX session created"); 296 mConnected = true; 297 } catch (IOException e) { 298 ContentProfileErrorReportUtils.report( 299 BluetoothProfile.OPP, 300 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 301 BluetoothStatsLog 302 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 303 6); 304 Log.e(TAG, "OBEX session connect error"); 305 } 306 } 307 synchronized (this) { 308 mWaitingForRemote = false; 309 } 310 } 311 doSend()312 private void doSend() { 313 314 int status = BluetoothShare.STATUS_SUCCESS; 315 316 /* connection is established too fast to get first mInfo */ 317 while (mFileInfo == null) { 318 try { 319 Thread.sleep(50); 320 } catch (InterruptedException e) { 321 ContentProfileErrorReportUtils.report( 322 BluetoothProfile.OPP, 323 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 324 BluetoothStatsLog 325 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 326 7); 327 status = BluetoothShare.STATUS_CANCELED; 328 } 329 } 330 if (!mConnected) { 331 // Obex connection error 332 status = BluetoothShare.STATUS_CONNECTION_ERROR; 333 } 334 if (status == BluetoothShare.STATUS_SUCCESS) { 335 /* do real send */ 336 if (mFileInfo.mFileName != null) { 337 status = sendFile(mFileInfo); 338 } else { 339 /* this is invalid request */ 340 status = mFileInfo.mStatus; 341 } 342 mWaitingForShare = true; 343 } else { 344 Constants.updateShareStatus(mContext1, mInfo.mId, status); 345 } 346 347 Message msg = Message.obtain(mCallbackHandler); 348 msg.what = 349 (status == BluetoothShare.STATUS_SUCCESS) 350 ? BluetoothOppObexSession.MSG_SHARE_COMPLETE 351 : BluetoothOppObexSession.MSG_SESSION_ERROR; 352 mInfo.mStatus = status; 353 msg.obj = mInfo; 354 msg.sendToTarget(); 355 } 356 357 /* 358 * Validate this ShareInfo 359 */ processShareInfo()360 private BluetoothOppSendFileInfo processShareInfo() { 361 Log.v(TAG, "Client thread processShareInfo() " + mInfo.mId); 362 363 BluetoothOppSendFileInfo fileInfo = BluetoothOppUtility.getSendFileInfo(mInfo.mUri); 364 if (fileInfo.mFileName == null || fileInfo.mLength == 0) { 365 Log.v(TAG, "BluetoothOppSendFileInfo get invalid file"); 366 Constants.updateShareStatus(mContext1, mInfo.mId, fileInfo.mStatus); 367 368 } else { 369 Log.v(TAG, "Generate BluetoothOppSendFileInfo:"); 370 Log.v(TAG, "filename :" + fileInfo.mFileName); 371 Log.v(TAG, "length :" + fileInfo.mLength); 372 Log.v(TAG, "mimetype :" + fileInfo.mMimetype); 373 374 ContentValues updateValues = new ContentValues(); 375 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId); 376 377 updateValues.put(BluetoothShare.FILENAME_HINT, fileInfo.mFileName); 378 updateValues.put(BluetoothShare.TOTAL_BYTES, fileInfo.mLength); 379 updateValues.put(BluetoothShare.MIMETYPE, fileInfo.mMimetype); 380 BluetoothMethodProxy.getInstance() 381 .contentResolverUpdate( 382 mContext1.getContentResolver(), 383 contentUri, 384 updateValues, 385 null, 386 null); 387 } 388 return fileInfo; 389 } 390 391 @VisibleForTesting sendFile(BluetoothOppSendFileInfo fileInfo)392 int sendFile(BluetoothOppSendFileInfo fileInfo) { 393 boolean error = false; 394 int responseCode = -1; 395 long position = 0; 396 int status = BluetoothShare.STATUS_SUCCESS; 397 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId); 398 ContentValues updateValues; 399 HeaderSet request = new HeaderSet(); 400 ClientOperation putOperation = null; 401 OutputStream outputStream = null; 402 InputStream inputStream = null; 403 try { 404 synchronized (this) { 405 mWaitingForRemote = true; 406 } 407 try { 408 Log.v(TAG, "Set header items for " + fileInfo.mFileName); 409 request.setHeader(HeaderSet.NAME, fileInfo.mFileName); 410 request.setHeader(HeaderSet.TYPE, fileInfo.mMimetype); 411 412 applyRemoteDeviceQuirks(request, mInfo.mDestination, fileInfo.mFileName); 413 Constants.updateShareStatus( 414 mContext1, mInfo.mId, BluetoothShare.STATUS_RUNNING); 415 416 request.setHeader(HeaderSet.LENGTH, fileInfo.mLength); 417 418 Log.v(TAG, "put headerset for " + fileInfo.mFileName); 419 putOperation = (ClientOperation) mCs.put(request); 420 } catch (IllegalArgumentException e) { 421 ContentProfileErrorReportUtils.report( 422 BluetoothProfile.OPP, 423 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 424 BluetoothStatsLog 425 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 426 8); 427 status = BluetoothShare.STATUS_OBEX_DATA_ERROR; 428 Constants.updateShareStatus(mContext1, mInfo.mId, status); 429 430 Log.e(TAG, "Error setting header items for request: " + e); 431 error = true; 432 } catch (IOException e) { 433 ContentProfileErrorReportUtils.report( 434 BluetoothProfile.OPP, 435 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 436 BluetoothStatsLog 437 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 438 9); 439 status = BluetoothShare.STATUS_OBEX_DATA_ERROR; 440 Constants.updateShareStatus(mContext1, mInfo.mId, status); 441 442 Log.e(TAG, "Error when put HeaderSet "); 443 error = true; 444 } 445 synchronized (this) { 446 mWaitingForRemote = false; 447 } 448 449 if (!error) { 450 try { 451 Log.v(TAG, "openOutputStream " + fileInfo.mFileName); 452 outputStream = putOperation.openOutputStream(); 453 inputStream = putOperation.openInputStream(); 454 } catch (IOException e) { 455 ContentProfileErrorReportUtils.report( 456 BluetoothProfile.OPP, 457 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 458 BluetoothStatsLog 459 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 460 10); 461 status = BluetoothShare.STATUS_OBEX_DATA_ERROR; 462 Constants.updateShareStatus(mContext1, mInfo.mId, status); 463 Log.e(TAG, "Error when openOutputStream"); 464 error = true; 465 } 466 } 467 if (!error) { 468 updateValues = new ContentValues(); 469 updateValues.put(BluetoothShare.CURRENT_BYTES, 0); 470 updateValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_RUNNING); 471 mContext1.getContentResolver().update(contentUri, updateValues, null, null); 472 } 473 474 if (!error) { 475 int readLength = 0; 476 long percent = 0; 477 long prevPercent = 0; 478 boolean okToProceed = false; 479 long timestamp = 0; 480 long currentTime = 0; 481 long prevTimestamp = SystemClock.elapsedRealtime(); 482 int outputBufferSize = putOperation.getMaxPacketSize(); 483 byte[] buffer = new byte[outputBufferSize]; 484 BufferedInputStream a = new BufferedInputStream(fileInfo.mInputStream, 0x4000); 485 486 if (!mInterrupted && (position != fileInfo.mLength)) { 487 readLength = readFully(a, buffer, outputBufferSize); 488 489 mCallbackHandler.sendMessageDelayed( 490 mCallbackHandler.obtainMessage( 491 BluetoothOppObexSession.MSG_CONNECT_TIMEOUT), 492 BluetoothOppObexSession.SESSION_TIMEOUT); 493 synchronized (this) { 494 mWaitingForRemote = true; 495 } 496 497 // first packet will block here 498 outputStream.write(buffer, 0, readLength); 499 500 position += readLength; 501 502 if (position == fileInfo.mLength) { 503 // if file length is smaller than buffer size, only one packet 504 // so block point is here 505 outputStream.close(); 506 outputStream = null; 507 } 508 509 /* check remote accept or reject */ 510 responseCode = putOperation.getResponseCode(); 511 512 mCallbackHandler.removeMessages( 513 BluetoothOppObexSession.MSG_CONNECT_TIMEOUT); 514 synchronized (this) { 515 mWaitingForRemote = false; 516 } 517 518 if (responseCode == ResponseCodes.OBEX_HTTP_CONTINUE 519 || responseCode == ResponseCodes.OBEX_HTTP_OK) { 520 Log.v(TAG, "Remote accept"); 521 okToProceed = true; 522 updateValues = new ContentValues(); 523 updateValues.put(BluetoothShare.CURRENT_BYTES, position); 524 mContext1 525 .getContentResolver() 526 .update(contentUri, updateValues, null, null); 527 mNumFilesAttemptedToSend++; 528 } else { 529 Log.i(TAG, "Remote reject, Response code is " + responseCode); 530 } 531 } 532 533 while (!mInterrupted && okToProceed && (position < fileInfo.mLength)) { 534 timestamp = SystemClock.elapsedRealtime(); 535 536 readLength = a.read(buffer, 0, outputBufferSize); 537 outputStream.write(buffer, 0, readLength); 538 539 /* check remote abort */ 540 responseCode = putOperation.getResponseCode(); 541 Log.v(TAG, "Response code is " + responseCode); 542 if (responseCode != ResponseCodes.OBEX_HTTP_CONTINUE 543 && responseCode != ResponseCodes.OBEX_HTTP_OK) { 544 /* abort happens */ 545 okToProceed = false; 546 } else { 547 position += readLength; 548 currentTime = SystemClock.elapsedRealtime(); 549 Log.v( 550 TAG, 551 "Sending file position = " 552 + position 553 + " readLength " 554 + readLength 555 + " bytes took " 556 + (currentTime - timestamp) 557 + " ms"); 558 // Update the Progress Bar only if there is change in percentage 559 // or once per a period to notify NFC of this transfer is still alive 560 percent = position * 100 / fileInfo.mLength; 561 if (percent > prevPercent 562 || currentTime - prevTimestamp > Constants.NFC_ALIVE_CHECK_MS) { 563 updateValues = new ContentValues(); 564 updateValues.put(BluetoothShare.CURRENT_BYTES, position); 565 mContext1 566 .getContentResolver() 567 .update(contentUri, updateValues, null, null); 568 prevPercent = percent; 569 prevTimestamp = currentTime; 570 } 571 } 572 } 573 574 if (responseCode == ResponseCodes.OBEX_HTTP_FORBIDDEN 575 || responseCode == ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE) { 576 Log.i( 577 TAG, 578 "Remote reject file " 579 + fileInfo.mFileName 580 + " length " 581 + fileInfo.mLength); 582 status = BluetoothShare.STATUS_FORBIDDEN; 583 } else if (responseCode == ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE) { 584 Log.i(TAG, "Remote reject file type " + fileInfo.mMimetype); 585 status = BluetoothShare.STATUS_NOT_ACCEPTABLE; 586 } else if (!mInterrupted && position == fileInfo.mLength) { 587 Log.i( 588 TAG, 589 "SendFile finished send out file " 590 + fileInfo.mFileName 591 + " length " 592 + fileInfo.mLength); 593 } else { 594 error = true; 595 status = BluetoothShare.STATUS_CANCELED; 596 putOperation.abort(); 597 /* interrupted */ 598 Log.i( 599 TAG, 600 "SendFile interrupted when send out file " 601 + fileInfo.mFileName 602 + " at " 603 + position 604 + " of " 605 + fileInfo.mLength); 606 } 607 } 608 } catch (IOException e) { 609 ContentProfileErrorReportUtils.report( 610 BluetoothProfile.OPP, 611 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 612 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 613 11); 614 handleSendException(e.toString()); 615 } catch (NullPointerException e) { 616 ContentProfileErrorReportUtils.report( 617 BluetoothProfile.OPP, 618 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 619 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 620 12); 621 handleSendException(e.toString()); 622 } catch (IndexOutOfBoundsException e) { 623 ContentProfileErrorReportUtils.report( 624 BluetoothProfile.OPP, 625 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 626 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 627 13); 628 handleSendException(e.toString()); 629 } finally { 630 try { 631 if (outputStream != null) { 632 outputStream.close(); 633 } 634 } catch (IOException e) { 635 ContentProfileErrorReportUtils.report( 636 BluetoothProfile.OPP, 637 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 638 BluetoothStatsLog 639 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 640 14); 641 Log.e(TAG, "Error when closing output stream after send"); 642 } 643 644 // Close InputStream and remove SendFileInfo from map 645 BluetoothOppUtility.closeSendFileInfo(mInfo.mUri); 646 try { 647 if (!error) { 648 responseCode = putOperation.getResponseCode(); 649 if (responseCode != -1) { 650 Log.v(TAG, "Get response code " + responseCode); 651 if (responseCode != ResponseCodes.OBEX_HTTP_OK) { 652 Log.i(TAG, "Response error code is " + responseCode); 653 status = BluetoothShare.STATUS_UNHANDLED_OBEX_CODE; 654 if (responseCode == ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE) { 655 status = BluetoothShare.STATUS_NOT_ACCEPTABLE; 656 } 657 if (responseCode == ResponseCodes.OBEX_HTTP_FORBIDDEN 658 || responseCode == ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE) { 659 status = BluetoothShare.STATUS_FORBIDDEN; 660 } 661 } 662 } else { 663 // responseCode is -1, which means connection error 664 status = BluetoothShare.STATUS_CONNECTION_ERROR; 665 } 666 } 667 668 Constants.updateShareStatus(mContext1, mInfo.mId, status); 669 670 if (inputStream != null) { 671 inputStream.close(); 672 } 673 if (putOperation != null) { 674 putOperation.close(); 675 } 676 } catch (IOException e) { 677 ContentProfileErrorReportUtils.report( 678 BluetoothProfile.OPP, 679 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 680 BluetoothStatsLog 681 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 682 15); 683 Log.e(TAG, "Error when closing stream after send"); 684 685 // Socket has been closed due to the response timeout in the framework, 686 // mark the transfer as failure. 687 if (position != fileInfo.mLength) { 688 status = BluetoothShare.STATUS_FORBIDDEN; 689 Constants.updateShareStatus(mContext1, mInfo.mId, status); 690 } 691 } 692 } 693 BluetoothOppUtility.cancelNotification(mContext); 694 return status; 695 } 696 handleSendException(String exception)697 private void handleSendException(String exception) { 698 Log.e(TAG, "Error when sending file: " + exception); 699 // Update interrupted outbound content resolver entry when 700 // error during transfer. 701 Constants.updateShareStatus( 702 mContext1, mInfo.mId, BluetoothShare.STATUS_OBEX_DATA_ERROR); 703 mCallbackHandler.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT); 704 } 705 706 @Override interrupt()707 public void interrupt() { 708 super.interrupt(); 709 synchronized (this) { 710 if (mWaitingForRemote) { 711 Log.v(TAG, "Interrupted when waitingForRemote"); 712 try { 713 mTransport1.close(); 714 } catch (IOException e) { 715 ContentProfileErrorReportUtils.report( 716 BluetoothProfile.OPP, 717 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION, 718 BluetoothStatsLog 719 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 720 16); 721 Log.e(TAG, "mTransport.close error"); 722 } 723 Message msg = Message.obtain(mCallbackHandler); 724 msg.what = BluetoothOppObexSession.MSG_SHARE_INTERRUPTED; 725 if (mInfo != null) { 726 msg.obj = mInfo; 727 } 728 msg.sendToTarget(); 729 } 730 } 731 } 732 } 733 applyRemoteDeviceQuirks(HeaderSet request, String address, String filename)734 public static void applyRemoteDeviceQuirks(HeaderSet request, String address, String filename) { 735 if (address == null) { 736 return; 737 } 738 if (address.startsWith("00:04:48")) { 739 // Poloroid Pogo 740 // Rejects filenames with more than one '.'. Rename to '_'. 741 // for example: 'a.b.jpg' -> 'a_b.jpg' 742 // 'abc.jpg' NOT CHANGED 743 char[] c = filename.toCharArray(); 744 boolean firstDot = true; 745 boolean modified = false; 746 for (int i = c.length - 1; i >= 0; i--) { 747 if (c[i] == '.') { 748 if (!firstDot) { 749 modified = true; 750 c[i] = '_'; 751 } 752 firstDot = false; 753 } 754 } 755 756 if (modified) { 757 String newFilename = new String(c); 758 request.setHeader(HeaderSet.NAME, newFilename); 759 Log.i( 760 TAG, 761 "Sending file \"" 762 + filename 763 + "\" as \"" 764 + newFilename 765 + "\" to workaround Poloroid filename quirk"); 766 } 767 } 768 } 769 770 @Override unblock()771 public void unblock() { 772 // Not used for client case 773 } 774 } 775