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