1/*
2 * Copyright (C) 2012 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.hfp;
18
19import android.bluetooth.BluetoothDevice;
20import android.bluetooth.BluetoothHeadset;
21import android.bluetooth.BluetoothProfile;
22import android.bluetooth.IBluetoothHeadset;
23import android.content.BroadcastReceiver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.pm.PackageManager;
28import android.media.AudioManager;
29import android.os.Handler;
30import android.os.Message;
31import android.provider.Settings;
32import android.util.Log;
33import com.android.bluetooth.btservice.ProfileService;
34import com.android.bluetooth.Utils;
35import java.util.ArrayList;
36import java.util.List;
37import java.util.Iterator;
38import java.util.Map;
39
40/**
41 * Provides Bluetooth Headset and Handsfree profile, as a service in
42 * the Bluetooth application.
43 * @hide
44 */
45public class HeadsetService extends ProfileService {
46    private static final boolean DBG = false;
47    private static final String TAG = "HeadsetService";
48    private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE;
49
50    private HeadsetStateMachine mStateMachine;
51    private static HeadsetService sHeadsetService;
52
53    protected String getName() {
54        return TAG;
55    }
56
57    public IProfileServiceBinder initBinder() {
58        return new BluetoothHeadsetBinder(this);
59    }
60
61    protected boolean start() {
62        mStateMachine = HeadsetStateMachine.make(this);
63        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
64        filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
65        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
66        try {
67            registerReceiver(mHeadsetReceiver, filter);
68        } catch (Exception e) {
69            Log.w(TAG,"Unable to register headset receiver",e);
70        }
71        setHeadsetService(this);
72        return true;
73    }
74
75    protected boolean stop() {
76        try {
77            unregisterReceiver(mHeadsetReceiver);
78        } catch (Exception e) {
79            Log.w(TAG,"Unable to unregister headset receiver",e);
80        }
81        if (mStateMachine != null) {
82            mStateMachine.doQuit();
83        }
84        return true;
85    }
86
87    protected boolean cleanup() {
88        if (mStateMachine != null) {
89            mStateMachine.cleanup();
90        }
91        clearHeadsetService();
92        return true;
93    }
94
95    private final BroadcastReceiver mHeadsetReceiver = new BroadcastReceiver() {
96        @Override
97        public void onReceive(Context context, Intent intent) {
98            String action = intent.getAction();
99            if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
100                mStateMachine.sendMessage(HeadsetStateMachine.INTENT_BATTERY_CHANGED, intent);
101            } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
102                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
103                if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
104                    mStateMachine.sendMessage(HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED,
105                                              intent);
106                }
107            }
108            else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
109                int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
110                                               BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
111                if (requestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
112                    Log.v(TAG, "Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY");
113                    mStateMachine.handleAccessPermissionResult(intent);
114                }
115            }
116        }
117    };
118
119    /**
120     * Handlers for incoming service calls
121     */
122    private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub implements IProfileServiceBinder {
123        private HeadsetService mService;
124
125        public BluetoothHeadsetBinder(HeadsetService svc) {
126            mService = svc;
127        }
128        public boolean cleanup() {
129            mService = null;
130            return true;
131        }
132
133        private HeadsetService getService() {
134            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
135                Log.w(TAG,"Headset call not allowed for non-active user");
136                return null;
137            }
138
139            if (mService  != null && mService.isAvailable()) {
140                return mService;
141            }
142            return null;
143        }
144
145        public boolean connect(BluetoothDevice device) {
146            HeadsetService service = getService();
147            if (service == null) return false;
148            return service.connect(device);
149        }
150
151        public boolean disconnect(BluetoothDevice device) {
152            HeadsetService service = getService();
153            if (service == null) return false;
154            if (DBG) Log.d(TAG, "disconnect in HeadsetService");
155            return service.disconnect(device);
156        }
157
158        public List<BluetoothDevice> getConnectedDevices() {
159            HeadsetService service = getService();
160            if (service == null) return new ArrayList<BluetoothDevice>(0);
161            return service.getConnectedDevices();
162        }
163
164        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
165            HeadsetService service = getService();
166            if (service == null) return new ArrayList<BluetoothDevice>(0);
167            return service.getDevicesMatchingConnectionStates(states);
168        }
169
170        public int getConnectionState(BluetoothDevice device) {
171            HeadsetService service = getService();
172            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
173            return service.getConnectionState(device);
174        }
175
176        public boolean setPriority(BluetoothDevice device, int priority) {
177            HeadsetService service = getService();
178            if (service == null) return false;
179            return service.setPriority(device, priority);
180        }
181
182        public int getPriority(BluetoothDevice device) {
183            HeadsetService service = getService();
184            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
185            return service.getPriority(device);
186        }
187
188        public boolean startVoiceRecognition(BluetoothDevice device) {
189            HeadsetService service = getService();
190            if (service == null) return false;
191            return service.startVoiceRecognition(device);
192        }
193
194        public boolean stopVoiceRecognition(BluetoothDevice device) {
195            HeadsetService service = getService();
196            if (service == null) return false;
197            return service.stopVoiceRecognition(device);
198        }
199
200        public boolean isAudioOn() {
201            HeadsetService service = getService();
202            if (service == null) return false;
203            return service.isAudioOn();
204        }
205
206        public boolean isAudioConnected(BluetoothDevice device) {
207            HeadsetService service = getService();
208            if (service == null) return false;
209            return service.isAudioConnected(device);
210        }
211
212        public int getBatteryUsageHint(BluetoothDevice device) {
213            HeadsetService service = getService();
214            if (service == null) return 0;
215            return service.getBatteryUsageHint(device);
216        }
217
218        public boolean acceptIncomingConnect(BluetoothDevice device) {
219            HeadsetService service = getService();
220            if (service == null) return false;
221            return service.acceptIncomingConnect(device);
222        }
223
224        public boolean rejectIncomingConnect(BluetoothDevice device) {
225            HeadsetService service = getService();
226            if (service == null) return false;
227            return service.rejectIncomingConnect(device);
228        }
229
230        public int getAudioState(BluetoothDevice device) {
231            HeadsetService service = getService();
232            if (service == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
233            return service.getAudioState(device);
234        }
235
236        public boolean connectAudio() {
237            HeadsetService service = getService();
238            if (service == null) return false;
239            return service.connectAudio();
240        }
241
242        public boolean disconnectAudio() {
243            HeadsetService service = getService();
244            if (service == null) return false;
245            return service.disconnectAudio();
246        }
247
248        public void setAudioRouteAllowed(boolean allowed) {
249            HeadsetService service = getService();
250            if (service == null) return;
251            service.setAudioRouteAllowed(allowed);
252        }
253
254        public boolean getAudioRouteAllowed() {
255            HeadsetService service = getService();
256            if (service != null) {
257                return service.getAudioRouteAllowed();
258            }
259
260            return false;
261        }
262
263        public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
264            HeadsetService service = getService();
265            if (service == null) return false;
266            return service.startScoUsingVirtualVoiceCall(device);
267        }
268
269        public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
270            HeadsetService service = getService();
271            if (service == null) return false;
272            return service.stopScoUsingVirtualVoiceCall(device);
273        }
274
275        public void phoneStateChanged(int numActive, int numHeld, int callState,
276                                      String number, int type) {
277            HeadsetService service = getService();
278            if (service == null) return;
279            service.phoneStateChanged(numActive, numHeld, callState, number, type);
280        }
281
282        public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
283                                 String number, int type) {
284            HeadsetService service = getService();
285            if (service == null) return;
286            service.clccResponse(index, direction, status, mode, mpty, number, type);
287        }
288
289        public boolean sendVendorSpecificResultCode(BluetoothDevice device,
290                                                    String command,
291                                                    String arg) {
292            HeadsetService service = getService();
293            if (service == null) {
294                return false;
295            }
296            return service.sendVendorSpecificResultCode(device, command, arg);
297        }
298
299        public boolean enableWBS() {
300            HeadsetService service = getService();
301            if (service == null) return false;
302            return service.enableWBS();
303        }
304
305        public boolean disableWBS() {
306            HeadsetService service = getService();
307            if (service == null) return false;
308            return service.disableWBS();
309        }
310
311        public void bindResponse(int ind_id, boolean ind_status) {
312            HeadsetService service = getService();
313            if (service == null) return;
314            service.bindResponse(ind_id, ind_status);
315        }
316    };
317
318    //API methods
319    public static synchronized HeadsetService getHeadsetService(){
320        if (sHeadsetService != null && sHeadsetService.isAvailable()) {
321            if (DBG) Log.d(TAG, "getHeadsetService(): returning " + sHeadsetService);
322            return sHeadsetService;
323        }
324        if (DBG)  {
325            if (sHeadsetService == null) {
326                Log.d(TAG, "getHeadsetService(): service is NULL");
327            } else if (!(sHeadsetService.isAvailable())) {
328                Log.d(TAG,"getHeadsetService(): service is not available");
329            }
330        }
331        return null;
332    }
333
334    private static synchronized void setHeadsetService(HeadsetService instance) {
335        if (instance != null && instance.isAvailable()) {
336            if (DBG) Log.d(TAG, "setHeadsetService(): set to: " + sHeadsetService);
337            sHeadsetService = instance;
338        } else {
339            if (DBG)  {
340                if (sHeadsetService == null) {
341                    Log.d(TAG, "setHeadsetService(): service not available");
342                } else if (!sHeadsetService.isAvailable()) {
343                    Log.d(TAG,"setHeadsetService(): service is cleaning up");
344                }
345            }
346        }
347    }
348
349    private static synchronized void clearHeadsetService() {
350        sHeadsetService = null;
351    }
352
353    public boolean connect(BluetoothDevice device) {
354        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
355                                       "Need BLUETOOTH ADMIN permission");
356
357        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
358            return false;
359        }
360
361        int connectionState = mStateMachine.getConnectionState(device);
362        Log.d(TAG,"connectionState = " + connectionState);
363        if (connectionState == BluetoothProfile.STATE_CONNECTED ||
364            connectionState == BluetoothProfile.STATE_CONNECTING) {
365            return false;
366        }
367
368        mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device);
369        return true;
370    }
371
372    boolean disconnect(BluetoothDevice device) {
373        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
374                                       "Need BLUETOOTH ADMIN permission");
375        int connectionState = mStateMachine.getConnectionState(device);
376        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
377            connectionState != BluetoothProfile.STATE_CONNECTING) {
378            return false;
379        }
380
381        mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device);
382        return true;
383    }
384
385    public List<BluetoothDevice> getConnectedDevices() {
386        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
387        return mStateMachine.getConnectedDevices();
388    }
389
390    private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
391        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
392        return mStateMachine.getDevicesMatchingConnectionStates(states);
393    }
394
395    int getConnectionState(BluetoothDevice device) {
396        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
397        return mStateMachine.getConnectionState(device);
398    }
399
400    public boolean setPriority(BluetoothDevice device, int priority) {
401        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
402                                       "Need BLUETOOTH_ADMIN permission");
403        Settings.Global.putInt(getContentResolver(),
404            Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
405            priority);
406        if (DBG) Log.d(TAG, "Saved priority " + device + " = " + priority);
407        return true;
408    }
409
410    public int getPriority(BluetoothDevice device) {
411        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
412                                       "Need BLUETOOTH_ADMIN permission");
413        int priority = Settings.Global.getInt(getContentResolver(),
414            Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
415            BluetoothProfile.PRIORITY_UNDEFINED);
416        return priority;
417    }
418
419    boolean startVoiceRecognition(BluetoothDevice device) {
420        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
421        int connectionState = mStateMachine.getConnectionState(device);
422        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
423            connectionState != BluetoothProfile.STATE_CONNECTING) {
424            return false;
425        }
426        mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START);
427        return true;
428    }
429
430    boolean stopVoiceRecognition(BluetoothDevice device) {
431        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
432        // It seem that we really need to check the AudioOn state.
433        // But since we allow startVoiceRecognition in STATE_CONNECTED and
434        // STATE_CONNECTING state, we do these 2 in this method
435        int connectionState = mStateMachine.getConnectionState(device);
436        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
437            connectionState != BluetoothProfile.STATE_CONNECTING) {
438            return false;
439        }
440        mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP);
441        // TODO is this return correct when the voice recognition is not on?
442        return true;
443    }
444
445    boolean isAudioOn() {
446        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
447        return mStateMachine.isAudioOn();
448    }
449
450    boolean isAudioConnected(BluetoothDevice device) {
451        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
452        return mStateMachine.isAudioConnected(device);
453    }
454
455    int getBatteryUsageHint(BluetoothDevice device) {
456        // TODO(BT) ask for BT stack support?
457        return 0;
458    }
459
460    boolean acceptIncomingConnect(BluetoothDevice device) {
461        // TODO(BT) remove it if stack does access control
462        return false;
463    }
464
465    boolean rejectIncomingConnect(BluetoothDevice device) {
466        // TODO(BT) remove it if stack does access control
467        return false;
468    }
469
470    int getAudioState(BluetoothDevice device) {
471        return mStateMachine.getAudioState(device);
472    }
473
474    public void setAudioRouteAllowed(boolean allowed) {
475        mStateMachine.setAudioRouteAllowed(allowed);
476    }
477
478    public boolean getAudioRouteAllowed() {
479        return mStateMachine.getAudioRouteAllowed();
480    }
481
482    boolean connectAudio() {
483        // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission
484        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
485        if (!mStateMachine.isConnected()) {
486            return false;
487        }
488        if (mStateMachine.isAudioOn()) {
489            return false;
490        }
491        mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO);
492        return true;
493    }
494
495    boolean disconnectAudio() {
496        // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission
497        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
498        if (!mStateMachine.isAudioOn()) {
499            return false;
500        }
501        mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO);
502        return true;
503    }
504
505    boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
506        /* Do not ignore request if HSM state is still Disconnected or
507           Pending, it will be processed when transitioned to Connected */
508        mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_START, device);
509        return true;
510    }
511
512    boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
513        int connectionState = mStateMachine.getConnectionState(device);
514        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
515            connectionState != BluetoothProfile.STATE_CONNECTING) {
516            return false;
517        }
518        mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_STOP, device);
519        return true;
520    }
521
522    private void phoneStateChanged(int numActive, int numHeld, int callState,
523                                  String number, int type) {
524        enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
525        Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.CALL_STATE_CHANGED);
526        msg.obj = new HeadsetCallState(numActive, numHeld, callState, number, type);
527        msg.arg1 = 0; // false
528        mStateMachine.sendMessage(msg);
529    }
530
531    private void clccResponse(int index, int direction, int status, int mode, boolean mpty,
532                             String number, int type) {
533        enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
534        mStateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE,
535            new HeadsetClccResponse(index, direction, status, mode, mpty, number, type));
536    }
537
538    private boolean sendVendorSpecificResultCode(BluetoothDevice device,
539                                                 String command,
540                                                 String arg) {
541        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
542        int connectionState = mStateMachine.getConnectionState(device);
543        if (connectionState != BluetoothProfile.STATE_CONNECTED) {
544            return false;
545        }
546        // Currently we support only "+ANDROID".
547        if (!command.equals(BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID)) {
548            Log.w(TAG, "Disallowed unsolicited result code command: " + command);
549            return false;
550        }
551        mStateMachine.sendMessage(HeadsetStateMachine.SEND_VENDOR_SPECIFIC_RESULT_CODE,
552                new HeadsetVendorSpecificResultCode(device, command, arg));
553        return true;
554    }
555
556    boolean enableWBS() {
557        // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission
558        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
559        if (!mStateMachine.isConnected()) {
560            return false;
561        }
562        if (mStateMachine.isAudioOn()) {
563            return false;
564        }
565
566        for (BluetoothDevice device: getConnectedDevices()) {
567            mStateMachine.sendMessage(HeadsetStateMachine.ENABLE_WBS,device);
568        }
569
570        return true;
571    }
572
573    boolean disableWBS() {
574        // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission
575        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
576        if (!mStateMachine.isConnected()) {
577            return false;
578        }
579        if (mStateMachine.isAudioOn()) {
580            return false;
581        }
582        for (BluetoothDevice device: getConnectedDevices()) {
583            mStateMachine.sendMessage(HeadsetStateMachine.DISABLE_WBS,device);
584        }
585        return true;
586    }
587
588    private boolean bindResponse(int ind_id, boolean ind_status) {
589        for (BluetoothDevice device: getConnectedDevices()) {
590            int connectionState = mStateMachine.getConnectionState(device);
591            if (connectionState != BluetoothProfile.STATE_CONNECTED &&
592                connectionState != BluetoothProfile.STATE_CONNECTING) {
593                continue;
594            }
595            if (DBG) Log.d("Bind Response sent for", device.getAddress());
596            Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.BIND_RESPONSE);
597            msg.obj = device;
598            msg.arg1 = ind_id;
599            msg.arg2 = (ind_status == true) ? 1 : 0;
600            mStateMachine.sendMessage(msg);
601            return true;
602        }
603        return false;
604    }
605
606    @Override
607    public void dump(StringBuilder sb) {
608        super.dump(sb);
609        if (mStateMachine != null) {
610            mStateMachine.dump(sb);
611        }
612    }
613}
614