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