HeadsetService.java revision df9d0fe584874f8cafa6ecedb3c3c054c9e99cba
1/*
2 * Copyright (C) 2012 Google Inc.
3 */
4
5package com.android.bluetooth.hfp;
6
7import android.bluetooth.BluetoothDevice;
8import android.bluetooth.BluetoothHeadset;
9import android.bluetooth.BluetoothProfile;
10import android.bluetooth.IBluetoothHeadset;
11import android.content.BroadcastReceiver;
12import android.content.Context;
13import android.content.Intent;
14import android.content.IntentFilter;
15import android.content.pm.PackageManager;
16import android.media.AudioManager;
17import android.os.Handler;
18import android.os.Message;
19import android.provider.Settings;
20import android.util.Log;
21import com.android.bluetooth.btservice.ProfileService;
22import com.android.bluetooth.Utils;
23import java.util.ArrayList;
24import java.util.List;
25import java.util.Iterator;
26import java.util.Map;
27
28/**
29 * Provides Bluetooth Headset and Handsfree profile, as a service in
30 * the Bluetooth application.
31 * @hide
32 */
33public class HeadsetService extends ProfileService {
34    private static final boolean DBG = false;
35    private static final String TAG = "HeadsetService";
36    private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE;
37
38    private HeadsetStateMachine mStateMachine;
39    private static HeadsetService sHeadsetService;
40
41    protected String getName() {
42        return TAG;
43    }
44
45    public IProfileServiceBinder initBinder() {
46        return new BluetoothHeadsetBinder(this);
47    }
48
49    protected boolean start() {
50        mStateMachine = HeadsetStateMachine.make(this);
51        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
52        filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
53        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
54        try {
55            registerReceiver(mHeadsetReceiver, filter);
56        } catch (Exception e) {
57            Log.w(TAG,"Unable to register headset receiver",e);
58        }
59        setHeadsetService(this);
60        return true;
61    }
62
63    protected boolean stop() {
64        try {
65            unregisterReceiver(mHeadsetReceiver);
66        } catch (Exception e) {
67            Log.w(TAG,"Unable to unregister headset receiver",e);
68        }
69        mStateMachine.doQuit();
70        return true;
71    }
72
73    protected boolean cleanup() {
74        if (mStateMachine != null) {
75            mStateMachine.cleanup();
76        }
77        clearHeadsetService();
78        return true;
79    }
80
81    private final BroadcastReceiver mHeadsetReceiver = new BroadcastReceiver() {
82        @Override
83        public void onReceive(Context context, Intent intent) {
84            String action = intent.getAction();
85            if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
86                mStateMachine.sendMessage(HeadsetStateMachine.INTENT_BATTERY_CHANGED, intent);
87            } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
88                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
89                if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
90                    mStateMachine.sendMessage(HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED,
91                                              intent);
92                }
93            }
94            else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
95                Log.v(TAG, "HeadsetService -  Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY");
96                mStateMachine.handleAccessPermissionResult(intent);
97            }
98        }
99    };
100
101    /**
102     * Handlers for incoming service calls
103     */
104    private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub implements IProfileServiceBinder {
105        private HeadsetService mService;
106
107        public BluetoothHeadsetBinder(HeadsetService svc) {
108            mService = svc;
109        }
110        public boolean cleanup() {
111            mService = null;
112            return true;
113        }
114
115        private HeadsetService getService() {
116            if (!Utils.checkCaller()) {
117                Log.w(TAG,"Headset call not allowed for non-active user");
118                return null;
119            }
120
121            if (mService  != null && mService.isAvailable()) {
122                return mService;
123            }
124            return null;
125        }
126
127        public boolean connect(BluetoothDevice device) {
128            HeadsetService service = getService();
129            if (service == null) return false;
130            return service.connect(device);
131        }
132
133        public boolean disconnect(BluetoothDevice device) {
134            HeadsetService service = getService();
135            if (service == null) return false;
136            return service.disconnect(device);
137        }
138
139        public List<BluetoothDevice> getConnectedDevices() {
140            HeadsetService service = getService();
141            if (service == null) return new ArrayList<BluetoothDevice>(0);
142            return service.getConnectedDevices();
143        }
144
145        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
146            HeadsetService service = getService();
147            if (service == null) return new ArrayList<BluetoothDevice>(0);
148            return service.getDevicesMatchingConnectionStates(states);
149        }
150
151        public int getConnectionState(BluetoothDevice device) {
152            HeadsetService service = getService();
153            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
154            return service.getConnectionState(device);
155        }
156
157        public boolean setPriority(BluetoothDevice device, int priority) {
158            HeadsetService service = getService();
159            if (service == null) return false;
160            return service.setPriority(device, priority);
161        }
162
163        public int getPriority(BluetoothDevice device) {
164            HeadsetService service = getService();
165            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
166            return service.getPriority(device);
167        }
168
169        public boolean startVoiceRecognition(BluetoothDevice device) {
170            HeadsetService service = getService();
171            if (service == null) return false;
172            return service.startVoiceRecognition(device);
173        }
174
175        public boolean stopVoiceRecognition(BluetoothDevice device) {
176            HeadsetService service = getService();
177            if (service == null) return false;
178            return service.stopVoiceRecognition(device);
179        }
180
181        public boolean isAudioOn() {
182            HeadsetService service = getService();
183            if (service == null) return false;
184            return service.isAudioOn();
185        }
186
187        public boolean isAudioConnected(BluetoothDevice device) {
188            HeadsetService service = getService();
189            if (service == null) return false;
190            return service.isAudioConnected(device);
191        }
192
193        public int getBatteryUsageHint(BluetoothDevice device) {
194            HeadsetService service = getService();
195            if (service == null) return 0;
196            return service.getBatteryUsageHint(device);
197        }
198
199        public boolean acceptIncomingConnect(BluetoothDevice device) {
200            HeadsetService service = getService();
201            if (service == null) return false;
202            return service.acceptIncomingConnect(device);
203        }
204
205        public boolean rejectIncomingConnect(BluetoothDevice device) {
206            HeadsetService service = getService();
207            if (service == null) return false;
208            return service.rejectIncomingConnect(device);
209        }
210
211        public int getAudioState(BluetoothDevice device) {
212            HeadsetService service = getService();
213            if (service == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
214            return service.getAudioState(device);
215        }
216
217        public boolean connectAudio() {
218            HeadsetService service = getService();
219            if (service == null) return false;
220            return service.connectAudio();
221        }
222
223        public boolean disconnectAudio() {
224            HeadsetService service = getService();
225            if (service == null) return false;
226            return service.disconnectAudio();
227        }
228
229        public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
230            HeadsetService service = getService();
231            if (service == null) return false;
232            return service.startScoUsingVirtualVoiceCall(device);
233        }
234
235        public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
236            HeadsetService service = getService();
237            if (service == null) return false;
238            return service.stopScoUsingVirtualVoiceCall(device);
239        }
240
241        public void phoneStateChanged(int numActive, int numHeld, int callState,
242                                      String number, int type) {
243            HeadsetService service = getService();
244            if (service == null) return;
245            service.phoneStateChanged(numActive, numHeld, callState, number, type);
246        }
247
248        public void roamChanged(boolean roam) {
249            HeadsetService service = getService();
250            if (service == null) return;
251            service.roamChanged(roam);
252        }
253
254        public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
255                                 String number, int type) {
256            HeadsetService service = getService();
257            if (service == null) return;
258            service.clccResponse(index, direction, status, mode, mpty, number, type);
259        }
260    };
261
262    //API methods
263    public static synchronized HeadsetService getHeadsetService(){
264        if (sHeadsetService != null && sHeadsetService.isAvailable()) {
265            if (DBG) Log.d(TAG, "getHeadsetService(): returning " + sHeadsetService);
266            return sHeadsetService;
267        }
268        if (DBG)  {
269            if (sHeadsetService == null) {
270                Log.d(TAG, "getHeadsetService(): service is NULL");
271            } else if (!(sHeadsetService.isAvailable())) {
272                Log.d(TAG,"getHeadsetService(): service is not available");
273            }
274        }
275        return null;
276    }
277
278    private static synchronized void setHeadsetService(HeadsetService instance) {
279        if (instance != null && instance.isAvailable()) {
280            if (DBG) Log.d(TAG, "setHeadsetService(): set to: " + sHeadsetService);
281            sHeadsetService = instance;
282        } else {
283            if (DBG)  {
284                if (sHeadsetService == null) {
285                    Log.d(TAG, "setHeadsetService(): service not available");
286                } else if (!sHeadsetService.isAvailable()) {
287                    Log.d(TAG,"setHeadsetService(): service is cleaning up");
288                }
289            }
290        }
291    }
292
293    private static synchronized void clearHeadsetService() {
294        sHeadsetService = null;
295    }
296
297    public boolean connect(BluetoothDevice device) {
298        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
299                                       "Need BLUETOOTH ADMIN permission");
300
301        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
302            return false;
303        }
304
305        int connectionState = mStateMachine.getConnectionState(device);
306        if (connectionState == BluetoothProfile.STATE_CONNECTED ||
307            connectionState == BluetoothProfile.STATE_CONNECTING) {
308            return false;
309        }
310
311        mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device);
312        return true;
313    }
314
315    boolean disconnect(BluetoothDevice device) {
316        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
317                                       "Need BLUETOOTH ADMIN permission");
318        int connectionState = mStateMachine.getConnectionState(device);
319        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
320            connectionState != BluetoothProfile.STATE_CONNECTING) {
321            return false;
322        }
323
324        mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device);
325        return true;
326    }
327
328    public List<BluetoothDevice> getConnectedDevices() {
329        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
330        return mStateMachine.getConnectedDevices();
331    }
332
333    private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
334        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
335        return mStateMachine.getDevicesMatchingConnectionStates(states);
336    }
337
338    int getConnectionState(BluetoothDevice device) {
339        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
340        return mStateMachine.getConnectionState(device);
341    }
342
343    public boolean setPriority(BluetoothDevice device, int priority) {
344        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
345                                       "Need BLUETOOTH_ADMIN permission");
346        Settings.Global.putInt(getContentResolver(),
347            Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
348            priority);
349        if (DBG) Log.d(TAG, "Saved priority " + device + " = " + priority);
350        return true;
351    }
352
353    public int getPriority(BluetoothDevice device) {
354        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
355                                       "Need BLUETOOTH_ADMIN permission");
356        int priority = Settings.Global.getInt(getContentResolver(),
357            Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
358            BluetoothProfile.PRIORITY_UNDEFINED);
359        return priority;
360    }
361
362    boolean startVoiceRecognition(BluetoothDevice device) {
363        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
364        int connectionState = mStateMachine.getConnectionState(device);
365        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
366            connectionState != BluetoothProfile.STATE_CONNECTING) {
367            return false;
368        }
369        mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START);
370        return true;
371    }
372
373    boolean stopVoiceRecognition(BluetoothDevice device) {
374        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
375        // It seem that we really need to check the AudioOn state.
376        // But since we allow startVoiceRecognition in STATE_CONNECTED and
377        // STATE_CONNECTING state, we do these 2 in this method
378        int connectionState = mStateMachine.getConnectionState(device);
379        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
380            connectionState != BluetoothProfile.STATE_CONNECTING) {
381            return false;
382        }
383        mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP);
384        // TODO is this return correct when the voice recognition is not on?
385        return true;
386    }
387
388    boolean isAudioOn() {
389        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
390        return mStateMachine.isAudioOn();
391    }
392
393    boolean isAudioConnected(BluetoothDevice device) {
394        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
395        return mStateMachine.isAudioConnected(device);
396    }
397
398    int getBatteryUsageHint(BluetoothDevice device) {
399        // TODO(BT) ask for BT stack support?
400        return 0;
401    }
402
403    boolean acceptIncomingConnect(BluetoothDevice device) {
404        // TODO(BT) remove it if stack does access control
405        return false;
406    }
407
408    boolean rejectIncomingConnect(BluetoothDevice device) {
409        // TODO(BT) remove it if stack does access control
410        return false;
411    }
412
413    int getAudioState(BluetoothDevice device) {
414        return mStateMachine.getAudioState(device);
415    }
416
417    boolean connectAudio() {
418        // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission
419        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
420        if (!mStateMachine.isConnected()) {
421            return false;
422        }
423        if (mStateMachine.isAudioOn()) {
424            return false;
425        }
426        mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO);
427        return true;
428    }
429
430    boolean disconnectAudio() {
431        // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission
432        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
433        if (!mStateMachine.isAudioOn()) {
434            return false;
435        }
436        mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO);
437        return true;
438    }
439
440    boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
441        int connectionState = mStateMachine.getConnectionState(device);
442        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
443            connectionState != BluetoothProfile.STATE_CONNECTING) {
444            return false;
445        }
446        mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_START, device);
447        return true;
448    }
449
450    boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
451        int connectionState = mStateMachine.getConnectionState(device);
452        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
453            connectionState != BluetoothProfile.STATE_CONNECTING) {
454            return false;
455        }
456        mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_STOP, device);
457        return true;
458    }
459
460    private void phoneStateChanged(int numActive, int numHeld, int callState,
461                                  String number, int type) {
462        enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
463        Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.CALL_STATE_CHANGED);
464        msg.obj = new HeadsetCallState(numActive, numHeld, callState, number, type);
465        msg.arg1 = 0; // false
466        mStateMachine.sendMessage(msg);
467    }
468
469    private void roamChanged(boolean roam) {
470        enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
471        mStateMachine.sendMessage(HeadsetStateMachine.ROAM_CHANGED, roam);
472    }
473
474    private void clccResponse(int index, int direction, int status, int mode, boolean mpty,
475                             String number, int type) {
476        enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
477        mStateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE,
478            new HeadsetClccResponse(index, direction, status, mode, mpty, number, type));
479    }
480
481}
482