1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 /** 18 * Bluetooth MAP MCE StateMachine 19 * (Disconnected) 20 * | ^ 21 * CONNECT | | DISCONNECTED 22 * V | 23 * (Connecting) (Disconnecting) 24 * | ^ 25 * CONNECTED | | DISCONNECT 26 * V | 27 * (Connected) 28 * 29 * Valid Transitions: State + Event -> Transition: 30 * 31 * Disconnected + CONNECT -> Connecting 32 * Connecting + CONNECTED -> Connected 33 * Connecting + TIMEOUT -> Disconnecting 34 * Connecting + DISCONNECT/CONNECT -> Defer Message 35 * Connected + DISCONNECT -> Disconnecting 36 * Connected + CONNECT -> Disconnecting + Defer Message 37 * Disconnecting + DISCONNECTED -> (Safe) Disconnected 38 * Disconnecting + TIMEOUT -> (Force) Disconnected 39 * Disconnecting + DISCONNECT/CONNECT : Defer Message 40 */ 41 package com.android.bluetooth.mapclient; 42 43 import android.app.Activity; 44 import android.app.PendingIntent; 45 import android.bluetooth.BluetoothAdapter; 46 import android.bluetooth.BluetoothDevice; 47 import android.bluetooth.BluetoothMapClient; 48 import android.bluetooth.BluetoothProfile; 49 import android.bluetooth.BluetoothUuid; 50 import android.bluetooth.SdpMasRecord; 51 import android.content.Intent; 52 import android.net.Uri; 53 import android.os.Message; 54 import android.telecom.PhoneAccount; 55 import android.telephony.SmsManager; 56 import android.util.Log; 57 58 import com.android.bluetooth.BluetoothMetricsProto; 59 import com.android.bluetooth.btservice.MetricsLogger; 60 import com.android.bluetooth.btservice.ProfileService; 61 import com.android.internal.annotations.VisibleForTesting; 62 import com.android.internal.util.IState; 63 import com.android.internal.util.State; 64 import com.android.internal.util.StateMachine; 65 import com.android.vcard.VCardConstants; 66 import com.android.vcard.VCardEntry; 67 import com.android.vcard.VCardProperty; 68 69 import java.util.ArrayList; 70 import java.util.Calendar; 71 import java.util.HashMap; 72 import java.util.List; 73 74 /* The MceStateMachine is responsible for setting up and maintaining a connection to a single 75 * specific Messaging Server Equipment endpoint. Upon connect command an SDP record is retrieved, 76 * a connection to the Message Access Server is created and a request to enable notification of new 77 * messages is sent. 78 */ 79 final class MceStateMachine extends StateMachine { 80 // Messages for events handled by the StateMachine 81 static final int MSG_MAS_CONNECTED = 1001; 82 static final int MSG_MAS_DISCONNECTED = 1002; 83 static final int MSG_MAS_REQUEST_COMPLETED = 1003; 84 static final int MSG_MAS_REQUEST_FAILED = 1004; 85 static final int MSG_MAS_SDP_DONE = 1005; 86 static final int MSG_MAS_SDP_FAILED = 1006; 87 static final int MSG_OUTBOUND_MESSAGE = 2001; 88 static final int MSG_INBOUND_MESSAGE = 2002; 89 static final int MSG_NOTIFICATION = 2003; 90 static final int MSG_GET_LISTING = 2004; 91 static final int MSG_GET_MESSAGE_LISTING = 2005; 92 93 private static final String TAG = "MceSM"; 94 private static final Boolean DBG = MapClientService.DBG; 95 private static final int TIMEOUT = 10000; 96 private static final int MAX_MESSAGES = 20; 97 private static final int MSG_CONNECT = 1; 98 private static final int MSG_DISCONNECT = 2; 99 private static final int MSG_CONNECTING_TIMEOUT = 3; 100 private static final int MSG_DISCONNECTING_TIMEOUT = 4; 101 // Folder names as defined in Bluetooth.org MAP spec V10 102 private static final String FOLDER_TELECOM = "telecom"; 103 private static final String FOLDER_MSG = "msg"; 104 private static final String FOLDER_OUTBOX = "outbox"; 105 private static final String FOLDER_INBOX = "inbox"; 106 private static final String INBOX_PATH = "telecom/msg/inbox"; 107 108 109 // Connectivity States 110 private int mPreviousState = BluetoothProfile.STATE_DISCONNECTED; 111 private State mDisconnected; 112 private State mConnecting; 113 private State mConnected; 114 private State mDisconnecting; 115 116 private final BluetoothDevice mDevice; 117 private MapClientService mService; 118 private MasClient mMasClient; 119 private HashMap<String, Bmessage> mSentMessageLog = new HashMap<>(MAX_MESSAGES); 120 private HashMap<Bmessage, PendingIntent> mSentReceiptRequested = new HashMap<>(MAX_MESSAGES); 121 private HashMap<Bmessage, PendingIntent> mDeliveryReceiptRequested = 122 new HashMap<>(MAX_MESSAGES); 123 private Bmessage.Type mDefaultMessageType = Bmessage.Type.SMS_CDMA; 124 125 MceStateMachine(MapClientService service, BluetoothDevice device) { 126 this(service, device, null); 127 } 128 129 @VisibleForTesting 130 MceStateMachine(MapClientService service, BluetoothDevice device, MasClient masClient) { 131 super(TAG); 132 mMasClient = masClient; 133 mService = service; 134 135 mPreviousState = BluetoothProfile.STATE_DISCONNECTED; 136 137 mDevice = device; 138 mDisconnected = new Disconnected(); 139 mConnecting = new Connecting(); 140 mDisconnecting = new Disconnecting(); 141 mConnected = new Connected(); 142 143 addState(mDisconnected); 144 addState(mConnecting); 145 addState(mDisconnecting); 146 addState(mConnected); 147 setInitialState(mConnecting); 148 start(); 149 } 150 151 public void doQuit() { 152 quitNow(); 153 } 154 155 @Override 156 protected void onQuitting() { 157 if (mService != null) { 158 mService.cleanupDevice(mDevice); 159 } 160 } 161 162 synchronized BluetoothDevice getDevice() { 163 return mDevice; 164 } 165 166 private void onConnectionStateChanged(int prevState, int state) { 167 // mDevice == null only at setInitialState 168 if (mDevice == null) { 169 return; 170 } 171 if (DBG) { 172 Log.d(TAG, "Connection state " + mDevice + ": " + prevState + "->" + state); 173 } 174 if (prevState != state && state == BluetoothProfile.STATE_CONNECTED) { 175 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.MAP_CLIENT); 176 } 177 Intent intent = new Intent(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED); 178 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 179 intent.putExtra(BluetoothProfile.EXTRA_STATE, state); 180 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 181 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 182 mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 183 } 184 185 public synchronized int getState() { 186 IState currentState = this.getCurrentState(); 187 if (currentState.getClass() == Disconnected.class) { 188 return BluetoothProfile.STATE_DISCONNECTED; 189 } 190 if (currentState.getClass() == Connected.class) { 191 return BluetoothProfile.STATE_CONNECTED; 192 } 193 if (currentState.getClass() == Connecting.class) { 194 return BluetoothProfile.STATE_CONNECTING; 195 } 196 if (currentState.getClass() == Disconnecting.class) { 197 return BluetoothProfile.STATE_DISCONNECTING; 198 } 199 return BluetoothProfile.STATE_DISCONNECTED; 200 } 201 202 public boolean disconnect() { 203 if (DBG) { 204 Log.d(TAG, "Disconnect Request " + mDevice.getAddress()); 205 } 206 sendMessage(MSG_DISCONNECT, mDevice); 207 return true; 208 } 209 210 public synchronized boolean sendMapMessage(Uri[] contacts, String message, 211 PendingIntent sentIntent, PendingIntent deliveredIntent) { 212 if (DBG) { 213 Log.d(TAG, "Send Message " + message); 214 } 215 if (contacts == null || contacts.length <= 0) { 216 return false; 217 } 218 if (this.getCurrentState() == mConnected) { 219 Bmessage bmsg = new Bmessage(); 220 // Set type and status. 221 bmsg.setType(getDefaultMessageType()); 222 bmsg.setStatus(Bmessage.Status.READ); 223 224 for (Uri contact : contacts) { 225 // Who to send the message to. 226 VCardEntry destEntry = new VCardEntry(); 227 VCardProperty destEntryPhone = new VCardProperty(); 228 if (DBG) { 229 Log.d(TAG, "Scheme " + contact.getScheme()); 230 } 231 if (PhoneAccount.SCHEME_TEL.equals(contact.getScheme())) { 232 destEntryPhone.setName(VCardConstants.PROPERTY_TEL); 233 destEntryPhone.addValues(contact.getSchemeSpecificPart()); 234 if (DBG) { 235 Log.d(TAG, "Sending to phone numbers " + destEntryPhone.getValueList()); 236 } 237 } else { 238 if (DBG) { 239 Log.w(TAG, "Scheme " + contact.getScheme() + " not supported."); 240 } 241 return false; 242 } 243 destEntry.addProperty(destEntryPhone); 244 bmsg.addRecipient(destEntry); 245 } 246 247 // Message of the body. 248 bmsg.setBodyContent(message); 249 if (sentIntent != null) { 250 mSentReceiptRequested.put(bmsg, sentIntent); 251 } 252 if (deliveredIntent != null) { 253 mDeliveryReceiptRequested.put(bmsg, deliveredIntent); 254 } 255 sendMessage(MSG_OUTBOUND_MESSAGE, bmsg); 256 return true; 257 } 258 return false; 259 } 260 261 synchronized boolean getMessage(String handle) { 262 if (DBG) { 263 Log.d(TAG, "getMessage" + handle); 264 } 265 if (this.getCurrentState() == mConnected) { 266 sendMessage(MSG_INBOUND_MESSAGE, handle); 267 return true; 268 } 269 return false; 270 } 271 272 synchronized boolean getUnreadMessages() { 273 if (DBG) { 274 Log.d(TAG, "getMessage"); 275 } 276 if (this.getCurrentState() == mConnected) { 277 sendMessage(MSG_GET_MESSAGE_LISTING, FOLDER_INBOX); 278 return true; 279 } 280 return false; 281 } 282 283 private String getContactURIFromPhone(String number) { 284 return PhoneAccount.SCHEME_TEL + ":" + number; 285 } 286 287 Bmessage.Type getDefaultMessageType() { 288 synchronized (mDefaultMessageType) { 289 return mDefaultMessageType; 290 } 291 } 292 293 void setDefaultMessageType(SdpMasRecord sdpMasRecord) { 294 int supportedMessageTypes = sdpMasRecord.getSupportedMessageTypes(); 295 synchronized (mDefaultMessageType) { 296 if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_CDMA) > 0) { 297 mDefaultMessageType = Bmessage.Type.SMS_CDMA; 298 } else if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_GSM) > 0) { 299 mDefaultMessageType = Bmessage.Type.SMS_GSM; 300 } 301 } 302 } 303 304 public void dump(StringBuilder sb) { 305 ProfileService.println(sb, "mCurrentDevice: " + mDevice.getAddress() + " (name = " 306 + mDevice.getName() + "), StateMachine: " + this.toString()); 307 } 308 309 class Disconnected extends State { 310 @Override 311 public void enter() { 312 if (DBG) { 313 Log.d(TAG, "Enter Disconnected: " + getCurrentMessage().what); 314 } 315 onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_DISCONNECTED); 316 mPreviousState = BluetoothProfile.STATE_DISCONNECTED; 317 quit(); 318 } 319 320 @Override 321 public void exit() { 322 mPreviousState = BluetoothProfile.STATE_DISCONNECTED; 323 } 324 } 325 326 class Connecting extends State { 327 @Override 328 public void enter() { 329 if (DBG) { 330 Log.d(TAG, "Enter Connecting: " + getCurrentMessage().what); 331 } 332 onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_CONNECTING); 333 334 BluetoothAdapter.getDefaultAdapter().cancelDiscovery(); 335 // When commanded to connect begin SDP to find the MAS server. 336 mDevice.sdpSearch(BluetoothUuid.MAS); 337 sendMessageDelayed(MSG_CONNECTING_TIMEOUT, TIMEOUT); 338 } 339 340 @Override 341 public boolean processMessage(Message message) { 342 if (DBG) { 343 Log.d(TAG, "processMessage" + this.getName() + message.what); 344 } 345 346 switch (message.what) { 347 case MSG_MAS_SDP_DONE: 348 if (DBG) { 349 Log.d(TAG, "SDP Complete"); 350 } 351 if (mMasClient == null) { 352 mMasClient = new MasClient(mDevice, MceStateMachine.this, 353 (SdpMasRecord) message.obj); 354 setDefaultMessageType((SdpMasRecord) message.obj); 355 } 356 break; 357 358 case MSG_MAS_CONNECTED: 359 transitionTo(mConnected); 360 break; 361 362 case MSG_MAS_DISCONNECTED: 363 transitionTo(mDisconnected); 364 break; 365 366 case MSG_CONNECTING_TIMEOUT: 367 transitionTo(mDisconnecting); 368 break; 369 370 case MSG_CONNECT: 371 case MSG_DISCONNECT: 372 deferMessage(message); 373 break; 374 375 default: 376 Log.w(TAG, "Unexpected message: " + message.what + " from state:" 377 + this.getName()); 378 return NOT_HANDLED; 379 } 380 return HANDLED; 381 } 382 383 @Override 384 public void exit() { 385 mPreviousState = BluetoothProfile.STATE_CONNECTING; 386 removeMessages(MSG_CONNECTING_TIMEOUT); 387 } 388 } 389 390 class Connected extends State { 391 @Override 392 public void enter() { 393 if (DBG) { 394 Log.d(TAG, "Enter Connected: " + getCurrentMessage().what); 395 } 396 onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_CONNECTED); 397 398 mMasClient.makeRequest(new RequestSetPath(FOLDER_TELECOM)); 399 mMasClient.makeRequest(new RequestSetPath(FOLDER_MSG)); 400 mMasClient.makeRequest(new RequestSetPath(FOLDER_INBOX)); 401 mMasClient.makeRequest(new RequestGetFolderListing(0, 0)); 402 mMasClient.makeRequest(new RequestSetPath(false)); 403 mMasClient.makeRequest(new RequestSetNotificationRegistration(true)); 404 } 405 406 @Override 407 public boolean processMessage(Message message) { 408 switch (message.what) { 409 case MSG_DISCONNECT: 410 if (mDevice.equals(message.obj)) { 411 transitionTo(mDisconnecting); 412 } 413 break; 414 415 case MSG_OUTBOUND_MESSAGE: 416 mMasClient.makeRequest( 417 new RequestPushMessage(FOLDER_OUTBOX, (Bmessage) message.obj, null, 418 false, false)); 419 break; 420 421 case MSG_INBOUND_MESSAGE: 422 mMasClient.makeRequest( 423 new RequestGetMessage((String) message.obj, MasClient.CharsetType.UTF_8, 424 false)); 425 break; 426 427 case MSG_NOTIFICATION: 428 processNotification(message); 429 break; 430 431 case MSG_GET_LISTING: 432 mMasClient.makeRequest(new RequestGetFolderListing(0, 0)); 433 break; 434 435 case MSG_GET_MESSAGE_LISTING: 436 // Get latest 50 Unread messages in the last week 437 MessagesFilter filter = new MessagesFilter(); 438 filter.setMessageType((byte) 0); 439 filter.setReadStatus(MessagesFilter.READ_STATUS_UNREAD); 440 Calendar calendar = Calendar.getInstance(); 441 calendar.add(Calendar.DATE, -7); 442 filter.setPeriod(calendar.getTime(), null); 443 mMasClient.makeRequest(new RequestGetMessagesListing( 444 (String) message.obj, 0, filter, 0, 50, 0)); 445 break; 446 447 case MSG_MAS_REQUEST_COMPLETED: 448 if (DBG) { 449 Log.d(TAG, "Completed request"); 450 } 451 if (message.obj instanceof RequestGetMessage) { 452 processInboundMessage((RequestGetMessage) message.obj); 453 } else if (message.obj instanceof RequestPushMessage) { 454 String messageHandle = ((RequestPushMessage) message.obj).getMsgHandle(); 455 if (DBG) { 456 Log.d(TAG, "Message Sent......." + messageHandle); 457 } 458 // ignore the top-order byte (converted to string) in the handle for now 459 mSentMessageLog.put(messageHandle.substring(2), 460 ((RequestPushMessage) message.obj).getBMsg()); 461 } else if (message.obj instanceof RequestGetMessagesListing) { 462 processMessageListing((RequestGetMessagesListing) message.obj); 463 } 464 break; 465 466 case MSG_CONNECT: 467 if (!mDevice.equals(message.obj)) { 468 deferMessage(message); 469 transitionTo(mDisconnecting); 470 } 471 break; 472 473 default: 474 Log.w(TAG, "Unexpected message: " + message.what + " from state:" 475 + this.getName()); 476 return NOT_HANDLED; 477 } 478 return HANDLED; 479 } 480 481 @Override 482 public void exit() { 483 mPreviousState = BluetoothProfile.STATE_CONNECTED; 484 } 485 486 private void processNotification(Message msg) { 487 if (DBG) { 488 Log.d(TAG, "Handler: msg: " + msg.what); 489 } 490 491 switch (msg.what) { 492 case MSG_NOTIFICATION: 493 EventReport ev = (EventReport) msg.obj; 494 if (DBG) { 495 Log.d(TAG, "Message Type = " + ev.getType()); 496 } 497 if (DBG) { 498 Log.d(TAG, "Message handle = " + ev.getHandle()); 499 } 500 switch (ev.getType()) { 501 502 case NEW_MESSAGE: 503 //mService.get().sendNewMessageNotification(ev); 504 mMasClient.makeRequest(new RequestGetMessage(ev.getHandle(), 505 MasClient.CharsetType.UTF_8, false)); 506 break; 507 508 case DELIVERY_SUCCESS: 509 case SENDING_SUCCESS: 510 notifySentMessageStatus(ev.getHandle(), ev.getType()); 511 break; 512 } 513 } 514 } 515 516 // Sets the specified message status to "read" (from "unread" status, mostly) 517 private void markMessageRead(RequestGetMessage request) { 518 if (DBG) Log.d(TAG, "markMessageRead"); 519 mMasClient.makeRequest(new RequestSetMessageStatus( 520 request.getHandle(), RequestSetMessageStatus.StatusIndicator.READ)); 521 } 522 523 // Sets the specified message status to "deleted" 524 private void markMessageDeleted(RequestGetMessage request) { 525 if (DBG) Log.d(TAG, "markMessageDeleted"); 526 mMasClient.makeRequest(new RequestSetMessageStatus( 527 request.getHandle(), RequestSetMessageStatus.StatusIndicator.DELETED)); 528 } 529 530 private void processMessageListing(RequestGetMessagesListing request) { 531 if (DBG) { 532 Log.d(TAG, "processMessageListing"); 533 } 534 ArrayList<com.android.bluetooth.mapclient.Message> messageHandles = request.getList(); 535 if (messageHandles != null) { 536 for (com.android.bluetooth.mapclient.Message handle : messageHandles) { 537 if (DBG) { 538 Log.d(TAG, "getting message "); 539 } 540 getMessage(handle.getHandle()); 541 } 542 } 543 } 544 545 private void processInboundMessage(RequestGetMessage request) { 546 Bmessage message = request.getMessage(); 547 if (DBG) { 548 Log.d(TAG, "Notify inbound Message" + message); 549 } 550 551 if (message == null) { 552 return; 553 } 554 if (!INBOX_PATH.equalsIgnoreCase(message.getFolder())) { 555 if (DBG) { 556 Log.d(TAG, "Ignoring message received in " + message.getFolder() + "."); 557 } 558 return; 559 } 560 switch (message.getType()) { 561 case SMS_CDMA: 562 case SMS_GSM: 563 if (DBG) { 564 Log.d(TAG, "Body: " + message.getBodyContent()); 565 } 566 if (DBG) { 567 Log.d(TAG, message.toString()); 568 } 569 if (DBG) { 570 Log.d(TAG, "Recipients" + message.getRecipients().toString()); 571 } 572 573 Intent intent = new Intent(); 574 intent.setAction(BluetoothMapClient.ACTION_MESSAGE_RECEIVED); 575 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 576 intent.putExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE, request.getHandle()); 577 intent.putExtra(android.content.Intent.EXTRA_TEXT, message.getBodyContent()); 578 VCardEntry originator = message.getOriginator(); 579 if (originator != null) { 580 if (DBG) { 581 Log.d(TAG, originator.toString()); 582 } 583 List<VCardEntry.PhoneData> phoneData = originator.getPhoneList(); 584 if (phoneData != null && phoneData.size() > 0) { 585 String phoneNumber = phoneData.get(0).getNumber(); 586 if (DBG) { 587 Log.d(TAG, "Originator number: " + phoneNumber); 588 } 589 intent.putExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_URI, 590 getContactURIFromPhone(phoneNumber)); 591 } 592 intent.putExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME, 593 originator.getDisplayName()); 594 } 595 mService.sendBroadcast(intent); 596 break; 597 598 case MMS: 599 case EMAIL: 600 default: 601 Log.e(TAG, "Received unhandled type" + message.getType().toString()); 602 break; 603 } 604 } 605 606 private void notifySentMessageStatus(String handle, EventReport.Type status) { 607 if (DBG) { 608 Log.d(TAG, "got a status for " + handle + " Status = " + status); 609 } 610 PendingIntent intentToSend = null; 611 // ignore the top-order byte (converted to string) in the handle for now 612 String shortHandle = handle.substring(2); 613 if (status == EventReport.Type.SENDING_FAILURE 614 || status == EventReport.Type.SENDING_SUCCESS) { 615 intentToSend = mSentReceiptRequested.remove(mSentMessageLog.get(shortHandle)); 616 } else if (status == EventReport.Type.DELIVERY_SUCCESS 617 || status == EventReport.Type.DELIVERY_FAILURE) { 618 intentToSend = mDeliveryReceiptRequested.remove(mSentMessageLog.get(shortHandle)); 619 } 620 621 if (intentToSend != null) { 622 try { 623 if (DBG) { 624 Log.d(TAG, "*******Sending " + intentToSend); 625 } 626 int result = Activity.RESULT_OK; 627 if (status == EventReport.Type.SENDING_FAILURE 628 || status == EventReport.Type.DELIVERY_FAILURE) { 629 result = SmsManager.RESULT_ERROR_GENERIC_FAILURE; 630 } 631 intentToSend.send(result); 632 } catch (PendingIntent.CanceledException e) { 633 Log.w(TAG, "Notification Request Canceled" + e); 634 } 635 } else { 636 Log.e(TAG, "Received a notification on message with handle = " 637 + handle + ", but it is NOT found in mSentMessageLog! where did it go?"); 638 } 639 } 640 } 641 642 class Disconnecting extends State { 643 @Override 644 public void enter() { 645 if (DBG) { 646 Log.d(TAG, "Enter Disconnecting: " + getCurrentMessage().what); 647 } 648 onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_DISCONNECTING); 649 650 if (mMasClient != null) { 651 mMasClient.makeRequest(new RequestSetNotificationRegistration(false)); 652 mMasClient.shutdown(); 653 sendMessageDelayed(MSG_DISCONNECTING_TIMEOUT, TIMEOUT); 654 } else { 655 // MAP was never connected 656 transitionTo(mDisconnected); 657 } 658 } 659 660 @Override 661 public boolean processMessage(Message message) { 662 switch (message.what) { 663 case MSG_DISCONNECTING_TIMEOUT: 664 case MSG_MAS_DISCONNECTED: 665 mMasClient = null; 666 transitionTo(mDisconnected); 667 break; 668 669 case MSG_CONNECT: 670 case MSG_DISCONNECT: 671 deferMessage(message); 672 break; 673 674 default: 675 Log.w(TAG, "Unexpected message: " + message.what + " from state:" 676 + this.getName()); 677 return NOT_HANDLED; 678 } 679 return HANDLED; 680 } 681 682 @Override 683 public void exit() { 684 mPreviousState = BluetoothProfile.STATE_DISCONNECTING; 685 removeMessages(MSG_DISCONNECTING_TIMEOUT); 686 } 687 } 688 689 void receiveEvent(EventReport ev) { 690 if (DBG) { 691 Log.d(TAG, "Message Type = " + ev.getType()); 692 } 693 if (DBG) { 694 Log.d(TAG, "Message handle = " + ev.getHandle()); 695 } 696 sendMessage(MSG_NOTIFICATION, ev); 697 } 698 } 699