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