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