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