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