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