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 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 
     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
     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
    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
    126     public void addShare(BluetoothOppShareInfo share) {
    127         mThread.addShare(share);
    128     }
    129 
    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 
    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 
    175         public void addShare(BluetoothOppShareInfo info) {
    176             mInfo = info;
    177             mFileInfo = processShareInfo();
    178             mWaitingForShare = false;
    179         }
    180 
    181         @Override
    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 
    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 
    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 
    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          */
    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 
    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 
    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
    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 
    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
    697     public void unblock() {
    698         // Not used for client case
    699     }
    700 
    701 }
    702