• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2014 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.hfpclient;
18 
19 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
21 import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
22 import static android.bluetooth.BluetoothProfile.STATE_CONNECTING;
23 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED;
24 import static android.content.pm.PackageManager.FEATURE_WATCH;
25 
26 import static java.util.Objects.requireNonNull;
27 
28 import android.bluetooth.BluetoothDevice;
29 import android.bluetooth.BluetoothHeadsetClientCall;
30 import android.bluetooth.BluetoothProfile;
31 import android.bluetooth.BluetoothSinkAudioPolicy;
32 import android.bluetooth.BluetoothStatusCodes;
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.os.BatteryManager;
39 import android.os.Bundle;
40 import android.os.HandlerThread;
41 import android.os.Message;
42 import android.os.SystemProperties;
43 import android.sysprop.BluetoothProperties;
44 import android.util.Log;
45 
46 import com.android.bluetooth.Utils;
47 import com.android.bluetooth.btservice.AdapterService;
48 import com.android.bluetooth.btservice.ProfileService;
49 import com.android.bluetooth.btservice.storage.DatabaseManager;
50 import com.android.internal.annotations.GuardedBy;
51 import com.android.internal.annotations.VisibleForTesting;
52 
53 import java.util.ArrayList;
54 import java.util.HashMap;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Set;
58 import java.util.UUID;
59 
60 /**
61  * Provides Bluetooth Headset Client (HF Role) profile, as a service in the Bluetooth application.
62  */
63 public class HeadsetClientService extends ProfileService {
64     private static final String TAG = HeadsetClientService.class.getSimpleName();
65 
66     // Maximum number of devices we can try connecting to in one session
67     private static final int MAX_STATE_MACHINES_POSSIBLE = 100;
68 
69     @VisibleForTesting static final int MAX_HFP_SCO_VOICE_CALL_VOLUME = 15; // HFP 1.5 spec.
70     @VisibleForTesting static final int MIN_HFP_SCO_VOICE_CALL_VOLUME = 1; // HFP 1.5 spec.
71 
72     // This is also used as a lock for shared data in {@link HeadsetClientService}
73     @GuardedBy("mStateMachineMap")
74     private final HashMap<BluetoothDevice, HeadsetClientStateMachine> mStateMachineMap =
75             new HashMap<>();
76 
77     private static HeadsetClientService sHeadsetClientService;
78 
79     private final HandlerThread mSmThread;
80     private final AdapterService mAdapterService;
81     private final DatabaseManager mDatabaseManager;
82     private final AudioManager mAudioManager;
83     private final NativeInterface mNativeInterface;
84     private final BatteryManager mBatteryManager;
85     private final HeadsetClientStateMachineFactory mSmFactory;
86     private final int mMaxAmVcVol;
87     private final int mMinAmVcVol;
88 
89     private int mLastBatteryLevel = -1;
90 
91     public static final String HFP_CLIENT_STOP_TAG = "hfp_client_stop_tag";
92 
HeadsetClientService(AdapterService adapterService)93     public HeadsetClientService(AdapterService adapterService) {
94         super(requireNonNull(adapterService));
95         mAdapterService = adapterService;
96         mDatabaseManager = requireNonNull(adapterService.getDatabase());
97         mAudioManager = requireNonNull(getSystemService(AudioManager.class));
98         mMaxAmVcVol = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL);
99         mMinAmVcVol = mAudioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL);
100 
101         // Setup the JNI service
102         mNativeInterface = NativeInterface.getInstance();
103         mNativeInterface.initialize();
104 
105         mBatteryManager = getSystemService(BatteryManager.class);
106 
107         // start AudioManager in a known state
108         mAudioManager.setHfpEnabled(false);
109 
110         mSmFactory = new HeadsetClientStateMachineFactory();
111         synchronized (mStateMachineMap) {
112             mStateMachineMap.clear();
113         }
114 
115         IntentFilter filter = new IntentFilter(AudioManager.ACTION_VOLUME_CHANGED);
116         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
117         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
118         registerReceiver(mBroadcastReceiver, filter);
119 
120         // Start the HfpClientConnectionService to create connection with telecom when HFP
121         // connection is available on non-wearable device.
122         if (getPackageManager() != null && !getPackageManager().hasSystemFeature(FEATURE_WATCH)) {
123             Intent startIntent = new Intent(this, HfpClientConnectionService.class);
124             startService(startIntent);
125         }
126 
127         // Create the thread on which all State Machines will run
128         mSmThread = new HandlerThread("HeadsetClient.SM");
129         mSmThread.start();
130 
131         setHeadsetClientService(this);
132     }
133 
isEnabled()134     public static boolean isEnabled() {
135         return BluetoothProperties.isProfileHfpHfEnabled().orElse(false);
136     }
137 
138     @Override
initBinder()139     public IProfileServiceBinder initBinder() {
140         return new HeadsetClientServiceBinder(this);
141     }
142 
143     @Override
cleanup()144     public void cleanup() {
145         Log.i(TAG, "Cleanup Headset Client Service");
146 
147         synchronized (HeadsetClientService.class) {
148             if (sHeadsetClientService == null) {
149                 Log.w(TAG, "cleanup() called before initialization");
150                 return;
151             }
152 
153             // Stop the HfpClientConnectionService for non-wearables devices.
154             if (getPackageManager() != null
155                     && !getPackageManager().hasSystemFeature(FEATURE_WATCH)) {
156                 Intent stopIntent = new Intent(this, HfpClientConnectionService.class);
157                 sHeadsetClientService.stopService(stopIntent);
158             }
159         }
160 
161         setHeadsetClientService(null);
162 
163         unregisterReceiver(mBroadcastReceiver);
164 
165         synchronized (mStateMachineMap) {
166             for (HeadsetClientStateMachine sm : mStateMachineMap.values()) {
167                 sm.doQuit();
168             }
169             mStateMachineMap.clear();
170         }
171 
172         // Stop the handler thread
173         mSmThread.quit();
174 
175         mNativeInterface.cleanup();
176     }
177 
hfToAmVol(int hfVol)178     int hfToAmVol(int hfVol) {
179         int amRange = mMaxAmVcVol - mMinAmVcVol;
180         int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME;
181         int amVol =
182                 (int)
183                                 Math.round(
184                                         (hfVol - MIN_HFP_SCO_VOICE_CALL_VOLUME)
185                                                 * ((double) amRange / hfRange))
186                         + mMinAmVcVol;
187         Log.d(TAG, "HF -> AM " + hfVol + " " + amVol);
188         return amVol;
189     }
190 
amToHfVol(int amVol)191     int amToHfVol(int amVol) {
192         int amRange = (mMaxAmVcVol > mMinAmVcVol) ? (mMaxAmVcVol - mMinAmVcVol) : 1;
193         int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME;
194         int hfVol =
195                 (int) Math.round((amVol - mMinAmVcVol) * ((double) hfRange / amRange))
196                         + MIN_HFP_SCO_VOICE_CALL_VOLUME;
197         Log.d(TAG, "AM -> HF " + amVol + " " + hfVol);
198         return hfVol;
199     }
200 
201     private final BroadcastReceiver mBroadcastReceiver =
202             new BroadcastReceiver() {
203                 @Override
204                 public void onReceive(Context context, Intent intent) {
205                     String action = intent.getAction();
206 
207                     // We handle the volume changes for Voice calls here since HFP audio volume
208                     // control does
209                     // not go through audio manager (audio mixer). see
210                     // ({@link HeadsetClientStateMachine#SET_SPEAKER_VOLUME} in
211                     // {@link HeadsetClientStateMachine} for details.
212                     if (action.equals(AudioManager.ACTION_VOLUME_CHANGED)) {
213                         Log.d(
214                                 TAG,
215                                 "Volume changed for stream: "
216                                         + intent.getIntExtra(
217                                                 AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1));
218                         int streamType =
219                                 intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
220                         if (streamType == AudioManager.STREAM_VOICE_CALL) {
221                             int streamValue =
222                                     intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
223                             int hfVol = amToHfVol(streamValue);
224                             Log.d(
225                                     TAG,
226                                     "Setting volume to audio manager: "
227                                             + streamValue
228                                             + " hands free: "
229                                             + hfVol);
230                             mAudioManager.setHfpVolume(hfVol);
231                             synchronized (mStateMachineMap) {
232                                 for (HeadsetClientStateMachine sm : mStateMachineMap.values()) {
233                                     if (sm != null) {
234                                         sm.sendMessage(
235                                                 HeadsetClientStateMachine.SET_SPEAKER_VOLUME,
236                                                 streamValue);
237                                     }
238                                 }
239                             }
240                         }
241                     } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
242                         int batteryIndicatorID = 2;
243                         int batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
244 
245                         if (batteryLevel == mLastBatteryLevel) {
246                             return;
247                         }
248                         mLastBatteryLevel = batteryLevel;
249 
250                         Log.d(
251                                 TAG,
252                                 "Send battery level update BIEV(2," + batteryLevel + ") command");
253 
254                         synchronized (mStateMachineMap) {
255                             for (HeadsetClientStateMachine sm : mStateMachineMap.values()) {
256                                 if (sm != null) {
257                                     sm.sendMessage(
258                                             HeadsetClientStateMachine.SEND_BIEV,
259                                             batteryIndicatorID,
260                                             batteryLevel);
261                                 }
262                             }
263                         }
264                     }
265                 }
266             };
267 
268     /**
269      * Convert {@code HfpClientCall} to legacy {@code BluetoothHeadsetClientCall} still used by some
270      * clients.
271      */
toLegacyCall(HfpClientCall call)272     static BluetoothHeadsetClientCall toLegacyCall(HfpClientCall call) {
273         if (call == null) return null;
274         return new BluetoothHeadsetClientCall(
275                 call.getDevice(),
276                 call.getId(),
277                 call.getUUID(),
278                 call.getState(),
279                 call.getNumber(),
280                 call.isMultiParty(),
281                 call.isOutgoing(),
282                 call.isInBandRing());
283     }
284 
285     // API methods
getHeadsetClientService()286     public static synchronized HeadsetClientService getHeadsetClientService() {
287         if (sHeadsetClientService == null) {
288             Log.w(TAG, "getHeadsetClientService(): service is null");
289             return null;
290         }
291         if (!sHeadsetClientService.isAvailable()) {
292             Log.w(TAG, "getHeadsetClientService(): service is not available ");
293             return null;
294         }
295         return sHeadsetClientService;
296     }
297 
298     /** Set a {@link HeadsetClientService} instance. */
299     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
setHeadsetClientService(HeadsetClientService instance)300     public static synchronized void setHeadsetClientService(HeadsetClientService instance) {
301         Log.d(TAG, "setHeadsetClientService(): set to: " + instance);
302         sHeadsetClientService = instance;
303     }
304 
connect(BluetoothDevice device)305     public boolean connect(BluetoothDevice device) {
306         Log.d(TAG, "connect " + device);
307         if (getConnectionPolicy(device) == CONNECTION_POLICY_FORBIDDEN) {
308             Log.w(
309                     TAG,
310                     "Connection not allowed: <"
311                             + device.getAddress()
312                             + "> is CONNECTION_POLICY_FORBIDDEN");
313             return false;
314         }
315         HeadsetClientStateMachine sm = getStateMachine(device, true);
316         if (sm == null) {
317             Log.e(TAG, "Cannot allocate SM for device " + device);
318             return false;
319         }
320 
321         sm.sendMessage(HeadsetClientStateMachine.CONNECT, device);
322         return true;
323     }
324 
325     /**
326      * Disconnects hfp client for the remote bluetooth device
327      *
328      * @param device is the device with which we are attempting to disconnect the profile
329      * @return true if hfp client profile successfully disconnected, false otherwise
330      */
disconnect(BluetoothDevice device)331     public boolean disconnect(BluetoothDevice device) {
332         HeadsetClientStateMachine sm = getStateMachine(device);
333         if (sm == null) {
334             Log.e(TAG, "SM does not exist for device " + device);
335             return false;
336         }
337 
338         int connectionState = sm.getConnectionState(device);
339         if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) {
340             return false;
341         }
342 
343         sm.sendMessage(HeadsetClientStateMachine.DISCONNECT, device);
344         return true;
345     }
346 
347     /**
348      * @return A list of connected {@link BluetoothDevice}.
349      */
getConnectedDevices()350     public List<BluetoothDevice> getConnectedDevices() {
351         ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
352         synchronized (mStateMachineMap) {
353             for (BluetoothDevice bd : mStateMachineMap.keySet()) {
354                 HeadsetClientStateMachine sm = mStateMachineMap.get(bd);
355                 if (sm != null && sm.getConnectionState(bd) == STATE_CONNECTED) {
356                     connectedDevices.add(bd);
357                 }
358             }
359         }
360         return connectedDevices;
361     }
362 
getDevicesMatchingConnectionStates(int[] states)363     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
364         List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
365         synchronized (mStateMachineMap) {
366             for (BluetoothDevice bd : mStateMachineMap.keySet()) {
367                 for (int state : states) {
368                     HeadsetClientStateMachine sm = mStateMachineMap.get(bd);
369                     if (sm != null && sm.getConnectionState(bd) == state) {
370                         devices.add(bd);
371                     }
372                 }
373             }
374         }
375         return devices;
376     }
377 
378     /**
379      * Get the current connection state of the profile
380      *
381      * @param device is the remote bluetooth device
382      * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected, {@link
383      *     BluetoothProfile#STATE_CONNECTING} if this profile is being connected, {@link
384      *     BluetoothProfile#STATE_CONNECTED} if this profile is connected, or {@link
385      *     BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
386      */
getConnectionState(BluetoothDevice device)387     public int getConnectionState(BluetoothDevice device) {
388         HeadsetClientStateMachine sm = getStateMachine(device);
389         if (sm != null) {
390             return sm.getConnectionState(device);
391         }
392 
393         return STATE_DISCONNECTED;
394     }
395 
396     /**
397      * Set connection policy of the profile and connects it if connectionPolicy is {@link
398      * BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is {@link
399      * BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
400      *
401      * <p>The device should already be paired. Connection policy can be one of: {@link
402      * BluetoothProfile#CONNECTION_POLICY_ALLOWED}, {@link
403      * BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link
404      * BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
405      *
406      * @param device Paired bluetooth device
407      * @param connectionPolicy is the connection policy to set to for this profile
408      * @return true if connectionPolicy is set, false on error
409      */
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)410     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
411         Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
412 
413         if (!mDatabaseManager.setProfileConnectionPolicy(
414                 device, BluetoothProfile.HEADSET_CLIENT, connectionPolicy)) {
415             return false;
416         }
417         if (connectionPolicy == CONNECTION_POLICY_ALLOWED) {
418             connect(device);
419         } else if (connectionPolicy == CONNECTION_POLICY_FORBIDDEN) {
420             disconnect(device);
421         }
422         return true;
423     }
424 
425     /**
426      * Get the connection policy of the profile.
427      *
428      * <p>The connection policy can be any of: {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
429      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link
430      * BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
431      *
432      * @param device Bluetooth device
433      * @return connection policy of the device
434      */
getConnectionPolicy(BluetoothDevice device)435     public int getConnectionPolicy(BluetoothDevice device) {
436         return mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET_CLIENT);
437     }
438 
startVoiceRecognition(BluetoothDevice device)439     boolean startVoiceRecognition(BluetoothDevice device) {
440         HeadsetClientStateMachine sm = getStateMachine(device);
441         if (sm == null) {
442             Log.e(TAG, "SM does not exist for device " + device);
443             return false;
444         }
445         int connectionState = sm.getConnectionState(device);
446         if (connectionState != STATE_CONNECTED) {
447             return false;
448         }
449         sm.sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_START);
450         return true;
451     }
452 
stopVoiceRecognition(BluetoothDevice device)453     boolean stopVoiceRecognition(BluetoothDevice device) {
454         HeadsetClientStateMachine sm = getStateMachine(device);
455         if (sm == null) {
456             Log.e(TAG, "SM does not exist for device " + device);
457             return false;
458         }
459         int connectionState = sm.getConnectionState(device);
460         if (connectionState != STATE_CONNECTED) {
461             return false;
462         }
463         sm.sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_STOP);
464         return true;
465     }
466 
467     /**
468      * Gets audio state of the connection with {@code device}.
469      *
470      * <p>Can be one of {@link STATE_AUDIO_CONNECTED}, {@link STATE_AUDIO_CONNECTING}, or {@link
471      * STATE_AUDIO_DISCONNECTED}.
472      */
getAudioState(BluetoothDevice device)473     public int getAudioState(BluetoothDevice device) {
474         HeadsetClientStateMachine sm = getStateMachine(device);
475         if (sm == null) {
476             Log.e(TAG, "SM does not exist for device " + device);
477             return -1;
478         }
479 
480         return sm.getAudioState(device);
481     }
482 
setAudioRouteAllowed(BluetoothDevice device, boolean allowed)483     public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) {
484         Log.i(
485                 TAG,
486                 "setAudioRouteAllowed: device="
487                         + device
488                         + ", allowed="
489                         + allowed
490                         + ", "
491                         + Utils.getUidPidString());
492         synchronized (mStateMachineMap) {
493             HeadsetClientStateMachine sm = mStateMachineMap.get(device);
494             if (sm != null) {
495                 sm.setAudioRouteAllowed(allowed);
496             }
497         }
498     }
499 
getAudioRouteAllowed(BluetoothDevice device)500     public boolean getAudioRouteAllowed(BluetoothDevice device) {
501         synchronized (mStateMachineMap) {
502             HeadsetClientStateMachine sm = mStateMachineMap.get(device);
503             if (sm != null) {
504                 return sm.getAudioRouteAllowed();
505             }
506         }
507         return false;
508     }
509 
510     /**
511      * sends the {@link BluetoothSinkAudioPolicy} object to the state machine of the corresponding
512      * device to store and send to the remote device using Android specific AT commands.
513      *
514      * @param device for whom the policies to be set
515      * @param policies to be set policies
516      */
setAudioPolicy(BluetoothDevice device, BluetoothSinkAudioPolicy policies)517     public void setAudioPolicy(BluetoothDevice device, BluetoothSinkAudioPolicy policies) {
518         Log.i(
519                 TAG,
520                 "setAudioPolicy: device="
521                         + device
522                         + ", "
523                         + policies.toString()
524                         + ", "
525                         + Utils.getUidPidString());
526         HeadsetClientStateMachine sm = getStateMachine(device);
527         if (sm != null) {
528             sm.setAudioPolicy(policies);
529         }
530     }
531 
532     /**
533      * sets the audio policy feature support status for the corresponding device.
534      *
535      * @param device for whom the policies to be set
536      * @param supported support status
537      */
setAudioPolicyRemoteSupported(BluetoothDevice device, boolean supported)538     public void setAudioPolicyRemoteSupported(BluetoothDevice device, boolean supported) {
539         Log.i(TAG, "setAudioPolicyRemoteSupported: " + supported);
540         HeadsetClientStateMachine sm = getStateMachine(device);
541         if (sm != null) {
542             sm.setAudioPolicyRemoteSupported(supported);
543         }
544     }
545 
546     /**
547      * gets the audio policy feature support status for the corresponding device.
548      *
549      * @param device for whom the policies to be set
550      * @return int support status
551      */
getAudioPolicyRemoteSupported(BluetoothDevice device)552     public int getAudioPolicyRemoteSupported(BluetoothDevice device) {
553         HeadsetClientStateMachine sm = getStateMachine(device);
554         if (sm != null) {
555             return sm.getAudioPolicyRemoteSupported();
556         }
557         return BluetoothStatusCodes.FEATURE_NOT_CONFIGURED;
558     }
559 
connectAudio(BluetoothDevice device)560     public boolean connectAudio(BluetoothDevice device) {
561         Log.i(TAG, "connectAudio: device=" + device + ", " + Utils.getUidPidString());
562         HeadsetClientStateMachine sm = getStateMachine(device);
563         if (sm == null) {
564             Log.e(TAG, "SM does not exist for device " + device);
565             return false;
566         }
567 
568         if (!sm.isConnected()) {
569             return false;
570         }
571         if (sm.isAudioOn()) {
572             return false;
573         }
574         sm.sendMessage(HeadsetClientStateMachine.CONNECT_AUDIO);
575         return true;
576     }
577 
disconnectAudio(BluetoothDevice device)578     public boolean disconnectAudio(BluetoothDevice device) {
579         HeadsetClientStateMachine sm = getStateMachine(device);
580         if (sm == null) {
581             Log.e(TAG, "SM does not exist for device " + device);
582             return false;
583         }
584 
585         if (!sm.isAudioOn()) {
586             return false;
587         }
588         sm.sendMessage(HeadsetClientStateMachine.DISCONNECT_AUDIO);
589         return true;
590     }
591 
holdCall(BluetoothDevice device)592     public boolean holdCall(BluetoothDevice device) {
593         HeadsetClientStateMachine sm = getStateMachine(device);
594         if (sm == null) {
595             Log.e(TAG, "SM does not exist for device " + device);
596             return false;
597         }
598 
599         int connectionState = sm.getConnectionState(device);
600         if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) {
601             return false;
602         }
603         Message msg = sm.obtainMessage(HeadsetClientStateMachine.HOLD_CALL);
604         sm.sendMessage(msg);
605         return true;
606     }
607 
acceptCall(BluetoothDevice device, int flag)608     public boolean acceptCall(BluetoothDevice device, int flag) {
609         /* Phone calls from a single device are supported, hang up any calls on the other phone */
610         synchronized (mStateMachineMap) {
611             for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry :
612                     mStateMachineMap.entrySet()) {
613                 if (entry.getValue() == null || entry.getKey().equals(device)) {
614                     continue;
615                 }
616                 int connectionState = entry.getValue().getConnectionState(entry.getKey());
617                 Log.d(
618                         TAG,
619                         "Accepting a call on device "
620                                 + device
621                                 + ". Possibly disconnecting on "
622                                 + entry.getValue());
623                 if (connectionState == STATE_CONNECTED) {
624                     entry.getValue()
625                             .obtainMessage(HeadsetClientStateMachine.TERMINATE_CALL)
626                             .sendToTarget();
627                 }
628             }
629         }
630         HeadsetClientStateMachine sm = getStateMachine(device);
631         if (sm == null) {
632             Log.e(TAG, "SM does not exist for device " + device);
633             return false;
634         }
635 
636         int connectionState = sm.getConnectionState(device);
637         if (connectionState != STATE_CONNECTED) {
638             return false;
639         }
640         Message msg = sm.obtainMessage(HeadsetClientStateMachine.ACCEPT_CALL);
641         msg.arg1 = flag;
642         sm.sendMessage(msg);
643         return true;
644     }
645 
rejectCall(BluetoothDevice device)646     public boolean rejectCall(BluetoothDevice device) {
647         HeadsetClientStateMachine sm = getStateMachine(device);
648         if (sm == null) {
649             Log.e(TAG, "SM does not exist for device " + device);
650             return false;
651         }
652 
653         int connectionState = sm.getConnectionState(device);
654         if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) {
655             return false;
656         }
657 
658         Message msg = sm.obtainMessage(HeadsetClientStateMachine.REJECT_CALL);
659         sm.sendMessage(msg);
660         return true;
661     }
662 
terminateCall(BluetoothDevice device, UUID uuid)663     public boolean terminateCall(BluetoothDevice device, UUID uuid) {
664         HeadsetClientStateMachine sm = getStateMachine(device);
665         if (sm == null) {
666             Log.e(TAG, "SM does not exist for device " + device);
667             return false;
668         }
669 
670         int connectionState = sm.getConnectionState(device);
671         if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) {
672             return false;
673         }
674 
675         Message msg = sm.obtainMessage(HeadsetClientStateMachine.TERMINATE_CALL);
676         msg.obj = uuid;
677         sm.sendMessage(msg);
678         return true;
679     }
680 
enterPrivateMode(BluetoothDevice device, int index)681     public boolean enterPrivateMode(BluetoothDevice device, int index) {
682         HeadsetClientStateMachine sm = getStateMachine(device);
683         if (sm == null) {
684             Log.e(TAG, "SM does not exist for device " + device);
685             return false;
686         }
687 
688         int connectionState = sm.getConnectionState(device);
689         if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) {
690             return false;
691         }
692 
693         Message msg = sm.obtainMessage(HeadsetClientStateMachine.ENTER_PRIVATE_MODE);
694         msg.arg1 = index;
695         sm.sendMessage(msg);
696         return true;
697     }
698 
dial(BluetoothDevice device, String number)699     public HfpClientCall dial(BluetoothDevice device, String number) {
700         HeadsetClientStateMachine sm = getStateMachine(device);
701         if (sm == null) {
702             Log.e(TAG, "SM does not exist for device " + device);
703             return null;
704         }
705 
706         int connectionState = sm.getConnectionState(device);
707         if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) {
708             return null;
709         }
710 
711         // Some platform does not support three way calling (ex: watch)
712         final boolean support_three_way_calling =
713                 SystemProperties.getBoolean(
714                         "bluetooth.headset_client.three_way_calling.enabled", true);
715         if (!support_three_way_calling && !getCurrentCalls(device).isEmpty()) {
716             Log.e(TAG, "dial(" + device + "): Line is busy, reject dialing");
717             return null;
718         }
719 
720         HfpClientCall call =
721                 new HfpClientCall(
722                         device,
723                         HeadsetClientStateMachine.HF_ORIGINATED_CALL_ID,
724                         HfpClientCall.CALL_STATE_DIALING,
725                         number,
726                         false /* multiparty */,
727                         true /* outgoing */,
728                         sm.getInBandRing());
729         Message msg = sm.obtainMessage(HeadsetClientStateMachine.DIAL_NUMBER);
730         msg.obj = call;
731         sm.sendMessage(msg);
732         return call;
733     }
734 
sendDTMF(BluetoothDevice device, byte code)735     public boolean sendDTMF(BluetoothDevice device, byte code) {
736         HeadsetClientStateMachine sm = getStateMachine(device);
737         if (sm == null) {
738             Log.e(TAG, "SM does not exist for device " + device);
739             return false;
740         }
741 
742         int connectionState = sm.getConnectionState(device);
743         if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) {
744             return false;
745         }
746         Message msg = sm.obtainMessage(HeadsetClientStateMachine.SEND_DTMF);
747         msg.arg1 = code;
748         sm.sendMessage(msg);
749         return true;
750     }
751 
getLastVoiceTagNumber(BluetoothDevice device)752     public boolean getLastVoiceTagNumber(BluetoothDevice device) {
753         return false;
754     }
755 
getCurrentCalls(BluetoothDevice device)756     public List<HfpClientCall> getCurrentCalls(BluetoothDevice device) {
757         HeadsetClientStateMachine sm = getStateMachine(device);
758         if (sm == null) {
759             Log.e(TAG, "SM does not exist for device " + device);
760             return null;
761         }
762 
763         int connectionState = sm.getConnectionState(device);
764         if (connectionState != STATE_CONNECTED) {
765             return null;
766         }
767         return sm.getCurrentCalls();
768     }
769 
explicitCallTransfer(BluetoothDevice device)770     public boolean explicitCallTransfer(BluetoothDevice device) {
771         HeadsetClientStateMachine sm = getStateMachine(device);
772         if (sm == null) {
773             Log.e(TAG, "SM does not exist for device " + device);
774             return false;
775         }
776 
777         int connectionState = sm.getConnectionState(device);
778         if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) {
779             return false;
780         }
781         Message msg = sm.obtainMessage(HeadsetClientStateMachine.EXPLICIT_CALL_TRANSFER);
782         sm.sendMessage(msg);
783         return true;
784     }
785 
786     /** Send vendor AT command. */
sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand)787     public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) {
788         HeadsetClientStateMachine sm = getStateMachine(device);
789         if (sm == null) {
790             Log.e(TAG, "SM does not exist for device " + device);
791             return false;
792         }
793 
794         int connectionState = sm.getConnectionState(device);
795         if (connectionState != STATE_CONNECTED) {
796             return false;
797         }
798 
799         Message msg =
800                 sm.obtainMessage(
801                         HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND, vendorId, 0, atCommand);
802         sm.sendMessage(msg);
803         return true;
804     }
805 
getCurrentAgEvents(BluetoothDevice device)806     public Bundle getCurrentAgEvents(BluetoothDevice device) {
807         HeadsetClientStateMachine sm = getStateMachine(device);
808         if (sm == null) {
809             Log.e(TAG, "SM does not exist for device " + device);
810             return null;
811         }
812 
813         int connectionState = sm.getConnectionState(device);
814         if (connectionState != STATE_CONNECTED) {
815             return null;
816         }
817         return sm.getCurrentAgEvents();
818     }
819 
getCurrentAgFeaturesBundle(BluetoothDevice device)820     public Bundle getCurrentAgFeaturesBundle(BluetoothDevice device) {
821         HeadsetClientStateMachine sm = getStateMachine(device);
822         if (sm == null) {
823             Log.e(TAG, "SM does not exist for device " + device);
824             return null;
825         }
826         int connectionState = sm.getConnectionState(device);
827         if (connectionState != STATE_CONNECTED) {
828             return null;
829         }
830         return sm.getCurrentAgFeaturesBundle();
831     }
832 
getCurrentAgFeatures(BluetoothDevice device)833     public Set<Integer> getCurrentAgFeatures(BluetoothDevice device) {
834         HeadsetClientStateMachine sm = getStateMachine(device);
835         if (sm == null) {
836             Log.e(TAG, "SM does not exist for device " + device);
837             return null;
838         }
839         int connectionState = sm.getConnectionState(device);
840         if (connectionState != STATE_CONNECTED) {
841             return null;
842         }
843         return sm.getCurrentAgFeatures();
844     }
845 
846     // Handle messages from native (JNI) to java
messageFromNative(StackEvent stackEvent)847     public void messageFromNative(StackEvent stackEvent) {
848         requireNonNull(stackEvent.device);
849 
850         HeadsetClientStateMachine sm =
851                 getStateMachine(stackEvent.device, isConnectionEvent(stackEvent));
852         if (sm == null) {
853             throw new IllegalStateException(
854                     "State machine not found for stack event: " + stackEvent);
855         }
856         sm.sendMessage(StackEvent.STACK_EVENT, stackEvent);
857     }
858 
isConnectionEvent(StackEvent stackEvent)859     private static boolean isConnectionEvent(StackEvent stackEvent) {
860         if (stackEvent.type == StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
861             if ((stackEvent.valueInt == HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING)
862                     || (stackEvent.valueInt
863                             == HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED)) {
864                 return true;
865             }
866         }
867         return false;
868     }
869 
getStateMachine(BluetoothDevice device)870     private HeadsetClientStateMachine getStateMachine(BluetoothDevice device) {
871         return getStateMachine(device, false);
872     }
873 
getStateMachine( BluetoothDevice device, boolean isConnectionEvent)874     private HeadsetClientStateMachine getStateMachine(
875             BluetoothDevice device, boolean isConnectionEvent) {
876         if (device == null) {
877             Log.e(TAG, "getStateMachine failed: Device cannot be null");
878             return null;
879         }
880 
881         HeadsetClientStateMachine sm;
882         synchronized (mStateMachineMap) {
883             sm = mStateMachineMap.get(device);
884         }
885 
886         if (sm != null) {
887             Log.d(TAG, "Found SM for device " + device);
888         } else if (isConnectionEvent) {
889             // The only time a new state machine should be created when none was found is for
890             // connection events.
891             sm = allocateStateMachine(device);
892             if (sm == null) {
893                 Log.e(TAG, "SM could not be allocated for device " + device);
894             }
895         }
896         return sm;
897     }
898 
allocateStateMachine(BluetoothDevice device)899     private HeadsetClientStateMachine allocateStateMachine(BluetoothDevice device) {
900         if (device == null) {
901             Log.e(TAG, "allocateStateMachine failed: Device cannot be null");
902             return null;
903         }
904 
905         if (getHeadsetClientService() == null) {
906             // Preconditions: {@code setHeadsetClientService(this)} is the last thing {@code start}
907             // does, and {@code setHeadsetClientService(null)} is (one of) the first thing
908             // {@code stop does}.
909             Log.e(
910                     TAG,
911                     "Cannot allocate SM if service has begun stopping or has not completed"
912                             + " startup.");
913             return null;
914         }
915 
916         synchronized (mStateMachineMap) {
917             HeadsetClientStateMachine sm = mStateMachineMap.get(device);
918             if (sm != null) {
919                 Log.d(TAG, "allocateStateMachine: SM already exists for device " + device);
920                 return sm;
921             }
922 
923             // There is a possibility of a DOS attack if someone populates here with a lot of fake
924             // BluetoothAddresses. If it so happens instead of blowing up we can at least put a
925             // limit on how long the attack would survive
926             if (mStateMachineMap.keySet().size() > MAX_STATE_MACHINES_POSSIBLE) {
927                 Log.e(
928                         TAG,
929                         "Max state machines reached, possible DOS attack "
930                                 + MAX_STATE_MACHINES_POSSIBLE);
931                 return null;
932             }
933 
934             // Allocate a new SM
935             Log.d(TAG, "Creating a new state machine");
936             sm = mSmFactory.make(mAdapterService, this, mSmThread, mNativeInterface);
937             mStateMachineMap.put(device, sm);
938             return sm;
939         }
940     }
941 
942     // Check if any of the state machines have routed the SCO audio stream.
isScoRouted()943     boolean isScoRouted() {
944         synchronized (mStateMachineMap) {
945             for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry :
946                     mStateMachineMap.entrySet()) {
947                 if (entry.getValue() != null) {
948                     int audioState = entry.getValue().getAudioState(entry.getKey());
949                     if (audioState == HeadsetClientHalConstants.AUDIO_STATE_CONNECTED) {
950                         Log.d(
951                                 TAG,
952                                 "Device "
953                                         + entry.getKey()
954                                         + " audio state "
955                                         + audioState
956                                         + " Connected");
957                         return true;
958                     }
959                 }
960             }
961         }
962         return false;
963     }
964 
handleBatteryLevelChanged(BluetoothDevice device, int batteryLevel)965     void handleBatteryLevelChanged(BluetoothDevice device, int batteryLevel) {
966         mAdapterService.getRemoteDevices().handleAgBatteryLevelChanged(device, batteryLevel);
967     }
968 
969     @Override
dump(StringBuilder sb)970     public void dump(StringBuilder sb) {
971         super.dump(sb);
972         synchronized (mStateMachineMap) {
973             for (HeadsetClientStateMachine sm : mStateMachineMap.values()) {
974                 if (sm != null) {
975                     sm.dump(sb);
976                 }
977             }
978 
979             sb.append("\n");
980             HfpClientConnectionService.dump(sb);
981         }
982     }
983 
984     // For testing
getStateMachineMap()985     protected Map<BluetoothDevice, HeadsetClientStateMachine> getStateMachineMap() {
986         synchronized (mStateMachineMap) {
987             return mStateMachineMap;
988         }
989     }
990 
getAudioManager()991     protected AudioManager getAudioManager() {
992         return mAudioManager;
993     }
994 
updateBatteryLevel()995     protected void updateBatteryLevel() {
996         int batteryLevel = mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
997         int batteryIndicatorID = 2;
998 
999         synchronized (mStateMachineMap) {
1000             for (HeadsetClientStateMachine sm : mStateMachineMap.values()) {
1001                 if (sm != null) {
1002                     sm.sendMessage(
1003                             HeadsetClientStateMachine.SEND_BIEV, batteryIndicatorID, batteryLevel);
1004                 }
1005             }
1006         }
1007     }
1008 }
1009