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