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.FileNotFoundException;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.io.OutputStream;
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 && mFileInfo.mInsertUri != 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                 updateValues.put(BluetoothShare.URI, mFileInfo.mInsertUri.toString());
371                 mContext.getContentResolver().update(contentUri, updateValues, null, null);
372 
373                 mInfo.mUri = mFileInfo.mInsertUri;
374                 status = receiveFile(mFileInfo, op);
375                 /*
376                  * TODO map status to obex response code
377                  */
378                 if (status != BluetoothShare.STATUS_SUCCESS) {
379                     obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
380                 }
381                 Constants.updateShareStatus(mContext, mInfo.mId, status);
382             }
383 
384             if (status == BluetoothShare.STATUS_SUCCESS) {
385                 Message msg = Message.obtain(mCallback, BluetoothOppObexSession.MSG_SHARE_COMPLETE);
386                 msg.obj = mInfo;
387                 msg.sendToTarget();
388             } else {
389                 if (mCallback != null) {
390                     Message msg =
391                             Message.obtain(mCallback, BluetoothOppObexSession.MSG_SESSION_ERROR);
392                     mInfo.mStatus = status;
393                     msg.obj = mInfo;
394                     msg.sendToTarget();
395                 }
396             }
397         } else if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED
398                 || mAccepted == BluetoothShare.USER_CONFIRMATION_TIMEOUT) {
399             /* user actively deny the inbound transfer */
400             /*
401              * Note There is a question: what's next if user deny the first obj?
402              * Option 1 :continue prompt for next objects
403              * Option 2 :reject next objects and finish the session
404              * Now we take option 2:
405              */
406 
407             Log.i(TAG, "Rejected incoming request");
408             if (mFileInfo.mInsertUri != null) {
409                 mContext.getContentResolver().delete(mFileInfo.mInsertUri, null, null);
410             }
411             // set status as local cancel
412             status = BluetoothShare.STATUS_CANCELED;
413             Constants.updateShareStatus(mContext, mInfo.mId, status);
414             obexResponse = ResponseCodes.OBEX_HTTP_FORBIDDEN;
415 
416             Message msg = Message.obtain(mCallback);
417             msg.what = BluetoothOppObexSession.MSG_SHARE_INTERRUPTED;
418             mInfo.mStatus = status;
419             msg.obj = mInfo;
420             msg.sendToTarget();
421         }
422         return obexResponse;
423     }
424 
receiveFile(BluetoothOppReceiveFileInfo fileInfo, Operation op)425     private int receiveFile(BluetoothOppReceiveFileInfo fileInfo, Operation op) {
426         /*
427          * implement receive file
428          */
429         int status = -1;
430         OutputStream os = null;
431         InputStream is = null;
432         boolean error = false;
433         try {
434             is = op.openInputStream();
435         } catch (IOException e1) {
436             Log.e(TAG, "Error when openInputStream");
437             status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
438             error = true;
439         }
440 
441         Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId);
442 
443         if (!error) {
444             ContentValues updateValues = new ContentValues();
445             updateValues.put(BluetoothShare._DATA, fileInfo.mFileName);
446             mContext.getContentResolver().update(contentUri, updateValues, null, null);
447         }
448 
449         long position = 0;
450         long percent;
451         long prevPercent = 0;
452 
453         if (!error) {
454             try {
455                 os = mContext.getContentResolver().openOutputStream(fileInfo.mInsertUri);
456             } catch (FileNotFoundException e) {
457                 Log.e(TAG, "Error when openOutputStream");
458                 error = true;
459             }
460         }
461 
462         if (!error) {
463             int outputBufferSize = op.getMaxPacketSize();
464             byte[] b = new byte[outputBufferSize];
465             int readLength;
466             long timestamp = 0;
467             long currentTime;
468             long prevTimestamp = SystemClock.elapsedRealtime();
469             try {
470                 while ((!mInterrupted) && (position != fileInfo.mLength)) {
471 
472                     if (V) {
473                         timestamp = SystemClock.elapsedRealtime();
474                     }
475 
476                     readLength = is.read(b);
477 
478                     if (readLength == -1) {
479                         if (D) {
480                             Log.d(TAG, "Receive file reached stream end at position" + position);
481                         }
482                         break;
483                     }
484 
485                     os.write(b, 0, readLength);
486                     position += readLength;
487                     percent = position * 100 / fileInfo.mLength;
488                     currentTime = SystemClock.elapsedRealtime();
489 
490                     if (V) {
491                         Log.v(TAG,
492                                 "Receive file position = " + position + " readLength " + readLength
493                                         + " bytes took " + (currentTime - timestamp) + " ms");
494                     }
495 
496                     // Update the Progress Bar only if there is change in percentage
497                     // or once per a period to notify NFC of this transfer is still alive
498                     if (percent > prevPercent
499                             || currentTime - prevTimestamp > Constants.NFC_ALIVE_CHECK_MS) {
500                         ContentValues updateValues = new ContentValues();
501                         updateValues.put(BluetoothShare.CURRENT_BYTES, position);
502                         mContext.getContentResolver().update(contentUri, updateValues, null, null);
503                         prevPercent = percent;
504                         prevTimestamp = currentTime;
505                     }
506                 }
507             } catch (IOException e1) {
508                 Log.e(TAG, "Error when receiving file: " + e1);
509                 /* OBEX Abort packet received from remote device */
510                 if ("Abort Received".equals(e1.getMessage())) {
511                     status = BluetoothShare.STATUS_CANCELED;
512                 } else {
513                     status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
514                 }
515                 error = true;
516             }
517         }
518 
519         if (mInterrupted) {
520             if (D) {
521                 Log.d(TAG, "receiving file interrupted by user.");
522             }
523             status = BluetoothShare.STATUS_CANCELED;
524         } else {
525             if (position == fileInfo.mLength) {
526                 if (D) {
527                     Log.d(TAG, "Receiving file completed for " + fileInfo.mFileName);
528                 }
529                 status = BluetoothShare.STATUS_SUCCESS;
530             } else {
531                 if (D) {
532                     Log.d(TAG, "Reading file failed at " + position + " of " + fileInfo.mLength);
533                 }
534                 if (status == -1) {
535                     status = BluetoothShare.STATUS_UNKNOWN_ERROR;
536                 }
537             }
538         }
539 
540         if (os != null) {
541             try {
542                 os.flush();
543                 os.close();
544             } catch (IOException e) {
545                 Log.e(TAG, "Error when closing stream after send");
546             }
547         }
548         BluetoothOppUtility.cancelNotification(mContext);
549         return status;
550     }
551 
processShareInfo()552     private BluetoothOppReceiveFileInfo processShareInfo() {
553         if (D) {
554             Log.d(TAG, "processShareInfo() " + mInfo.mId);
555         }
556         BluetoothOppReceiveFileInfo fileInfo =
557                 BluetoothOppReceiveFileInfo.generateFileInfo(mContext, mInfo.mId);
558         if (V) {
559             Log.v(TAG, "Generate BluetoothOppReceiveFileInfo:");
560             Log.v(TAG, "filename  :" + fileInfo.mFileName);
561             Log.v(TAG, "length    :" + fileInfo.mLength);
562             Log.v(TAG, "status    :" + fileInfo.mStatus);
563         }
564         return fileInfo;
565     }
566 
567     @Override
onConnect(HeaderSet request, HeaderSet reply)568     public int onConnect(HeaderSet request, HeaderSet reply) {
569 
570         if (D) {
571             Log.d(TAG, "onConnect");
572         }
573         if (V) {
574             Constants.logHeader(request);
575         }
576         Long objectCount = null;
577         try {
578             byte[] uuid = (byte[]) request.getHeader(HeaderSet.TARGET);
579             if (V) {
580                 Log.v(TAG, "onConnect(): uuid =" + Arrays.toString(uuid));
581             }
582             if (uuid != null) {
583                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
584             }
585 
586             objectCount = (Long) request.getHeader(HeaderSet.COUNT);
587         } catch (IOException e) {
588             Log.e(TAG, e.toString());
589             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
590         }
591         String destination;
592         if (mTransport instanceof BluetoothObexTransport) {
593             destination = ((BluetoothObexTransport) mTransport).getRemoteAddress();
594         } else {
595             destination = "FF:FF:FF:00:00:00";
596         }
597         boolean isHandover = BluetoothOppManager.getInstance(mContext).isWhitelisted(destination);
598         if (isHandover) {
599             // Notify the handover requester file transfer has started
600             Intent intent = new Intent(Constants.ACTION_HANDOVER_STARTED);
601             if (objectCount != null) {
602                 intent.putExtra(Constants.EXTRA_BT_OPP_OBJECT_COUNT, objectCount.intValue());
603             } else {
604                 intent.putExtra(Constants.EXTRA_BT_OPP_OBJECT_COUNT,
605                         Constants.COUNT_HEADER_UNAVAILABLE);
606             }
607             intent.putExtra(Constants.EXTRA_BT_OPP_ADDRESS, destination);
608             mContext.sendBroadcast(intent, Constants.HANDOVER_STATUS_PERMISSION);
609         }
610         mTimestamp = System.currentTimeMillis();
611         mNumFilesAttemptedToReceive = 0;
612         return ResponseCodes.OBEX_HTTP_OK;
613     }
614 
615     @Override
onDisconnect(HeaderSet req, HeaderSet resp)616     public void onDisconnect(HeaderSet req, HeaderSet resp) {
617         if (D) {
618             Log.d(TAG, "onDisconnect");
619         }
620         if (mNumFilesAttemptedToReceive > 0) {
621             // Log incoming OPP transfer if more than one file is accepted by user
622             MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.OPP);
623         }
624         resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
625     }
626 
releaseWakeLocks()627     private synchronized void releaseWakeLocks() {
628         if (mPartialWakeLock.isHeld()) {
629             mPartialWakeLock.release();
630         }
631     }
632 
633     @Override
onClose()634     public void onClose() {
635         if (D) {
636             Log.d(TAG, "onClose");
637         }
638         releaseWakeLocks();
639         mBluetoothOppService.acceptNewConnections();
640         BluetoothOppUtility.cancelNotification(mContext);
641         /* onClose could happen even before start() where mCallback is set */
642         if (mCallback != null) {
643             Message msg = Message.obtain(mCallback);
644             msg.what = BluetoothOppObexSession.MSG_SESSION_COMPLETE;
645             msg.obj = mInfo;
646             msg.sendToTarget();
647         }
648     }
649 }
650