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