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 /**
     60  * This class runs as an OBEX server
     61  */
     62 public class BluetoothOppObexServerSession extends ServerRequestHandler implements
     63         BluetoothOppObexSession {
     64 
     65     private static final String TAG = "BtOppObexServer";
     66     private static final boolean D = Constants.DEBUG;
     67     private static final boolean V = Constants.VERBOSE;
     68 
     69     private ObexTransport mTransport;
     70 
     71     private Context mContext;
     72 
     73     private Handler mCallback = null;
     74 
     75     /* status when server is blocking for user/auto confirmation */
     76     private boolean mServerBlocking = true;
     77 
     78     /* the current transfer info */
     79     private BluetoothOppShareInfo mInfo;
     80 
     81     /* info id when we insert the record */
     82     private int mLocalShareInfoId;
     83 
     84     private int mAccepted = BluetoothShare.USER_CONFIRMATION_PENDING;
     85 
     86     private boolean mInterrupted = false;
     87 
     88     private ServerSession mSession;
     89 
     90     private long mTimestamp;
     91 
     92     private BluetoothOppReceiveFileInfo mFileInfo;
     93 
     94     private WakeLock mWakeLock;
     95 
     96     private WakeLock mPartialWakeLock;
     97 
     98     boolean mTimeoutMsgSent = false;
     99 
    100     public BluetoothOppObexServerSession(Context context, ObexTransport transport) {
    101         mContext = context;
    102         mTransport = transport;
    103         PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
    104         mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP
    105                 | PowerManager.ON_AFTER_RELEASE, TAG);
    106         mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
    107     }
    108 
    109     public void unblock() {
    110         mServerBlocking = false;
    111     }
    112 
    113     /**
    114      * Called when connection is accepted from remote, to retrieve the first
    115      * Header then wait for user confirmation
    116      */
    117     public void preStart() {
    118         if (D) Log.d(TAG, "acquire full WakeLock");
    119         mWakeLock.acquire();
    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 BluetoothOppRfcommTransport) {
    185             destination = ((BluetoothOppRfcommTransport)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         values.put(BluetoothShare.TOTAL_BYTES, length.intValue());
    270         values.put(BluetoothShare.MIMETYPE, mimeType);
    271 
    272         values.put(BluetoothShare.DESTINATION, destination);
    273 
    274         values.put(BluetoothShare.DIRECTION, BluetoothShare.DIRECTION_INBOUND);
    275         values.put(BluetoothShare.TIMESTAMP, mTimestamp);
    276 
    277         boolean needConfirm = true;
    278         /** It's not first put if !serverBlocking, so we auto accept it */
    279         if (!mServerBlocking) {
    280             values.put(BluetoothShare.USER_CONFIRMATION,
    281                     BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED);
    282             needConfirm = false;
    283         }
    284 
    285         if (isWhitelisted) {
    286             values.put(BluetoothShare.USER_CONFIRMATION,
    287                     BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
    288             needConfirm = false;
    289 
    290         }
    291 
    292         Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
    293         mLocalShareInfoId = Integer.parseInt(contentUri.getPathSegments().get(1));
    294 
    295         if (needConfirm) {
    296             Intent in = new Intent(BluetoothShare.INCOMING_FILE_CONFIRMATION_REQUEST_ACTION);
    297             in.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
    298             mContext.sendBroadcast(in);
    299         }
    300 
    301         if (V) Log.v(TAG, "insert contentUri: " + contentUri);
    302         if (V) Log.v(TAG, "mLocalShareInfoId = " + mLocalShareInfoId);
    303 
    304         if (V) Log.v(TAG, "acquire partial WakeLock");
    305 
    306 
    307         synchronized (this) {
    308             if (mWakeLock.isHeld()) {
    309                 mPartialWakeLock.acquire();
    310                 mWakeLock.release();
    311             }
    312             mServerBlocking = true;
    313             try {
    314 
    315                 while (mServerBlocking) {
    316                     wait(1000);
    317                     if (mCallback != null && !mTimeoutMsgSent) {
    318                         mCallback.sendMessageDelayed(mCallback
    319                                 .obtainMessage(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT),
    320                                 BluetoothOppObexSession.SESSION_TIMEOUT);
    321                         mTimeoutMsgSent = true;
    322                         if (V) Log.v(TAG, "MSG_CONNECT_TIMEOUT sent");
    323                     }
    324                 }
    325             } catch (InterruptedException e) {
    326                 if (V) Log.v(TAG, "Interrupted in onPut blocking");
    327             }
    328         }
    329         if (D) Log.d(TAG, "Server unblocked ");
    330         synchronized (this) {
    331             if (mCallback != null && mTimeoutMsgSent) {
    332                 mCallback.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
    333             }
    334         }
    335 
    336         /* we should have mInfo now */
    337 
    338         /*
    339          * TODO check if this mInfo match the one that we insert before server
    340          * blocking? just to make sure no error happens
    341          */
    342         if (mInfo.mId != mLocalShareInfoId) {
    343             Log.e(TAG, "Unexpected error!");
    344         }
    345         mAccepted = mInfo.mConfirm;
    346 
    347         if (V) Log.v(TAG, "after confirm: userAccepted=" + mAccepted);
    348         int status = BluetoothShare.STATUS_SUCCESS;
    349 
    350         if (mAccepted == BluetoothShare.USER_CONFIRMATION_CONFIRMED
    351                 || mAccepted == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED
    352                 || mAccepted == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED) {
    353             /* Confirm or auto-confirm */
    354 
    355             if (mFileInfo.mFileName == null) {
    356                 status = mFileInfo.mStatus;
    357                 /* TODO need to check if this line is correct */
    358                 mInfo.mStatus = mFileInfo.mStatus;
    359                 Constants.updateShareStatus(mContext, mInfo.mId, status);
    360                 obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    361 
    362             }
    363 
    364             if (mFileInfo.mFileName != null) {
    365 
    366                 ContentValues updateValues = new ContentValues();
    367                 contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId);
    368                 updateValues.put(BluetoothShare._DATA, mFileInfo.mFileName);
    369                 updateValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_RUNNING);
    370                 mContext.getContentResolver().update(contentUri, updateValues, null, null);
    371 
    372                 status = receiveFile(mFileInfo, op);
    373                 /*
    374                  * TODO map status to obex response code
    375                  */
    376                 if (status != BluetoothShare.STATUS_SUCCESS) {
    377                     obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    378                 }
    379                 Constants.updateShareStatus(mContext, mInfo.mId, status);
    380             }
    381 
    382             if (status == BluetoothShare.STATUS_SUCCESS) {
    383                 Message msg = Message.obtain(mCallback, BluetoothOppObexSession.MSG_SHARE_COMPLETE);
    384                 msg.obj = mInfo;
    385                 msg.sendToTarget();
    386             } else {
    387                 if (mCallback != null) {
    388                     Message msg = Message.obtain(mCallback,
    389                             BluetoothOppObexSession.MSG_SESSION_ERROR);
    390                     mInfo.mStatus = status;
    391                     msg.obj = mInfo;
    392                     msg.sendToTarget();
    393                 }
    394             }
    395         } else if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED
    396                 || mAccepted == BluetoothShare.USER_CONFIRMATION_TIMEOUT) {
    397             /* user actively deny the inbound transfer */
    398             /*
    399              * Note There is a question: what's next if user deny the first obj?
    400              * Option 1 :continue prompt for next objects
    401              * Option 2 :reject next objects and finish the session
    402              * Now we take option 2:
    403              */
    404 
    405             Log.i(TAG, "Rejected incoming request");
    406             if (mFileInfo.mFileName != null) {
    407                 try {
    408                     mFileInfo.mOutputStream.close();
    409                 } catch (IOException e) {
    410                     Log.e(TAG, "error close file stream");
    411                 }
    412                 new File(mFileInfo.mFileName).delete();
    413             }
    414             // set status as local cancel
    415             status = BluetoothShare.STATUS_CANCELED;
    416             Constants.updateShareStatus(mContext, mInfo.mId, status);
    417             obexResponse = ResponseCodes.OBEX_HTTP_FORBIDDEN;
    418 
    419             Message msg = Message.obtain(mCallback);
    420             msg.what = BluetoothOppObexSession.MSG_SHARE_INTERRUPTED;
    421             mInfo.mStatus = status;
    422             msg.obj = mInfo;
    423             msg.sendToTarget();
    424         }
    425         return obexResponse;
    426     }
    427 
    428     private int receiveFile(BluetoothOppReceiveFileInfo fileInfo, Operation op) {
    429         /*
    430          * implement receive file
    431          */
    432         int status = -1;
    433         BufferedOutputStream bos = null;
    434 
    435         InputStream is = null;
    436         boolean error = false;
    437         try {
    438             is = op.openInputStream();
    439         } catch (IOException e1) {
    440             Log.e(TAG, "Error when openInputStream");
    441             status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
    442             error = true;
    443         }
    444 
    445         Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId);
    446 
    447         if (!error) {
    448             ContentValues updateValues = new ContentValues();
    449             updateValues.put(BluetoothShare._DATA, fileInfo.mFileName);
    450             mContext.getContentResolver().update(contentUri, updateValues, null, null);
    451         }
    452 
    453         int position = 0;
    454         if (!error) {
    455             bos = new BufferedOutputStream(fileInfo.mOutputStream, 0x10000);
    456         }
    457 
    458         if (!error) {
    459             int outputBufferSize = op.getMaxPacketSize();
    460             byte[] b = new byte[outputBufferSize];
    461             int readLength = 0;
    462             long timestamp = 0;
    463             try {
    464                 while ((!mInterrupted) && (position != fileInfo.mLength)) {
    465 
    466                     if (V) timestamp = System.currentTimeMillis();
    467 
    468                     readLength = is.read(b);
    469 
    470                     if (readLength == -1) {
    471                         if (D) Log.d(TAG, "Receive file reached stream end at position" + position);
    472                         break;
    473                     }
    474 
    475                     bos.write(b, 0, readLength);
    476                     position += readLength;
    477 
    478                     if (V) {
    479                         Log.v(TAG, "Receive file position = " + position + " readLength "
    480                                 + readLength + " bytes took "
    481                                 + (System.currentTimeMillis() - timestamp) + " ms");
    482                     }
    483 
    484                     ContentValues updateValues = new ContentValues();
    485                     updateValues.put(BluetoothShare.CURRENT_BYTES, position);
    486                     mContext.getContentResolver().update(contentUri, updateValues, null, null);
    487                 }
    488             } catch (IOException e1) {
    489                 Log.e(TAG, "Error when receiving file");
    490                 /* OBEX Abort packet received from remote device */
    491                 if ("Abort Received".equals(e1.getMessage())) {
    492                     status = BluetoothShare.STATUS_CANCELED;
    493                 } else {
    494                     status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
    495                 }
    496                 error = true;
    497             }
    498         }
    499 
    500         if (mInterrupted) {
    501             if (D) Log.d(TAG, "receiving file interrupted by user.");
    502             status = BluetoothShare.STATUS_CANCELED;
    503         } else {
    504             if (position == fileInfo.mLength) {
    505                 if (D) Log.d(TAG, "Receiving file completed for " + fileInfo.mFileName);
    506                 status = BluetoothShare.STATUS_SUCCESS;
    507             } else {
    508                 if (D) Log.d(TAG, "Reading file failed at " + position + " of " + fileInfo.mLength);
    509                 if (status == -1) {
    510                     status = BluetoothShare.STATUS_UNKNOWN_ERROR;
    511                 }
    512             }
    513         }
    514 
    515         if (bos != null) {
    516             try {
    517                 bos.close();
    518             } catch (IOException e) {
    519                 Log.e(TAG, "Error when closing stream after send");
    520             }
    521         }
    522         return status;
    523     }
    524 
    525     private BluetoothOppReceiveFileInfo processShareInfo() {
    526         if (D) Log.d(TAG, "processShareInfo() " + mInfo.mId);
    527         BluetoothOppReceiveFileInfo fileInfo = BluetoothOppReceiveFileInfo.generateFileInfo(
    528                 mContext, mInfo.mId);
    529         if (V) {
    530             Log.v(TAG, "Generate BluetoothOppReceiveFileInfo:");
    531             Log.v(TAG, "filename  :" + fileInfo.mFileName);
    532             Log.v(TAG, "length    :" + fileInfo.mLength);
    533             Log.v(TAG, "status    :" + fileInfo.mStatus);
    534         }
    535         return fileInfo;
    536     }
    537 
    538     @Override
    539     public int onConnect(HeaderSet request, HeaderSet reply) {
    540 
    541         if (D) Log.d(TAG, "onConnect");
    542         if (V) Constants.logHeader(request);
    543         Long objectCount = null;
    544         try {
    545             byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
    546             if (V) Log.v(TAG, "onConnect(): uuid =" + Arrays.toString(uuid));
    547             if(uuid != null) {
    548                  return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    549             }
    550 
    551             objectCount = (Long) request.getHeader(HeaderSet.COUNT);
    552         } catch (IOException e) {
    553             Log.e(TAG, e.toString());
    554             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    555         }
    556         String destination;
    557         if (mTransport instanceof BluetoothOppRfcommTransport) {
    558             destination = ((BluetoothOppRfcommTransport)mTransport).getRemoteAddress();
    559         } else {
    560             destination = "FF:FF:FF:00:00:00";
    561         }
    562         boolean isHandover = BluetoothOppManager.getInstance(mContext).
    563                 isWhitelisted(destination);
    564         if (isHandover) {
    565             // Notify the handover requester file transfer has started
    566             Intent intent = new Intent(Constants.ACTION_HANDOVER_STARTED);
    567             if (objectCount != null) {
    568                 intent.putExtra(Constants.EXTRA_BT_OPP_OBJECT_COUNT, objectCount.intValue());
    569             } else {
    570                 intent.putExtra(Constants.EXTRA_BT_OPP_OBJECT_COUNT,
    571                         Constants.COUNT_HEADER_UNAVAILABLE);
    572             }
    573             intent.putExtra(Constants.EXTRA_BT_OPP_ADDRESS, destination);
    574             mContext.sendBroadcast(intent, Constants.HANDOVER_STATUS_PERMISSION);
    575         }
    576         mTimestamp = System.currentTimeMillis();
    577         return ResponseCodes.OBEX_HTTP_OK;
    578     }
    579 
    580     @Override
    581     public void onDisconnect(HeaderSet req, HeaderSet resp) {
    582         if (D) Log.d(TAG, "onDisconnect");
    583         resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
    584     }
    585 
    586     private synchronized void releaseWakeLocks() {
    587         if (mWakeLock.isHeld()) {
    588             mWakeLock.release();
    589         }
    590         if (mPartialWakeLock.isHeld()) {
    591             mPartialWakeLock.release();
    592         }
    593     }
    594 
    595     @Override
    596     public void onClose() {
    597         if (V) Log.v(TAG, "release WakeLock");
    598         releaseWakeLocks();
    599 
    600         /* onClose could happen even before start() where mCallback is set */
    601         if (mCallback != null) {
    602             Message msg = Message.obtain(mCallback);
    603             msg.what = BluetoothOppObexSession.MSG_SESSION_COMPLETE;
    604             msg.obj = mInfo;
    605             msg.sendToTarget();
    606         }
    607     }
    608 }
    609