• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.SuppressLint;
18 import android.bluetooth.BluetoothDevice;
19 import android.bluetooth.BluetoothProfile;
20 import android.bluetooth.BluetoothProtoEnums;
21 import android.bluetooth.BluetoothSocket;
22 import android.bluetooth.SdpMnsRecord;
23 import android.os.Handler;
24 import android.os.HandlerThread;
25 import android.os.Looper;
26 import android.os.Message;
27 import android.os.ParcelUuid;
28 import android.util.Log;
29 import android.util.SparseBooleanArray;
30 
31 import com.android.bluetooth.BluetoothObexTransport;
32 import com.android.bluetooth.BluetoothStatsLog;
33 import com.android.bluetooth.content_profiles.ContentProfileErrorReportUtils;
34 import com.android.obex.ClientOperation;
35 import com.android.obex.ClientSession;
36 import com.android.obex.HeaderSet;
37 import com.android.obex.ObexTransport;
38 import com.android.obex.ResponseCodes;
39 
40 import java.io.IOException;
41 import java.io.OutputStream;
42 
43 /**
44  * The Message Notification Service class runs its own message handler thread, to avoid executing
45  * long operations on the MAP service Thread. This handler context is passed to the content
46  * observers, hence all call-backs (and thereby transmission of data) is executed from this thread.
47  */
48 // Next tag value for ContentProfileErrorReportUtils.report(): 16
49 public class BluetoothMnsObexClient {
50     private static final String TAG = BluetoothMnsObexClient.class.getSimpleName();
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 final 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 
310     @SuppressLint("AndroidFrameworkRequiresPermission") // TODO: b/350563786
connect()311     public void connect() {
312 
313         mConnected = true;
314 
315         BluetoothSocket btSocket = null;
316         try {
317             // TODO: Do SDP record search again?
318             if (isValidMnsRecord() && mMnsRecord.getL2capPsm() > 0) {
319                 // Do L2CAP connect
320                 btSocket = mRemoteDevice.createL2capSocket(mMnsRecord.getL2capPsm());
321 
322             } else if (isValidMnsRecord() && mMnsRecord.getRfcommChannelNumber() > 0) {
323                 // Do Rfcomm connect
324                 btSocket = mRemoteDevice.createRfcommSocket(mMnsRecord.getRfcommChannelNumber());
325             } else {
326                 // This should not happen...
327                 Log.e(TAG, "Invalid SDP content - attempt a connect to UUID...");
328                 ContentProfileErrorReportUtils.report(
329                         BluetoothProfile.MAP,
330                         BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
331                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR,
332                         4);
333                 // TODO: Why insecure? - is it because the link is already encrypted?
334                 btSocket =
335                         mRemoteDevice.createInsecureRfcommSocketToServiceRecord(
336                                 BLUETOOTH_UUID_OBEX_MNS.getUuid());
337             }
338             btSocket.connect();
339         } catch (IOException e) {
340             ContentProfileErrorReportUtils.report(
341                     BluetoothProfile.MAP,
342                     BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
343                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
344                     5);
345             Log.e(TAG, "BtSocket Connect error " + e.getMessage(), e);
346             // TODO: do we need to report error somewhere?
347             mConnected = false;
348             return;
349         }
350 
351         mTransport = new BluetoothObexTransport(btSocket);
352 
353         try {
354             mClientSession = new ClientSession(mTransport);
355         } catch (IOException e1) {
356             ContentProfileErrorReportUtils.report(
357                     BluetoothProfile.MAP,
358                     BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
359                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
360                     6);
361             Log.e(TAG, "OBEX session create error " + e1.getMessage());
362             mConnected = false;
363         }
364         if (mConnected && mClientSession != null) {
365             boolean connected = false;
366             HeaderSet hs = new HeaderSet();
367             // bb582b41-420c-11db-b0de-0800200c9a66
368             byte[] mnsTarget = {
369                 (byte) 0xbb,
370                 (byte) 0x58,
371                 (byte) 0x2b,
372                 (byte) 0x41,
373                 (byte) 0x42,
374                 (byte) 0x0c,
375                 (byte) 0x11,
376                 (byte) 0xdb,
377                 (byte) 0xb0,
378                 (byte) 0xde,
379                 (byte) 0x08,
380                 (byte) 0x00,
381                 (byte) 0x20,
382                 (byte) 0x0c,
383                 (byte) 0x9a,
384                 (byte) 0x66
385             };
386             hs.setHeader(HeaderSet.TARGET, mnsTarget);
387 
388             try {
389                 mHsConnect = mClientSession.connect(hs);
390                 Log.d(TAG, "OBEX session created");
391                 connected = true;
392             } catch (IOException e) {
393                 ContentProfileErrorReportUtils.report(
394                         BluetoothProfile.MAP,
395                         BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
396                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
397                         7);
398                 Log.e(TAG, "OBEX session connect error " + e.getMessage());
399             }
400             mConnected = connected;
401         }
402     }
403 
404     /**
405      * Call this method to queue an event report to be send to the MNS server.
406      *
407      * @param eventBytes the encoded event data.
408      * @param masInstanceId the MasId of the instance sending the event.
409      */
sendEvent(byte[] eventBytes, int masInstanceId)410     public void sendEvent(byte[] eventBytes, int masInstanceId) {
411         // We need to check for null, to handle shutdown.
412         if (mHandler != null) {
413             Message msg = mHandler.obtainMessage(MSG_MNS_SEND_EVENT, masInstanceId, 0, eventBytes);
414             if (msg != null) {
415                 msg.sendToTarget();
416             }
417         }
418         notifyUpdateWakeLock();
419     }
420 
notifyMnsSdpSearch()421     private void notifyMnsSdpSearch() {
422         if (mCallback != null) {
423             Message msg = Message.obtain(mCallback);
424             msg.what = BluetoothMapService.MSG_MNS_SDP_SEARCH;
425             msg.sendToTarget();
426         }
427     }
428 
sendEventHandler(byte[] eventBytes, int masInstanceId)429     private int sendEventHandler(byte[] eventBytes, int masInstanceId) {
430 
431         boolean error = false;
432         int responseCode = -1;
433         HeaderSet request;
434         int maxChunkSize, bytesToWrite, bytesWritten = 0;
435         ClientSession clientSession = mClientSession;
436 
437         if ((!mConnected) || (clientSession == null)) {
438             Log.w(TAG, "sendEvent after disconnect:" + mConnected);
439             ContentProfileErrorReportUtils.report(
440                     BluetoothProfile.MAP,
441                     BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
442                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_WARN,
443                     8);
444             return responseCode;
445         }
446 
447         request = new HeaderSet();
448         BluetoothMapAppParams appParams = new BluetoothMapAppParams();
449         appParams.setMasInstanceId(masInstanceId);
450 
451         ClientOperation putOperation = null;
452         OutputStream outputStream = null;
453 
454         try {
455             request.setHeader(HeaderSet.TYPE, TYPE_EVENT);
456             request.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.encodeParams());
457 
458             if (mHsConnect.mConnectionID != null) {
459                 request.mConnectionID = new byte[4];
460                 System.arraycopy(mHsConnect.mConnectionID, 0, request.mConnectionID, 0, 4);
461             } else {
462                 Log.w(TAG, "sendEvent: no connection ID");
463                 ContentProfileErrorReportUtils.report(
464                         BluetoothProfile.MAP,
465                         BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
466                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_WARN,
467                         9);
468             }
469 
470             // Send the header first and then the body
471             try {
472                 Log.v(TAG, "Send headerset Event ");
473                 putOperation = (ClientOperation) clientSession.put(request);
474                 // TODO - Should this be kept or Removed
475 
476             } catch (IOException e) {
477                 ContentProfileErrorReportUtils.report(
478                         BluetoothProfile.MAP,
479                         BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
480                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
481                         10);
482                 Log.e(TAG, "Error when put HeaderSet " + e.getMessage());
483                 error = true;
484             }
485             if (!error) {
486                 try {
487                     Log.v(TAG, "Send headerset Event ");
488                     outputStream = putOperation.openOutputStream();
489                 } catch (IOException e) {
490                     ContentProfileErrorReportUtils.report(
491                             BluetoothProfile.MAP,
492                             BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
493                             BluetoothStatsLog
494                                     .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
495                             11);
496                     Log.e(TAG, "Error when opening OutputStream " + e.getMessage());
497                     error = true;
498                 }
499             }
500 
501             if (!error) {
502 
503                 maxChunkSize = putOperation.getMaxPacketSize();
504 
505                 while (bytesWritten < eventBytes.length) {
506                     bytesToWrite = Math.min(maxChunkSize, eventBytes.length - bytesWritten);
507                     outputStream.write(eventBytes, bytesWritten, bytesToWrite);
508                     bytesWritten += bytesToWrite;
509                 }
510 
511                 if (bytesWritten == eventBytes.length) {
512                     Log.i(TAG, "SendEvent finished send length" + eventBytes.length);
513                 } else {
514                     error = true;
515                     putOperation.abort();
516                     Log.i(TAG, "SendEvent interrupted");
517                 }
518             }
519         } catch (IOException e) {
520             ContentProfileErrorReportUtils.report(
521                     BluetoothProfile.MAP,
522                     BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
523                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
524                     12);
525             Log.e(TAG, "Error when sending event", e);
526             error = true;
527         } catch (IndexOutOfBoundsException e) {
528             ContentProfileErrorReportUtils.report(
529                     BluetoothProfile.MAP,
530                     BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
531                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
532                     13);
533             Log.e(TAG, "Error when sending event", e);
534             error = true;
535         } finally {
536             try {
537                 if (outputStream != null) {
538                     outputStream.close();
539                 }
540             } catch (IOException e) {
541                 ContentProfileErrorReportUtils.report(
542                         BluetoothProfile.MAP,
543                         BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
544                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
545                         14);
546                 Log.e(TAG, "Error when closing stream after send " + e.getMessage());
547             }
548             try {
549                 if ((!error) && (putOperation != null)) {
550                     responseCode = putOperation.getResponseCode();
551                     if (responseCode != -1) {
552                         Log.v(TAG, "Put response code " + responseCode);
553                         if (responseCode != ResponseCodes.OBEX_HTTP_OK) {
554                             Log.i(TAG, "Response error code is " + responseCode);
555                         }
556                     }
557                 }
558                 if (putOperation != null) {
559                     putOperation.close();
560                 }
561             } catch (IOException e) {
562                 ContentProfileErrorReportUtils.report(
563                         BluetoothProfile.MAP,
564                         BluetoothProtoEnums.BLUETOOTH_MNS_OBEX_CLIENT,
565                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
566                         15);
567                 Log.e(TAG, "Error when closing stream after send " + e.getMessage());
568             }
569         }
570 
571         return responseCode;
572     }
573 
notifyUpdateWakeLock()574     private void notifyUpdateWakeLock() {
575         if (mCallback != null) {
576             Message msg = Message.obtain(mCallback);
577             msg.what = BluetoothMapService.MSG_ACQUIRE_WAKE_LOCK;
578             msg.sendToTarget();
579         }
580     }
581 }
582