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