HeadsetStateMachine.java revision dbd53d223fddcad41820c3ded5d36058c5910e7f
1/*
2 * Copyright (C) 2012 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
17package com.android.bluetooth.hfp;
18
19import android.bluetooth.BluetoothAssignedNumbers;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothHeadset;
22import android.bluetooth.BluetoothProfile;
23import android.content.ActivityNotFoundException;
24import android.content.Context;
25import android.content.Intent;
26import android.media.AudioManager;
27import android.net.Uri;
28import android.os.IDeviceIdleController;
29import android.os.Looper;
30import android.os.Message;
31import android.os.RemoteException;
32import android.os.ServiceManager;
33import android.os.SystemClock;
34import android.os.UserHandle;
35import android.support.annotation.VisibleForTesting;
36import android.telephony.PhoneNumberUtils;
37import android.util.Log;
38
39import com.android.bluetooth.btservice.AdapterService;
40import com.android.bluetooth.btservice.ProfileService;
41import com.android.internal.util.State;
42import com.android.internal.util.StateMachine;
43
44import java.io.FileDescriptor;
45import java.io.PrintWriter;
46import java.io.StringWriter;
47import java.util.ArrayList;
48import java.util.HashMap;
49import java.util.Map;
50import java.util.Objects;
51import java.util.Scanner;
52
53/**
54 * A Bluetooth Handset StateMachine
55 *                        (Disconnected)
56 *                           |      ^
57 *                   CONNECT |      | DISCONNECTED
58 *                           V      |
59 *                  (Connecting)   (Disconnecting)
60 *                           |      ^
61 *                 CONNECTED |      | DISCONNECT
62 *                           V      |
63 *                          (Connected)
64 *                           |      ^
65 *             CONNECT_AUDIO |      | AUDIO_DISCONNECTED
66 *                           V      |
67 *             (AudioConnecting)   (AudioDiconnecting)
68 *                           |      ^
69 *           AUDIO_CONNECTED |      | DISCONNECT_AUDIO
70 *                           V      |
71 *                           (AudioOn)
72 */
73@VisibleForTesting
74public class HeadsetStateMachine extends StateMachine {
75    private static final String TAG = "HeadsetStateMachine";
76    private static final boolean DBG = false;
77
78    private static final String HEADSET_NAME = "bt_headset_name";
79    private static final String HEADSET_NREC = "bt_headset_nrec";
80    private static final String HEADSET_WBS = "bt_wbs";
81    private static final String HEADSET_AUDIO_FEATURE_ON = "on";
82    private static final String HEADSET_AUDIO_FEATURE_OFF = "off";
83
84    /* Telephone URI scheme */
85    private static final String SCHEME_TEL = "tel";
86
87    static final int CONNECT = 1;
88    static final int DISCONNECT = 2;
89    static final int CONNECT_AUDIO = 3;
90    static final int DISCONNECT_AUDIO = 4;
91    static final int VOICE_RECOGNITION_START = 5;
92    static final int VOICE_RECOGNITION_STOP = 6;
93
94    // message.obj is an intent AudioManager.VOLUME_CHANGED_ACTION
95    // EXTRA_VOLUME_STREAM_TYPE is STREAM_BLUETOOTH_SCO
96    static final int INTENT_SCO_VOLUME_CHANGED = 7;
97    static final int INTENT_CONNECTION_ACCESS_REPLY = 8;
98    static final int CALL_STATE_CHANGED = 9;
99    static final int DEVICE_STATE_CHANGED = 10;
100    static final int SEND_CCLC_RESPONSE = 11;
101    static final int SEND_VENDOR_SPECIFIC_RESULT_CODE = 12;
102    static final int SEND_BSIR = 13;
103
104    static final int VIRTUAL_CALL_START = 14;
105    static final int VIRTUAL_CALL_STOP = 15;
106
107    static final int STACK_EVENT = 101;
108    private static final int DIALING_OUT_TIMEOUT = 102;
109    private static final int START_VR_TIMEOUT = 103;
110    private static final int CLCC_RSP_TIMEOUT = 104;
111
112    private static final int CONNECT_TIMEOUT = 201;
113
114    private static final int DIALING_OUT_TIMEOUT_MS = 10000;
115    private static final int START_VR_TIMEOUT_MS = 5000;
116    private static final int CLCC_RSP_TIMEOUT_MS = 5000;
117    // NOTE: the value is not "final" - it is modified in the unit tests
118    @VisibleForTesting static int sConnectTimeoutMs = 30000;
119
120    private final BluetoothDevice mDevice;
121
122    // State machine states
123    private final Disconnected mDisconnected = new Disconnected();
124    private final Connecting mConnecting = new Connecting();
125    private final Disconnecting mDisconnecting = new Disconnecting();
126    private final Connected mConnected = new Connected();
127    private final AudioOn mAudioOn = new AudioOn();
128    private final AudioConnecting mAudioConnecting = new AudioConnecting();
129    private final AudioDisconnecting mAudioDisconnecting = new AudioDisconnecting();
130    private HeadsetStateBase mPrevState;
131
132    // Run time dependencies
133    private final HeadsetService mHeadsetService;
134    private final AdapterService mAdapterService;
135    private final HeadsetNativeInterface mNativeInterface;
136    private final HeadsetSystemInterface mSystemInterface;
137
138    // Runtime states
139    private boolean mVirtualCallStarted;
140    private boolean mVoiceRecognitionStarted;
141    private boolean mWaitingForVoiceRecognition;
142    private boolean mDialingOut;
143    private int mSpeakerVolume;
144    private int mMicVolume;
145    // The timestamp when the device entered connecting/connected state
146    private long mConnectingTimestampMs = Long.MIN_VALUE;
147    // Audio Parameters like NREC
148    private final HashMap<String, String> mAudioParams = new HashMap<>();
149    // AT Phone book keeps a group of states used by AT+CPBR commands
150    private final AtPhonebook mPhonebook;
151
152    // Keys are AT commands, and values are the company IDs.
153    private static final Map<String, Integer> VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID;
154    // Intent that get sent during voice recognition events.
155    private static final Intent VOICE_COMMAND_INTENT;
156
157    static {
158        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID = new HashMap<>();
159        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
160                BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT,
161                BluetoothAssignedNumbers.PLANTRONICS);
162        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
163                BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID,
164                BluetoothAssignedNumbers.GOOGLE);
165        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
166                BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XAPL,
167                BluetoothAssignedNumbers.APPLE);
168        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
169                BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV,
170                BluetoothAssignedNumbers.APPLE);
171        VOICE_COMMAND_INTENT = new Intent(Intent.ACTION_VOICE_COMMAND);
172        VOICE_COMMAND_INTENT.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
173    }
174
175    private HeadsetStateMachine(BluetoothDevice device, Looper looper,
176            HeadsetService headsetService, AdapterService adapterService,
177            HeadsetNativeInterface nativeInterface, HeadsetSystemInterface systemInterface) {
178        super(TAG, Objects.requireNonNull(looper, "looper cannot be null"));
179        // Enable/Disable StateMachine debug logs
180        setDbg(DBG);
181        mDevice = Objects.requireNonNull(device, "device cannot be null");
182        mHeadsetService = Objects.requireNonNull(headsetService, "headsetService cannot be null");
183        mNativeInterface =
184                Objects.requireNonNull(nativeInterface, "nativeInterface cannot be null");
185        mSystemInterface =
186                Objects.requireNonNull(systemInterface, "systemInterface cannot be null");
187        mAdapterService = Objects.requireNonNull(adapterService, "AdapterService cannot be null");
188        // Create phonebook helper
189        mPhonebook = new AtPhonebook(mHeadsetService, mNativeInterface);
190        // Initialize state machine
191        addState(mDisconnected);
192        addState(mConnecting);
193        addState(mDisconnecting);
194        addState(mConnected);
195        addState(mAudioOn);
196        addState(mAudioConnecting);
197        addState(mAudioDisconnecting);
198        setInitialState(mDisconnected);
199    }
200
201    static HeadsetStateMachine make(BluetoothDevice device, Looper looper,
202            HeadsetService headsetService, AdapterService adapterService,
203            HeadsetNativeInterface nativeInterface, HeadsetSystemInterface systemInterface) {
204        HeadsetStateMachine stateMachine =
205                new HeadsetStateMachine(device, looper, headsetService, adapterService,
206                        nativeInterface, systemInterface);
207        stateMachine.start();
208        Log.i(TAG, "Created state machine " + stateMachine + " for " + device);
209        return stateMachine;
210    }
211
212    static void destroy(HeadsetStateMachine stateMachine) {
213        Log.i(TAG, "destroy");
214        if (stateMachine == null) {
215            Log.w(TAG, "destroy(), stateMachine is null");
216            return;
217        }
218        stateMachine.quitNow();
219        stateMachine.cleanup();
220    }
221
222    public void cleanup() {
223        if (mPhonebook != null) {
224            mPhonebook.cleanup();
225        }
226        mAudioParams.clear();
227    }
228
229    public void dump(StringBuilder sb) {
230        ProfileService.println(sb, "  mCurrentDevice: " + mDevice);
231        ProfileService.println(sb, "  mCurrentState: " + getCurrentState());
232        ProfileService.println(sb, "  mPrevState: " + mPrevState);
233        ProfileService.println(sb, "  mConnectionState: " + getConnectionState());
234        ProfileService.println(sb, "  mAudioState: " + getAudioState());
235        ProfileService.println(sb, "  mVirtualCallStarted: " + mVirtualCallStarted);
236        ProfileService.println(sb, "  mVoiceRecognitionStarted: " + mVoiceRecognitionStarted);
237        ProfileService.println(sb, "  mWaitingForVoiceRecognition: " + mWaitingForVoiceRecognition);
238        ProfileService.println(sb, "  mDialingOut: " + mDialingOut);
239        ProfileService.println(sb, "  mSpeakerVolume: " + mSpeakerVolume);
240        ProfileService.println(sb, "  mMicVolume: " + mMicVolume);
241        ProfileService.println(sb,
242                "  mConnectingTimestampMs(uptimeMillis): " + mConnectingTimestampMs);
243        ProfileService.println(sb, "  StateMachine: " + this);
244        // Dump the state machine logs
245        StringWriter stringWriter = new StringWriter();
246        PrintWriter printWriter = new PrintWriter(stringWriter);
247        super.dump(new FileDescriptor(), printWriter, new String[]{});
248        printWriter.flush();
249        stringWriter.flush();
250        ProfileService.println(sb, "  StateMachineLog:");
251        Scanner scanner = new Scanner(stringWriter.toString());
252        while (scanner.hasNextLine()) {
253            String line = scanner.nextLine();
254            ProfileService.println(sb, "    " + line);
255        }
256        scanner.close();
257    }
258
259    /**
260     * Base class for states used in this state machine to share common infrastructures
261     */
262    private abstract class HeadsetStateBase extends State {
263        @Override
264        public void enter() {
265            // Crash if mPrevState is null and state is not Disconnected
266            if (!(this instanceof Disconnected) && mPrevState == null) {
267                throw new IllegalStateException("mPrevState is null on enter()");
268            }
269            enforceValidConnectionStateTransition();
270        }
271
272        @Override
273        public void exit() {
274            mPrevState = this;
275        }
276
277        @Override
278        public String toString() {
279            return getName();
280        }
281
282        /**
283         * Broadcast audio and connection state changes to the system. This should be called at the
284         * end of enter() method after all the setup is done
285         */
286        void broadcastStateTransitions() {
287            if (mPrevState == null) {
288                return;
289            }
290            // TODO: Add STATE_AUDIO_DISCONNECTING constant to get rid of the 2nd part of this logic
291            if (getAudioStateInt() != mPrevState.getAudioStateInt() || (
292                    mPrevState instanceof AudioDisconnecting && this instanceof AudioOn)) {
293                stateLogD("audio state changed: " + mDevice + ": " + mPrevState + " -> " + this);
294                broadcastAudioState(mDevice, mPrevState.getAudioStateInt(), getAudioStateInt());
295            }
296            if (getConnectionStateInt() != mPrevState.getConnectionStateInt()) {
297                stateLogD(
298                        "connection state changed: " + mDevice + ": " + mPrevState + " -> " + this);
299                broadcastConnectionState(mDevice, mPrevState.getConnectionStateInt(),
300                        getConnectionStateInt());
301            }
302        }
303
304        // Should not be called from enter() method
305        void broadcastConnectionState(BluetoothDevice device, int fromState, int toState) {
306            stateLogD("broadcastConnectionState " + device + ": " + fromState + "->" + toState);
307            if (fromState == BluetoothProfile.STATE_CONNECTED && isVirtualCallInProgress()) {
308                // Headset is disconnecting, stop Virtual call if active.
309                terminateScoUsingVirtualVoiceCall();
310            }
311            mHeadsetService.onConnectionStateChangedFromStateMachine(device, fromState, toState);
312            Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
313            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState);
314            intent.putExtra(BluetoothProfile.EXTRA_STATE, toState);
315            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
316            intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
317            mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL,
318                    HeadsetService.BLUETOOTH_PERM);
319        }
320
321        // Should not be called from enter() method
322        void broadcastAudioState(BluetoothDevice device, int fromState, int toState) {
323            stateLogD("broadcastAudioState: " + device + ": " + fromState + "->" + toState);
324            if (fromState == BluetoothHeadset.STATE_AUDIO_CONNECTED && isVirtualCallInProgress()) {
325                // When SCO gets disconnected during call transfer, Virtual call
326                // needs to be cleaned up.So call terminateScoUsingVirtualVoiceCall.
327                terminateScoUsingVirtualVoiceCall();
328            }
329            mHeadsetService.onAudioStateChangedFromStateMachine(device, fromState, toState);
330            Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
331            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState);
332            intent.putExtra(BluetoothProfile.EXTRA_STATE, toState);
333            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
334            mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL,
335                    HeadsetService.BLUETOOTH_PERM);
336        }
337
338        /**
339         * Verify if the current state transition is legal. This is supposed to be called from
340         * enter() method and crash if the state transition is out of the specification
341         *
342         * Note:
343         * This method uses state objects to verify transition because these objects should be final
344         * and any other instances are invalid
345         */
346        void enforceValidConnectionStateTransition() {
347            boolean result = false;
348            if (this == mDisconnected) {
349                result = mPrevState == null || mPrevState == mConnecting
350                        || mPrevState == mDisconnecting
351                        // TODO: edges to be removed after native stack refactoring
352                        // all transitions to disconnected state should go through a pending state
353                        // also, states should not go directly from an active audio state to
354                        // disconnected state
355                        || mPrevState == mConnected || mPrevState == mAudioOn
356                        || mPrevState == mAudioConnecting || mPrevState == mAudioDisconnecting;
357            } else if (this == mConnecting) {
358                result = mPrevState == mDisconnected;
359            } else if (this == mDisconnecting) {
360                result = mPrevState == mConnected
361                        // TODO: edges to be removed after native stack refactoring
362                        // all transitions to disconnecting state should go through connected state
363                        || mPrevState == mAudioConnecting || mPrevState == mAudioOn
364                        || mPrevState == mAudioDisconnecting;
365            } else if (this == mConnected) {
366                result = mPrevState == mConnecting || mPrevState == mAudioDisconnecting
367                        || mPrevState == mDisconnecting || mPrevState == mAudioConnecting
368                        // TODO: edges to be removed after native stack refactoring
369                        // all transitions to connected state should go through a pending state
370                        || mPrevState == mAudioOn || mPrevState == mDisconnected;
371            } else if (this == mAudioConnecting) {
372                result = mPrevState == mConnected;
373            } else if (this == mAudioDisconnecting) {
374                result = mPrevState == mAudioOn;
375            } else if (this == mAudioOn) {
376                result = mPrevState == mAudioConnecting || mPrevState == mAudioDisconnecting
377                        // TODO: edges to be removed after native stack refactoring
378                        // all transitions to audio connected state should go through a pending
379                        // state
380                        || mPrevState == mConnected;
381            }
382            if (!result) {
383                throw new IllegalStateException(
384                        "Invalid state transition from " + mPrevState + " to " + this
385                                + " for device " + mDevice);
386            }
387        }
388
389        void stateLogD(String msg) {
390            log(getName() + ": currentDevice=" + mDevice + ", msg=" + msg);
391        }
392
393        void stateLogW(String msg) {
394            logw(getName() + ": currentDevice=" + mDevice + ", msg=" + msg);
395        }
396
397        void stateLogE(String msg) {
398            loge(getName() + ": currentDevice=" + mDevice + ", msg=" + msg);
399        }
400
401        void stateLogV(String msg) {
402            logv(getName() + ": currentDevice=" + mDevice + ", msg=" + msg);
403        }
404
405        void stateLogI(String msg) {
406            logi(getName() + ": currentDevice=" + mDevice + ", msg=" + msg);
407        }
408
409        void stateLogWtfStack(String msg) {
410            Log.wtfStack(TAG, getName() + ": " + msg);
411        }
412
413        /**
414         * Process connection event
415         *
416         * @param message the current message for the event
417         * @param state connection state to transition to
418         */
419        public abstract void processConnectionEvent(Message message, int state);
420
421        /**
422         * Get a state value from {@link BluetoothProfile} that represents the connection state of
423         * this headset state
424         *
425         * @return a value in {@link BluetoothProfile#STATE_DISCONNECTED},
426         * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, or
427         * {@link BluetoothProfile#STATE_DISCONNECTING}
428         */
429        abstract int getConnectionStateInt();
430
431        /**
432         * Get an audio state value from {@link BluetoothHeadset}
433         * @return a value in {@link BluetoothHeadset#STATE_AUDIO_DISCONNECTED},
434         * {@link BluetoothHeadset#STATE_AUDIO_CONNECTING}, or
435         * {@link BluetoothHeadset#STATE_AUDIO_CONNECTED}
436         */
437        abstract int getAudioStateInt();
438
439    }
440
441    class Disconnected extends HeadsetStateBase {
442        @Override
443        int getConnectionStateInt() {
444            return BluetoothProfile.STATE_DISCONNECTED;
445        }
446
447        @Override
448        int getAudioStateInt() {
449            return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
450        }
451
452        @Override
453        public void enter() {
454            super.enter();
455            mConnectingTimestampMs = Long.MIN_VALUE;
456            mPhonebook.resetAtState();
457            mSystemInterface.getHeadsetPhoneState().listenForPhoneState(false);
458            mVoiceRecognitionStarted = false;
459            mWaitingForVoiceRecognition = false;
460            mAudioParams.clear();
461            broadcastStateTransitions();
462            // Remove the state machine for unbonded devices
463            if (mPrevState != null
464                    && mAdapterService.getBondState(mDevice) == BluetoothDevice.BOND_NONE) {
465                getHandler().post(() -> mHeadsetService.removeStateMachine(mDevice));
466            }
467        }
468
469        @Override
470        public boolean processMessage(Message message) {
471            switch (message.what) {
472                case CONNECT:
473                    BluetoothDevice device = (BluetoothDevice) message.obj;
474                    stateLogD("Connecting to " + device);
475                    if (!mDevice.equals(device)) {
476                        stateLogE(
477                                "CONNECT failed, device=" + device + ", currentDevice=" + mDevice);
478                        break;
479                    }
480                    if (!mNativeInterface.connectHfp(device)) {
481                        stateLogE("CONNECT failed for connectHfp(" + device + ")");
482                        // No state transition is involved, fire broadcast immediately
483                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
484                                BluetoothProfile.STATE_DISCONNECTED);
485                        break;
486                    }
487                    transitionTo(mConnecting);
488                    break;
489                case DISCONNECT:
490                    // ignore
491                    break;
492                case CALL_STATE_CHANGED:
493                    stateLogD("Ignoring CALL_STATE_CHANGED event");
494                    break;
495                case DEVICE_STATE_CHANGED:
496                    stateLogD("Ignoring DEVICE_STATE_CHANGED event");
497                    break;
498                case STACK_EVENT:
499                    HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
500                    stateLogD("STACK_EVENT: " + event);
501                    if (!mDevice.equals(event.device)) {
502                        stateLogE("Event device does not match currentDevice[" + mDevice
503                                + "], event: " + event);
504                        break;
505                    }
506                    switch (event.type) {
507                        case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
508                            processConnectionEvent(message, event.valueInt);
509                            break;
510                        default:
511                            stateLogE("Unexpected stack event: " + event);
512                            break;
513                    }
514                    break;
515                default:
516                    stateLogE("Unexpected msg " + getMessageName(message.what) + ": " + message);
517                    return NOT_HANDLED;
518            }
519            return HANDLED;
520        }
521
522        @Override
523        public void processConnectionEvent(Message message, int state) {
524            stateLogD("processConnectionEvent, state=" + state);
525            switch (state) {
526                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
527                    stateLogW("ignore DISCONNECTED event");
528                    break;
529                // Both events result in Connecting state as SLC establishment is still required
530                case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
531                case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
532                    if (mHeadsetService.okToAcceptConnection(mDevice)) {
533                        stateLogI("accept incoming connection");
534                        transitionTo(mConnecting);
535                    } else {
536                        stateLogI("rejected incoming HF, priority=" + mHeadsetService.getPriority(
537                                mDevice) + " bondState=" + mAdapterService.getBondState(mDevice));
538                        // Reject the connection and stay in Disconnected state itself
539                        if (!mNativeInterface.disconnectHfp(mDevice)) {
540                            stateLogE("failed to disconnect");
541                        }
542                        // Indicate rejection to other components.
543                        broadcastConnectionState(mDevice, BluetoothProfile.STATE_DISCONNECTED,
544                                BluetoothProfile.STATE_DISCONNECTED);
545                    }
546                    break;
547                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
548                    stateLogW("Ignore DISCONNECTING event");
549                    break;
550                default:
551                    stateLogE("Incorrect state: " + state);
552                    break;
553            }
554        }
555    }
556
557    // Per HFP 1.7.1 spec page 23/144, Pending state needs to handle
558    //      AT+BRSF, AT+CIND, AT+CMER, AT+BIND, +CHLD
559    // commands during SLC establishment
560    class Connecting extends HeadsetStateBase {
561        @Override
562        int getConnectionStateInt() {
563            return BluetoothProfile.STATE_CONNECTING;
564        }
565
566        @Override
567        int getAudioStateInt() {
568            return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
569        }
570
571        @Override
572        public void enter() {
573            super.enter();
574            mConnectingTimestampMs = SystemClock.uptimeMillis();
575            sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs);
576            broadcastStateTransitions();
577        }
578
579        @Override
580        public boolean processMessage(Message message) {
581            switch (message.what) {
582                case CONNECT:
583                case CONNECT_AUDIO:
584                case DISCONNECT:
585                    deferMessage(message);
586                    break;
587                case CONNECT_TIMEOUT: {
588                    // We timed out trying to connect, transition to Disconnected state
589                    BluetoothDevice device = (BluetoothDevice) message.obj;
590                    if (!mDevice.equals(device)) {
591                        stateLogE("Unknown device timeout " + device);
592                        break;
593                    }
594                    stateLogW("CONNECT_TIMEOUT");
595                    transitionTo(mDisconnected);
596                    break;
597                }
598                case CALL_STATE_CHANGED:
599                    stateLogD("ignoring CALL_STATE_CHANGED event");
600                    break;
601                case DEVICE_STATE_CHANGED:
602                    stateLogD("ignoring DEVICE_STATE_CHANGED event");
603                    break;
604                case STACK_EVENT:
605                    HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
606                    stateLogD("STACK_EVENT: " + event);
607                    if (!mDevice.equals(event.device)) {
608                        stateLogE("Event device does not match currentDevice[" + mDevice
609                                + "], event: " + event);
610                        break;
611                    }
612                    switch (event.type) {
613                        case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
614                            processConnectionEvent(message, event.valueInt);
615                            break;
616                        case HeadsetStackEvent.EVENT_TYPE_AT_CHLD:
617                            processAtChld(event.valueInt, event.device);
618                            break;
619                        case HeadsetStackEvent.EVENT_TYPE_AT_CIND:
620                            processAtCind(event.device);
621                            break;
622                        case HeadsetStackEvent.EVENT_TYPE_WBS:
623                            processWBSEvent(event.valueInt);
624                            break;
625                        case HeadsetStackEvent.EVENT_TYPE_BIND:
626                            processAtBind(event.valueString, event.device);
627                            break;
628                        // Unexpected AT commands, we only handle them for comparability reasons
629                        case HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED:
630                            stateLogW("Unexpected VR event, device=" + event.device + ", state="
631                                    + event.valueInt);
632                            processVrEvent(event.valueInt, event.device);
633                            break;
634                        case HeadsetStackEvent.EVENT_TYPE_DIAL_CALL:
635                            stateLogW("Unexpected dial event, device=" + event.device);
636                            processDialCall(event.valueString, event.device);
637                            break;
638                        case HeadsetStackEvent.EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
639                            stateLogW("Unexpected subscriber number event for" + event.device
640                                    + ", state=" + event.valueInt);
641                            processSubscriberNumberRequest(event.device);
642                            break;
643                        case HeadsetStackEvent.EVENT_TYPE_AT_COPS:
644                            stateLogW("Unexpected COPS event for " + event.device);
645                            processAtCops(event.device);
646                            break;
647                        case HeadsetStackEvent.EVENT_TYPE_AT_CLCC:
648                            Log.w(TAG, "Connecting: Unexpected CLCC event for" + event.device);
649                            processAtClcc(event.device);
650                            break;
651                        case HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT:
652                            stateLogW("Unexpected unknown AT event for" + event.device + ", cmd="
653                                    + event.valueString);
654                            processUnknownAt(event.valueString, event.device);
655                            break;
656                        case HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED:
657                            stateLogW("Unexpected key-press event for " + event.device);
658                            processKeyPressed(event.device);
659                            break;
660                        case HeadsetStackEvent.EVENT_TYPE_BIEV:
661                            stateLogW("Unexpected BIEV event for " + event.device + ", indId="
662                                    + event.valueInt + ", indVal=" + event.valueInt2);
663                            processAtBiev(event.valueInt, event.valueInt2, event.device);
664                            break;
665                        case HeadsetStackEvent.EVENT_TYPE_VOLUME_CHANGED:
666                            stateLogW("Unexpected volume event for " + event.device);
667                            processVolumeEvent(event.valueInt, event.valueInt2);
668                            break;
669                        case HeadsetStackEvent.EVENT_TYPE_ANSWER_CALL:
670                            stateLogW("Unexpected answer event for " + event.device);
671                            mSystemInterface.answerCall(event.device);
672                            break;
673                        case HeadsetStackEvent.EVENT_TYPE_HANGUP_CALL:
674                            stateLogW("Unexpected hangup event for " + event.device);
675                            mSystemInterface.hangupCall(event.device, isVirtualCallInProgress());
676                            break;
677                        default:
678                            stateLogE("Unexpected event: " + event);
679                            break;
680                    }
681                    break;
682                default:
683                    stateLogE("Unexpected msg " + getMessageName(message.what) + ": " + message);
684                    return NOT_HANDLED;
685            }
686            return HANDLED;
687        }
688
689        @Override
690        public void processConnectionEvent(Message message, int state) {
691            stateLogD("processConnectionEvent, state=" + state);
692            switch (state) {
693                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
694                    stateLogW("Disconnected");
695                    transitionTo(mDisconnected);
696                    break;
697                case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
698                    stateLogD("RFCOMM connected");
699                    break;
700                case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
701                    stateLogD("SLC connected");
702                    transitionTo(mConnected);
703                    break;
704                case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
705                    // Ignored
706                    break;
707                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
708                    stateLogW("Disconnecting");
709                    break;
710                default:
711                    stateLogE("Incorrect state " + state);
712                    break;
713            }
714        }
715
716        @Override
717        public void exit() {
718            removeMessages(CONNECT_TIMEOUT);
719            super.exit();
720        }
721    }
722
723    class Disconnecting extends HeadsetStateBase {
724        @Override
725        int getConnectionStateInt() {
726            return BluetoothProfile.STATE_DISCONNECTING;
727        }
728
729        @Override
730        int getAudioStateInt() {
731            return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
732        }
733
734        @Override
735        public void enter() {
736            super.enter();
737            sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs);
738            broadcastStateTransitions();
739        }
740
741        @Override
742        public boolean processMessage(Message message) {
743            switch (message.what) {
744                case CONNECT:
745                case CONNECT_AUDIO:
746                case DISCONNECT:
747                    deferMessage(message);
748                    break;
749                case CONNECT_TIMEOUT: {
750                    BluetoothDevice device = (BluetoothDevice) message.obj;
751                    if (!mDevice.equals(device)) {
752                        stateLogE("Unknown device timeout " + device);
753                        break;
754                    }
755                    stateLogE("timeout");
756                    transitionTo(mDisconnected);
757                    break;
758                }
759                case STACK_EVENT:
760                    HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
761                    stateLogD("STACK_EVENT: " + event);
762                    if (!mDevice.equals(event.device)) {
763                        stateLogE("Event device does not match currentDevice[" + mDevice
764                                + "], event: " + event);
765                        break;
766                    }
767                    switch (event.type) {
768                        case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
769                            processConnectionEvent(message, event.valueInt);
770                            break;
771                        default:
772                            stateLogE("Unexpected event: " + event);
773                            break;
774                    }
775                    break;
776                default:
777                    stateLogE("Unexpected msg " + getMessageName(message.what) + ": " + message);
778                    return NOT_HANDLED;
779            }
780            return HANDLED;
781        }
782
783        // in Disconnecting state
784        @Override
785        public void processConnectionEvent(Message message, int state) {
786            switch (state) {
787                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
788                    stateLogD("processConnectionEvent: Disconnected");
789                    transitionTo(mDisconnected);
790                    break;
791                case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
792                    stateLogD("processConnectionEvent: Connected");
793                    transitionTo(mConnected);
794                    break;
795                default:
796                    stateLogE("processConnectionEvent: Bad state: " + state);
797                    break;
798            }
799        }
800
801        @Override
802        public void exit() {
803            removeMessages(CONNECT_TIMEOUT);
804            super.exit();
805        }
806    }
807
808    /**
809     * Base class for Connected, AudioConnecting, AudioOn, AudioDisconnecting states
810     */
811    private abstract class ConnectedBase extends HeadsetStateBase {
812        @Override
813        int getConnectionStateInt() {
814            return BluetoothProfile.STATE_CONNECTED;
815        }
816
817        /**
818         * Handle common messages in connected states. However, state specific messages must be
819         * handled individually.
820         *
821         * @param message Incoming message to handle
822         * @return True if handled successfully, False otherwise
823         */
824        @Override
825        public boolean processMessage(Message message) {
826            switch (message.what) {
827                case CONNECT:
828                case DISCONNECT:
829                case CONNECT_AUDIO:
830                case DISCONNECT_AUDIO:
831                case CONNECT_TIMEOUT:
832                    throw new IllegalStateException(
833                            "Illegal message in generic handler: " + message);
834                case VOICE_RECOGNITION_START: {
835                    BluetoothDevice device = (BluetoothDevice) message.obj;
836                    if (!mDevice.equals(device)) {
837                        stateLogW("VOICE_RECOGNITION_START failed " + device
838                                + " is not currentDevice");
839                        break;
840                    }
841                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED);
842                    break;
843                }
844                case VOICE_RECOGNITION_STOP: {
845                    BluetoothDevice device = (BluetoothDevice) message.obj;
846                    if (!mDevice.equals(device)) {
847                        stateLogW("VOICE_RECOGNITION_STOP failed " + device
848                                + " is not currentDevice");
849                        break;
850                    }
851                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED);
852                    break;
853                }
854                case CALL_STATE_CHANGED:
855                    processCallState((HeadsetCallState) message.obj, message.arg1 == 1);
856                    break;
857                case DEVICE_STATE_CHANGED:
858                    mNativeInterface.notifyDeviceStatus(mDevice, (HeadsetDeviceState) message.obj);
859                    break;
860                case SEND_CCLC_RESPONSE:
861                    processSendClccResponse((HeadsetClccResponse) message.obj);
862                    break;
863                case CLCC_RSP_TIMEOUT: {
864                    BluetoothDevice device = (BluetoothDevice) message.obj;
865                    if (!mDevice.equals(device)) {
866                        stateLogW("CLCC_RSP_TIMEOUT failed " + device + " is not currentDevice");
867                        break;
868                    }
869                    mNativeInterface.clccResponse(device, 0, 0, 0, 0, false, "", 0);
870                }
871                break;
872                case SEND_VENDOR_SPECIFIC_RESULT_CODE:
873                    processSendVendorSpecificResultCode(
874                            (HeadsetVendorSpecificResultCode) message.obj);
875                    break;
876                case SEND_BSIR:
877                    mNativeInterface.sendBsir(mDevice, message.arg1 == 1);
878                    break;
879                case DIALING_OUT_TIMEOUT: {
880                    BluetoothDevice device = (BluetoothDevice) message.obj;
881                    if (!mDevice.equals(device)) {
882                        stateLogW("DIALING_OUT_TIMEOUT failed " + device + " is not currentDevice");
883                        break;
884                    }
885                    if (mDialingOut) {
886                        mDialingOut = false;
887                        mNativeInterface.atResponseCode(device,
888                                HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
889                    }
890                }
891                break;
892                case VIRTUAL_CALL_START: {
893                    BluetoothDevice device = (BluetoothDevice) message.obj;
894                    if (!mDevice.equals(device)) {
895                        stateLogW("VIRTUAL_CALL_START failed " + device + " is not currentDevice");
896                        break;
897                    }
898                    initiateScoUsingVirtualVoiceCall();
899                    break;
900                }
901                case VIRTUAL_CALL_STOP: {
902                    BluetoothDevice device = (BluetoothDevice) message.obj;
903                    if (!mDevice.equals(device)) {
904                        stateLogW("VIRTUAL_CALL_STOP failed " + device + " is not currentDevice");
905                        break;
906                    }
907                    terminateScoUsingVirtualVoiceCall();
908                    break;
909                }
910                case START_VR_TIMEOUT: {
911                    BluetoothDevice device = (BluetoothDevice) message.obj;
912                    if (!mDevice.equals(device)) {
913                        stateLogW("START_VR_TIMEOUT failed " + device + " is not currentDevice");
914                        break;
915                    }
916                    if (mWaitingForVoiceRecognition) {
917                        mWaitingForVoiceRecognition = false;
918                        stateLogE("Timeout waiting for voice recognition to start");
919                        mNativeInterface.atResponseCode(device,
920                                HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
921                    }
922                }
923                break;
924                case INTENT_CONNECTION_ACCESS_REPLY:
925                    handleAccessPermissionResult((Intent) message.obj);
926                    break;
927                case STACK_EVENT:
928                    HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
929                    stateLogD("STACK_EVENT: " + event);
930                    if (!mDevice.equals(event.device)) {
931                        stateLogE("Event device does not match currentDevice[" + mDevice
932                                + "], event: " + event);
933                        break;
934                    }
935                    switch (event.type) {
936                        case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
937                            processConnectionEvent(message, event.valueInt);
938                            break;
939                        case HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
940                            processAudioEvent(event.valueInt);
941                            break;
942                        case HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED:
943                            processVrEvent(event.valueInt, event.device);
944                            break;
945                        case HeadsetStackEvent.EVENT_TYPE_ANSWER_CALL:
946                            mSystemInterface.answerCall(event.device);
947                            break;
948                        case HeadsetStackEvent.EVENT_TYPE_HANGUP_CALL:
949                            mSystemInterface.hangupCall(event.device, mVirtualCallStarted);
950                            break;
951                        case HeadsetStackEvent.EVENT_TYPE_VOLUME_CHANGED:
952                            processVolumeEvent(event.valueInt, event.valueInt2);
953                            break;
954                        case HeadsetStackEvent.EVENT_TYPE_DIAL_CALL:
955                            processDialCall(event.valueString, event.device);
956                            break;
957                        case HeadsetStackEvent.EVENT_TYPE_SEND_DTMF:
958                            mSystemInterface.sendDtmf(event.valueInt, event.device);
959                            break;
960                        case HeadsetStackEvent.EVENT_TYPE_NOICE_REDUCTION:
961                            processNoiseReductionEvent(event.valueInt == 1);
962                            break;
963                        case HeadsetStackEvent.EVENT_TYPE_WBS:
964                            processWBSEvent(event.valueInt);
965                            break;
966                        case HeadsetStackEvent.EVENT_TYPE_AT_CHLD:
967                            processAtChld(event.valueInt, event.device);
968                            break;
969                        case HeadsetStackEvent.EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
970                            processSubscriberNumberRequest(event.device);
971                            break;
972                        case HeadsetStackEvent.EVENT_TYPE_AT_CIND:
973                            processAtCind(event.device);
974                            break;
975                        case HeadsetStackEvent.EVENT_TYPE_AT_COPS:
976                            processAtCops(event.device);
977                            break;
978                        case HeadsetStackEvent.EVENT_TYPE_AT_CLCC:
979                            processAtClcc(event.device);
980                            break;
981                        case HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT:
982                            processUnknownAt(event.valueString, event.device);
983                            break;
984                        case HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED:
985                            processKeyPressed(event.device);
986                            break;
987                        case HeadsetStackEvent.EVENT_TYPE_BIND:
988                            processAtBind(event.valueString, event.device);
989                            break;
990                        case HeadsetStackEvent.EVENT_TYPE_BIEV:
991                            processAtBiev(event.valueInt, event.valueInt2, event.device);
992                            break;
993                        default:
994                            stateLogE("Unknown stack event: " + event);
995                            break;
996                    }
997                    break;
998                default:
999                    stateLogE("Unexpected msg " + getMessageName(message.what) + ": " + message);
1000                    return NOT_HANDLED;
1001            }
1002            return HANDLED;
1003        }
1004
1005        @Override
1006        public void processConnectionEvent(Message message, int state) {
1007            stateLogD("processConnectionEvent, state=" + state);
1008            switch (state) {
1009                case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
1010                    stateLogE("processConnectionEvent: RFCOMM connected again, shouldn't happen");
1011                    break;
1012                case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
1013                    stateLogE("processConnectionEvent: SLC connected again, shouldn't happen");
1014                    break;
1015                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
1016                    stateLogI("processConnectionEvent: Disconnecting");
1017                    transitionTo(mDisconnecting);
1018                    break;
1019                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
1020                    stateLogI("processConnectionEvent: Disconnected");
1021                    transitionTo(mDisconnected);
1022                    break;
1023                default:
1024                    stateLogE("processConnectionEvent: bad state: " + state);
1025                    break;
1026            }
1027        }
1028
1029        /**
1030         * Each state should handle audio events differently
1031         *
1032         * @param state audio state
1033         */
1034        public abstract void processAudioEvent(int state);
1035    }
1036
1037    class Connected extends ConnectedBase {
1038        @Override
1039        int getAudioStateInt() {
1040            return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
1041        }
1042
1043        @Override
1044        public void enter() {
1045            super.enter();
1046            if (mConnectingTimestampMs == Long.MIN_VALUE) {
1047                mConnectingTimestampMs = SystemClock.uptimeMillis();
1048            }
1049            // start phone state listener here so that the CIND response as part of SLC can be
1050            // responded to, correctly.
1051            // listenForPhoneState(boolean) internally handles multiple calls to start listen
1052            mSystemInterface.getHeadsetPhoneState().listenForPhoneState(true);
1053            if (mPrevState == mConnecting) {
1054                // Reset NREC on connect event. Headset will override later
1055                processNoiseReductionEvent(true);
1056                // Query phone state for initial setup
1057                mSystemInterface.queryPhoneState();
1058                // Remove pending connection attempts that were deferred during the pending
1059                // state. This is to prevent auto connect attempts from disconnecting
1060                // devices that previously successfully connected.
1061                removeDeferredMessages(CONNECT);
1062            }
1063            broadcastStateTransitions();
1064        }
1065
1066        @Override
1067        public boolean processMessage(Message message) {
1068            switch (message.what) {
1069                case CONNECT: {
1070                    BluetoothDevice device = (BluetoothDevice) message.obj;
1071                    stateLogW("CONNECT, ignored, device=" + device + ", currentDevice" + mDevice);
1072                    break;
1073                }
1074                case DISCONNECT: {
1075                    BluetoothDevice device = (BluetoothDevice) message.obj;
1076                    stateLogD("DISCONNECT from device=" + device);
1077                    if (!mDevice.equals(device)) {
1078                        stateLogW("DISCONNECT, device " + device + " not connected");
1079                        break;
1080                    }
1081                    if (!mNativeInterface.disconnectHfp(device)) {
1082                        // broadcast immediately as no state transition is involved
1083                        stateLogE("DISCONNECT from " + device + " failed");
1084                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
1085                                BluetoothProfile.STATE_CONNECTED);
1086                        break;
1087                    }
1088                    transitionTo(mDisconnecting);
1089                }
1090                break;
1091                case CONNECT_AUDIO:
1092                    stateLogD("CONNECT_AUDIO, device=" + mDevice);
1093                    if (!isScoAcceptable()) {
1094                        stateLogW("CONNECT_AUDIO No Active/Held call, no call setup, and no "
1095                                + "in-band ringing, not allowing SCO, device=" + mDevice);
1096                        break;
1097                    }
1098                    if (!mNativeInterface.connectAudio(mDevice)) {
1099                        stateLogE("Failed to connect SCO audio for " + mDevice);
1100                        // No state change involved, fire broadcast immediately
1101                        broadcastAudioState(mDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
1102                                BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
1103                        break;
1104                    }
1105                    transitionTo(mAudioConnecting);
1106                    break;
1107                case DISCONNECT_AUDIO:
1108                    stateLogD("ignore DISCONNECT_AUDIO, device=" + mDevice);
1109                    // ignore
1110                    break;
1111                default:
1112                    return super.processMessage(message);
1113            }
1114            return HANDLED;
1115        }
1116
1117        @Override
1118        public void processAudioEvent(int state) {
1119            stateLogD("processAudioEvent, state=" + state);
1120            switch (state) {
1121                case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
1122                    if (!isScoAcceptable()) {
1123                        stateLogW("processAudioEvent: reject incoming audio connection");
1124                        if (!mNativeInterface.disconnectAudio(mDevice)) {
1125                            stateLogE("processAudioEvent: failed to disconnect audio");
1126                        }
1127                        break;
1128                    }
1129                    stateLogI("processAudioEvent: audio connected");
1130                    transitionTo(mAudioOn);
1131                    break;
1132                case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
1133                    if (!isScoAcceptable()) {
1134                        stateLogW("processAudioEvent: reject incoming pending audio connection");
1135                        if (!mNativeInterface.disconnectAudio(mDevice)) {
1136                            stateLogE("processAudioEvent: failed to disconnect pending audio");
1137                        }
1138                        break;
1139                    }
1140                    stateLogI("processAudioEvent: audio connecting");
1141                    transitionTo(mAudioConnecting);
1142                    break;
1143                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
1144                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
1145                    // ignore
1146                    break;
1147                default:
1148                    stateLogE("processAudioEvent: bad state: " + state);
1149                    break;
1150            }
1151        }
1152    }
1153
1154    class AudioConnecting extends ConnectedBase {
1155        @Override
1156        int getAudioStateInt() {
1157            return BluetoothHeadset.STATE_AUDIO_CONNECTING;
1158        }
1159
1160        @Override
1161        public void enter() {
1162            super.enter();
1163            sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs);
1164            broadcastStateTransitions();
1165        }
1166
1167        @Override
1168        public boolean processMessage(Message message) {
1169            switch (message.what) {
1170                case CONNECT:
1171                case DISCONNECT:
1172                case CONNECT_AUDIO:
1173                case DISCONNECT_AUDIO:
1174                    deferMessage(message);
1175                    break;
1176                case CONNECT_TIMEOUT: {
1177                    BluetoothDevice device = (BluetoothDevice) message.obj;
1178                    if (!mDevice.equals(device)) {
1179                        stateLogW("CONNECT_TIMEOUT for unknown device " + device);
1180                        break;
1181                    }
1182                    stateLogW("CONNECT_TIMEOUT");
1183                    transitionTo(mConnected);
1184                    break;
1185                }
1186                default:
1187                    return super.processMessage(message);
1188            }
1189            return HANDLED;
1190        }
1191
1192        @Override
1193        public void processAudioEvent(int state) {
1194            switch (state) {
1195                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
1196                    stateLogW("processAudioEvent: audio connection failed");
1197                    transitionTo(mConnected);
1198                    break;
1199                case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
1200                    // ignore, already in audio connecting state
1201                    break;
1202                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
1203                    // ignore, there is no BluetoothHeadset.STATE_AUDIO_DISCONNECTING
1204                    break;
1205                case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
1206                    stateLogI("processAudioEvent: audio connected");
1207                    transitionTo(mAudioOn);
1208                    break;
1209                default:
1210                    stateLogE("processAudioEvent: bad state: " + state);
1211                    break;
1212            }
1213        }
1214
1215        @Override
1216        public void exit() {
1217            removeMessages(CONNECT_TIMEOUT);
1218            super.exit();
1219        }
1220    }
1221
1222    class AudioOn extends ConnectedBase {
1223        @Override
1224        int getAudioStateInt() {
1225            return BluetoothHeadset.STATE_AUDIO_CONNECTED;
1226        }
1227
1228        @Override
1229        public void enter() {
1230            super.enter();
1231            removeDeferredMessages(CONNECT_AUDIO);
1232            // Set active device to current active SCO device when the current active device
1233            // is different from mCurrentDevice. This is to accommodate active device state
1234            // mis-match between native and Java.
1235            if (!mDevice.equals(mHeadsetService.getActiveDevice())) {
1236                mHeadsetService.setActiveDevice(mDevice);
1237            }
1238            setAudioParameters();
1239            mSystemInterface.getAudioManager().setBluetoothScoOn(true);
1240            broadcastStateTransitions();
1241        }
1242
1243        @Override
1244        public boolean processMessage(Message message) {
1245            switch (message.what) {
1246                case CONNECT: {
1247                    BluetoothDevice device = (BluetoothDevice) message.obj;
1248                    stateLogW("CONNECT, ignored, device=" + device + ", currentDevice" + mDevice);
1249                    break;
1250                }
1251                case DISCONNECT: {
1252                    BluetoothDevice device = (BluetoothDevice) message.obj;
1253                    stateLogD("DISCONNECT, device=" + device);
1254                    if (!mDevice.equals(device)) {
1255                        stateLogW("DISCONNECT, device " + device + " not connected");
1256                        break;
1257                    }
1258                    // Disconnect BT SCO first
1259                    if (!mNativeInterface.disconnectAudio(mDevice)) {
1260                        stateLogW("DISCONNECT failed, device=" + mDevice);
1261                        // if disconnect BT SCO failed, transition to mConnected state to force
1262                        // disconnect device
1263                    }
1264                    deferMessage(obtainMessage(DISCONNECT, mDevice));
1265                    transitionTo(mAudioDisconnecting);
1266                    break;
1267                }
1268                case CONNECT_AUDIO: {
1269                    BluetoothDevice device = (BluetoothDevice) message.obj;
1270                    if (!mDevice.equals(device)) {
1271                        stateLogW("CONNECT_AUDIO device is not connected " + device);
1272                        break;
1273                    }
1274                    stateLogW("CONNECT_AUDIO device auido is already connected " + device);
1275                    break;
1276                }
1277                case DISCONNECT_AUDIO: {
1278                    BluetoothDevice device = (BluetoothDevice) message.obj;
1279                    if (!mDevice.equals(device)) {
1280                        stateLogW("DISCONNECT_AUDIO, failed, device=" + device + ", currentDevice="
1281                                + mDevice);
1282                        break;
1283                    }
1284                    if (mNativeInterface.disconnectAudio(mDevice)) {
1285                        stateLogD("DISCONNECT_AUDIO, device=" + mDevice);
1286                        transitionTo(mAudioDisconnecting);
1287                    } else {
1288                        stateLogW("DISCONNECT_AUDIO failed, device=" + mDevice);
1289                    }
1290                    break;
1291                }
1292                case INTENT_SCO_VOLUME_CHANGED:
1293                    processIntentScoVolume((Intent) message.obj, mDevice);
1294                    break;
1295                case STACK_EVENT:
1296                    HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
1297                    stateLogD("STACK_EVENT: " + event);
1298                    if (!mDevice.equals(event.device)) {
1299                        stateLogE("Event device does not match currentDevice[" + mDevice
1300                                + "], event: " + event);
1301                        break;
1302                    }
1303                    switch (event.type) {
1304                        case HeadsetStackEvent.EVENT_TYPE_WBS:
1305                            stateLogE("Cannot change WBS state when audio is connected: " + event);
1306                            break;
1307                        default:
1308                            super.processMessage(message);
1309                            break;
1310                    }
1311                    break;
1312                default:
1313                    return super.processMessage(message);
1314            }
1315            return HANDLED;
1316        }
1317
1318        @Override
1319        public void processAudioEvent(int state) {
1320            switch (state) {
1321                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
1322                    stateLogI("processAudioEvent: audio disconnected by remote");
1323                    transitionTo(mConnected);
1324                    break;
1325                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
1326                    stateLogI("processAudioEvent: audio being disconnected by remote");
1327                    transitionTo(mAudioDisconnecting);
1328                    break;
1329                default:
1330                    stateLogE("processAudioEvent: bad state: " + state);
1331                    break;
1332            }
1333        }
1334
1335        private void processIntentScoVolume(Intent intent, BluetoothDevice device) {
1336            int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
1337            if (mSpeakerVolume != volumeValue) {
1338                mSpeakerVolume = volumeValue;
1339                mNativeInterface.setVolume(device, HeadsetHalConstants.VOLUME_TYPE_SPK,
1340                        mSpeakerVolume);
1341            }
1342        }
1343
1344        @Override
1345        public void exit() {
1346            mSystemInterface.getAudioManager().setBluetoothScoOn(false);
1347            super.exit();
1348        }
1349    }
1350
1351    class AudioDisconnecting extends ConnectedBase {
1352        @Override
1353        int getAudioStateInt() {
1354            // TODO: need BluetoothHeadset.STATE_AUDIO_DISCONNECTING
1355            return BluetoothHeadset.STATE_AUDIO_CONNECTED;
1356        }
1357
1358        @Override
1359        public void enter() {
1360            super.enter();
1361            sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs);
1362            broadcastStateTransitions();
1363        }
1364
1365        @Override
1366        public boolean processMessage(Message message) {
1367            switch (message.what) {
1368                case CONNECT:
1369                case DISCONNECT:
1370                case CONNECT_AUDIO:
1371                case DISCONNECT_AUDIO:
1372                    deferMessage(message);
1373                    break;
1374                case CONNECT_TIMEOUT: {
1375                    BluetoothDevice device = (BluetoothDevice) message.obj;
1376                    if (!mDevice.equals(device)) {
1377                        stateLogW("CONNECT_TIMEOUT for unknown device " + device);
1378                        break;
1379                    }
1380                    stateLogW("CONNECT_TIMEOUT");
1381                    transitionTo(mConnected);
1382                    break;
1383                }
1384                default:
1385                    return super.processMessage(message);
1386            }
1387            return HANDLED;
1388        }
1389
1390        @Override
1391        public void processAudioEvent(int state) {
1392            switch (state) {
1393                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
1394                    stateLogI("processAudioEvent: audio disconnected");
1395                    transitionTo(mConnected);
1396                    break;
1397                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
1398                    // ignore
1399                    break;
1400                case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
1401                    stateLogW("processAudioEvent: audio disconnection failed");
1402                    transitionTo(mAudioOn);
1403                    break;
1404                case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
1405                    // ignore, see if it goes into connected state, otherwise, timeout
1406                    break;
1407                default:
1408                    stateLogE("processAudioEvent: bad state: " + state);
1409                    break;
1410            }
1411        }
1412
1413        @Override
1414        public void exit() {
1415            removeMessages(CONNECT_TIMEOUT);
1416            super.exit();
1417        }
1418    }
1419
1420    /**
1421     * Get the underlying device tracked by this state machine
1422     *
1423     * @return device in focus
1424     */
1425    @VisibleForTesting
1426    public synchronized BluetoothDevice getDevice() {
1427        return mDevice;
1428    }
1429
1430    /**
1431     * Get the current connection state of this state machine
1432     *
1433     * @return current connection state, one of {@link BluetoothProfile#STATE_DISCONNECTED},
1434     * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, or
1435     * {@link BluetoothProfile#STATE_DISCONNECTING}
1436     */
1437    @VisibleForTesting
1438    public synchronized int getConnectionState() {
1439        HeadsetStateBase state = (HeadsetStateBase) getCurrentState();
1440        if (state == null) {
1441            return BluetoothHeadset.STATE_DISCONNECTED;
1442        }
1443        return state.getConnectionStateInt();
1444    }
1445
1446    /**
1447     * Get the current audio state of this state machine
1448     *
1449     * @return current audio state, one of {@link BluetoothHeadset#STATE_AUDIO_DISCONNECTED},
1450     * {@link BluetoothHeadset#STATE_AUDIO_CONNECTING}, or
1451     * {@link BluetoothHeadset#STATE_AUDIO_CONNECTED}
1452     */
1453    public synchronized int getAudioState() {
1454        HeadsetStateBase state = (HeadsetStateBase) getCurrentState();
1455        if (state == null) {
1456            return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
1457        }
1458        return state.getAudioStateInt();
1459    }
1460
1461    private void processVrEvent(int state, BluetoothDevice device) {
1462        Log.d(TAG, "processVrEvent: state=" + state + " mVoiceRecognitionStarted: "
1463                + mVoiceRecognitionStarted + " mWaitingforVoiceRecognition: "
1464                + mWaitingForVoiceRecognition + " isInCall: " + mSystemInterface.isInCall());
1465        if (state == HeadsetHalConstants.VR_STATE_STARTED) {
1466            if (!isVirtualCallInProgress() && !mSystemInterface.isInCall()) {
1467                IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
1468                        ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
1469                if (dic != null) {
1470                    try {
1471                        dic.exitIdle("voice-command");
1472                    } catch (RemoteException e) {
1473                    }
1474                }
1475                try {
1476                    mHeadsetService.startActivity(VOICE_COMMAND_INTENT);
1477                } catch (ActivityNotFoundException e) {
1478                    mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR,
1479                            0);
1480                    return;
1481                }
1482                expectVoiceRecognition(device);
1483            } else {
1484                // send error response if call is ongoing
1485                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1486            }
1487        } else if (state == HeadsetHalConstants.VR_STATE_STOPPED) {
1488            if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition) {
1489                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
1490                mVoiceRecognitionStarted = false;
1491                mWaitingForVoiceRecognition = false;
1492                if (!mSystemInterface.isInCall() && (getAudioState()
1493                        != BluetoothHeadset.STATE_AUDIO_DISCONNECTED)) {
1494                    mNativeInterface.disconnectAudio(mDevice);
1495                    mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
1496                }
1497            } else {
1498                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1499            }
1500        } else {
1501            Log.e(TAG, "Bad Voice Recognition state: " + state);
1502        }
1503    }
1504
1505    private void processLocalVrEvent(int state) {
1506        if (state == HeadsetHalConstants.VR_STATE_STARTED) {
1507            boolean needAudio = true;
1508            if (mVoiceRecognitionStarted || mSystemInterface.isInCall()) {
1509                Log.e(TAG, "Voice recognition started when call is active. isInCall:"
1510                        + mSystemInterface.isInCall() + " mVoiceRecognitionStarted: "
1511                        + mVoiceRecognitionStarted);
1512                return;
1513            }
1514            mVoiceRecognitionStarted = true;
1515
1516            if (mWaitingForVoiceRecognition) {
1517                if (!hasMessages(START_VR_TIMEOUT)) {
1518                    return;
1519                }
1520                Log.d(TAG, "Voice recognition started successfully");
1521                mWaitingForVoiceRecognition = false;
1522                mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
1523                removeMessages(START_VR_TIMEOUT);
1524            } else {
1525                Log.d(TAG, "Voice recognition started locally");
1526                needAudio = mNativeInterface.startVoiceRecognition(mDevice);
1527            }
1528
1529            if (needAudio && getAudioState() == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
1530                Log.d(TAG, "Initiating audio connection for Voice Recognition");
1531                // At this stage, we need to be sure that AVDTP is not streaming. This is needed
1532                // to be compliant with the AV+HFP Whitepaper as we cannot have A2DP in
1533                // streaming state while a SCO connection is established.
1534                // This is needed for VoiceDial scenario alone and not for
1535                // incoming call/outgoing call scenarios as the phone enters MODE_RINGTONE
1536                // or MODE_IN_CALL which shall automatically suspend the AVDTP stream if needed.
1537                // Whereas for VoiceDial we want to activate the SCO connection but we are still
1538                // in MODE_NORMAL and hence the need to explicitly suspend the A2DP stream
1539                mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
1540                mNativeInterface.connectAudio(mDevice);
1541            }
1542
1543            if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
1544                mSystemInterface.getVoiceRecognitionWakeLock().release();
1545            }
1546        } else {
1547            Log.d(TAG, "Voice Recognition stopped. mVoiceRecognitionStarted: "
1548                    + mVoiceRecognitionStarted + " mWaitingForVoiceRecognition: "
1549                    + mWaitingForVoiceRecognition);
1550            if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition) {
1551                mVoiceRecognitionStarted = false;
1552                mWaitingForVoiceRecognition = false;
1553
1554                if (mNativeInterface.stopVoiceRecognition(mDevice) && !mSystemInterface.isInCall()
1555                        && getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
1556                    mNativeInterface.disconnectAudio(mDevice);
1557                    mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
1558                }
1559            }
1560        }
1561    }
1562
1563    private synchronized void expectVoiceRecognition(BluetoothDevice device) {
1564        mWaitingForVoiceRecognition = true;
1565        mHeadsetService.setActiveDevice(device);
1566        sendMessageDelayed(START_VR_TIMEOUT, device, START_VR_TIMEOUT_MS);
1567        if (!mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
1568            mSystemInterface.getVoiceRecognitionWakeLock().acquire(START_VR_TIMEOUT_MS);
1569        }
1570    }
1571
1572    public long getConnectingTimestampMs() {
1573        return mConnectingTimestampMs;
1574    }
1575
1576    /*
1577     * Put the AT command, company ID, arguments, and device in an Intent and broadcast it.
1578     */
1579    private void broadcastVendorSpecificEventIntent(String command, int companyId, int commandType,
1580            Object[] arguments, BluetoothDevice device) {
1581        log("broadcastVendorSpecificEventIntent(" + command + ")");
1582        Intent intent = new Intent(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT);
1583        intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, command);
1584        intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, commandType);
1585        // assert: all elements of args are Serializable
1586        intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments);
1587        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
1588
1589        intent.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "."
1590                + Integer.toString(companyId));
1591
1592        mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, HeadsetService.BLUETOOTH_PERM);
1593    }
1594
1595    private void setAudioParameters() {
1596        String keyValuePairs = String.join(";", new String[]{
1597                HEADSET_NAME + "=" + getCurrentDeviceName(),
1598                HEADSET_NREC + "=" + mAudioParams.getOrDefault(HEADSET_NREC,
1599                        HEADSET_AUDIO_FEATURE_OFF),
1600                HEADSET_WBS + "=" + mAudioParams.getOrDefault(HEADSET_WBS,
1601                        HEADSET_AUDIO_FEATURE_OFF)
1602        });
1603        Log.i(TAG, "setAudioParameters for " + mDevice + ": " + keyValuePairs);
1604        mSystemInterface.getAudioManager().setParameters(keyValuePairs);
1605    }
1606
1607    private String parseUnknownAt(String atString) {
1608        StringBuilder atCommand = new StringBuilder(atString.length());
1609
1610        for (int i = 0; i < atString.length(); i++) {
1611            char c = atString.charAt(i);
1612            if (c == '"') {
1613                int j = atString.indexOf('"', i + 1); // search for closing "
1614                if (j == -1) { // unmatched ", insert one.
1615                    atCommand.append(atString.substring(i, atString.length()));
1616                    atCommand.append('"');
1617                    break;
1618                }
1619                atCommand.append(atString.substring(i, j + 1));
1620                i = j;
1621            } else if (c != ' ') {
1622                atCommand.append(Character.toUpperCase(c));
1623            }
1624        }
1625        return atCommand.toString();
1626    }
1627
1628    private int getAtCommandType(String atCommand) {
1629        int commandType = AtPhonebook.TYPE_UNKNOWN;
1630        String atString = null;
1631        atCommand = atCommand.trim();
1632        if (atCommand.length() > 5) {
1633            atString = atCommand.substring(5);
1634            if (atString.startsWith("?")) { // Read
1635                commandType = AtPhonebook.TYPE_READ;
1636            } else if (atString.startsWith("=?")) { // Test
1637                commandType = AtPhonebook.TYPE_TEST;
1638            } else if (atString.startsWith("=")) { // Set
1639                commandType = AtPhonebook.TYPE_SET;
1640            } else {
1641                commandType = AtPhonebook.TYPE_UNKNOWN;
1642            }
1643        }
1644        return commandType;
1645    }
1646
1647    /* Method to check if Virtual Call in Progress */
1648    private boolean isVirtualCallInProgress() {
1649        return mVirtualCallStarted;
1650    }
1651
1652    private void setVirtualCallInProgress(boolean state) {
1653        mVirtualCallStarted = state;
1654    }
1655
1656    // NOTE: Currently the VirtualCall API does not support handling of call transfers. If it is
1657    // initiated from the handsfree device, HeadsetStateMachine will end the virtual call by
1658    // calling terminateScoUsingVirtualVoiceCall() in broadcastAudioState()
1659    private boolean initiateScoUsingVirtualVoiceCall() {
1660        log("initiateScoUsingVirtualVoiceCall: Received");
1661        // 1. Check if the SCO state is idle
1662        if (mSystemInterface.isInCall() || mVoiceRecognitionStarted) {
1663            Log.e(TAG, "initiateScoUsingVirtualVoiceCall: Call in progress.");
1664            return false;
1665        }
1666
1667        // 2. Send virtual phone state changed to initialize SCO
1668        processCallState(new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0),
1669                true);
1670        processCallState(new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0),
1671                true);
1672        processCallState(new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0),
1673                true);
1674        setVirtualCallInProgress(true);
1675        // Done
1676        log("initiateScoUsingVirtualVoiceCall: Done");
1677        return true;
1678    }
1679
1680    private synchronized boolean terminateScoUsingVirtualVoiceCall() {
1681        log("terminateScoUsingVirtualVoiceCall: Received");
1682
1683        if (!isVirtualCallInProgress()) {
1684            Log.w(TAG, "terminateScoUsingVirtualVoiceCall: No present call to terminate");
1685            return false;
1686        }
1687
1688        // 2. Send virtual phone state changed to close SCO
1689        processCallState(new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0),
1690                true);
1691        setVirtualCallInProgress(false);
1692        // Done
1693        log("terminateScoUsingVirtualVoiceCall: Done");
1694        return true;
1695    }
1696
1697
1698    private void processDialCall(String number, BluetoothDevice device) {
1699        String dialNumber;
1700        if (mDialingOut) {
1701            log("processDialCall, already dialling");
1702            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1703            return;
1704        }
1705        if ((number == null) || (number.length() == 0)) {
1706            dialNumber = mPhonebook.getLastDialledNumber();
1707            if (dialNumber == null) {
1708                log("processDialCall, last dial number null");
1709                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1710                return;
1711            }
1712        } else if (number.charAt(0) == '>') {
1713            // Yuck - memory dialling requested.
1714            // Just dial last number for now
1715            if (number.startsWith(">9999")) { // for PTS test
1716                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1717                return;
1718            }
1719            log("processDialCall, memory dial do last dial for now");
1720            dialNumber = mPhonebook.getLastDialledNumber();
1721            if (dialNumber == null) {
1722                log("processDialCall, last dial number null");
1723                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1724                return;
1725            }
1726        } else {
1727            // Remove trailing ';'
1728            if (number.charAt(number.length() - 1) == ';') {
1729                number = number.substring(0, number.length() - 1);
1730            }
1731
1732            dialNumber = PhoneNumberUtils.convertPreDial(number);
1733        }
1734        // Check for virtual call to terminate before sending Call Intent
1735        terminateScoUsingVirtualVoiceCall();
1736        mHeadsetService.setActiveDevice(mDevice);
1737        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1738                Uri.fromParts(SCHEME_TEL, dialNumber, null));
1739        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1740        mHeadsetService.startActivity(intent);
1741        // TODO(BT) continue send OK reults code after call starts
1742        //          hold wait lock, start a timer, set wait call flag
1743        //          Get call started indication from bluetooth phone
1744        mDialingOut = true;
1745        sendMessageDelayed(DIALING_OUT_TIMEOUT, device, DIALING_OUT_TIMEOUT_MS);
1746    }
1747
1748    private void processVolumeEvent(int volumeType, int volume) {
1749        // Only current active device can change SCO volume
1750        if (!mDevice.equals(mHeadsetService.getActiveDevice())) {
1751            Log.w(TAG, "processVolumeEvent, ignored because " + mDevice + " is not active");
1752            return;
1753        }
1754        if (volumeType == HeadsetHalConstants.VOLUME_TYPE_SPK) {
1755            mSpeakerVolume = volume;
1756            int flag = (getCurrentState() == mAudioOn) ? AudioManager.FLAG_SHOW_UI : 0;
1757            mSystemInterface.getAudioManager()
1758                    .setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, flag);
1759        } else if (volumeType == HeadsetHalConstants.VOLUME_TYPE_MIC) {
1760            // Not used currently
1761            mMicVolume = volume;
1762        } else {
1763            Log.e(TAG, "Bad volume type: " + volumeType);
1764        }
1765    }
1766
1767    private void processCallState(HeadsetCallState callState, boolean isVirtualCall) {
1768        mSystemInterface.getHeadsetPhoneState().setNumActiveCall(callState.mNumActive);
1769        mSystemInterface.getHeadsetPhoneState().setNumHeldCall(callState.mNumHeld);
1770        mSystemInterface.getHeadsetPhoneState().setCallState(callState.mCallState);
1771        if (mDialingOut) {
1772            if (callState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING) {
1773                if (!hasMessages(DIALING_OUT_TIMEOUT)) {
1774                    return;
1775                }
1776                mHeadsetService.setActiveDevice(mDevice);
1777                mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
1778                removeMessages(DIALING_OUT_TIMEOUT);
1779            } else if (callState.mCallState == HeadsetHalConstants.CALL_STATE_ACTIVE
1780                    || callState.mCallState == HeadsetHalConstants.CALL_STATE_IDLE) {
1781                mDialingOut = false;
1782            }
1783        }
1784
1785        log("processCallState: mNumActive: " + callState.mNumActive + " mNumHeld: "
1786                + callState.mNumHeld + " mCallState: " + callState.mCallState);
1787        log("processCallState: mNumber: " + callState.mNumber + " mType: " + callState.mType);
1788
1789        if (isVirtualCall) {
1790            // virtual call state update
1791            if (getCurrentState() != mDisconnected) {
1792                mNativeInterface.phoneStateChange(mDevice, callState);
1793            }
1794        } else {
1795            // circuit-switch voice call update
1796            // stop virtual voice call if there is a CSV call ongoing
1797            if (callState.mNumActive > 0 || callState.mNumHeld > 0
1798                    || callState.mCallState != HeadsetHalConstants.CALL_STATE_IDLE) {
1799                terminateScoUsingVirtualVoiceCall();
1800            }
1801
1802            // Specific handling for case of starting MO/MT call while VOIP
1803            // ongoing, terminateScoUsingVirtualVoiceCall() resets callState
1804            // INCOMING/DIALING to IDLE. Some HS send AT+CIND? to read call
1805            // and get wrong value of callsetup. This case is hit only
1806            // SCO for VOIP call is not terminated via SDK API call.
1807            if (mSystemInterface.getHeadsetPhoneState().getCallState() != callState.mCallState) {
1808                mSystemInterface.getHeadsetPhoneState().setCallState(callState.mCallState);
1809            }
1810
1811            // at this step: if there is virtual call ongoing, it means there is no CSV call
1812            // let virtual call continue and skip phone state update
1813            if (!isVirtualCallInProgress()) {
1814                if (getCurrentState() != mDisconnected) {
1815                    mNativeInterface.phoneStateChange(mDevice, callState);
1816                }
1817            }
1818        }
1819    }
1820
1821    private void processNoiseReductionEvent(boolean enable) {
1822        String prevNrec = mAudioParams.getOrDefault(HEADSET_NREC, HEADSET_AUDIO_FEATURE_OFF);
1823        String newNrec = enable ? HEADSET_AUDIO_FEATURE_ON : HEADSET_AUDIO_FEATURE_OFF;
1824        mAudioParams.put(HEADSET_NREC, newNrec);
1825        log("processNoiseReductionEvent: " + HEADSET_NREC + " change " + prevNrec + " -> "
1826                + newNrec);
1827        if (getAudioState() == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
1828            setAudioParameters();
1829        }
1830    }
1831
1832    private void processWBSEvent(int wbsConfig) {
1833        String prevWbs = mAudioParams.getOrDefault(HEADSET_WBS, HEADSET_AUDIO_FEATURE_OFF);
1834        switch (wbsConfig) {
1835            case HeadsetHalConstants.BTHF_WBS_YES:
1836                mAudioParams.put(HEADSET_WBS, HEADSET_AUDIO_FEATURE_ON);
1837                break;
1838            case HeadsetHalConstants.BTHF_WBS_NO:
1839            case HeadsetHalConstants.BTHF_WBS_NONE:
1840                mAudioParams.put(HEADSET_WBS, HEADSET_AUDIO_FEATURE_OFF);
1841                break;
1842            default:
1843                Log.e(TAG, "processWBSEvent: unknown wbsConfig " + wbsConfig);
1844                return;
1845        }
1846        log("processWBSEvent: " + HEADSET_NREC + " change " + prevWbs + " -> " + mAudioParams.get(
1847                HEADSET_WBS));
1848    }
1849
1850    private void processAtChld(int chld, BluetoothDevice device) {
1851        if (mSystemInterface.processChld(chld)) {
1852            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
1853        } else {
1854            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1855        }
1856    }
1857
1858    private void processSubscriberNumberRequest(BluetoothDevice device) {
1859        String number = mSystemInterface.getSubscriberNumber();
1860        if (number != null) {
1861            mNativeInterface.atResponseString(device,
1862                    "+CNUM: ,\"" + number + "\"," + PhoneNumberUtils.toaFromString(number) + ",,4");
1863            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
1864        } else {
1865            Log.e(TAG, "getSubscriberNumber returns null");
1866            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1867        }
1868    }
1869
1870    private void processAtCind(BluetoothDevice device) {
1871        int call, callSetup;
1872        final HeadsetPhoneState phoneState = mSystemInterface.getHeadsetPhoneState();
1873
1874        /* Handsfree carkits expect that +CIND is properly responded to
1875         Hence we ensure that a proper response is sent
1876         for the virtual call too.*/
1877        if (isVirtualCallInProgress()) {
1878            call = 1;
1879            callSetup = 0;
1880        } else {
1881            // regular phone call
1882            call = phoneState.getNumActiveCall();
1883            callSetup = phoneState.getNumHeldCall();
1884        }
1885
1886        mNativeInterface.cindResponse(device, phoneState.getCindService(), call, callSetup,
1887                phoneState.getCallState(), phoneState.getCindSignal(), phoneState.getCindRoam(),
1888                phoneState.getCindBatteryCharge());
1889    }
1890
1891    private void processAtCops(BluetoothDevice device) {
1892        String operatorName = mSystemInterface.getNetworkOperator();
1893        if (operatorName == null) {
1894            operatorName = "";
1895        }
1896        mNativeInterface.copsResponse(device, operatorName);
1897    }
1898
1899    private void processAtClcc(BluetoothDevice device) {
1900        if (isVirtualCallInProgress()) {
1901            // In virtual call, send our phone number instead of remote phone number
1902            String phoneNumber = mSystemInterface.getSubscriberNumber();
1903            if (phoneNumber == null) {
1904                phoneNumber = "";
1905            }
1906            int type = PhoneNumberUtils.toaFromString(phoneNumber);
1907            mNativeInterface.clccResponse(device, 1, 0, 0, 0, false, phoneNumber, type);
1908            mNativeInterface.clccResponse(device, 0, 0, 0, 0, false, "", 0);
1909        } else {
1910            // In Telecom call, ask Telecom to send send remote phone number
1911            if (!mSystemInterface.listCurrentCalls()) {
1912                Log.e(TAG, "processAtClcc: failed to list current calls for " + device);
1913                mNativeInterface.clccResponse(device, 0, 0, 0, 0, false, "", 0);
1914            } else {
1915                sendMessageDelayed(CLCC_RSP_TIMEOUT, device, CLCC_RSP_TIMEOUT_MS);
1916            }
1917        }
1918    }
1919
1920    private void processAtCscs(String atString, int type, BluetoothDevice device) {
1921        log("processAtCscs - atString = " + atString);
1922        if (mPhonebook != null) {
1923            mPhonebook.handleCscsCommand(atString, type, device);
1924        } else {
1925            Log.e(TAG, "Phonebook handle null for At+CSCS");
1926            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1927        }
1928    }
1929
1930    private void processAtCpbs(String atString, int type, BluetoothDevice device) {
1931        log("processAtCpbs - atString = " + atString);
1932        if (mPhonebook != null) {
1933            mPhonebook.handleCpbsCommand(atString, type, device);
1934        } else {
1935            Log.e(TAG, "Phonebook handle null for At+CPBS");
1936            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1937        }
1938    }
1939
1940    private void processAtCpbr(String atString, int type, BluetoothDevice device) {
1941        log("processAtCpbr - atString = " + atString);
1942        if (mPhonebook != null) {
1943            mPhonebook.handleCpbrCommand(atString, type, device);
1944        } else {
1945            Log.e(TAG, "Phonebook handle null for At+CPBR");
1946            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1947        }
1948    }
1949
1950    /**
1951     * Find a character ch, ignoring quoted sections.
1952     * Return input.length() if not found.
1953     */
1954    private static int findChar(char ch, String input, int fromIndex) {
1955        for (int i = fromIndex; i < input.length(); i++) {
1956            char c = input.charAt(i);
1957            if (c == '"') {
1958                i = input.indexOf('"', i + 1);
1959                if (i == -1) {
1960                    return input.length();
1961                }
1962            } else if (c == ch) {
1963                return i;
1964            }
1965        }
1966        return input.length();
1967    }
1968
1969    /**
1970     * Break an argument string into individual arguments (comma delimited).
1971     * Integer arguments are turned into Integer objects. Otherwise a String
1972     * object is used.
1973     */
1974    private static Object[] generateArgs(String input) {
1975        int i = 0;
1976        int j;
1977        ArrayList<Object> out = new ArrayList<Object>();
1978        while (i <= input.length()) {
1979            j = findChar(',', input, i);
1980
1981            String arg = input.substring(i, j);
1982            try {
1983                out.add(new Integer(arg));
1984            } catch (NumberFormatException e) {
1985                out.add(arg);
1986            }
1987
1988            i = j + 1; // move past comma
1989        }
1990        return out.toArray();
1991    }
1992
1993    /**
1994     * Process vendor specific AT commands
1995     *
1996     * @param atString AT command after the "AT+" prefix
1997     * @param device Remote device that has sent this command
1998     */
1999    private void processVendorSpecificAt(String atString, BluetoothDevice device) {
2000        log("processVendorSpecificAt - atString = " + atString);
2001
2002        // Currently we accept only SET type commands.
2003        int indexOfEqual = atString.indexOf("=");
2004        if (indexOfEqual == -1) {
2005            Log.e(TAG, "processVendorSpecificAt: command type error in " + atString);
2006            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
2007            return;
2008        }
2009
2010        String command = atString.substring(0, indexOfEqual);
2011        Integer companyId = VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.get(command);
2012        if (companyId == null) {
2013            Log.e(TAG, "processVendorSpecificAt: unsupported command: " + atString);
2014            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
2015            return;
2016        }
2017
2018        String arg = atString.substring(indexOfEqual + 1);
2019        if (arg.startsWith("?")) {
2020            Log.e(TAG, "processVendorSpecificAt: command type error in " + atString);
2021            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
2022            return;
2023        }
2024
2025        Object[] args = generateArgs(arg);
2026        if (command.equals(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XAPL)) {
2027            processAtXapl(args, device);
2028        }
2029        broadcastVendorSpecificEventIntent(command, companyId, BluetoothHeadset.AT_CMD_TYPE_SET,
2030                args, device);
2031        mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
2032    }
2033
2034    /**
2035     * Process AT+XAPL AT command
2036     *
2037     * @param args command arguments after the equal sign
2038     * @param device Remote device that has sent this command
2039     */
2040    private void processAtXapl(Object[] args, BluetoothDevice device) {
2041        if (args.length != 2) {
2042            Log.w(TAG, "processAtXapl() args length must be 2: " + String.valueOf(args.length));
2043            return;
2044        }
2045        if (!(args[0] instanceof String) || !(args[1] instanceof Integer)) {
2046            Log.w(TAG, "processAtXapl() argument types not match");
2047            return;
2048        }
2049        // feature = 2 indicates that we support battery level reporting only
2050        mNativeInterface.atResponseString(device, "+XAPL=iPhone," + String.valueOf(2));
2051    }
2052
2053    private void processUnknownAt(String atString, BluetoothDevice device) {
2054        if (device == null) {
2055            Log.w(TAG, "processUnknownAt device is null");
2056            return;
2057        }
2058        log("processUnknownAt - atString = " + atString);
2059        String atCommand = parseUnknownAt(atString);
2060        int commandType = getAtCommandType(atCommand);
2061        if (atCommand.startsWith("+CSCS")) {
2062            processAtCscs(atCommand.substring(5), commandType, device);
2063        } else if (atCommand.startsWith("+CPBS")) {
2064            processAtCpbs(atCommand.substring(5), commandType, device);
2065        } else if (atCommand.startsWith("+CPBR")) {
2066            processAtCpbr(atCommand.substring(5), commandType, device);
2067        } else {
2068            processVendorSpecificAt(atCommand, device);
2069        }
2070    }
2071
2072    private void processKeyPressed(BluetoothDevice device) {
2073        final HeadsetPhoneState phoneState = mSystemInterface.getHeadsetPhoneState();
2074        if (phoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING) {
2075            mSystemInterface.answerCall(device);
2076        } else if (phoneState.getNumActiveCall() > 0) {
2077            if (getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
2078                mHeadsetService.setActiveDevice(mDevice);
2079                mNativeInterface.connectAudio(mDevice);
2080            } else {
2081                mSystemInterface.hangupCall(device, false);
2082            }
2083        } else {
2084            String dialNumber = mPhonebook.getLastDialledNumber();
2085            if (dialNumber == null) {
2086                log("processKeyPressed, last dial number null");
2087                return;
2088            }
2089            mHeadsetService.setActiveDevice(mDevice);
2090            Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
2091                    Uri.fromParts(SCHEME_TEL, dialNumber, null));
2092            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2093            mHeadsetService.startActivity(intent);
2094        }
2095    }
2096
2097    /**
2098     * Send HF indicator value changed intent
2099     *
2100     * @param device Device whose HF indicator value has changed
2101     * @param indId Indicator ID [0-65535]
2102     * @param indValue Indicator Value [0-65535], -1 means invalid but indId is supported
2103     */
2104    private void sendIndicatorIntent(BluetoothDevice device, int indId, int indValue) {
2105        Intent intent = new Intent(BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED);
2106        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
2107        intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, indId);
2108        intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, indValue);
2109
2110        mHeadsetService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
2111    }
2112
2113    private void processAtBind(String atString, BluetoothDevice device) {
2114        log("processAtBind: " + atString);
2115
2116        // Parse the AT String to find the Indicator Ids that are supported
2117        int indId = 0;
2118        int iter = 0;
2119        int iter1 = 0;
2120
2121        while (iter < atString.length()) {
2122            iter1 = findChar(',', atString, iter);
2123            String id = atString.substring(iter, iter1);
2124
2125            try {
2126                indId = Integer.valueOf(id);
2127            } catch (NumberFormatException e) {
2128                Log.e(TAG, Log.getStackTraceString(new Throwable()));
2129            }
2130
2131            switch (indId) {
2132                case HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY:
2133                    log("Send Broadcast intent for the Enhanced Driver Safety indicator.");
2134                    sendIndicatorIntent(device, indId, -1);
2135                    break;
2136                case HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS:
2137                    log("Send Broadcast intent for the Battery Level indicator.");
2138                    sendIndicatorIntent(device, indId, -1);
2139                    break;
2140                default:
2141                    log("Invalid HF Indicator Received");
2142                    break;
2143            }
2144
2145            iter = iter1 + 1; // move past comma
2146        }
2147    }
2148
2149    private void processAtBiev(int indId, int indValue, BluetoothDevice device) {
2150        log("processAtBiev: ind_id=" + indId + ", ind_value=" + indValue);
2151        sendIndicatorIntent(device, indId, indValue);
2152    }
2153
2154    private void processSendClccResponse(HeadsetClccResponse clcc) {
2155        if (!hasMessages(CLCC_RSP_TIMEOUT)) {
2156            return;
2157        }
2158        if (clcc.mIndex == 0) {
2159            removeMessages(CLCC_RSP_TIMEOUT);
2160        }
2161        mNativeInterface.clccResponse(mDevice, clcc.mIndex, clcc.mDirection, clcc.mStatus,
2162                clcc.mMode, clcc.mMpty, clcc.mNumber, clcc.mType);
2163    }
2164
2165    private void processSendVendorSpecificResultCode(HeadsetVendorSpecificResultCode resultCode) {
2166        String stringToSend = resultCode.mCommand + ": ";
2167        if (resultCode.mArg != null) {
2168            stringToSend += resultCode.mArg;
2169        }
2170        mNativeInterface.atResponseString(resultCode.mDevice, stringToSend);
2171    }
2172
2173    private String getCurrentDeviceName() {
2174        String deviceName = mAdapterService.getRemoteName(mDevice);
2175        if (deviceName == null) {
2176            return "<unknown>";
2177        }
2178        return deviceName;
2179    }
2180
2181    // Accept incoming SCO only when there is in-band ringing, incoming call,
2182    // active call, VR activated, active VOIP call
2183    private boolean isScoAcceptable() {
2184        if (mHeadsetService.getForceScoAudio()) {
2185            return true;
2186        }
2187        BluetoothDevice activeDevice = mHeadsetService.getActiveDevice();
2188        if (!mDevice.equals(activeDevice)) {
2189            Log.w(TAG, "isScoAcceptable: rejected SCO since " + mDevice
2190                    + " is not the current active device " + activeDevice);
2191            return false;
2192        }
2193        if (!mHeadsetService.getAudioRouteAllowed()) {
2194            Log.w(TAG, "isScoAcceptabl: rejected SCO since audio route is not allowed");
2195            return false;
2196        }
2197        if (mSystemInterface.isInCall() || mVoiceRecognitionStarted) {
2198            return true;
2199        }
2200        if (mSystemInterface.isRinging() && mHeadsetService.isInbandRingingEnabled()) {
2201            return true;
2202        }
2203        Log.w(TAG, "isScoAcceptable: rejected SCO, inCall=" + mSystemInterface.isInCall()
2204                + ", voiceRecognition=" + mVoiceRecognitionStarted + ", ringing=" + mSystemInterface
2205                .isRinging() + ", inbandRinging=" + mHeadsetService.isInbandRingingEnabled());
2206        return false;
2207    }
2208
2209    @Override
2210    protected void log(String msg) {
2211        if (DBG) {
2212            super.log(msg);
2213        }
2214    }
2215
2216    @Override
2217    protected String getLogRecString(Message msg) {
2218        StringBuilder builder = new StringBuilder();
2219        builder.append(getMessageName(msg.what));
2220        builder.append(": ");
2221        builder.append("arg1=")
2222                .append(msg.arg1)
2223                .append(", arg2=")
2224                .append(msg.arg2)
2225                .append(", obj=");
2226        if (msg.obj instanceof HeadsetMessageObject) {
2227            HeadsetMessageObject object = (HeadsetMessageObject) msg.obj;
2228            object.buildString(builder);
2229        } else {
2230            builder.append(msg.obj);
2231        }
2232        return builder.toString();
2233    }
2234
2235    private void handleAccessPermissionResult(Intent intent) {
2236        log("handleAccessPermissionResult");
2237        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
2238        if (!mPhonebook.getCheckingAccessPermission()) {
2239            return;
2240        }
2241        int atCommandResult = 0;
2242        int atCommandErrorCode = 0;
2243        // HeadsetBase headset = mHandsfree.getHeadset();
2244        // ASSERT: (headset != null) && headSet.isConnected()
2245        // REASON: mCheckingAccessPermission is true, otherwise resetAtState
2246        // has set mCheckingAccessPermission to false
2247        if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
2248            if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
2249                    BluetoothDevice.CONNECTION_ACCESS_NO)
2250                    == BluetoothDevice.CONNECTION_ACCESS_YES) {
2251                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
2252                    mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
2253                }
2254                atCommandResult = mPhonebook.processCpbrCommand(device);
2255            } else {
2256                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
2257                    mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
2258                }
2259            }
2260        }
2261        mPhonebook.setCpbrIndex(-1);
2262        mPhonebook.setCheckingAccessPermission(false);
2263        if (atCommandResult >= 0) {
2264            mNativeInterface.atResponseCode(device, atCommandResult, atCommandErrorCode);
2265        } else {
2266            log("handleAccessPermissionResult - RESULT_NONE");
2267        }
2268    }
2269
2270    private static String getMessageName(int what) {
2271        switch (what) {
2272            case CONNECT:
2273                return "CONNECT";
2274            case DISCONNECT:
2275                return "DISCONNECT";
2276            case CONNECT_AUDIO:
2277                return "CONNECT_AUDIO";
2278            case DISCONNECT_AUDIO:
2279                return "DISCONNECT_AUDIO";
2280            case VOICE_RECOGNITION_START:
2281                return "VOICE_RECOGNITION_START";
2282            case VOICE_RECOGNITION_STOP:
2283                return "VOICE_RECOGNITION_STOP";
2284            case INTENT_SCO_VOLUME_CHANGED:
2285                return "INTENT_SCO_VOLUME_CHANGED";
2286            case INTENT_CONNECTION_ACCESS_REPLY:
2287                return "INTENT_CONNECTION_ACCESS_REPLY";
2288            case CALL_STATE_CHANGED:
2289                return "CALL_STATE_CHANGED";
2290            case DEVICE_STATE_CHANGED:
2291                return "DEVICE_STATE_CHANGED";
2292            case SEND_CCLC_RESPONSE:
2293                return "SEND_CCLC_RESPONSE";
2294            case SEND_VENDOR_SPECIFIC_RESULT_CODE:
2295                return "SEND_VENDOR_SPECIFIC_RESULT_CODE";
2296            case VIRTUAL_CALL_START:
2297                return "VIRTUAL_CALL_START";
2298            case VIRTUAL_CALL_STOP:
2299                return "VIRTUAL_CALL_STOP";
2300            case STACK_EVENT:
2301                return "STACK_EVENT";
2302            case DIALING_OUT_TIMEOUT:
2303                return "DIALING_OUT_TIMEOUT";
2304            case START_VR_TIMEOUT:
2305                return "START_VR_TIMEOUT";
2306            case CLCC_RSP_TIMEOUT:
2307                return "CLCC_RSP_TIMEOUT";
2308            case CONNECT_TIMEOUT:
2309                return "CONNECT_TIMEOUT";
2310            default:
2311                return "UNKNOWN";
2312        }
2313    }
2314}
2315