Home | History | Annotate | Download | only in hfp
      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 
     17 package com.android.bluetooth.hfp;
     18 
     19 import android.bluetooth.BluetoothAssignedNumbers;
     20 import android.bluetooth.BluetoothDevice;
     21 import android.bluetooth.BluetoothHeadset;
     22 import android.bluetooth.BluetoothProfile;
     23 import android.content.Intent;
     24 import android.media.AudioManager;
     25 import android.os.Looper;
     26 import android.os.Message;
     27 import android.os.SystemClock;
     28 import android.os.UserHandle;
     29 import android.support.annotation.VisibleForTesting;
     30 import android.telephony.PhoneNumberUtils;
     31 import android.telephony.PhoneStateListener;
     32 import android.util.Log;
     33 
     34 import com.android.bluetooth.btservice.AdapterService;
     35 import com.android.bluetooth.btservice.ProfileService;
     36 import com.android.internal.util.State;
     37 import com.android.internal.util.StateMachine;
     38 
     39 import java.io.FileDescriptor;
     40 import java.io.PrintWriter;
     41 import java.io.StringWriter;
     42 import java.util.ArrayList;
     43 import java.util.HashMap;
     44 import java.util.Map;
     45 import java.util.Objects;
     46 import 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
     69 public 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