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