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