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