1 /*
2  * Copyright (C) 2014 Samsung System LSI
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 package com.android.bluetooth.map;
16 
17 import android.bluetooth.BluetoothDevice;
18 import android.bluetooth.BluetoothProfile;
19 import android.bluetooth.BluetoothProtoEnums;
20 import android.bluetooth.BluetoothSocket;
21 import android.bluetooth.SdpMnsRecord;
22 import android.os.Handler;
23 import android.os.HandlerThread;
24 import android.os.Looper;
25 import android.os.Message;
26 import android.os.ParcelUuid;
27 import android.util.Log;
28 import android.util.SparseBooleanArray;
29 
30 import com.android.bluetooth.BluetoothObexTransport;
31 import com.android.bluetooth.BluetoothStatsLog;
32 import com.android.bluetooth.content_profiles.ContentProfileErrorReportUtils;
33 import com.android.obex.ClientOperation;
34 import com.android.obex.ClientSession;
35 import com.android.obex.HeaderSet;
36 import com.android.obex.ObexTransport;
37 import com.android.obex.ResponseCodes;
38 
39 import java.io.IOException;
40 import java.io.OutputStream;
41 
42 /**
43  * The Message Notification Service class runs its own message handler thread, to avoid executing
44  * long operations on the MAP service Thread. This handler context is passed to the content
45  * observers, hence all call-backs (and thereby transmission of data) is executed from this thread.
46  */
47 // Next tag value for ContentProfileErrorReportUtils.report(): 16
48 public class BluetoothMnsObexClient {
49 
50     private static final String TAG = "BluetoothMnsObexClient";
51 
52     private ObexTransport mTransport;
53     public Handler mHandler = null;
54     private static final String TYPE_EVENT = "x-bt/MAP-event-report";
55     private ClientSession mClientSession;
56     private boolean mConnected = false;
57     BluetoothDevice mRemoteDevice;
58     private SparseBooleanArray mRegisteredMasIds = new SparseBooleanArray(1);
59 
60     private HeaderSet mHsConnect = null;
61     private Handler mCallback = null;
62     private SdpMnsRecord mMnsRecord;
63     // Used by the MAS to forward notification registrations
64     public static final int MSG_MNS_NOTIFICATION_REGISTRATION = 1;
65     public static final int MSG_MNS_SEND_EVENT = 2;
66     public static final int MSG_MNS_SDP_SEARCH_REGISTRATION = 3;
67 
68     // Copy SdpManager.SDP_INTENT_DELAY - The timeout to wait for reply from native.
69     private static final int MNS_SDP_SEARCH_DELAY = 6000;
70     public MnsSdpSearchInfo mMnsLstRegRqst = null;
71     private static final int MNS_NOTIFICATION_DELAY = 10;
72     public static final ParcelUuid BLUETOOTH_UUID_OBEX_MNS =
73             ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
74 
BluetoothMnsObexClient( BluetoothDevice remoteDevice, SdpMnsRecord mnsRecord, Handler callback)75     public BluetoothMnsObexClient(
76             BluetoothDevice remoteDevice, SdpMnsRecord mnsRecord, Handler callback) {
77         if (remoteDevice == null) {
78             throw new NullPointerException("Obex transport is null");
79         }
80         mRemoteDevice = remoteDevice;
81         HandlerThread thread = new HandlerThread("BluetoothMnsObexClient");
82         thread.start();
83         /* This will block until the looper have started, hence it will be safe to use it,
84         when the constructor completes */
85         Looper looper = thread.getLooper();
86         mHandler = new MnsObexClientHandler(looper);
87         mCallback = callback;
88         mMnsRecord = mnsRecord;
89     }
90 
getMessageHandler()91     public Handler getMessageHandler() {
92         return mHandler;
93     }
94 
95     static class MnsSdpSearchInfo {
96         private boolean mIsSearchInProgress;
97         public int lastMasId;
98         public int lastNotificationStatus;
99 
MnsSdpSearchInfo(boolean isSearchON, int masId, int notification)100         MnsSdpSearchInfo(boolean isSearchON, int masId, int notification) {
101             mIsSearchInProgress = isSearchON;
102             lastMasId = masId;
103             lastNotificationStatus = notification;
104         }
105 
isSearchInProgress()106         public boolean isSearchInProgress() {
107             return mIsSearchInProgress;
108         }
109 
setIsSearchInProgress(boolean isSearchON)110         public void setIsSearchInProgress(boolean isSearchON) {
111             mIsSearchInProgress = isSearchON;
112         }
113     }
114 
115     private final class MnsObexClientHandler extends Handler {
MnsObexClientHandler(Looper looper)116         private MnsObexClientHandler(Looper looper) {
117             super(looper);
118         }
119 
120         @Override
handleMessage(Message msg)121         public void handleMessage(Message msg) {
122             switch (msg.what) {
123                 case MSG_MNS_NOTIFICATION_REGISTRATION:
124                     Log.v(TAG, "Reg  masId:  " + msg.arg1 + " notfStatus: " + msg.arg2);
125                     if (isValidMnsRecord()) {
126                         handleRegistration(msg.arg1 /*masId*/, msg.arg2 /*status*/);
127                     } else {
128                         // Should not happen
129                         Log.d(TAG, "MNS SDP info not available yet - Cannot Connect.");
130                     }
131                     break;
132                 case MSG_MNS_SEND_EVENT:
133                     sendEventHandler((byte[]) msg.obj /*byte[]*/, msg.arg1 /*masId*/);
134                     break;
135                 case MSG_MNS_SDP_SEARCH_REGISTRATION:
136                     // Initiate SDP Search
137                     notifyMnsSdpSearch();
138                     // Save the mns search info
139                     mMnsLstRegRqst = new MnsSdpSearchInfo(true, msg.arg1, msg.arg2);
140                     // Handle notification registration.
141                     Message msgReg =
142                             mHandler.obtainMessage(
143                                     MSG_MNS_NOTIFICATION_REGISTRATION, msg.arg1, msg.arg2);
144                     Log.v(TAG, "SearchReg  masId:  " + msg.arg1 + " notfStatus: " + msg.arg2);
145                     mHandler.sendMessageDelayed(msgReg, MNS_SDP_SEARCH_DELAY);
146                     break;
147                 default:
148                     break;
149             }
150         }
151     }
152 
isConnected()153     public boolean isConnected() {
154         return mConnected;
155     }
156 
157     /**
158      * Disconnect the connection to MNS server. Call this when the MAS client requests a
159      * de-registration on events.
160      */
disconnect()161     public synchronized void disconnect() {
162         try {
163             if (mClientSession != null) {
164                 mClientSession.disconnect(null);
165                 Log.d(TAG, "OBEX session disconnected");
166             }
167         } catch (IOException e) {
168             ContentProfileErrorReportUtils.report(
169                     BluetoothProfile.MAP,
170                     BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
171                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
172                     0);
173             Log.w(TAG, "OBEX session disconnect error " + e.getMessage());
174         }
175         try {
176             if (mClientSession != null) {
177                 Log.d(TAG, "OBEX session close mClientSession");
178                 mClientSession.close();
179                 mClientSession = null;
180                 Log.d(TAG, "OBEX session closed");
181             }
182         } catch (IOException e) {
183             ContentProfileErrorReportUtils.report(
184                     BluetoothProfile.MAP,
185                     BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
186                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
187                     1);
188             Log.w(TAG, "OBEX session close error:" + e.getMessage());
189         }
190         if (mTransport != null) {
191             try {
192                 Log.d(TAG, "Close Obex Transport");
193                 mTransport.close();
194                 mTransport = null;
195                 mConnected = false;
196                 Log.d(TAG, "Obex Transport Closed");
197             } catch (IOException e) {
198                 ContentProfileErrorReportUtils.report(
199                         BluetoothProfile.MAP,
200                         BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
201                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
202                         2);
203                 Log.e(TAG, "mTransport.close error: " + e.getMessage());
204             }
205         }
206     }
207 
208     /** Shutdown the MNS. */
shutdown()209     public synchronized void shutdown() {
210         /* should shutdown handler thread first to make sure
211          * handleRegistration won't be called when disconnect
212          */
213         if (mHandler != null) {
214             // Shut down the thread
215             mHandler.removeCallbacksAndMessages(null);
216             Looper looper = mHandler.getLooper();
217             if (looper != null) {
218                 looper.quit();
219             }
220         }
221 
222         /* Disconnect if connected */
223         disconnect();
224 
225         mRegisteredMasIds.clear();
226     }
227 
228     /** We store a list of registered MasIds only to control connect/disconnect */
handleRegistration(int masId, int notificationStatus)229     public synchronized void handleRegistration(int masId, int notificationStatus) {
230         Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")");
231         boolean sendObserverRegistration = true;
232         if (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) {
233             mRegisteredMasIds.delete(masId);
234             if (mMnsLstRegRqst != null && mMnsLstRegRqst.lastMasId == masId) {
235                 // Clear last saved MNSSdpSearchInfo , if Disconnect requested for same MasId.
236                 mMnsLstRegRqst = null;
237             }
238         } else if (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
239             /* Connect if we do not have a connection, and start the content observers providing
240              * this thread as Handler.
241              */
242             if (!isConnected()) {
243                 Log.d(TAG, "handleRegistration: connect");
244                 connect();
245             }
246             sendObserverRegistration = isConnected();
247             mRegisteredMasIds.put(masId, true); // We don't use the value for anything
248 
249             // Clear last saved MNSSdpSearchInfo after connect is processed.
250             mMnsLstRegRqst = null;
251         }
252 
253         if (mRegisteredMasIds.size() == 0) {
254             // No more registrations - disconnect
255             Log.d(TAG, "handleRegistration: disconnect");
256             disconnect();
257         }
258 
259         // Register ContentObserver After connect/disconnect MNS channel.
260         Log.v(TAG, "Send  registerObserver: " + sendObserverRegistration);
261         if (mCallback != null && sendObserverRegistration) {
262             Message msg = Message.obtain(mCallback);
263             msg.what = BluetoothMapService.MSG_OBSERVER_REGISTRATION;
264             msg.arg1 = masId;
265             msg.arg2 = notificationStatus;
266             msg.sendToTarget();
267         }
268     }
269 
isValidMnsRecord()270     public boolean isValidMnsRecord() {
271         return (mMnsRecord != null);
272     }
273 
setMnsRecord(SdpMnsRecord mnsRecord)274     public void setMnsRecord(SdpMnsRecord mnsRecord) {
275         Log.v(TAG, "setMNSRecord");
276         if (isValidMnsRecord()) {
277             Log.w(TAG, "MNS Record already available. Still update.");
278             ContentProfileErrorReportUtils.report(
279                     BluetoothProfile.MAP,
280                     BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
281                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_WARN,
282                     3);
283         }
284         mMnsRecord = mnsRecord;
285         if (mMnsLstRegRqst != null) {
286             // SDP Search completed.
287             mMnsLstRegRqst.setIsSearchInProgress(false);
288             if (mHandler.hasMessages(MSG_MNS_NOTIFICATION_REGISTRATION)) {
289                 mHandler.removeMessages(MSG_MNS_NOTIFICATION_REGISTRATION);
290                 // Search Result obtained within MNS_SDP_SEARCH_DELAY timeout
291                 if (!isValidMnsRecord()) {
292                     // SDP info still not available for last trial.
293                     // Clear saved info.
294                     mMnsLstRegRqst = null;
295                 } else {
296                     Log.v(TAG, "Handle registration for last saved request");
297                     Message msgReg = mHandler.obtainMessage(MSG_MNS_NOTIFICATION_REGISTRATION);
298                     msgReg.arg1 = mMnsLstRegRqst.lastMasId;
299                     msgReg.arg2 = mMnsLstRegRqst.lastNotificationStatus;
300                     Log.v(TAG, "SearchReg  masId:  " + msgReg.arg1 + " notfStatus: " + msgReg.arg2);
301                     // Handle notification registration.
302                     mHandler.sendMessageDelayed(msgReg, MNS_NOTIFICATION_DELAY);
303                 }
304             }
305         } else {
306             Log.v(TAG, "No last saved MNSSDPInfo to handle");
307         }
308     }
309 
connect()310     public void connect() {
311 
312         mConnected = true;
313 
314         BluetoothSocket btSocket = null;
315         try {
316             // TODO: Do SDP record search again?
317             if (isValidMnsRecord() && mMnsRecord.getL2capPsm() > 0) {
318                 // Do L2CAP connect
319                 btSocket = mRemoteDevice.createL2capSocket(mMnsRecord.getL2capPsm());
320 
321             } else if (isValidMnsRecord() && mMnsRecord.getRfcommChannelNumber() > 0) {
322                 // Do Rfcomm connect
323                 btSocket = mRemoteDevice.createRfcommSocket(mMnsRecord.getRfcommChannelNumber());
324             } else {
325                 // This should not happen...
326                 Log.e(TAG, "Invalid SDP content - attempt a connect to UUID...");
327                 ContentProfileErrorReportUtils.report(
328                         BluetoothProfile.MAP,
329                         BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
330                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR,
331                         4);
332                 // TODO: Why insecure? - is it because the link is already encrypted?
333                 btSocket =
334                         mRemoteDevice.createInsecureRfcommSocketToServiceRecord(
335                                 BLUETOOTH_UUID_OBEX_MNS.getUuid());
336             }
337             btSocket.connect();
338         } catch (IOException e) {
339             ContentProfileErrorReportUtils.report(
340                     BluetoothProfile.MAP,
341                     BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
342                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
343                     5);
344             Log.e(TAG, "BtSocket Connect error " + e.getMessage(), e);
345             // TODO: do we need to report error somewhere?
346             mConnected = false;
347             return;
348         }
349 
350         mTransport = new BluetoothObexTransport(btSocket);
351 
352         try {
353             mClientSession = new ClientSession(mTransport);
354         } catch (IOException e1) {
355             ContentProfileErrorReportUtils.report(
356                     BluetoothProfile.MAP,
357                     BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
358                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
359                     6);
360             Log.e(TAG, "OBEX session create error " + e1.getMessage());
361             mConnected = false;
362         }
363         if (mConnected && mClientSession != null) {
364             boolean connected = false;
365             HeaderSet hs = new HeaderSet();
366             // bb582b41-420c-11db-b0de-0800200c9a66
367             byte[] mnsTarget = {
368                 (byte) 0xbb,
369                 (byte) 0x58,
370                 (byte) 0x2b,
371                 (byte) 0x41,
372                 (byte) 0x42,
373                 (byte) 0x0c,
374                 (byte) 0x11,
375                 (byte) 0xdb,
376                 (byte) 0xb0,
377                 (byte) 0xde,
378                 (byte) 0x08,
379                 (byte) 0x00,
380                 (byte) 0x20,
381                 (byte) 0x0c,
382                 (byte) 0x9a,
383                 (byte) 0x66
384             };
385             hs.setHeader(HeaderSet.TARGET, mnsTarget);
386 
387             try {
388                 mHsConnect = mClientSession.connect(hs);
389                 Log.d(TAG, "OBEX session created");
390                 connected = true;
391             } catch (IOException e) {
392                 ContentProfileErrorReportUtils.report(
393                         BluetoothProfile.MAP,
394                         BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
395                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
396                         7);
397                 Log.e(TAG, "OBEX session connect error " + e.getMessage());
398             }
399             mConnected = connected;
400         }
401     }
402 
403     /**
404      * Call this method to queue an event report to be send to the MNS server.
405      *
406      * @param eventBytes the encoded event data.
407      * @param masInstanceId the MasId of the instance sending the event.
408      */
sendEvent(byte[] eventBytes, int masInstanceId)409     public void sendEvent(byte[] eventBytes, int masInstanceId) {
410         // We need to check for null, to handle shutdown.
411         if (mHandler != null) {
412             Message msg = mHandler.obtainMessage(MSG_MNS_SEND_EVENT, masInstanceId, 0, eventBytes);
413             if (msg != null) {
414                 msg.sendToTarget();
415             }
416         }
417         notifyUpdateWakeLock();
418     }
419 
notifyMnsSdpSearch()420     private void notifyMnsSdpSearch() {
421         if (mCallback != null) {
422             Message msg = Message.obtain(mCallback);
423             msg.what = BluetoothMapService.MSG_MNS_SDP_SEARCH;
424             msg.sendToTarget();
425         }
426     }
427 
sendEventHandler(byte[] eventBytes, int masInstanceId)428     private int sendEventHandler(byte[] eventBytes, int masInstanceId) {
429 
430         boolean error = false;
431         int responseCode = -1;
432         HeaderSet request;
433         int maxChunkSize, bytesToWrite, bytesWritten = 0;
434         ClientSession clientSession = mClientSession;
435 
436         if ((!mConnected) || (clientSession == null)) {
437             Log.w(TAG, "sendEvent after disconnect:" + mConnected);
438             ContentProfileErrorReportUtils.report(
439                     BluetoothProfile.MAP,
440                     BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
441                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_WARN,
442                     8);
443             return responseCode;
444         }
445 
446         request = new HeaderSet();
447         BluetoothMapAppParams appParams = new BluetoothMapAppParams();
448         appParams.setMasInstanceId(masInstanceId);
449 
450         ClientOperation putOperation = null;
451         OutputStream outputStream = null;
452 
453         try {
454             request.setHeader(HeaderSet.TYPE, TYPE_EVENT);
455             request.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.encodeParams());
456 
457             if (mHsConnect.mConnectionID != null) {
458                 request.mConnectionID = new byte[4];
459                 System.arraycopy(mHsConnect.mConnectionID, 0, request.mConnectionID, 0, 4);
460             } else {
461                 Log.w(TAG, "sendEvent: no connection ID");
462                 ContentProfileErrorReportUtils.report(
463                         BluetoothProfile.MAP,
464                         BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
465                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_WARN,
466                         9);
467             }
468 
469             // Send the header first and then the body
470             try {
471                 Log.v(TAG, "Send headerset Event ");
472                 putOperation = (ClientOperation) clientSession.put(request);
473                 // TODO - Should this be kept or Removed
474 
475             } catch (IOException e) {
476                 ContentProfileErrorReportUtils.report(
477                         BluetoothProfile.MAP,
478                         BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
479                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
480                         10);
481                 Log.e(TAG, "Error when put HeaderSet " + e.getMessage());
482                 error = true;
483             }
484             if (!error) {
485                 try {
486                     Log.v(TAG, "Send headerset Event ");
487                     outputStream = putOperation.openOutputStream();
488                 } catch (IOException e) {
489                     ContentProfileErrorReportUtils.report(
490                             BluetoothProfile.MAP,
491                             BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
492                             BluetoothStatsLog
493                                     .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
494                             11);
495                     Log.e(TAG, "Error when opening OutputStream " + e.getMessage());
496                     error = true;
497                 }
498             }
499 
500             if (!error) {
501 
502                 maxChunkSize = putOperation.getMaxPacketSize();
503 
504                 while (bytesWritten < eventBytes.length) {
505                     bytesToWrite = Math.min(maxChunkSize, eventBytes.length - bytesWritten);
506                     outputStream.write(eventBytes, bytesWritten, bytesToWrite);
507                     bytesWritten += bytesToWrite;
508                 }
509 
510                 if (bytesWritten == eventBytes.length) {
511                     Log.i(TAG, "SendEvent finished send length" + eventBytes.length);
512                 } else {
513                     error = true;
514                     putOperation.abort();
515                     Log.i(TAG, "SendEvent interrupted");
516                 }
517             }
518         } catch (IOException e) {
519             ContentProfileErrorReportUtils.report(
520                     BluetoothProfile.MAP,
521                     BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
522                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
523                     12);
524             handleSendException(e.toString());
525             error = true;
526         } catch (IndexOutOfBoundsException e) {
527             ContentProfileErrorReportUtils.report(
528                     BluetoothProfile.MAP,
529                     BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
530                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
531                     13);
532             handleSendException(e.toString());
533             error = true;
534         } finally {
535             try {
536                 if (outputStream != null) {
537                     outputStream.close();
538                 }
539             } catch (IOException e) {
540                 ContentProfileErrorReportUtils.report(
541                         BluetoothProfile.MAP,
542                         BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
543                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
544                         14);
545                 Log.e(TAG, "Error when closing stream after send " + e.getMessage());
546             }
547             try {
548                 if ((!error) && (putOperation != null)) {
549                     responseCode = putOperation.getResponseCode();
550                     if (responseCode != -1) {
551                         Log.v(TAG, "Put response code " + responseCode);
552                         if (responseCode != ResponseCodes.OBEX_HTTP_OK) {
553                             Log.i(TAG, "Response error code is " + responseCode);
554                         }
555                     }
556                 }
557                 if (putOperation != null) {
558                     putOperation.close();
559                 }
560             } catch (IOException e) {
561                 ContentProfileErrorReportUtils.report(
562                         BluetoothProfile.MAP,
563                         BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
564                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
565                         15);
566                 Log.e(TAG, "Error when closing stream after send " + e.getMessage());
567             }
568         }
569 
570         return responseCode;
571     }
572 
handleSendException(String exception)573     private void handleSendException(String exception) {
574         Log.e(TAG, "Error when sending event: " + exception);
575     }
576 
notifyUpdateWakeLock()577     private void notifyUpdateWakeLock() {
578         if (mCallback != null) {
579             Message msg = Message.obtain(mCallback);
580             msg.what = BluetoothMapService.MSG_ACQUIRE_WAKE_LOCK;
581             msg.sendToTarget();
582         }
583     }
584 }
585