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