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