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 
17 package com.android.bluetooth.hfp;
18 
19 import android.bluetooth.BluetoothDevice;
20 import android.bluetooth.BluetoothHeadset;
21 import android.bluetooth.BluetoothProfile;
22 import android.bluetooth.IBluetoothHeadset;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.PackageManager;
28 import android.media.AudioManager;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.provider.Settings;
32 import android.util.Log;
33 import com.android.bluetooth.btservice.ProfileService;
34 import com.android.bluetooth.Utils;
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.Iterator;
38 import java.util.Map;
39 
40 /**
41  * Provides Bluetooth Headset and Handsfree profile, as a service in
42  * the Bluetooth application.
43  * @hide
44  */
45 public 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 
getName()53     protected String getName() {
54         return TAG;
55     }
56 
initBinder()57     public IProfileServiceBinder initBinder() {
58         return new BluetoothHeadsetBinder(this);
59     }
60 
start()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 
stop()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 
cleanup()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 
BluetoothHeadsetBinder(HeadsetService svc)123         public BluetoothHeadsetBinder(HeadsetService svc) {
124             mService = svc;
125         }
cleanup()126         public boolean cleanup() {
127             mService = null;
128             return true;
129         }
130 
getService()131         private HeadsetService getService() {
132             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
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 
connect(BluetoothDevice device)143         public boolean connect(BluetoothDevice device) {
144             HeadsetService service = getService();
145             if (service == null) return false;
146             return service.connect(device);
147         }
148 
disconnect(BluetoothDevice device)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 
getConnectedDevices()156         public List<BluetoothDevice> getConnectedDevices() {
157             HeadsetService service = getService();
158             if (service == null) return new ArrayList<BluetoothDevice>(0);
159             return service.getConnectedDevices();
160         }
161 
getDevicesMatchingConnectionStates(int[] states)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 
getConnectionState(BluetoothDevice device)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 
setPriority(BluetoothDevice device, int priority)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 
getPriority(BluetoothDevice device)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 
startVoiceRecognition(BluetoothDevice device)186         public boolean startVoiceRecognition(BluetoothDevice device) {
187             HeadsetService service = getService();
188             if (service == null) return false;
189             return service.startVoiceRecognition(device);
190         }
191 
stopVoiceRecognition(BluetoothDevice device)192         public boolean stopVoiceRecognition(BluetoothDevice device) {
193             HeadsetService service = getService();
194             if (service == null) return false;
195             return service.stopVoiceRecognition(device);
196         }
197 
isAudioOn()198         public boolean isAudioOn() {
199             HeadsetService service = getService();
200             if (service == null) return false;
201             return service.isAudioOn();
202         }
203 
isAudioConnected(BluetoothDevice device)204         public boolean isAudioConnected(BluetoothDevice device) {
205             HeadsetService service = getService();
206             if (service == null) return false;
207             return service.isAudioConnected(device);
208         }
209 
getBatteryUsageHint(BluetoothDevice device)210         public int getBatteryUsageHint(BluetoothDevice device) {
211             HeadsetService service = getService();
212             if (service == null) return 0;
213             return service.getBatteryUsageHint(device);
214         }
215 
acceptIncomingConnect(BluetoothDevice device)216         public boolean acceptIncomingConnect(BluetoothDevice device) {
217             HeadsetService service = getService();
218             if (service == null) return false;
219             return service.acceptIncomingConnect(device);
220         }
221 
rejectIncomingConnect(BluetoothDevice device)222         public boolean rejectIncomingConnect(BluetoothDevice device) {
223             HeadsetService service = getService();
224             if (service == null) return false;
225             return service.rejectIncomingConnect(device);
226         }
227 
getAudioState(BluetoothDevice device)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 
connectAudio()234         public boolean connectAudio() {
235             HeadsetService service = getService();
236             if (service == null) return false;
237             return service.connectAudio();
238         }
239 
disconnectAudio()240         public boolean disconnectAudio() {
241             HeadsetService service = getService();
242             if (service == null) return false;
243             return service.disconnectAudio();
244         }
245 
startScoUsingVirtualVoiceCall(BluetoothDevice device)246         public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
247             HeadsetService service = getService();
248             if (service == null) return false;
249             return service.startScoUsingVirtualVoiceCall(device);
250         }
251 
stopScoUsingVirtualVoiceCall(BluetoothDevice device)252         public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
253             HeadsetService service = getService();
254             if (service == null) return false;
255             return service.stopScoUsingVirtualVoiceCall(device);
256         }
257 
phoneStateChanged(int numActive, int numHeld, int callState, String number, int type)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 
clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type)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 
sendVendorSpecificResultCode(BluetoothDevice device, String command, String arg)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 
enableWBS()282         public boolean enableWBS() {
283             HeadsetService service = getService();
284             if (service == null) return false;
285             return service.enableWBS();
286         }
287 
disableWBS()288         public boolean disableWBS() {
289             HeadsetService service = getService();
290             if (service == null) return false;
291             return service.disableWBS();
292         }
293     };
294 
295     //API methods
getHeadsetService()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 
setHeadsetService(HeadsetService instance)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 
clearHeadsetService()326     private static synchronized void clearHeadsetService() {
327         sHeadsetService = null;
328     }
329 
connect(BluetoothDevice device)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 
disconnect(BluetoothDevice device)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 
getConnectedDevices()362     public List<BluetoothDevice> getConnectedDevices() {
363         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
364         return mStateMachine.getConnectedDevices();
365     }
366 
getDevicesMatchingConnectionStates(int[] states)367     private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
368         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
369         return mStateMachine.getDevicesMatchingConnectionStates(states);
370     }
371 
getConnectionState(BluetoothDevice device)372     int getConnectionState(BluetoothDevice device) {
373         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
374         return mStateMachine.getConnectionState(device);
375     }
376 
setPriority(BluetoothDevice device, int priority)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 
getPriority(BluetoothDevice device)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 
startVoiceRecognition(BluetoothDevice device)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 
stopVoiceRecognition(BluetoothDevice device)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 
isAudioOn()422     boolean isAudioOn() {
423         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
424         return mStateMachine.isAudioOn();
425     }
426 
isAudioConnected(BluetoothDevice device)427     boolean isAudioConnected(BluetoothDevice device) {
428         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
429         return mStateMachine.isAudioConnected(device);
430     }
431 
getBatteryUsageHint(BluetoothDevice device)432     int getBatteryUsageHint(BluetoothDevice device) {
433         // TODO(BT) ask for BT stack support?
434         return 0;
435     }
436 
acceptIncomingConnect(BluetoothDevice device)437     boolean acceptIncomingConnect(BluetoothDevice device) {
438         // TODO(BT) remove it if stack does access control
439         return false;
440     }
441 
rejectIncomingConnect(BluetoothDevice device)442     boolean rejectIncomingConnect(BluetoothDevice device) {
443         // TODO(BT) remove it if stack does access control
444         return false;
445     }
446 
getAudioState(BluetoothDevice device)447     int getAudioState(BluetoothDevice device) {
448         return mStateMachine.getAudioState(device);
449     }
450 
connectAudio()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 
disconnectAudio()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 
startScoUsingVirtualVoiceCall(BluetoothDevice device)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 
stopScoUsingVirtualVoiceCall(BluetoothDevice device)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 
phoneStateChanged(int numActive, int numHeld, int callState, String number, int type)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 
clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type)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 
sendVendorSpecificResultCode(BluetoothDevice device, String command, String arg)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 
enableWBS()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 
disableWBS()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     @Override
dump(StringBuilder sb)561     public void dump(StringBuilder sb) {
562         super.dump(sb);
563         if (mStateMachine != null) {
564             mStateMachine.dump(sb);
565         }
566     }
567 }
568