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