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 java.io.BufferedOutputStream;
     36 import java.io.File;
     37 import java.io.IOException;
     38 import java.io.InputStream;
     39 import java.util.Arrays;
     40 
     41 import android.app.NotificationManager;
     42 import android.content.ContentValues;
     43 import android.content.Context;
     44 import android.content.Intent;
     45 import android.net.Uri;
     46 import android.os.Handler;
     47 import android.os.Message;
     48 import android.os.PowerManager;
     49 import android.os.PowerManager.WakeLock;
     50 import android.util.Log;
     51 import android.webkit.MimeTypeMap;
     52 
     53 import javax.obex.HeaderSet;
     54 import javax.obex.ObexTransport;
     55 import javax.obex.Operation;
     56 import javax.obex.ResponseCodes;
     57 import javax.obex.ServerRequestHandler;
     58 import javax.obex.ServerSession;
     59 
     60 import com.android.bluetooth.BluetoothObexTransport;
     61 import com.android.bluetooth.ObexServerSockets;
     62 
     63 /**
     64  * This class runs as an OBEX server
     65  */
     66 public class BluetoothOppObexServerSession extends ServerRequestHandler implements
     67         BluetoothOppObexSession {
     68 
     69     private static final String TAG = "BtOppObexServer";
     70     private static final boolean D = Constants.DEBUG;
     71     private static final boolean V = Constants.VERBOSE;
     72 
     73     private ObexTransport mTransport;
     74 
     75     private Context mContext;
     76 
     77     private Handler mCallback = null;
     78 
     79     /* status when server is blocking for user/auto confirmation */
     80     private boolean mServerBlocking = true;
     81 
     82     /* the current transfer info */
     83     private BluetoothOppShareInfo mInfo;
     84 
     85     /* info id when we insert the record */
     86     private int mLocalShareInfoId;
     87 
     88     private int mAccepted = BluetoothShare.USER_CONFIRMATION_PENDING;
     89 
     90     private boolean mInterrupted = false;
     91 
     92     private ServerSession mSession;
     93 
     94     private long mTimestamp;
     95 
     96     private BluetoothOppReceiveFileInfo mFileInfo;
     97 
     98     private WakeLock mPartialWakeLock;
     99 
    100     boolean mTimeoutMsgSent = false;
    101 
    102     private ObexServerSockets mServerSocket;
    103 
    104     public BluetoothOppObexServerSession(
    105             Context context, ObexTransport transport, ObexServerSockets serverSocket) {
    106         mContext = context;
    107         mTransport = transport;
    108         mServerSocket = serverSocket;
    109         PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
    110         mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
    111         mPartialWakeLock.setReferenceCounted(false);
    112     }
    113 
    114     public void unblock() {
    115         mServerBlocking = false;
    116     }
    117 
    118     /**
    119      * Called when connection is accepted from remote, to retrieve the first
    120      * Header then wait for user confirmation
    121      */
    122     public void preStart() {
    123         try {
    124             if (D) Log.d(TAG, "Create ServerSession with transport " + mTransport.toString());
    125             mSession = new ServerSession(mTransport, this, null);
    126         } catch (IOException e) {
    127             Log.e(TAG, "Create server session error" + e);
    128         }
    129     }
    130 
    131     /**
    132      * Called from BluetoothOppTransfer to start the "Transfer"
    133      */
    134     public void start(Handler handler, int numShares) {
    135         if (D) Log.d(TAG, "Start!");
    136         mCallback = handler;
    137 
    138     }
    139 
    140     /**
    141      * Called from BluetoothOppTransfer to cancel the "Transfer" Otherwise,
    142      * server should end by itself.
    143      */
    144     public void stop() {
    145         /*
    146          * TODO now we implement in a tough way, just close the socket.
    147          * maybe need nice way
    148          */
    149         if (D) Log.d(TAG, "Stop!");
    150         mInterrupted = true;
    151         if (mSession != null) {
    152             try {
    153                 mSession.close();
    154                 mTransport.close();
    155             } catch (IOException e) {
    156                 Log.e(TAG, "close mTransport error" + e);
    157             }
    158         }
    159         mCallback = null;
    160         mSession = null;
    161     }
    162 
    163     public void addShare(BluetoothOppShareInfo info) {
    164         if (D) Log.d(TAG, "addShare for id " + info.mId);
    165         mInfo = info;
    166         mFileInfo = processShareInfo();
    167     }
    168 
    169     @Override
    170     public int onPut(Operation op) {
    171         if (D) Log.d(TAG, "onPut " + op.toString());
    172         HeaderSet request;
    173         String name, mimeType;
    174         Long length;
    175 
    176         int obexResponse = ResponseCodes.OBEX_HTTP_OK;
    177 
    178         /**
    179          * For multiple objects, reject further objects after user deny the
    180          * first one
    181          */
    182         if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED) {
    183             return ResponseCodes.OBEX_HTTP_FORBIDDEN;
    184         }
    185 
    186         String destination;
    187         if (mTransport instanceof BluetoothObexTransport) {
    188             destination = ((BluetoothObexTransport)mTransport).getRemoteAddress();
    189         } else {
    190             destination = "FF:FF:FF:00:00:00";
    191         }
    192         boolean isWhitelisted = BluetoothOppManager.getInstance(mContext).
    193                 isWhitelisted(destination);
    194 
    195         try {
    196             boolean pre_reject = false;
    197 
    198             request = op.getReceivedHeader();
    199             if (V) Constants.logHeader(request);
    200             name = (String)request.getHeader(HeaderSet.NAME);
    201             length = (Long)request.getHeader(HeaderSet.LENGTH);
    202             mimeType = (String)request.getHeader(HeaderSet.TYPE);
    203 
    204             if (length == 0) {
    205                 if (D) Log.w(TAG, "length is 0, reject the transfer");
    206                 pre_reject = true;
    207                 obexResponse = ResponseCodes.OBEX_HTTP_LENGTH_REQUIRED;
    208             }
    209 
    210             if (name == null || name.equals("")) {
    211                 if (D) Log.w(TAG, "name is null or empty, reject the transfer");
    212                 pre_reject = true;
    213                 obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    214             }
    215 
    216             if (!pre_reject) {
    217                 /* first we look for Mimetype in Android map */
    218                 String extension, type;
    219                 int dotIndex = name.lastIndexOf(".");
    220                 if (dotIndex < 0 && mimeType == null) {
    221                     if (D) Log.w(TAG, "There is no file extension or mime type," +
    222                             "reject the transfer");
    223                     pre_reject = true;
    224                     obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    225                 } else {
    226                     extension = name.substring(dotIndex + 1).toLowerCase();
    227                     MimeTypeMap map = MimeTypeMap.getSingleton();
    228                     type = map.getMimeTypeFromExtension(extension);
    229                     if (V) Log.v(TAG, "Mimetype guessed from extension " + extension + " is " + type);
    230                     if (type != null) {
    231                         mimeType = type;
    232 
    233                     } else {
    234                         if (mimeType == null) {
    235                             if (D) Log.w(TAG, "Can't get mimetype, reject the transfer");
    236                             pre_reject = true;
    237                             obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE;
    238                         }
    239                     }
    240                     if (mimeType != null) {
    241                         mimeType = mimeType.toLowerCase();
    242                     }
    243                 }
    244             }
    245 
    246             // Reject policy: anything outside the "white list" plus unspecified
    247             // MIME Types. Also reject everything in the "black list".
    248             if (!pre_reject
    249                     && (mimeType == null
    250                             || (!isWhitelisted && !Constants.mimeTypeMatches(mimeType,
    251                                     Constants.ACCEPTABLE_SHARE_INBOUND_TYPES))
    252                             || Constants.mimeTypeMatches(mimeType,
    253                                     Constants.UNACCEPTABLE_SHARE_INBOUND_TYPES))) {
    254                 if (D) Log.w(TAG, "mimeType is null or in unacceptable list, reject the transfer");
    255                 pre_reject = true;
    256                 obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE;
    257             }
    258 
    259             if (pre_reject && obexResponse != ResponseCodes.OBEX_HTTP_OK) {
    260                 // some bad implemented client won't send disconnect
    261                 return obexResponse;
    262             }
    263 
    264         } catch (IOException e) {
    265             Log.e(TAG, "get getReceivedHeaders error " + e);
    266             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    267         }
    268 
    269         ContentValues values = new ContentValues();
    270 
    271         values.put(BluetoothShare.FILENAME_HINT, name);
    272 
    273         values.put(BluetoothShare.TOTAL_BYTES, length);
    274 
    275         values.put(BluetoothShare.MIMETYPE, mimeType);
    276 
    277         values.put(BluetoothShare.DESTINATION, destination);
    278 
    279         values.put(BluetoothShare.DIRECTION, BluetoothShare.DIRECTION_INBOUND);
    280         values.put(BluetoothShare.TIMESTAMP, mTimestamp);
    281 
    282         /** It's not first put if !serverBlocking, so we auto accept it */
    283         if (!mServerBlocking && (mAccepted == BluetoothShare.USER_CONFIRMATION_CONFIRMED ||
    284                 mAccepted == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED)) {
    285             values.put(BluetoothShare.USER_CONFIRMATION,
    286                     BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED);
    287         }
    288 
    289         if (isWhitelisted) {
    290             values.put(BluetoothShare.USER_CONFIRMATION,
    291                     BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
    292 
    293         }
    294 
    295         Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
    296         mLocalShareInfoId = Integer.parseInt(contentUri.getPathSegments().get(1));
    297 
    298         if (V) Log.v(TAG, "insert contentUri: " + contentUri);
    299         if (V) Log.v(TAG, "mLocalShareInfoId = " + mLocalShareInfoId);
    300 
    301         synchronized (this) {
    302             mPartialWakeLock.acquire();
    303             mServerBlocking = true;
    304             try {
    305 
    306                 while (mServerBlocking) {
    307                     wait(1000);
    308                     if (mCallback != null && !mTimeoutMsgSent) {
    309                         mCallback.sendMessageDelayed(mCallback
    310                                 .obtainMessage(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT),
    311                                 BluetoothOppObexSession.SESSION_TIMEOUT);
    312                         mTimeoutMsgSent = true;
    313                         if (V) Log.v(TAG, "MSG_CONNECT_TIMEOUT sent");
    314                     }
    315                 }
    316             } catch (InterruptedException e) {
    317                 if (V) Log.v(TAG, "Interrupted in onPut blocking");
    318             }
    319         }
    320         if (D) Log.d(TAG, "Server unblocked ");
    321         synchronized (this) {
    322             if (mCallback != null && mTimeoutMsgSent) {
    323                 mCallback.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
    324             }
    325         }
    326 
    327         /* we should have mInfo now */
    328 
    329         /*
    330          * TODO check if this mInfo match the one that we insert before server
    331          * blocking? just to make sure no error happens
    332          */
    333         if (mInfo.mId != mLocalShareInfoId) {
    334             Log.e(TAG, "Unexpected error!");
    335         }
    336         mAccepted = mInfo.mConfirm;
    337 
    338         if (V) Log.v(TAG, "after confirm: userAccepted=" + mAccepted);
    339         int status = BluetoothShare.STATUS_SUCCESS;
    340 
    341         if (mAccepted == BluetoothShare.USER_CONFIRMATION_CONFIRMED
    342                 || mAccepted == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED
    343                 || mAccepted == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED) {
    344             /* Confirm or auto-confirm */
    345 
    346             if (mFileInfo.mFileName == null) {
    347                 status = mFileInfo.mStatus;
    348                 /* TODO need to check if this line is correct */
    349                 mInfo.mStatus = mFileInfo.mStatus;
    350                 Constants.updateShareStatus(mContext, mInfo.mId, status);
    351                 obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    352 
    353             }
    354 
    355             if (mFileInfo.mFileName != null) {
    356 
    357                 ContentValues updateValues = new ContentValues();
    358                 contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId);
    359                 updateValues.put(BluetoothShare._DATA, mFileInfo.mFileName);
    360                 updateValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_RUNNING);
    361                 mContext.getContentResolver().update(contentUri, updateValues, null, null);
    362 
    363                 status = receiveFile(mFileInfo, op);
    364                 /*
    365                  * TODO map status to obex response code
    366                  */
    367                 if (status != BluetoothShare.STATUS_SUCCESS) {
    368                     obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    369                 }
    370                 Constants.updateShareStatus(mContext, mInfo.mId, status);
    371             }
    372 
    373             if (status == BluetoothShare.STATUS_SUCCESS) {
    374                 Message msg = Message.obtain(mCallback, BluetoothOppObexSession.MSG_SHARE_COMPLETE);
    375                 msg.obj = mInfo;
    376                 msg.sendToTarget();
    377             } else {
    378                 if (mCallback != null) {
    379                     Message msg = Message.obtain(mCallback,
    380                             BluetoothOppObexSession.MSG_SESSION_ERROR);
    381                     mInfo.mStatus = status;
    382                     msg.obj = mInfo;
    383                     msg.sendToTarget();
    384                 }
    385             }
    386         } else if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED
    387                 || mAccepted == BluetoothShare.USER_CONFIRMATION_TIMEOUT) {
    388             /* user actively deny the inbound transfer */
    389             /*
    390              * Note There is a question: what's next if user deny the first obj?
    391              * Option 1 :continue prompt for next objects
    392              * Option 2 :reject next objects and finish the session
    393              * Now we take option 2:
    394              */
    395 
    396             Log.i(TAG, "Rejected incoming request");
    397             if (mFileInfo.mFileName != null) {
    398                 try {
    399                     mFileInfo.mOutputStream.close();
    400                 } catch (IOException e) {
    401                     Log.e(TAG, "error close file stream");
    402                 }
    403                 new File(mFileInfo.mFileName).delete();
    404             }
    405             // set status as local cancel
    406             status = BluetoothShare.STATUS_CANCELED;
    407             Constants.updateShareStatus(mContext, mInfo.mId, status);
    408             obexResponse = ResponseCodes.OBEX_HTTP_FORBIDDEN;
    409 
    410             Message msg = Message.obtain(mCallback);
    411             msg.what = BluetoothOppObexSession.MSG_SHARE_INTERRUPTED;
    412             mInfo.mStatus = status;
    413             msg.obj = mInfo;
    414             msg.sendToTarget();
    415         }
    416         return obexResponse;
    417     }
    418 
    419     private int receiveFile(BluetoothOppReceiveFileInfo fileInfo, Operation op) {
    420         /*
    421          * implement receive file
    422          */
    423         int status = -1;
    424         BufferedOutputStream bos = null;
    425 
    426         InputStream is = null;
    427         boolean error = false;
    428         try {
    429             is = op.openInputStream();
    430         } catch (IOException e1) {
    431             Log.e(TAG, "Error when openInputStream");
    432             status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
    433             error = true;
    434         }
    435 
    436         Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId);
    437 
    438         if (!error) {
    439             ContentValues updateValues = new ContentValues();
    440             updateValues.put(BluetoothShare._DATA, fileInfo.mFileName);
    441             mContext.getContentResolver().update(contentUri, updateValues, null, null);
    442         }
    443 
    444         long position = 0;
    445         long percent = 0;
    446         long prevPercent = 0;
    447 
    448         if (!error) {
    449             bos = new BufferedOutputStream(fileInfo.mOutputStream, 0x10000);
    450         }
    451 
    452         if (!error) {
    453             int outputBufferSize = op.getMaxPacketSize();
    454             byte[] b = new byte[outputBufferSize];
    455             int readLength = 0;
    456             long timestamp = 0;
    457             try {
    458                 while ((!mInterrupted) && (position != fileInfo.mLength)) {
    459 
    460                     if (V) timestamp = System.currentTimeMillis();
    461 
    462                     readLength = is.read(b);
    463 
    464                     if (readLength == -1) {
    465                         if (D) Log.d(TAG, "Receive file reached stream end at position" + position);
    466                         break;
    467                     }
    468 
    469                     bos.write(b, 0, readLength);
    470                     position += readLength;
    471                     percent = position * 100 / fileInfo.mLength;
    472 
    473                     if (V) {
    474                         Log.v(TAG, "Receive file position = " + position + " readLength "
    475                                 + readLength + " bytes took "
    476                                 + (System.currentTimeMillis() - timestamp) + " ms");
    477                     }
    478 
    479                     // Update the Progress Bar only if there is change in percentage
    480                     if (percent > prevPercent) {
    481                         ContentValues updateValues = new ContentValues();
    482                         updateValues.put(BluetoothShare.CURRENT_BYTES, position);
    483                         mContext.getContentResolver().update(contentUri, updateValues, null, null);
    484                         prevPercent = percent;
    485                     }
    486                 }
    487             } catch (IOException e1) {
    488                 Log.e(TAG, "Error when receiving file: " + e1);
    489                 /* OBEX Abort packet received from remote device */
    490                 if ("Abort Received".equals(e1.getMessage())) {
    491                     status = BluetoothShare.STATUS_CANCELED;
    492                 } else {
    493                     status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
    494                 }
    495                 error = true;
    496             }
    497         }
    498 
    499         if (mInterrupted) {
    500             if (D) Log.d(TAG, "receiving file interrupted by user.");
    501             status = BluetoothShare.STATUS_CANCELED;
    502         } else {
    503             if (position == fileInfo.mLength) {
    504                 if (D) Log.d(TAG, "Receiving file completed for " + fileInfo.mFileName);
    505                 status = BluetoothShare.STATUS_SUCCESS;
    506             } else {
    507                 if (D) Log.d(TAG, "Reading file failed at " + position + " of " + fileInfo.mLength);
    508                 if (status == -1) {
    509                     status = BluetoothShare.STATUS_UNKNOWN_ERROR;
    510                 }
    511             }
    512         }
    513 
    514         if (bos != null) {
    515             try {
    516                 bos.close();
    517             } catch (IOException e) {
    518                 Log.e(TAG, "Error when closing stream after send");
    519             }
    520         }
    521         return status;
    522     }
    523 
    524     private BluetoothOppReceiveFileInfo processShareInfo() {
    525         if (D) Log.d(TAG, "processShareInfo() " + mInfo.mId);
    526         BluetoothOppReceiveFileInfo fileInfo = BluetoothOppReceiveFileInfo.generateFileInfo(
    527                 mContext, mInfo.mId);
    528         if (V) {
    529             Log.v(TAG, "Generate BluetoothOppReceiveFileInfo:");
    530             Log.v(TAG, "filename  :" + fileInfo.mFileName);
    531             Log.v(TAG, "length    :" + fileInfo.mLength);
    532             Log.v(TAG, "status    :" + fileInfo.mStatus);
    533         }
    534         return fileInfo;
    535     }
    536 
    537     @Override
    538     public int onConnect(HeaderSet request, HeaderSet reply) {
    539 
    540         if (D) Log.d(TAG, "onConnect");
    541         if (V) Constants.logHeader(request);
    542         Long objectCount = null;
    543         try {
    544             byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
    545             if (V) Log.v(TAG, "onConnect(): uuid =" + Arrays.toString(uuid));
    546             if(uuid != null) {
    547                  return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    548             }
    549 
    550             objectCount = (Long) request.getHeader(HeaderSet.COUNT);
    551         } catch (IOException e) {
    552             Log.e(TAG, e.toString());
    553             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    554         }
    555         String destination;
    556         if (mTransport instanceof BluetoothObexTransport) {
    557             destination = ((BluetoothObexTransport)mTransport).getRemoteAddress();
    558         } else {
    559             destination = "FF:FF:FF:00:00:00";
    560         }
    561         boolean isHandover = BluetoothOppManager.getInstance(mContext).
    562                 isWhitelisted(destination);
    563         if (isHandover) {
    564             // Notify the handover requester file transfer has started
    565             Intent intent = new Intent(Constants.ACTION_HANDOVER_STARTED);
    566             if (objectCount != null) {
    567                 intent.putExtra(Constants.EXTRA_BT_OPP_OBJECT_COUNT, objectCount.intValue());
    568             } else {
    569                 intent.putExtra(Constants.EXTRA_BT_OPP_OBJECT_COUNT,
    570                         Constants.COUNT_HEADER_UNAVAILABLE);
    571             }
    572             intent.putExtra(Constants.EXTRA_BT_OPP_ADDRESS, destination);
    573             mContext.sendBroadcast(intent, Constants.HANDOVER_STATUS_PERMISSION);
    574         }
    575         mTimestamp = System.currentTimeMillis();
    576         return ResponseCodes.OBEX_HTTP_OK;
    577     }
    578 
    579     @Override
    580     public void onDisconnect(HeaderSet req, HeaderSet resp) {
    581         if (D) Log.d(TAG, "onDisconnect");
    582         resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
    583     }
    584 
    585     private synchronized void releaseWakeLocks() {
    586         if (mPartialWakeLock.isHeld()) {
    587             mPartialWakeLock.release();
    588         }
    589     }
    590 
    591     @Override
    592     public void onClose() {
    593         if (D) Log.d(TAG, "onClose");
    594         releaseWakeLocks();
    595 
    596         if (mServerSocket != null) {
    597             if (D) Log.d(TAG, "prepareForNewConnect");
    598             mServerSocket.prepareForNewConnect();
    599         }
    600 
    601         NotificationManager nm =
    602                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
    603         nm.cancel(BluetoothOppNotification.NOTIFICATION_ID_PROGRESS);
    604 
    605         /* onClose could happen even before start() where mCallback is set */
    606         if (mCallback != null) {
    607             Message msg = Message.obtain(mCallback);
    608             msg.what = BluetoothOppObexSession.MSG_SESSION_COMPLETE;
    609             msg.obj = mInfo;
    610             msg.sendToTarget();
    611         }
    612     }
    613 }
    614