HeadsetService.java revision cdc8ec8d84c7f16b851238da0d80b51335c4ea07
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        mStateMachine.doQuit();
82        return true;
83    }
84
85    protected boolean cleanup() {
86        if (mStateMachine != null) {
87            mStateMachine.cleanup();
88        }
89        clearHeadsetService();
90        return true;
91    }
92
93    private final BroadcastReceiver mHeadsetReceiver = new BroadcastReceiver() {
94        @Override
95        public void onReceive(Context context, Intent intent) {
96            String action = intent.getAction();
97            if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
98                mStateMachine.sendMessage(HeadsetStateMachine.INTENT_BATTERY_CHANGED, intent);
99            } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
100                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
101                if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
102                    mStateMachine.sendMessage(HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED,
103                                              intent);
104                }
105            }
106            else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
107                Log.v(TAG, "HeadsetService -  Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY");
108                mStateMachine.handleAccessPermissionResult(intent);
109            }
110        }
111    };
112
113    /**
114     * Handlers for incoming service calls
115     */
116    private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub implements IProfileServiceBinder {
117        private HeadsetService mService;
118
119        public BluetoothHeadsetBinder(HeadsetService svc) {
120            mService = svc;
121        }
122        public boolean cleanup() {
123            mService = null;
124            return true;
125        }
126
127        private HeadsetService getService() {
128            if (!Utils.checkCaller()) {
129                Log.w(TAG,"Headset call not allowed for non-active user");
130                return null;
131            }
132
133            if (mService  != null && mService.isAvailable()) {
134                return mService;
135            }
136            return null;
137        }
138
139        public boolean connect(BluetoothDevice device) {
140            HeadsetService service = getService();
141            if (service == null) return false;
142            return service.connect(device);
143        }
144
145        public boolean disconnect(BluetoothDevice device) {
146            HeadsetService service = getService();
147            if (service == null) return false;
148            return service.disconnect(device);
149        }
150
151        public List<BluetoothDevice> getConnectedDevices() {
152            HeadsetService service = getService();
153            if (service == null) return new ArrayList<BluetoothDevice>(0);
154            return service.getConnectedDevices();
155        }
156
157        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
158            HeadsetService service = getService();
159            if (service == null) return new ArrayList<BluetoothDevice>(0);
160            return service.getDevicesMatchingConnectionStates(states);
161        }
162
163        public int getConnectionState(BluetoothDevice device) {
164            HeadsetService service = getService();
165            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
166            return service.getConnectionState(device);
167        }
168
169        public boolean setPriority(BluetoothDevice device, int priority) {
170            HeadsetService service = getService();
171            if (service == null) return false;
172            return service.setPriority(device, priority);
173        }
174
175        public int getPriority(BluetoothDevice device) {
176            HeadsetService service = getService();
177            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
178            return service.getPriority(device);
179        }
180
181        public boolean startVoiceRecognition(BluetoothDevice device) {
182            HeadsetService service = getService();
183            if (service == null) return false;
184            return service.startVoiceRecognition(device);
185        }
186
187        public boolean stopVoiceRecognition(BluetoothDevice device) {
188            HeadsetService service = getService();
189            if (service == null) return false;
190            return service.stopVoiceRecognition(device);
191        }
192
193        public boolean isAudioOn() {
194            HeadsetService service = getService();
195            if (service == null) return false;
196            return service.isAudioOn();
197        }
198
199        public boolean isAudioConnected(BluetoothDevice device) {
200            HeadsetService service = getService();
201            if (service == null) return false;
202            return service.isAudioConnected(device);
203        }
204
205        public int getBatteryUsageHint(BluetoothDevice device) {
206            HeadsetService service = getService();
207            if (service == null) return 0;
208            return service.getBatteryUsageHint(device);
209        }
210
211        public boolean acceptIncomingConnect(BluetoothDevice device) {
212            HeadsetService service = getService();
213            if (service == null) return false;
214            return service.acceptIncomingConnect(device);
215        }
216
217        public boolean rejectIncomingConnect(BluetoothDevice device) {
218            HeadsetService service = getService();
219            if (service == null) return false;
220            return service.rejectIncomingConnect(device);
221        }
222
223        public int getAudioState(BluetoothDevice device) {
224            HeadsetService service = getService();
225            if (service == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
226            return service.getAudioState(device);
227        }
228
229        public boolean connectAudio() {
230            HeadsetService service = getService();
231            if (service == null) return false;
232            return service.connectAudio();
233        }
234
235        public boolean disconnectAudio() {
236            HeadsetService service = getService();
237            if (service == null) return false;
238            return service.disconnectAudio();
239        }
240
241        public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
242            HeadsetService service = getService();
243            if (service == null) return false;
244            return service.startScoUsingVirtualVoiceCall(device);
245        }
246
247        public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
248            HeadsetService service = getService();
249            if (service == null) return false;
250            return service.stopScoUsingVirtualVoiceCall(device);
251        }
252
253        public void phoneStateChanged(int numActive, int numHeld, int callState,
254                                      String number, int type) {
255            HeadsetService service = getService();
256            if (service == null) return;
257            service.phoneStateChanged(numActive, numHeld, callState, number, type);
258        }
259
260        public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
261                                 String number, int type) {
262            HeadsetService service = getService();
263            if (service == null) return;
264            service.clccResponse(index, direction, status, mode, mpty, number, type);
265        }
266
267        public boolean sendVendorSpecificResultCode(BluetoothDevice device,
268                                                    String command,
269                                                    String arg) {
270            HeadsetService service = getService();
271            if (service == null) {
272                return false;
273            }
274            return service.sendVendorSpecificResultCode(device, command, arg);
275        }
276    };
277
278    //API methods
279    public static synchronized HeadsetService getHeadsetService(){
280        if (sHeadsetService != null && sHeadsetService.isAvailable()) {
281            if (DBG) Log.d(TAG, "getHeadsetService(): returning " + sHeadsetService);
282            return sHeadsetService;
283        }
284        if (DBG)  {
285            if (sHeadsetService == null) {
286                Log.d(TAG, "getHeadsetService(): service is NULL");
287            } else if (!(sHeadsetService.isAvailable())) {
288                Log.d(TAG,"getHeadsetService(): service is not available");
289            }
290        }
291        return null;
292    }
293
294    private static synchronized void setHeadsetService(HeadsetService instance) {
295        if (instance != null && instance.isAvailable()) {
296            if (DBG) Log.d(TAG, "setHeadsetService(): set to: " + sHeadsetService);
297            sHeadsetService = instance;
298        } else {
299            if (DBG)  {
300                if (sHeadsetService == null) {
301                    Log.d(TAG, "setHeadsetService(): service not available");
302                } else if (!sHeadsetService.isAvailable()) {
303                    Log.d(TAG,"setHeadsetService(): service is cleaning up");
304                }
305            }
306        }
307    }
308
309    private static synchronized void clearHeadsetService() {
310        sHeadsetService = null;
311    }
312
313    public boolean connect(BluetoothDevice device) {
314        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
315                                       "Need BLUETOOTH ADMIN permission");
316
317        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
318            return false;
319        }
320
321        int connectionState = mStateMachine.getConnectionState(device);
322        if (connectionState == BluetoothProfile.STATE_CONNECTED ||
323            connectionState == BluetoothProfile.STATE_CONNECTING) {
324            return false;
325        }
326
327        mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device);
328        return true;
329    }
330
331    boolean disconnect(BluetoothDevice device) {
332        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
333                                       "Need BLUETOOTH ADMIN permission");
334        int connectionState = mStateMachine.getConnectionState(device);
335        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
336            connectionState != BluetoothProfile.STATE_CONNECTING) {
337            return false;
338        }
339
340        mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device);
341        return true;
342    }
343
344    public List<BluetoothDevice> getConnectedDevices() {
345        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
346        return mStateMachine.getConnectedDevices();
347    }
348
349    private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
350        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
351        return mStateMachine.getDevicesMatchingConnectionStates(states);
352    }
353
354    int getConnectionState(BluetoothDevice device) {
355        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
356        return mStateMachine.getConnectionState(device);
357    }
358
359    public boolean setPriority(BluetoothDevice device, int priority) {
360        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
361                                       "Need BLUETOOTH_ADMIN permission");
362        Settings.Global.putInt(getContentResolver(),
363            Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
364            priority);
365        if (DBG) Log.d(TAG, "Saved priority " + device + " = " + priority);
366        return true;
367    }
368
369    public int getPriority(BluetoothDevice device) {
370        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
371                                       "Need BLUETOOTH_ADMIN permission");
372        int priority = Settings.Global.getInt(getContentResolver(),
373            Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
374            BluetoothProfile.PRIORITY_UNDEFINED);
375        return priority;
376    }
377
378    boolean startVoiceRecognition(BluetoothDevice device) {
379        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
380        int connectionState = mStateMachine.getConnectionState(device);
381        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
382            connectionState != BluetoothProfile.STATE_CONNECTING) {
383            return false;
384        }
385        mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START);
386        return true;
387    }
388
389    boolean stopVoiceRecognition(BluetoothDevice device) {
390        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
391        // It seem that we really need to check the AudioOn state.
392        // But since we allow startVoiceRecognition in STATE_CONNECTED and
393        // STATE_CONNECTING state, we do these 2 in this method
394        int connectionState = mStateMachine.getConnectionState(device);
395        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
396            connectionState != BluetoothProfile.STATE_CONNECTING) {
397            return false;
398        }
399        mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP);
400        // TODO is this return correct when the voice recognition is not on?
401        return true;
402    }
403
404    boolean isAudioOn() {
405        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
406        return mStateMachine.isAudioOn();
407    }
408
409    boolean isAudioConnected(BluetoothDevice device) {
410        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
411        return mStateMachine.isAudioConnected(device);
412    }
413
414    int getBatteryUsageHint(BluetoothDevice device) {
415        // TODO(BT) ask for BT stack support?
416        return 0;
417    }
418
419    boolean acceptIncomingConnect(BluetoothDevice device) {
420        // TODO(BT) remove it if stack does access control
421        return false;
422    }
423
424    boolean rejectIncomingConnect(BluetoothDevice device) {
425        // TODO(BT) remove it if stack does access control
426        return false;
427    }
428
429    int getAudioState(BluetoothDevice device) {
430        return mStateMachine.getAudioState(device);
431    }
432
433    boolean connectAudio() {
434        // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission
435        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
436        if (!mStateMachine.isConnected()) {
437            return false;
438        }
439        if (mStateMachine.isAudioOn()) {
440            return false;
441        }
442        mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO);
443        return true;
444    }
445
446    boolean disconnectAudio() {
447        // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission
448        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
449        if (!mStateMachine.isAudioOn()) {
450            return false;
451        }
452        mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO);
453        return true;
454    }
455
456    boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
457        int connectionState = mStateMachine.getConnectionState(device);
458        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
459            connectionState != BluetoothProfile.STATE_CONNECTING) {
460            return false;
461        }
462        mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_START, device);
463        return true;
464    }
465
466    boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
467        int connectionState = mStateMachine.getConnectionState(device);
468        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
469            connectionState != BluetoothProfile.STATE_CONNECTING) {
470            return false;
471        }
472        mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_STOP, device);
473        return true;
474    }
475
476    private void phoneStateChanged(int numActive, int numHeld, int callState,
477                                  String number, int type) {
478        enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
479        Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.CALL_STATE_CHANGED);
480        msg.obj = new HeadsetCallState(numActive, numHeld, callState, number, type);
481        msg.arg1 = 0; // false
482        mStateMachine.sendMessage(msg);
483    }
484
485    private void clccResponse(int index, int direction, int status, int mode, boolean mpty,
486                             String number, int type) {
487        enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
488        mStateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE,
489            new HeadsetClccResponse(index, direction, status, mode, mpty, number, type));
490    }
491
492    private boolean sendVendorSpecificResultCode(BluetoothDevice device,
493                                                 String command,
494                                                 String arg) {
495        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
496        int connectionState = mStateMachine.getConnectionState(device);
497        if (connectionState != BluetoothProfile.STATE_CONNECTED) {
498            return false;
499        }
500        // Currently we support only "+ANDROID".
501        if (!command.equals(BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID)) {
502            Log.w(TAG, "Disallowed unsolicited result code command: " + command);
503            return false;
504        }
505        mStateMachine.sendMessage(HeadsetStateMachine.SEND_VENDOR_SPECIFIC_RESULT_CODE,
506                new HeadsetVendorSpecificResultCode(command, arg));
507        return true;
508    }
509
510}
511