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