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