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