• 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 import static android.Manifest.permission.MODIFY_PHONE_STATE;
21 
22 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
23 
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.bluetooth.BluetoothDevice;
27 import android.bluetooth.BluetoothHeadset;
28 import android.bluetooth.BluetoothProfile;
29 import android.bluetooth.BluetoothUuid;
30 import android.bluetooth.IBluetoothHeadset;
31 import android.content.Attributable;
32 import android.content.AttributionSource;
33 import android.content.BroadcastReceiver;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.media.AudioManager;
38 import android.net.Uri;
39 import android.os.BatteryManager;
40 import android.os.Handler;
41 import android.os.HandlerThread;
42 import android.os.Looper;
43 import android.os.ParcelUuid;
44 import android.os.SystemProperties;
45 import android.os.UserHandle;
46 import android.telecom.PhoneAccount;
47 import android.util.Log;
48 
49 import com.android.bluetooth.BluetoothMetricsProto;
50 import com.android.bluetooth.BluetoothStatsLog;
51 import com.android.bluetooth.Utils;
52 import com.android.bluetooth.btservice.AdapterService;
53 import com.android.bluetooth.btservice.MetricsLogger;
54 import com.android.bluetooth.btservice.ProfileService;
55 import com.android.bluetooth.btservice.storage.DatabaseManager;
56 import com.android.internal.annotations.VisibleForTesting;
57 
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Comparator;
61 import java.util.HashMap;
62 import java.util.List;
63 import java.util.Objects;
64 
65 /**
66  * Provides Bluetooth Headset and Handsfree profile, as a service in the Bluetooth application.
67  *
68  * Three modes for SCO audio:
69  * Mode 1: Telecom call through {@link #phoneStateChanged(int, int, int, String, int, String,
70  *         boolean)}
71  * Mode 2: Virtual call through {@link #startScoUsingVirtualVoiceCall()}
72  * Mode 3: Voice recognition through {@link #startVoiceRecognition(BluetoothDevice)}
73  *
74  * When one mode is active, other mode cannot be started. API user has to terminate existing modes
75  * using the correct API or just {@link #disconnectAudio()} if user is a system service, before
76  * starting a new mode.
77  *
78  * {@link #connectAudio()} will start SCO audio at one of the above modes, but won't change mode
79  * {@link #disconnectAudio()} can happen in any mode to disconnect SCO
80  *
81  * When audio is disconnected, only Mode 1 Telecom call will be persisted, both Mode 2 virtual call
82  * and Mode 3 voice call will be terminated upon SCO termination and client has to restart the mode.
83  *
84  * NOTE: SCO termination can either be initiated on the AG side or the HF side
85  * TODO(b/79660380): As a workaround, voice recognition will be terminated if virtual call or
86  * Telecom call is initiated while voice recognition is ongoing, in case calling app did not call
87  * {@link #stopVoiceRecognition(BluetoothDevice)}
88  *
89  * AG - Audio Gateway, device running this {@link HeadsetService}, e.g. Android Phone
90  * HF - Handsfree device, device running headset client, e.g. Wireless headphones or car kits
91  */
92 public class HeadsetService extends ProfileService {
93     private static final String TAG = "HeadsetService";
94     private static final boolean DBG = false;
95     private static final String DISABLE_INBAND_RINGING_PROPERTY =
96             "persist.bluetooth.disableinbandringing";
97     private static final ParcelUuid[] HEADSET_UUIDS = {BluetoothUuid.HSP, BluetoothUuid.HFP};
98     private static final int[] CONNECTING_CONNECTED_STATES =
99             {BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED};
100     private static final int DIALING_OUT_TIMEOUT_MS = 10000;
101 
102     private int mMaxHeadsetConnections = 1;
103     private BluetoothDevice mActiveDevice;
104     private AdapterService mAdapterService;
105     private DatabaseManager mDatabaseManager;
106     private HandlerThread mStateMachinesThread;
107     private Handler mStateMachinesThreadHandler;
108     // This is also used as a lock for shared data in HeadsetService
109     private final HashMap<BluetoothDevice, HeadsetStateMachine> mStateMachines = new HashMap<>();
110     private HeadsetNativeInterface mNativeInterface;
111     private HeadsetSystemInterface mSystemInterface;
112     private boolean mAudioRouteAllowed = true;
113     // Indicates whether SCO audio needs to be forced to open regardless ANY OTHER restrictions
114     private boolean mForceScoAudio;
115     private boolean mInbandRingingRuntimeDisable;
116     private boolean mVirtualCallStarted;
117     // Non null value indicates a pending dialing out event is going on
118     private DialingOutTimeoutEvent mDialingOutTimeoutEvent;
119     private boolean mVoiceRecognitionStarted;
120     // Non null value indicates a pending voice recognition request from headset is going on
121     private VoiceRecognitionTimeoutEvent mVoiceRecognitionTimeoutEvent;
122     // Timeout when voice recognition is started by remote device
123     @VisibleForTesting static int sStartVrTimeoutMs = 5000;
124     private boolean mStarted;
125     private boolean mCreated;
126     private static HeadsetService sHeadsetService;
127 
128     @Override
initBinder()129     public IProfileServiceBinder initBinder() {
130         return new BluetoothHeadsetBinder(this);
131     }
132 
133     @Override
create()134     protected void create() {
135         Log.i(TAG, "create()");
136         if (mCreated) {
137             throw new IllegalStateException("create() called twice");
138         }
139         mCreated = true;
140     }
141 
142     @Override
start()143     protected boolean start() {
144         Log.i(TAG, "start()");
145         if (mStarted) {
146             throw new IllegalStateException("start() called twice");
147         }
148         // Step 1: Get AdapterService and DatabaseManager, should never be null
149         mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
150                 "AdapterService cannot be null when HeadsetService starts");
151         mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(),
152                 "DatabaseManager cannot be null when HeadsetService starts");
153         // Step 2: Start handler thread for state machines
154         mStateMachinesThread = new HandlerThread("HeadsetService.StateMachines");
155         mStateMachinesThread.start();
156         // Step 3: Initialize system interface
157         mSystemInterface = HeadsetObjectsFactory.getInstance().makeSystemInterface(this);
158         // Step 4: Initialize native interface
159         mMaxHeadsetConnections = mAdapterService.getMaxConnectedAudioDevices();
160         mNativeInterface = HeadsetObjectsFactory.getInstance().getNativeInterface();
161         // Add 1 to allow a pending device to be connecting or disconnecting
162         mNativeInterface.init(mMaxHeadsetConnections + 1, isInbandRingingEnabled());
163         // Step 5: Check if state machine table is empty, crash if not
164         if (mStateMachines.size() > 0) {
165             throw new IllegalStateException(
166                     "start(): mStateMachines is not empty, " + mStateMachines.size()
167                             + " is already created. Was stop() called properly?");
168         }
169         // Step 6: Setup broadcast receivers
170         IntentFilter filter = new IntentFilter();
171         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
172         filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
173         filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
174         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
175         registerReceiver(mHeadsetReceiver, filter);
176         // Step 7: Mark service as started
177         setHeadsetService(this);
178         mStarted = true;
179         return true;
180     }
181 
182     @Override
stop()183     protected boolean stop() {
184         Log.i(TAG, "stop()");
185         if (!mStarted) {
186             Log.w(TAG, "stop() called before start()");
187             // Still return true because it is considered "stopped" and doesn't have any functional
188             // impact on the user
189             return true;
190         }
191         // Step 7: Mark service as stopped
192         mStarted = false;
193         setHeadsetService(null);
194         // Step 6: Tear down broadcast receivers
195         unregisterReceiver(mHeadsetReceiver);
196         synchronized (mStateMachines) {
197             // Reset active device to null
198             mActiveDevice = null;
199             mInbandRingingRuntimeDisable = false;
200             mForceScoAudio = false;
201             mAudioRouteAllowed = true;
202             mMaxHeadsetConnections = 1;
203             mVoiceRecognitionStarted = false;
204             mVirtualCallStarted = false;
205             if (mDialingOutTimeoutEvent != null) {
206                 getStateMachinesThreadHandler()
207                         .removeCallbacks(mDialingOutTimeoutEvent);
208                 mDialingOutTimeoutEvent = null;
209             }
210             if (mVoiceRecognitionTimeoutEvent != null) {
211                 getStateMachinesThreadHandler()
212                         .removeCallbacks(mVoiceRecognitionTimeoutEvent);
213                 mVoiceRecognitionTimeoutEvent = null;
214                 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
215                     mSystemInterface.getVoiceRecognitionWakeLock().release();
216                 }
217             }
218             // Step 5: Destroy state machines
219             for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
220                 HeadsetObjectsFactory.getInstance().destroyStateMachine(stateMachine);
221             }
222             mStateMachines.clear();
223         }
224         // Step 4: Destroy native interface
225         mNativeInterface.cleanup();
226         // Step 3: Destroy system interface
227         mSystemInterface.stop();
228         // Step 2: Stop handler thread
229         mStateMachinesThread.quitSafely();
230         mStateMachinesThread = null;
231         mStateMachinesThreadHandler = null;
232         // Step 1: Clear
233         synchronized (mStateMachines) {
234             mAdapterService = null;
235         }
236         return true;
237     }
238 
239     @Override
cleanup()240     protected void cleanup() {
241         Log.i(TAG, "cleanup");
242         if (!mCreated) {
243             Log.w(TAG, "cleanup() called before create()");
244         }
245         mCreated = false;
246     }
247 
248     /**
249      * Checks if this service object is able to accept binder calls
250      *
251      * @return True if the object can accept binder calls, False otherwise
252      */
isAlive()253     public boolean isAlive() {
254         return isAvailable() && mCreated && mStarted;
255     }
256 
257     /**
258      * Get the {@link Looper} for the state machine thread. This is used in testing and helper
259      * objects
260      *
261      * @return {@link Looper} for the state machine thread
262      */
263     @VisibleForTesting
getStateMachinesThreadLooper()264     public Looper getStateMachinesThreadLooper() {
265         return mStateMachinesThread.getLooper();
266     }
267 
268     interface StateMachineTask {
execute(HeadsetStateMachine stateMachine)269         void execute(HeadsetStateMachine stateMachine);
270     }
271 
doForStateMachine(BluetoothDevice device, StateMachineTask task)272     private boolean doForStateMachine(BluetoothDevice device, StateMachineTask task) {
273         synchronized (mStateMachines) {
274             HeadsetStateMachine stateMachine = mStateMachines.get(device);
275             if (stateMachine == null) {
276                 return false;
277             }
278             task.execute(stateMachine);
279         }
280         return true;
281     }
282 
doForEachConnectedStateMachine(StateMachineTask task)283     private void doForEachConnectedStateMachine(StateMachineTask task) {
284         synchronized (mStateMachines) {
285             for (BluetoothDevice device : getConnectedDevices()) {
286                 task.execute(mStateMachines.get(device));
287             }
288         }
289     }
290 
onDeviceStateChanged(HeadsetDeviceState deviceState)291     void onDeviceStateChanged(HeadsetDeviceState deviceState) {
292         doForEachConnectedStateMachine(
293                 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.DEVICE_STATE_CHANGED,
294                         deviceState));
295     }
296 
297     /**
298      * Handle messages from native (JNI) to Java. This needs to be synchronized to avoid posting
299      * messages to state machine before start() is done
300      *
301      * @param stackEvent event from native stack
302      */
messageFromNative(HeadsetStackEvent stackEvent)303     void messageFromNative(HeadsetStackEvent stackEvent) {
304         Objects.requireNonNull(stackEvent.device,
305                 "Device should never be null, event: " + stackEvent);
306         synchronized (mStateMachines) {
307             HeadsetStateMachine stateMachine = mStateMachines.get(stackEvent.device);
308             if (stackEvent.type == HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
309                 switch (stackEvent.valueInt) {
310                     case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
311                     case HeadsetHalConstants.CONNECTION_STATE_CONNECTING: {
312                         // Create new state machine if none is found
313                         if (stateMachine == null) {
314                             stateMachine = HeadsetObjectsFactory.getInstance()
315                                     .makeStateMachine(stackEvent.device,
316                                             mStateMachinesThread.getLooper(), this, mAdapterService,
317                                             mNativeInterface, mSystemInterface);
318                             mStateMachines.put(stackEvent.device, stateMachine);
319                         }
320                         break;
321                     }
322                 }
323             }
324             if (stateMachine == null) {
325                 throw new IllegalStateException(
326                         "State machine not found for stack event: " + stackEvent);
327             }
328             stateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT, stackEvent);
329         }
330     }
331 
332     private final BroadcastReceiver mHeadsetReceiver = new BroadcastReceiver() {
333         @Override
334         public void onReceive(Context context, Intent intent) {
335             String action = intent.getAction();
336             if (action == null) {
337                 Log.w(TAG, "mHeadsetReceiver, action is null");
338                 return;
339             }
340             switch (action) {
341                 case Intent.ACTION_BATTERY_CHANGED: {
342                     int batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
343                     int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
344                     if (batteryLevel < 0 || scale <= 0) {
345                         Log.e(TAG, "Bad Battery Changed intent: batteryLevel=" + batteryLevel
346                                 + ", scale=" + scale);
347                         return;
348                     }
349                     int cindBatteryLevel = Math.round(batteryLevel * 5 / ((float) scale));
350                     mSystemInterface.getHeadsetPhoneState().setCindBatteryCharge(cindBatteryLevel);
351                     break;
352                 }
353                 case AudioManager.VOLUME_CHANGED_ACTION: {
354                     int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
355                     if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
356                         doForEachConnectedStateMachine(stateMachine -> stateMachine.sendMessage(
357                                 HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED, intent));
358                     }
359                     break;
360                 }
361                 case BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY: {
362                     int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
363                             BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
364                     BluetoothDevice device =
365                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
366                     logD("Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY, device=" + device
367                             + ", type=" + requestType);
368                     if (requestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
369                         synchronized (mStateMachines) {
370                             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
371                             if (stateMachine == null) {
372                                 Log.wtf(TAG, "Cannot find state machine for " + device);
373                                 return;
374                             }
375                             stateMachine.sendMessage(
376                                     HeadsetStateMachine.INTENT_CONNECTION_ACCESS_REPLY, intent);
377                         }
378                     }
379                     break;
380                 }
381                 case BluetoothDevice.ACTION_BOND_STATE_CHANGED: {
382                     int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
383                             BluetoothDevice.ERROR);
384                     BluetoothDevice device = Objects.requireNonNull(
385                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE),
386                             "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
387                     logD("Bond state changed for device: " + device + " state: " + state);
388                     if (state != BluetoothDevice.BOND_NONE) {
389                         break;
390                     }
391                     synchronized (mStateMachines) {
392                         HeadsetStateMachine stateMachine = mStateMachines.get(device);
393                         if (stateMachine == null) {
394                             break;
395                         }
396                         if (stateMachine.getConnectionState()
397                                 != BluetoothProfile.STATE_DISCONNECTED) {
398                             break;
399                         }
400                         removeStateMachine(device);
401                     }
402                     break;
403                 }
404                 default:
405                     Log.w(TAG, "Unknown action " + action);
406             }
407         }
408     };
409 
410     /**
411      * Handlers for incoming service calls
412      */
413     private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub
414             implements IProfileServiceBinder {
415         private volatile HeadsetService mService;
416 
BluetoothHeadsetBinder(HeadsetService svc)417         BluetoothHeadsetBinder(HeadsetService svc) {
418             mService = svc;
419         }
420 
421         @Override
cleanup()422         public void cleanup() {
423             mService = null;
424         }
425 
426         @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getService(AttributionSource source)427         private HeadsetService getService(AttributionSource source) {
428             if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
429                     || !Utils.checkServiceAvailable(mService, TAG)
430                     || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
431                 return null;
432             }
433             return mService;
434         }
435 
436         @Override
connect(BluetoothDevice device)437         public boolean connect(BluetoothDevice device) {
438             return connectWithAttribution(device, Utils.getCallingAttributionSource());
439         }
440 
441         @Override
connectWithAttribution(BluetoothDevice device, AttributionSource source)442         public boolean connectWithAttribution(BluetoothDevice device, AttributionSource source) {
443             Attributable.setAttributionSource(device, source);
444             HeadsetService service = getService(source);
445             if (service == null) {
446                 return false;
447             }
448             return service.connect(device);
449         }
450 
451         @Override
disconnect(BluetoothDevice device)452         public boolean disconnect(BluetoothDevice device) {
453             return disconnectWithAttribution(device, Utils.getCallingAttributionSource());
454         }
455 
456         @Override
disconnectWithAttribution(BluetoothDevice device, AttributionSource source)457         public boolean disconnectWithAttribution(BluetoothDevice device, AttributionSource source) {
458             Attributable.setAttributionSource(device, source);
459             HeadsetService service = getService(source);
460             if (service == null) {
461                 return false;
462             }
463             return service.disconnect(device);
464         }
465 
466         @Override
getConnectedDevices()467         public List<BluetoothDevice> getConnectedDevices() {
468             return getConnectedDevicesWithAttribution(Utils.getCallingAttributionSource());
469         }
470 
471         @Override
getConnectedDevicesWithAttribution(AttributionSource source)472         public List<BluetoothDevice> getConnectedDevicesWithAttribution(AttributionSource source) {
473             HeadsetService service = getService(source);
474             if (service == null) {
475                 return new ArrayList<BluetoothDevice>(0);
476             }
477             return service.getConnectedDevices();
478         }
479 
480         @Override
getDevicesMatchingConnectionStates(int[] states, AttributionSource source)481         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
482                 AttributionSource source) {
483             HeadsetService service = getService(source);
484             if (service == null) {
485                 return new ArrayList<BluetoothDevice>(0);
486             }
487             return service.getDevicesMatchingConnectionStates(states);
488         }
489 
490         @Override
getConnectionState(BluetoothDevice device)491         public int getConnectionState(BluetoothDevice device) {
492             return getConnectionStateWithAttribution(device, Utils.getCallingAttributionSource());
493         }
494 
495         @Override
getConnectionStateWithAttribution(BluetoothDevice device, AttributionSource source)496         public int getConnectionStateWithAttribution(BluetoothDevice device,
497                 AttributionSource source) {
498             Attributable.setAttributionSource(device, source);
499             HeadsetService service = getService(source);
500             if (service == null) {
501                 return BluetoothProfile.STATE_DISCONNECTED;
502             }
503             return service.getConnectionState(device);
504         }
505 
506         @Override
setPriority(BluetoothDevice device, int connectionPolicy, AttributionSource source)507         public boolean setPriority(BluetoothDevice device, int connectionPolicy,
508                 AttributionSource source) {
509             Attributable.setAttributionSource(device, source);
510             HeadsetService service = getService(source);
511             if (service == null) {
512                 return false;
513             }
514             return service.setConnectionPolicy(device, connectionPolicy);
515         }
516 
517         @Override
setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source)518         public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
519                 AttributionSource source) {
520             Attributable.setAttributionSource(device, source);
521             HeadsetService service = getService(source);
522             if (service == null) {
523                 return false;
524             }
525             enforceBluetoothPrivilegedPermission(service);
526             return service.setConnectionPolicy(device, connectionPolicy);
527         }
528 
529         @Override
getPriority(BluetoothDevice device, AttributionSource source)530         public int getPriority(BluetoothDevice device, AttributionSource source) {
531             Attributable.setAttributionSource(device, source);
532             HeadsetService service = getService(source);
533             if (service == null) {
534                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
535             }
536             return service.getConnectionPolicy(device);
537         }
538 
539         @Override
getConnectionPolicy(BluetoothDevice device, AttributionSource source)540         public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
541             Attributable.setAttributionSource(device, source);
542             HeadsetService service = getService(source);
543             if (service == null) {
544                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
545             }
546             enforceBluetoothPrivilegedPermission(service);
547             return service.getConnectionPolicy(device);
548         }
549 
550         @Override
isNoiseReductionSupported(BluetoothDevice device, AttributionSource source)551         public boolean isNoiseReductionSupported(BluetoothDevice device, AttributionSource source) {
552             Attributable.setAttributionSource(device, source);
553             HeadsetService service = getService(source);
554             if (service == null) {
555                 return false;
556             }
557             return service.isNoiseReductionSupported(device);
558         }
559 
560         @Override
isVoiceRecognitionSupported(BluetoothDevice device, AttributionSource source)561         public boolean isVoiceRecognitionSupported(BluetoothDevice device,
562                 AttributionSource source) {
563             Attributable.setAttributionSource(device, source);
564             HeadsetService service = getService(source);
565             if (service == null) {
566                 return false;
567             }
568             return service.isVoiceRecognitionSupported(device);
569         }
570 
571         @Override
startVoiceRecognition(BluetoothDevice device, AttributionSource source)572         public boolean startVoiceRecognition(BluetoothDevice device, AttributionSource source) {
573             Attributable.setAttributionSource(device, source);
574             HeadsetService service = getService(source);
575             if (service == null) {
576                 return false;
577             }
578             return service.startVoiceRecognition(device);
579         }
580 
581         @Override
stopVoiceRecognition(BluetoothDevice device, AttributionSource source)582         public boolean stopVoiceRecognition(BluetoothDevice device, AttributionSource source) {
583             Attributable.setAttributionSource(device, source);
584             HeadsetService service = getService(source);
585             if (service == null) {
586                 return false;
587             }
588             return service.stopVoiceRecognition(device);
589         }
590 
591         @Override
isAudioOn(AttributionSource source)592         public boolean isAudioOn(AttributionSource source) {
593             HeadsetService service = getService(source);
594             if (service == null) {
595                 return false;
596             }
597             return service.isAudioOn();
598         }
599 
600         @Override
isAudioConnected(BluetoothDevice device, AttributionSource source)601         public boolean isAudioConnected(BluetoothDevice device, AttributionSource source) {
602             Attributable.setAttributionSource(device, source);
603             HeadsetService service = getService(source);
604             if (service == null) {
605                 return false;
606             }
607             return service.isAudioConnected(device);
608         }
609 
610         @Override
getAudioState(BluetoothDevice device, AttributionSource source)611         public int getAudioState(BluetoothDevice device, AttributionSource source) {
612             Attributable.setAttributionSource(device, source);
613             HeadsetService service = getService(source);
614             if (service == null) {
615                 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
616             }
617             return service.getAudioState(device);
618         }
619 
620         @Override
connectAudio(AttributionSource source)621         public boolean connectAudio(AttributionSource source) {
622             HeadsetService service = getService(source);
623             if (service == null) {
624                 return false;
625             }
626             return service.connectAudio();
627         }
628 
629         @Override
disconnectAudio(AttributionSource source)630         public boolean disconnectAudio(AttributionSource source) {
631             HeadsetService service = getService(source);
632             if (service == null) {
633                 return false;
634             }
635             return service.disconnectAudio();
636         }
637 
638         @Override
setAudioRouteAllowed(boolean allowed, AttributionSource source)639         public void setAudioRouteAllowed(boolean allowed, AttributionSource source) {
640             HeadsetService service = getService(source);
641             if (service == null) {
642                 return;
643             }
644             service.setAudioRouteAllowed(allowed);
645         }
646 
647         @Override
getAudioRouteAllowed(AttributionSource source)648         public boolean getAudioRouteAllowed(AttributionSource source) {
649             HeadsetService service = getService(source);
650             if (service != null) {
651                 return service.getAudioRouteAllowed();
652             }
653             return false;
654         }
655 
656         @Override
setForceScoAudio(boolean forced, AttributionSource source)657         public void setForceScoAudio(boolean forced, AttributionSource source) {
658             HeadsetService service = getService(source);
659             if (service == null) {
660                 return;
661             }
662             service.setForceScoAudio(forced);
663         }
664 
665         @Override
startScoUsingVirtualVoiceCall(AttributionSource source)666         public boolean startScoUsingVirtualVoiceCall(AttributionSource source) {
667             HeadsetService service = getService(source);
668             if (service == null) {
669                 return false;
670             }
671             return service.startScoUsingVirtualVoiceCall();
672         }
673 
674         @Override
stopScoUsingVirtualVoiceCall(AttributionSource source)675         public boolean stopScoUsingVirtualVoiceCall(AttributionSource source) {
676             HeadsetService service = getService(source);
677             if (service == null) {
678                 return false;
679             }
680             return service.stopScoUsingVirtualVoiceCall();
681         }
682 
683         @Override
phoneStateChanged(int numActive, int numHeld, int callState, String number, int type, String name, AttributionSource source)684         public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
685                 int type, String name, AttributionSource source) {
686             HeadsetService service = getService(source);
687             if (service == null) {
688                 return;
689             }
690             service.phoneStateChanged(numActive, numHeld, callState, number, type, name, false);
691         }
692 
693         @Override
clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type, AttributionSource source)694         public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
695                 String number, int type, AttributionSource source) {
696             HeadsetService service = getService(source);
697             if (service == null) {
698                 return;
699             }
700             service.clccResponse(index, direction, status, mode, mpty, number, type);
701         }
702 
703         @Override
sendVendorSpecificResultCode(BluetoothDevice device, String command, String arg, AttributionSource source)704         public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
705                 String arg, AttributionSource source) {
706             Attributable.setAttributionSource(device, source);
707             HeadsetService service = getService(source);
708             if (service == null) {
709                 return false;
710             }
711             return service.sendVendorSpecificResultCode(device, command, arg);
712         }
713 
714         @Override
setActiveDevice(BluetoothDevice device, AttributionSource source)715         public boolean setActiveDevice(BluetoothDevice device, AttributionSource source) {
716             Attributable.setAttributionSource(device, source);
717             HeadsetService service = getService(source);
718             if (service == null) {
719                 return false;
720             }
721             return service.setActiveDevice(device);
722         }
723 
724         @Override
getActiveDevice(AttributionSource source)725         public BluetoothDevice getActiveDevice(AttributionSource source) {
726             HeadsetService service = getService(source);
727             if (service == null) {
728                 return null;
729             }
730             return service.getActiveDevice();
731         }
732 
733         @Override
isInbandRingingEnabled(AttributionSource source)734         public boolean isInbandRingingEnabled(AttributionSource source) {
735             HeadsetService service = getService(source);
736             if (service == null) {
737                 return false;
738             }
739             return service.isInbandRingingEnabled();
740         }
741     }
742 
743     // API methods
getHeadsetService()744     public static synchronized HeadsetService getHeadsetService() {
745         if (sHeadsetService == null) {
746             Log.w(TAG, "getHeadsetService(): service is NULL");
747             return null;
748         }
749         if (!sHeadsetService.isAvailable()) {
750             Log.w(TAG, "getHeadsetService(): service is not available");
751             return null;
752         }
753         logD("getHeadsetService(): returning " + sHeadsetService);
754         return sHeadsetService;
755     }
756 
setHeadsetService(HeadsetService instance)757     private static synchronized void setHeadsetService(HeadsetService instance) {
758         logD("setHeadsetService(): set to: " + instance);
759         sHeadsetService = instance;
760     }
761 
762     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
connect(BluetoothDevice device)763     public boolean connect(BluetoothDevice device) {
764         if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
765             Log.w(TAG, "connect: CONNECTION_POLICY_FORBIDDEN, device=" + device + ", "
766                     + Utils.getUidPidString());
767             return false;
768         }
769         ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
770         if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) {
771             Log.e(TAG, "connect: Cannot connect to " + device + ": no headset UUID, "
772                     + Utils.getUidPidString());
773             return false;
774         }
775         synchronized (mStateMachines) {
776             Log.i(TAG, "connect: device=" + device + ", " + Utils.getUidPidString());
777             HeadsetStateMachine stateMachine = mStateMachines.get(device);
778             if (stateMachine == null) {
779                 stateMachine = HeadsetObjectsFactory.getInstance()
780                         .makeStateMachine(device, mStateMachinesThread.getLooper(), this,
781                                 mAdapterService, mNativeInterface, mSystemInterface);
782                 mStateMachines.put(device, stateMachine);
783             }
784             int connectionState = stateMachine.getConnectionState();
785             if (connectionState == BluetoothProfile.STATE_CONNECTED
786                     || connectionState == BluetoothProfile.STATE_CONNECTING) {
787                 Log.w(TAG, "connect: device " + device
788                         + " is already connected/connecting, connectionState=" + connectionState);
789                 return false;
790             }
791             List<BluetoothDevice> connectingConnectedDevices =
792                     getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
793             boolean disconnectExisting = false;
794             if (connectingConnectedDevices.size() >= mMaxHeadsetConnections) {
795                 // When there is maximum one device, we automatically disconnect the current one
796                 if (mMaxHeadsetConnections == 1) {
797                     disconnectExisting = true;
798                 } else {
799                     Log.w(TAG, "Max connection has reached, rejecting connection to " + device);
800                     return false;
801                 }
802             }
803             if (disconnectExisting) {
804                 for (BluetoothDevice connectingConnectedDevice : connectingConnectedDevices) {
805                     disconnect(connectingConnectedDevice);
806                 }
807                 setActiveDevice(null);
808             }
809             stateMachine.sendMessage(HeadsetStateMachine.CONNECT, device);
810         }
811         return true;
812     }
813 
814     /**
815      * Disconnects hfp from the passed in device
816      *
817      * @param device is the device with which we will disconnect hfp
818      * @return true if hfp is disconnected, false if the device is not connected
819      */
disconnect(BluetoothDevice device)820     public boolean disconnect(BluetoothDevice device) {
821         Log.i(TAG, "disconnect: device=" + device + ", " + Utils.getUidPidString());
822         synchronized (mStateMachines) {
823             HeadsetStateMachine stateMachine = mStateMachines.get(device);
824             if (stateMachine == null) {
825                 Log.w(TAG, "disconnect: device " + device + " not ever connected/connecting");
826                 return false;
827             }
828             int connectionState = stateMachine.getConnectionState();
829             if (connectionState != BluetoothProfile.STATE_CONNECTED
830                     && connectionState != BluetoothProfile.STATE_CONNECTING) {
831                 Log.w(TAG, "disconnect: device " + device
832                         + " not connected/connecting, connectionState=" + connectionState);
833                 return false;
834             }
835             stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device);
836         }
837         return true;
838     }
839 
getConnectedDevices()840     public List<BluetoothDevice> getConnectedDevices() {
841         ArrayList<BluetoothDevice> devices = new ArrayList<>();
842         synchronized (mStateMachines) {
843             for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
844                 if (stateMachine.getConnectionState() == BluetoothProfile.STATE_CONNECTED) {
845                     devices.add(stateMachine.getDevice());
846                 }
847             }
848         }
849         return devices;
850     }
851 
852     /**
853      * Same as the API method {@link BluetoothHeadset#getDevicesMatchingConnectionStates(int[])}
854      *
855      * @param states an array of states from {@link BluetoothProfile}
856      * @return a list of devices matching the array of connection states
857      */
858     @VisibleForTesting
getDevicesMatchingConnectionStates(int[] states)859     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
860         ArrayList<BluetoothDevice> devices = new ArrayList<>();
861         synchronized (mStateMachines) {
862             if (states == null || mAdapterService == null) {
863                 return devices;
864             }
865             final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
866             if (bondedDevices == null) {
867                 return devices;
868             }
869             for (BluetoothDevice device : bondedDevices) {
870                 final ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
871                 if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) {
872                     continue;
873                 }
874                 int connectionState = getConnectionState(device);
875                 for (int state : states) {
876                     if (connectionState == state) {
877                         devices.add(device);
878                         break;
879                     }
880                 }
881             }
882         }
883         return devices;
884     }
885 
getConnectionState(BluetoothDevice device)886     public int getConnectionState(BluetoothDevice device) {
887         synchronized (mStateMachines) {
888             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
889             if (stateMachine == null) {
890                 return BluetoothProfile.STATE_DISCONNECTED;
891             }
892             return stateMachine.getConnectionState();
893         }
894     }
895 
896     /**
897      * Set connection policy of the profile and connects it if connectionPolicy is
898      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
899      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
900      *
901      * <p> The device should already be paired.
902      * Connection policy can be one of:
903      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
904      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
905      * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
906      *
907      * @param device Paired bluetooth device
908      * @param connectionPolicy is the connection policy to set to for this profile
909      * @return true if connectionPolicy is set, false on error
910      */
911     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)912     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
913         Log.i(TAG, "setConnectionPolicy: device=" + device
914                 + ", connectionPolicy=" + connectionPolicy + ", " + Utils.getUidPidString());
915 
916         if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.HEADSET,
917                   connectionPolicy)) {
918             return false;
919         }
920         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
921             connect(device);
922         } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
923             disconnect(device);
924         }
925         return true;
926     }
927 
928     /**
929      * Get the connection policy of the profile.
930      *
931      * <p> The connection policy can be any of:
932      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
933      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
934      * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
935      *
936      * @param device Bluetooth device
937      * @return connection policy of the device
938      * @hide
939      */
getConnectionPolicy(BluetoothDevice device)940     public int getConnectionPolicy(BluetoothDevice device) {
941         return mDatabaseManager
942                 .getProfileConnectionPolicy(device, BluetoothProfile.HEADSET);
943     }
944 
isNoiseReductionSupported(BluetoothDevice device)945     boolean isNoiseReductionSupported(BluetoothDevice device) {
946         return mNativeInterface.isNoiseReductionSupported(device);
947     }
948 
isVoiceRecognitionSupported(BluetoothDevice device)949     boolean isVoiceRecognitionSupported(BluetoothDevice device) {
950         return mNativeInterface.isVoiceRecognitionSupported(device);
951     }
952 
953     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
startVoiceRecognition(BluetoothDevice device)954     boolean startVoiceRecognition(BluetoothDevice device) {
955         Log.i(TAG, "startVoiceRecognition: device=" + device + ", " + Utils.getUidPidString());
956         synchronized (mStateMachines) {
957             // TODO(b/79660380): Workaround in case voice recognition was not terminated properly
958             if (mVoiceRecognitionStarted) {
959                 boolean status = stopVoiceRecognition(mActiveDevice);
960                 Log.w(TAG, "startVoiceRecognition: voice recognition is still active, just called "
961                         + "stopVoiceRecognition, returned " + status + " on " + mActiveDevice
962                         + ", please try again");
963                 mVoiceRecognitionStarted = false;
964                 return false;
965             }
966             if (!isAudioModeIdle()) {
967                 Log.w(TAG, "startVoiceRecognition: audio mode not idle, active device is "
968                         + mActiveDevice);
969                 return false;
970             }
971             // Audio should not be on when no audio mode is active
972             if (isAudioOn()) {
973                 // Disconnect audio so that API user can try later
974                 boolean status = disconnectAudio();
975                 Log.w(TAG, "startVoiceRecognition: audio is still active, please wait for audio to"
976                         + " be disconnected, disconnectAudio() returned " + status
977                         + ", active device is " + mActiveDevice);
978                 return false;
979             }
980             if (device == null) {
981                 Log.i(TAG, "device is null, use active device " + mActiveDevice + " instead");
982                 device = mActiveDevice;
983             }
984             boolean pendingRequestByHeadset = false;
985             if (mVoiceRecognitionTimeoutEvent != null) {
986                 if (!mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice.equals(device)) {
987                     // TODO(b/79660380): Workaround when target device != requesting device
988                     Log.w(TAG, "startVoiceRecognition: device " + device
989                             + " is not the same as requesting device "
990                             + mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice
991                             + ", fall back to requesting device");
992                     device = mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice;
993                 }
994                 getStateMachinesThreadHandler().removeCallbacks(mVoiceRecognitionTimeoutEvent);
995                 mVoiceRecognitionTimeoutEvent = null;
996                 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
997                     mSystemInterface.getVoiceRecognitionWakeLock().release();
998                 }
999                 pendingRequestByHeadset = true;
1000             }
1001             if (!Objects.equals(device, mActiveDevice) && !setActiveDevice(device)) {
1002                 Log.w(TAG, "startVoiceRecognition: failed to set " + device + " as active");
1003                 return false;
1004             }
1005             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
1006             if (stateMachine == null) {
1007                 Log.w(TAG, "startVoiceRecognition: " + device + " is never connected");
1008                 return false;
1009             }
1010             int connectionState = stateMachine.getConnectionState();
1011             if (connectionState != BluetoothProfile.STATE_CONNECTED
1012                     && connectionState != BluetoothProfile.STATE_CONNECTING) {
1013                 Log.w(TAG, "startVoiceRecognition: " + device + " is not connected or connecting");
1014                 return false;
1015             }
1016             mVoiceRecognitionStarted = true;
1017             if (pendingRequestByHeadset) {
1018                 stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_RESULT,
1019                         1 /* success */, 0, device);
1020             } else {
1021                 stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START, device);
1022             }
1023             stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device);
1024         }
1025         return true;
1026     }
1027 
stopVoiceRecognition(BluetoothDevice device)1028     boolean stopVoiceRecognition(BluetoothDevice device) {
1029         Log.i(TAG, "stopVoiceRecognition: device=" + device + ", " + Utils.getUidPidString());
1030         synchronized (mStateMachines) {
1031             if (!Objects.equals(mActiveDevice, device)) {
1032                 Log.w(TAG, "startVoiceRecognition: requested device " + device
1033                         + " is not active, use active device " + mActiveDevice + " instead");
1034                 device = mActiveDevice;
1035             }
1036             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
1037             if (stateMachine == null) {
1038                 Log.w(TAG, "stopVoiceRecognition: " + device + " is never connected");
1039                 return false;
1040             }
1041             int connectionState = stateMachine.getConnectionState();
1042             if (connectionState != BluetoothProfile.STATE_CONNECTED
1043                     && connectionState != BluetoothProfile.STATE_CONNECTING) {
1044                 Log.w(TAG, "stopVoiceRecognition: " + device + " is not connected or connecting");
1045                 return false;
1046             }
1047             if (!mVoiceRecognitionStarted) {
1048                 Log.w(TAG, "stopVoiceRecognition: voice recognition was not started");
1049                 return false;
1050             }
1051             mVoiceRecognitionStarted = false;
1052             stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP, device);
1053             stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device);
1054         }
1055         return true;
1056     }
1057 
isAudioOn()1058     boolean isAudioOn() {
1059         return getNonIdleAudioDevices().size() > 0;
1060     }
1061 
isAudioConnected(BluetoothDevice device)1062     boolean isAudioConnected(BluetoothDevice device) {
1063         synchronized (mStateMachines) {
1064             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
1065             if (stateMachine == null) {
1066                 return false;
1067             }
1068             return stateMachine.getAudioState() == BluetoothHeadset.STATE_AUDIO_CONNECTED;
1069         }
1070     }
1071 
getAudioState(BluetoothDevice device)1072     int getAudioState(BluetoothDevice device) {
1073         synchronized (mStateMachines) {
1074             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
1075             if (stateMachine == null) {
1076                 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
1077             }
1078             return stateMachine.getAudioState();
1079         }
1080     }
1081 
setAudioRouteAllowed(boolean allowed)1082     public void setAudioRouteAllowed(boolean allowed) {
1083         Log.i(TAG, "setAudioRouteAllowed: allowed=" + allowed + ", " + Utils.getUidPidString());
1084         mAudioRouteAllowed = allowed;
1085         mNativeInterface.setScoAllowed(allowed);
1086     }
1087 
getAudioRouteAllowed()1088     public boolean getAudioRouteAllowed() {
1089         return mAudioRouteAllowed;
1090     }
1091 
setForceScoAudio(boolean forced)1092     public void setForceScoAudio(boolean forced) {
1093         Log.i(TAG, "setForceScoAudio: forced=" + forced + ", " + Utils.getUidPidString());
1094         mForceScoAudio = forced;
1095     }
1096 
1097     @VisibleForTesting
getForceScoAudio()1098     public boolean getForceScoAudio() {
1099         return mForceScoAudio;
1100     }
1101 
1102     /**
1103      * Get first available device for SCO audio
1104      *
1105      * @return first connected headset device
1106      */
1107     @VisibleForTesting
1108     @Nullable
getFirstConnectedAudioDevice()1109     public BluetoothDevice getFirstConnectedAudioDevice() {
1110         ArrayList<HeadsetStateMachine> stateMachines = new ArrayList<>();
1111         synchronized (mStateMachines) {
1112             List<BluetoothDevice> availableDevices =
1113                     getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
1114             for (BluetoothDevice device : availableDevices) {
1115                 final HeadsetStateMachine stateMachine = mStateMachines.get(device);
1116                 if (stateMachine == null) {
1117                     continue;
1118                 }
1119                 stateMachines.add(stateMachine);
1120             }
1121         }
1122         stateMachines.sort(Comparator.comparingLong(HeadsetStateMachine::getConnectingTimestampMs));
1123         if (stateMachines.size() > 0) {
1124             return stateMachines.get(0).getDevice();
1125         }
1126         return null;
1127     }
1128 
1129     /**
1130      * Process a change in the silence mode for a {@link BluetoothDevice}.
1131      *
1132      * @param device the device to change silence mode
1133      * @param silence true to enable silence mode, false to disable.
1134      * @return true on success, false on error
1135      */
1136     @VisibleForTesting
1137     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
setSilenceMode(BluetoothDevice device, boolean silence)1138     public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
1139         Log.d(TAG, "setSilenceMode(" + device + "): " + silence);
1140 
1141         if (silence && Objects.equals(mActiveDevice, device)) {
1142             setActiveDevice(null);
1143         } else if (!silence && mActiveDevice == null) {
1144             // Set the device as the active device if currently no active device.
1145             setActiveDevice(device);
1146         }
1147         synchronized (mStateMachines) {
1148             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
1149             if (stateMachine == null) {
1150                 Log.w(TAG, "setSilenceMode: device " + device
1151                         + " was never connected/connecting");
1152                 return false;
1153             }
1154             stateMachine.setSilenceDevice(silence);
1155         }
1156 
1157         return true;
1158     }
1159 
1160     /**
1161      * Set the active device.
1162      *
1163      * @param device the active device
1164      * @return true on success, otherwise false
1165      */
1166     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
setActiveDevice(BluetoothDevice device)1167     public boolean setActiveDevice(BluetoothDevice device) {
1168         Log.i(TAG, "setActiveDevice: device=" + device + ", " + Utils.getUidPidString());
1169         synchronized (mStateMachines) {
1170             if (device == null) {
1171                 // Clear the active device
1172                 if (mVoiceRecognitionStarted) {
1173                     if (!stopVoiceRecognition(mActiveDevice)) {
1174                         Log.w(TAG, "setActiveDevice: fail to stopVoiceRecognition from "
1175                                 + mActiveDevice);
1176                     }
1177                 }
1178                 if (mVirtualCallStarted) {
1179                     if (!stopScoUsingVirtualVoiceCall()) {
1180                         Log.w(TAG, "setActiveDevice: fail to stopScoUsingVirtualVoiceCall from "
1181                                 + mActiveDevice);
1182                     }
1183                 }
1184                 if (getAudioState(mActiveDevice) != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
1185                     if (!disconnectAudio(mActiveDevice)) {
1186                         Log.w(TAG, "setActiveDevice: disconnectAudio failed on " + mActiveDevice);
1187                     }
1188                 }
1189                 mActiveDevice = null;
1190                 broadcastActiveDevice(null);
1191                 return true;
1192             }
1193             if (device.equals(mActiveDevice)) {
1194                 Log.i(TAG, "setActiveDevice: device " + device + " is already active");
1195                 return true;
1196             }
1197             if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
1198                 Log.e(TAG, "setActiveDevice: Cannot set " + device
1199                         + " as active, device is not connected");
1200                 return false;
1201             }
1202             if (!mNativeInterface.setActiveDevice(device)) {
1203                 Log.e(TAG, "setActiveDevice: Cannot set " + device + " as active in native layer");
1204                 return false;
1205             }
1206             BluetoothDevice previousActiveDevice = mActiveDevice;
1207             mActiveDevice = device;
1208             if (getAudioState(previousActiveDevice) != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
1209                 if (!disconnectAudio(previousActiveDevice)) {
1210                     Log.e(TAG, "setActiveDevice: fail to disconnectAudio from "
1211                             + previousActiveDevice);
1212                     mActiveDevice = previousActiveDevice;
1213                     mNativeInterface.setActiveDevice(previousActiveDevice);
1214                     return false;
1215                 }
1216                 broadcastActiveDevice(mActiveDevice);
1217             } else if (shouldPersistAudio()) {
1218                 broadcastActiveDevice(mActiveDevice);
1219                 if (!connectAudio(mActiveDevice)) {
1220                     Log.e(TAG, "setActiveDevice: fail to connectAudio to " + mActiveDevice);
1221                     mActiveDevice = previousActiveDevice;
1222                     mNativeInterface.setActiveDevice(previousActiveDevice);
1223                     return false;
1224                 }
1225             } else {
1226                 broadcastActiveDevice(mActiveDevice);
1227             }
1228         }
1229         return true;
1230     }
1231 
1232     /**
1233      * Get the active device.
1234      *
1235      * @return the active device or null if no device is active
1236      */
getActiveDevice()1237     public BluetoothDevice getActiveDevice() {
1238         synchronized (mStateMachines) {
1239             return mActiveDevice;
1240         }
1241     }
1242 
connectAudio()1243     boolean connectAudio() {
1244         synchronized (mStateMachines) {
1245             BluetoothDevice device = mActiveDevice;
1246             if (device == null) {
1247                 Log.w(TAG, "connectAudio: no active device, " + Utils.getUidPidString());
1248                 return false;
1249             }
1250             return connectAudio(device);
1251         }
1252     }
1253 
connectAudio(BluetoothDevice device)1254     boolean connectAudio(BluetoothDevice device) {
1255         Log.i(TAG, "connectAudio: device=" + device + ", " + Utils.getUidPidString());
1256         synchronized (mStateMachines) {
1257             if (!isScoAcceptable(device)) {
1258                 Log.w(TAG, "connectAudio, rejected SCO request to " + device);
1259                 return false;
1260             }
1261             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
1262             if (stateMachine == null) {
1263                 Log.w(TAG, "connectAudio: device " + device + " was never connected/connecting");
1264                 return false;
1265             }
1266             if (stateMachine.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
1267                 Log.w(TAG, "connectAudio: profile not connected");
1268                 return false;
1269             }
1270             if (stateMachine.getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
1271                 logD("connectAudio: audio is not idle for device " + device);
1272                 return true;
1273             }
1274             if (isAudioOn()) {
1275                 Log.w(TAG, "connectAudio: audio is not idle, current audio devices are "
1276                         + Arrays.toString(getNonIdleAudioDevices().toArray()));
1277                 return false;
1278             }
1279             stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device);
1280         }
1281         return true;
1282     }
1283 
getNonIdleAudioDevices()1284     private List<BluetoothDevice> getNonIdleAudioDevices() {
1285         ArrayList<BluetoothDevice> devices = new ArrayList<>();
1286         synchronized (mStateMachines) {
1287             for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
1288                 if (stateMachine.getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
1289                     devices.add(stateMachine.getDevice());
1290                 }
1291             }
1292         }
1293         return devices;
1294     }
1295 
disconnectAudio()1296     boolean disconnectAudio() {
1297         boolean result = false;
1298         synchronized (mStateMachines) {
1299             for (BluetoothDevice device : getNonIdleAudioDevices()) {
1300                 if (disconnectAudio(device)) {
1301                     result = true;
1302                 } else {
1303                     Log.e(TAG, "disconnectAudio() from " + device + " failed");
1304                 }
1305             }
1306         }
1307         if (!result) {
1308             logD("disconnectAudio() no active audio connection");
1309         }
1310         return result;
1311     }
1312 
disconnectAudio(BluetoothDevice device)1313     boolean disconnectAudio(BluetoothDevice device) {
1314         synchronized (mStateMachines) {
1315             Log.i(TAG, "disconnectAudio: device=" + device + ", " + Utils.getUidPidString());
1316             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
1317             if (stateMachine == null) {
1318                 Log.w(TAG, "disconnectAudio: device " + device + " was never connected/connecting");
1319                 return false;
1320             }
1321             if (stateMachine.getAudioState() == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
1322                 Log.w(TAG, "disconnectAudio, audio is already disconnected for " + device);
1323                 return false;
1324             }
1325             stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device);
1326         }
1327         return true;
1328     }
1329 
isVirtualCallStarted()1330     boolean isVirtualCallStarted() {
1331         synchronized (mStateMachines) {
1332             return mVirtualCallStarted;
1333         }
1334     }
1335 
1336     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
startScoUsingVirtualVoiceCall()1337     private boolean startScoUsingVirtualVoiceCall() {
1338         Log.i(TAG, "startScoUsingVirtualVoiceCall: " + Utils.getUidPidString());
1339         synchronized (mStateMachines) {
1340             // TODO(b/79660380): Workaround in case voice recognition was not terminated properly
1341             if (mVoiceRecognitionStarted) {
1342                 boolean status = stopVoiceRecognition(mActiveDevice);
1343                 Log.w(TAG, "startScoUsingVirtualVoiceCall: voice recognition is still active, "
1344                         + "just called stopVoiceRecognition, returned " + status + " on "
1345                         + mActiveDevice + ", please try again");
1346                 mVoiceRecognitionStarted = false;
1347                 return false;
1348             }
1349             if (!isAudioModeIdle()) {
1350                 Log.w(TAG, "startScoUsingVirtualVoiceCall: audio mode not idle, active device is "
1351                         + mActiveDevice);
1352                 return false;
1353             }
1354             // Audio should not be on when no audio mode is active
1355             if (isAudioOn()) {
1356                 // Disconnect audio so that API user can try later
1357                 boolean status = disconnectAudio();
1358                 Log.w(TAG, "startScoUsingVirtualVoiceCall: audio is still active, please wait for "
1359                         + "audio to be disconnected, disconnectAudio() returned " + status
1360                         + ", active device is " + mActiveDevice);
1361                 return false;
1362             }
1363             if (mActiveDevice == null) {
1364                 Log.w(TAG, "startScoUsingVirtualVoiceCall: no active device");
1365                 return false;
1366             }
1367             mVirtualCallStarted = true;
1368             // Send virtual phone state changed to initialize SCO
1369             phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0, "", true);
1370             phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0, "", true);
1371             phoneStateChanged(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, "", true);
1372             return true;
1373         }
1374     }
1375 
1376     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
stopScoUsingVirtualVoiceCall()1377     boolean stopScoUsingVirtualVoiceCall() {
1378         Log.i(TAG, "stopScoUsingVirtualVoiceCall: " + Utils.getUidPidString());
1379         synchronized (mStateMachines) {
1380             // 1. Check if virtual call has already started
1381             if (!mVirtualCallStarted) {
1382                 Log.w(TAG, "stopScoUsingVirtualVoiceCall: virtual call not started");
1383                 return false;
1384             }
1385             mVirtualCallStarted = false;
1386             // 2. Send virtual phone state changed to close SCO
1387             phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, "", true);
1388         }
1389         return true;
1390     }
1391 
1392     class DialingOutTimeoutEvent implements Runnable {
1393         BluetoothDevice mDialingOutDevice;
1394 
DialingOutTimeoutEvent(BluetoothDevice fromDevice)1395         DialingOutTimeoutEvent(BluetoothDevice fromDevice) {
1396             mDialingOutDevice = fromDevice;
1397         }
1398 
1399         @Override
run()1400         public void run() {
1401             synchronized (mStateMachines) {
1402                 mDialingOutTimeoutEvent = null;
1403                 doForStateMachine(mDialingOutDevice, stateMachine -> stateMachine.sendMessage(
1404                         HeadsetStateMachine.DIALING_OUT_RESULT, 0 /* fail */, 0,
1405                         mDialingOutDevice));
1406             }
1407         }
1408 
1409         @Override
toString()1410         public String toString() {
1411             return "DialingOutTimeoutEvent[" + mDialingOutDevice + "]";
1412         }
1413     }
1414 
1415     /**
1416      * Dial an outgoing call as requested by the remote device
1417      *
1418      * @param fromDevice remote device that initiated this dial out action
1419      * @param dialNumber number to dial
1420      * @return true on successful dial out
1421      */
1422     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
1423     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
dialOutgoingCall(BluetoothDevice fromDevice, String dialNumber)1424     public boolean dialOutgoingCall(BluetoothDevice fromDevice, String dialNumber) {
1425         synchronized (mStateMachines) {
1426             Log.i(TAG, "dialOutgoingCall: from " + fromDevice);
1427             if (!isOnStateMachineThread()) {
1428                 Log.e(TAG, "dialOutgoingCall must be called from state machine thread");
1429                 return false;
1430             }
1431             if (mDialingOutTimeoutEvent != null) {
1432                 Log.e(TAG, "dialOutgoingCall, already dialing by " + mDialingOutTimeoutEvent);
1433                 return false;
1434             }
1435             if (isVirtualCallStarted()) {
1436                 if (!stopScoUsingVirtualVoiceCall()) {
1437                     Log.e(TAG, "dialOutgoingCall failed to stop current virtual call");
1438                     return false;
1439                 }
1440             }
1441             if (!setActiveDevice(fromDevice)) {
1442                 Log.e(TAG, "dialOutgoingCall failed to set active device to " + fromDevice);
1443                 return false;
1444             }
1445             Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1446                     Uri.fromParts(PhoneAccount.SCHEME_TEL, dialNumber, null));
1447             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1448             startActivity(intent);
1449             mDialingOutTimeoutEvent = new DialingOutTimeoutEvent(fromDevice);
1450             getStateMachinesThreadHandler()
1451                     .postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS);
1452             return true;
1453         }
1454     }
1455 
1456     /**
1457      * Check if any connected headset has started dialing calls
1458      *
1459      * @return true if some device has started dialing calls
1460      */
1461     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
hasDeviceInitiatedDialingOut()1462     public boolean hasDeviceInitiatedDialingOut() {
1463         synchronized (mStateMachines) {
1464             return mDialingOutTimeoutEvent != null;
1465         }
1466     }
1467 
1468     class VoiceRecognitionTimeoutEvent implements Runnable {
1469         BluetoothDevice mVoiceRecognitionDevice;
1470 
VoiceRecognitionTimeoutEvent(BluetoothDevice device)1471         VoiceRecognitionTimeoutEvent(BluetoothDevice device) {
1472             mVoiceRecognitionDevice = device;
1473         }
1474 
1475         @Override
run()1476         public void run() {
1477             synchronized (mStateMachines) {
1478                 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
1479                     mSystemInterface.getVoiceRecognitionWakeLock().release();
1480                 }
1481                 mVoiceRecognitionTimeoutEvent = null;
1482                 doForStateMachine(mVoiceRecognitionDevice, stateMachine -> stateMachine.sendMessage(
1483                         HeadsetStateMachine.VOICE_RECOGNITION_RESULT, 0 /* fail */, 0,
1484                         mVoiceRecognitionDevice));
1485             }
1486         }
1487 
1488         @Override
toString()1489         public String toString() {
1490             return "VoiceRecognitionTimeoutEvent[" + mVoiceRecognitionDevice + "]";
1491         }
1492     }
1493 
1494     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
startVoiceRecognitionByHeadset(BluetoothDevice fromDevice)1495     boolean startVoiceRecognitionByHeadset(BluetoothDevice fromDevice) {
1496         synchronized (mStateMachines) {
1497             Log.i(TAG, "startVoiceRecognitionByHeadset: from " + fromDevice);
1498             // TODO(b/79660380): Workaround in case voice recognition was not terminated properly
1499             if (mVoiceRecognitionStarted) {
1500                 boolean status = stopVoiceRecognition(mActiveDevice);
1501                 Log.w(TAG, "startVoiceRecognitionByHeadset: voice recognition is still active, "
1502                         + "just called stopVoiceRecognition, returned " + status + " on "
1503                         + mActiveDevice + ", please try again");
1504                 mVoiceRecognitionStarted = false;
1505                 return false;
1506             }
1507             if (fromDevice == null) {
1508                 Log.e(TAG, "startVoiceRecognitionByHeadset: fromDevice is null");
1509                 return false;
1510             }
1511             if (!isAudioModeIdle()) {
1512                 Log.w(TAG, "startVoiceRecognitionByHeadset: audio mode not idle, active device is "
1513                         + mActiveDevice);
1514                 return false;
1515             }
1516             // Audio should not be on when no audio mode is active
1517             if (isAudioOn()) {
1518                 // Disconnect audio so that user can try later
1519                 boolean status = disconnectAudio();
1520                 Log.w(TAG, "startVoiceRecognitionByHeadset: audio is still active, please wait for"
1521                         + " audio to be disconnected, disconnectAudio() returned " + status
1522                         + ", active device is " + mActiveDevice);
1523                 return false;
1524             }
1525             // Do not start new request until the current one is finished or timeout
1526             if (mVoiceRecognitionTimeoutEvent != null) {
1527                 Log.w(TAG, "startVoiceRecognitionByHeadset: failed request from " + fromDevice
1528                         + ", already pending by " + mVoiceRecognitionTimeoutEvent);
1529                 return false;
1530             }
1531             if (!setActiveDevice(fromDevice)) {
1532                 Log.w(TAG, "startVoiceRecognitionByHeadset: failed to set " + fromDevice
1533                         + " as active");
1534                 return false;
1535             }
1536             if (!mSystemInterface.activateVoiceRecognition()) {
1537                 Log.w(TAG, "startVoiceRecognitionByHeadset: failed request from " + fromDevice);
1538                 return false;
1539             }
1540             mVoiceRecognitionTimeoutEvent = new VoiceRecognitionTimeoutEvent(fromDevice);
1541             getStateMachinesThreadHandler()
1542                     .postDelayed(mVoiceRecognitionTimeoutEvent, sStartVrTimeoutMs);
1543 
1544             if (!mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
1545                 mSystemInterface.getVoiceRecognitionWakeLock().acquire(sStartVrTimeoutMs);
1546             }
1547             return true;
1548         }
1549     }
1550 
stopVoiceRecognitionByHeadset(BluetoothDevice fromDevice)1551     boolean stopVoiceRecognitionByHeadset(BluetoothDevice fromDevice) {
1552         synchronized (mStateMachines) {
1553             Log.i(TAG, "stopVoiceRecognitionByHeadset: from " + fromDevice);
1554             if (!Objects.equals(fromDevice, mActiveDevice)) {
1555                 Log.w(TAG, "stopVoiceRecognitionByHeadset: " + fromDevice
1556                         + " is not active, active device is " + mActiveDevice);
1557                 return false;
1558             }
1559             if (!mVoiceRecognitionStarted && mVoiceRecognitionTimeoutEvent == null) {
1560                 Log.w(TAG, "stopVoiceRecognitionByHeadset: voice recognition not started, device="
1561                         + fromDevice);
1562                 return false;
1563             }
1564             if (mVoiceRecognitionTimeoutEvent != null) {
1565                 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
1566                     mSystemInterface.getVoiceRecognitionWakeLock().release();
1567                 }
1568                 getStateMachinesThreadHandler()
1569                         .removeCallbacks(mVoiceRecognitionTimeoutEvent);
1570 
1571                 mVoiceRecognitionTimeoutEvent = null;
1572             }
1573             if (mVoiceRecognitionStarted) {
1574                 if (!disconnectAudio()) {
1575                     Log.w(TAG, "stopVoiceRecognitionByHeadset: failed to disconnect audio from "
1576                             + fromDevice);
1577                 }
1578                 mVoiceRecognitionStarted = false;
1579             }
1580             if (!mSystemInterface.deactivateVoiceRecognition()) {
1581                 Log.w(TAG, "stopVoiceRecognitionByHeadset: failed request from " + fromDevice);
1582                 return false;
1583             }
1584             return true;
1585         }
1586     }
1587 
1588     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
phoneStateChanged(int numActive, int numHeld, int callState, String number, int type, String name, boolean isVirtualCall)1589     private void phoneStateChanged(int numActive, int numHeld, int callState, String number,
1590             int type, String name, boolean isVirtualCall) {
1591         enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
1592         synchronized (mStateMachines) {
1593             // Should stop all other audio mode in this case
1594             if ((numActive + numHeld) > 0 || callState != HeadsetHalConstants.CALL_STATE_IDLE) {
1595                 if (!isVirtualCall && mVirtualCallStarted) {
1596                     // stop virtual voice call if there is an incoming Telecom call update
1597                     stopScoUsingVirtualVoiceCall();
1598                 }
1599                 if (mVoiceRecognitionStarted) {
1600                     // stop voice recognition if there is any incoming call
1601                     stopVoiceRecognition(mActiveDevice);
1602                 }
1603             }
1604             if (mDialingOutTimeoutEvent != null) {
1605                 // Send result to state machine when dialing starts
1606                 if (callState == HeadsetHalConstants.CALL_STATE_DIALING) {
1607                     getStateMachinesThreadHandler()
1608                             .removeCallbacks(mDialingOutTimeoutEvent);
1609                     doForStateMachine(mDialingOutTimeoutEvent.mDialingOutDevice,
1610                             stateMachine -> stateMachine.sendMessage(
1611                                     HeadsetStateMachine.DIALING_OUT_RESULT, 1 /* success */, 0,
1612                                     mDialingOutTimeoutEvent.mDialingOutDevice));
1613                 } else if (callState == HeadsetHalConstants.CALL_STATE_ACTIVE
1614                         || callState == HeadsetHalConstants.CALL_STATE_IDLE) {
1615                     // Clear the timeout event when the call is connected or disconnected
1616                     if (!getStateMachinesThreadHandler()
1617                             .hasCallbacks(mDialingOutTimeoutEvent)) {
1618                         mDialingOutTimeoutEvent = null;
1619                     }
1620                 }
1621             }
1622         }
1623         getStateMachinesThreadHandler().post(() -> {
1624             boolean isCallIdleBefore = mSystemInterface.isCallIdle();
1625             mSystemInterface.getHeadsetPhoneState().setNumActiveCall(numActive);
1626             mSystemInterface.getHeadsetPhoneState().setNumHeldCall(numHeld);
1627             mSystemInterface.getHeadsetPhoneState().setCallState(callState);
1628             // Suspend A2DP when call about is about to become active
1629             if (mActiveDevice != null && callState != HeadsetHalConstants.CALL_STATE_DISCONNECTED
1630                     && !mSystemInterface.isCallIdle() && isCallIdleBefore) {
1631                 mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
1632             }
1633         });
1634         doForEachConnectedStateMachine(
1635                 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
1636                         new HeadsetCallState(numActive, numHeld, callState, number, type, name)));
1637         getStateMachinesThreadHandler().post(() -> {
1638             if (callState == HeadsetHalConstants.CALL_STATE_IDLE
1639                     && mSystemInterface.isCallIdle() && !isAudioOn()) {
1640                 // Resume A2DP when call ended and SCO is not connected
1641                 mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
1642             }
1643         });
1644 
1645     }
1646 
1647     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type)1648     private void clccResponse(int index, int direction, int status, int mode, boolean mpty,
1649             String number, int type) {
1650         enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
1651         doForEachConnectedStateMachine(
1652                 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE,
1653                         new HeadsetClccResponse(index, direction, status, mode, mpty, number,
1654                                 type)));
1655     }
1656 
sendVendorSpecificResultCode(BluetoothDevice device, String command, String arg)1657     private boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
1658             String arg) {
1659         synchronized (mStateMachines) {
1660             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
1661             if (stateMachine == null) {
1662                 Log.w(TAG, "sendVendorSpecificResultCode: device " + device
1663                         + " was never connected/connecting");
1664                 return false;
1665             }
1666             int connectionState = stateMachine.getConnectionState();
1667             if (connectionState != BluetoothProfile.STATE_CONNECTED) {
1668                 return false;
1669             }
1670             // Currently we support only "+ANDROID".
1671             if (!command.equals(BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID)) {
1672                 Log.w(TAG, "Disallowed unsolicited result code command: " + command);
1673                 return false;
1674             }
1675             stateMachine.sendMessage(HeadsetStateMachine.SEND_VENDOR_SPECIFIC_RESULT_CODE,
1676                     new HeadsetVendorSpecificResultCode(device, command, arg));
1677         }
1678         return true;
1679     }
1680 
isInbandRingingEnabled()1681     boolean isInbandRingingEnabled() {
1682         return BluetoothHeadset.isInbandRingingSupported(this) && !SystemProperties.getBoolean(
1683                 DISABLE_INBAND_RINGING_PROPERTY, false) && !mInbandRingingRuntimeDisable;
1684     }
1685 
1686     /**
1687      * Called from {@link HeadsetStateMachine} in state machine thread when there is a connection
1688      * state change
1689      *
1690      * @param device remote device
1691      * @param fromState from which connection state is the change
1692      * @param toState to which connection state is the change
1693      */
1694     @VisibleForTesting
1695     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
onConnectionStateChangedFromStateMachine(BluetoothDevice device, int fromState, int toState)1696     public void onConnectionStateChangedFromStateMachine(BluetoothDevice device, int fromState,
1697             int toState) {
1698         synchronized (mStateMachines) {
1699             List<BluetoothDevice> audioConnectableDevices =
1700                     getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
1701             if (fromState != BluetoothProfile.STATE_CONNECTED
1702                     && toState == BluetoothProfile.STATE_CONNECTED) {
1703                 if (audioConnectableDevices.size() > 1) {
1704                     mInbandRingingRuntimeDisable = true;
1705                     doForEachConnectedStateMachine(
1706                             stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR,
1707                                     0));
1708                 }
1709                 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HEADSET);
1710             }
1711             if (fromState != BluetoothProfile.STATE_DISCONNECTED
1712                     && toState == BluetoothProfile.STATE_DISCONNECTED) {
1713                 if (audioConnectableDevices.size() <= 1) {
1714                     mInbandRingingRuntimeDisable = false;
1715                     doForEachConnectedStateMachine(
1716                             stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR,
1717                                     1));
1718                 }
1719                 if (device.equals(mActiveDevice)) {
1720                     setActiveDevice(null);
1721                 }
1722             }
1723         }
1724     }
1725 
1726     /**
1727      * Check if no audio mode is active
1728      *
1729      * @return false if virtual call, voice recognition, or Telecom call is active, true if all idle
1730      */
isAudioModeIdle()1731     private boolean isAudioModeIdle() {
1732         synchronized (mStateMachines) {
1733             if (mVoiceRecognitionStarted || mVirtualCallStarted || !mSystemInterface.isCallIdle()) {
1734                 Log.i(TAG, "isAudioModeIdle: not idle, mVoiceRecognitionStarted="
1735                         + mVoiceRecognitionStarted + ", mVirtualCallStarted=" + mVirtualCallStarted
1736                         + ", isCallIdle=" + mSystemInterface.isCallIdle());
1737                 return false;
1738             }
1739             return true;
1740         }
1741     }
1742 
shouldCallAudioBeActive()1743     private boolean shouldCallAudioBeActive() {
1744         return mSystemInterface.isInCall() || (mSystemInterface.isRinging()
1745                 && isInbandRingingEnabled());
1746     }
1747 
1748     /**
1749      * Only persist audio during active device switch when call audio is supposed to be active and
1750      * virtual call has not been started. Virtual call is ignored because AudioService and
1751      * applications should reconnect SCO during active device switch and forcing SCO connection
1752      * here will make AudioService think SCO is started externally instead of by one of its SCO
1753      * clients.
1754      *
1755      * @return true if call audio should be active and no virtual call is going on
1756      */
shouldPersistAudio()1757     private boolean shouldPersistAudio() {
1758         return !mVirtualCallStarted && shouldCallAudioBeActive();
1759     }
1760 
1761     /**
1762      * Called from {@link HeadsetStateMachine} in state machine thread when there is a audio
1763      * connection state change
1764      *
1765      * @param device remote device
1766      * @param fromState from which audio connection state is the change
1767      * @param toState to which audio connection state is the change
1768      */
1769     @VisibleForTesting
1770     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
onAudioStateChangedFromStateMachine(BluetoothDevice device, int fromState, int toState)1771     public void onAudioStateChangedFromStateMachine(BluetoothDevice device, int fromState,
1772             int toState) {
1773         synchronized (mStateMachines) {
1774             if (toState == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
1775                 if (fromState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
1776                     if (mActiveDevice != null && !mActiveDevice.equals(device)
1777                             && shouldPersistAudio()) {
1778                         if (!connectAudio(mActiveDevice)) {
1779                             Log.w(TAG, "onAudioStateChangedFromStateMachine, failed to connect"
1780                                     + " audio to new " + "active device " + mActiveDevice
1781                                     + ", after " + device + " is disconnected from SCO");
1782                         }
1783                     }
1784                 }
1785                 if (mVoiceRecognitionStarted) {
1786                     if (!stopVoiceRecognitionByHeadset(device)) {
1787                         Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop voice "
1788                                 + "recognition");
1789                     }
1790                 }
1791                 if (mVirtualCallStarted) {
1792                     if (!stopScoUsingVirtualVoiceCall()) {
1793                         Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop virtual "
1794                                 + "voice call");
1795                     }
1796                 }
1797                 // Unsuspend A2DP when SCO connection is gone and call state is idle
1798                 if (mSystemInterface.isCallIdle()) {
1799                     mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
1800                 }
1801             }
1802         }
1803     }
1804 
broadcastActiveDevice(BluetoothDevice device)1805     private void broadcastActiveDevice(BluetoothDevice device) {
1806         logD("broadcastActiveDevice: " + device);
1807         BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED,
1808                 BluetoothProfile.HEADSET, mAdapterService.obfuscateAddress(device),
1809                 mAdapterService.getMetricId(device));
1810         Intent intent = new Intent(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
1811         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
1812         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
1813                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
1814         sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT,
1815                 Utils.getTempAllowlistBroadcastOptions());
1816     }
1817 
1818     /**
1819      * Check whether it is OK to accept a headset connection from a remote device
1820      *
1821      * @param device remote device that initiates the connection
1822      * @return true if the connection is acceptable
1823      */
okToAcceptConnection(BluetoothDevice device)1824     public boolean okToAcceptConnection(BluetoothDevice device) {
1825         // Check if this is an incoming connection in Quiet mode.
1826         if (mAdapterService.isQuietModeEnabled()) {
1827             Log.w(TAG, "okToAcceptConnection: return false as quiet mode enabled");
1828             return false;
1829         }
1830         // Check connection policy and accept or reject the connection.
1831         int connectionPolicy = getConnectionPolicy(device);
1832         int bondState = mAdapterService.getBondState(device);
1833         // Allow this connection only if the device is bonded. Any attempt to connect while
1834         // bonding would potentially lead to an unauthorized connection.
1835         if (bondState != BluetoothDevice.BOND_BONDED) {
1836             Log.w(TAG, "okToAcceptConnection: return false, bondState=" + bondState);
1837             return false;
1838         } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
1839                 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
1840             // Otherwise, reject the connection if connection policy is not valid.
1841             Log.w(TAG, "okToAcceptConnection: return false, connectionPolicy=" + connectionPolicy);
1842             return false;
1843         }
1844         List<BluetoothDevice> connectingConnectedDevices =
1845                 getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
1846         if (connectingConnectedDevices.size() >= mMaxHeadsetConnections) {
1847             Log.w(TAG, "Maximum number of connections " + mMaxHeadsetConnections
1848                     + " was reached, rejecting connection from " + device);
1849             return false;
1850         }
1851         return true;
1852     }
1853 
1854     /**
1855      * Checks if SCO should be connected at current system state
1856      *
1857      * @param device device for SCO to be connected
1858      * @return true if SCO is allowed to be connected
1859      */
isScoAcceptable(BluetoothDevice device)1860     public boolean isScoAcceptable(BluetoothDevice device) {
1861         synchronized (mStateMachines) {
1862             if (device == null || !device.equals(mActiveDevice)) {
1863                 Log.w(TAG, "isScoAcceptable: rejected SCO since " + device
1864                         + " is not the current active device " + mActiveDevice);
1865                 return false;
1866             }
1867             if (mForceScoAudio) {
1868                 return true;
1869             }
1870             if (!mAudioRouteAllowed) {
1871                 Log.w(TAG, "isScoAcceptable: rejected SCO since audio route is not allowed");
1872                 return false;
1873             }
1874             if (mVoiceRecognitionStarted || mVirtualCallStarted) {
1875                 return true;
1876             }
1877             if (shouldCallAudioBeActive()) {
1878                 return true;
1879             }
1880             Log.w(TAG, "isScoAcceptable: rejected SCO, inCall=" + mSystemInterface.isInCall()
1881                     + ", voiceRecognition=" + mVoiceRecognitionStarted + ", ringing="
1882                     + mSystemInterface.isRinging() + ", inbandRinging=" + isInbandRingingEnabled()
1883                     + ", isVirtualCallStarted=" + mVirtualCallStarted);
1884             return false;
1885         }
1886     }
1887 
1888     /**
1889      * Remove state machine in {@link #mStateMachines} for a {@link BluetoothDevice}
1890      *
1891      * @param device device whose state machine is to be removed.
1892      */
removeStateMachine(BluetoothDevice device)1893     void removeStateMachine(BluetoothDevice device) {
1894         synchronized (mStateMachines) {
1895             HeadsetStateMachine stateMachine = mStateMachines.get(device);
1896             if (stateMachine == null) {
1897                 Log.w(TAG, "removeStateMachine(), " + device + " does not have a state machine");
1898                 return;
1899             }
1900             Log.i(TAG, "removeStateMachine(), removing state machine for device: " + device);
1901             HeadsetObjectsFactory.getInstance().destroyStateMachine(stateMachine);
1902             mStateMachines.remove(device);
1903         }
1904     }
1905 
isOnStateMachineThread()1906     private boolean isOnStateMachineThread() {
1907         final Looper myLooper = Looper.myLooper();
1908         return myLooper != null && (mStateMachinesThread != null) && (myLooper.getThread().getId()
1909                 == mStateMachinesThread.getId());
1910     }
1911 
1912     @Override
dump(StringBuilder sb)1913     public void dump(StringBuilder sb) {
1914         synchronized (mStateMachines) {
1915             super.dump(sb);
1916             ProfileService.println(sb, "mMaxHeadsetConnections: " + mMaxHeadsetConnections);
1917             ProfileService.println(sb, "DefaultMaxHeadsetConnections: "
1918                     + mAdapterService.getMaxConnectedAudioDevices());
1919             ProfileService.println(sb, "mActiveDevice: " + mActiveDevice);
1920             ProfileService.println(sb, "isInbandRingingEnabled: " + isInbandRingingEnabled());
1921             ProfileService.println(sb,
1922                     "isInbandRingingSupported: " + BluetoothHeadset.isInbandRingingSupported(this));
1923             ProfileService.println(sb,
1924                     "mInbandRingingRuntimeDisable: " + mInbandRingingRuntimeDisable);
1925             ProfileService.println(sb, "mAudioRouteAllowed: " + mAudioRouteAllowed);
1926             ProfileService.println(sb, "mVoiceRecognitionStarted: " + mVoiceRecognitionStarted);
1927             ProfileService.println(sb,
1928                     "mVoiceRecognitionTimeoutEvent: " + mVoiceRecognitionTimeoutEvent);
1929             ProfileService.println(sb, "mVirtualCallStarted: " + mVirtualCallStarted);
1930             ProfileService.println(sb, "mDialingOutTimeoutEvent: " + mDialingOutTimeoutEvent);
1931             ProfileService.println(sb, "mForceScoAudio: " + mForceScoAudio);
1932             ProfileService.println(sb, "mCreated: " + mCreated);
1933             ProfileService.println(sb, "mStarted: " + mStarted);
1934             ProfileService.println(sb,
1935                     "AudioManager.isBluetoothScoOn(): " + mSystemInterface.getAudioManager()
1936                             .isBluetoothScoOn());
1937             ProfileService.println(sb, "Telecom.isInCall(): " + mSystemInterface.isInCall());
1938             ProfileService.println(sb, "Telecom.isRinging(): " + mSystemInterface.isRinging());
1939             for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
1940                 ProfileService.println(sb,
1941                         "==== StateMachine for " + stateMachine.getDevice() + " ====");
1942                 stateMachine.dump(sb);
1943             }
1944         }
1945     }
1946 
logD(String message)1947     private static void logD(String message) {
1948         if (DBG) {
1949             Log.d(TAG, message);
1950         }
1951     }
1952 
getStateMachinesThreadHandler()1953     private Handler getStateMachinesThreadHandler() {
1954         if (mStateMachinesThreadHandler == null) {
1955             mStateMachinesThreadHandler = new Handler(mStateMachinesThread.getLooper());
1956         }
1957         return mStateMachinesThreadHandler;
1958     }
1959 }
1960