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