Home | History | Annotate | Download | only in hfp
      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 
     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.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 
    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     @Override
    561     public void dump(StringBuilder sb) {
    562         super.dump(sb);
    563         if (mStateMachine != null) {
    564             mStateMachine.dump(sb);
    565         }
    566     }
    567 }
    568