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