• 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.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 
BluetoothOppObexServerSession(Context context, ObexTransport transport, BluetoothOppService service)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
unblock()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      */
preStart()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
start(Handler handler, int numShares)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
stop()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
addShare(BluetoothOppShareInfo info)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
onPut(Operation op)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 
receiveFile(BluetoothOppReceiveFileInfo fileInfo, Operation op)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 
processShareInfo()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
onConnect(HeaderSet request, HeaderSet reply)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
onDisconnect(HeaderSet req, HeaderSet resp)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 
releaseWakeLocks()625     private synchronized void releaseWakeLocks() {
626         if (mPartialWakeLock.isHeld()) {
627             mPartialWakeLock.release();
628         }
629     }
630 
631     @Override
onClose()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