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