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