Home | History | Annotate | Download | only in connserv
      1 /*
      2  * Copyright (C) 2016 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 package com.android.bluetooth.hfpclient.connserv;
     17 
     18 import android.bluetooth.BluetoothAdapter;
     19 import android.bluetooth.BluetoothDevice;
     20 import android.bluetooth.BluetoothHeadsetClient;
     21 import android.bluetooth.BluetoothHeadsetClientCall;
     22 import android.bluetooth.BluetoothProfile;
     23 import android.content.BroadcastReceiver;
     24 import android.content.ComponentName;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.IntentFilter;
     28 import android.net.Uri;
     29 import android.os.Bundle;
     30 import android.telecom.Connection;
     31 import android.telecom.ConnectionRequest;
     32 import android.telecom.ConnectionService;
     33 import android.telecom.PhoneAccount;
     34 import android.telecom.PhoneAccountHandle;
     35 import android.telecom.TelecomManager;
     36 import android.util.Log;
     37 
     38 import com.android.bluetooth.hfpclient.HeadsetClientService;
     39 
     40 import java.util.Arrays;
     41 import java.util.HashMap;
     42 import java.util.Iterator;
     43 import java.util.List;
     44 import java.util.Map;
     45 import java.util.Objects;
     46 
     47 public class HfpClientConnectionService extends ConnectionService {
     48     private static final String TAG = "HfpClientConnService";
     49     private static final boolean DBG = true;
     50 
     51     public static final String HFP_SCHEME = "hfpc";
     52 
     53     private BluetoothAdapter mAdapter;
     54 
     55     // BluetoothHeadset proxy.
     56     private BluetoothHeadsetClient mHeadsetProfile;
     57     private TelecomManager mTelecomManager;
     58 
     59     private final Map<BluetoothDevice, HfpClientDeviceBlock> mDeviceBlocks = new HashMap<>();
     60 
     61     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
     62         @Override
     63         public void onReceive(Context context, Intent intent) {
     64             if (DBG) {
     65                 Log.d(TAG, "onReceive " + intent);
     66             }
     67             String action = intent != null ? intent.getAction() : null;
     68 
     69             if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
     70                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
     71                 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
     72 
     73                 if (newState == BluetoothProfile.STATE_CONNECTED) {
     74                     if (DBG) {
     75                         Log.d(TAG, "Established connection with " + device);
     76                     }
     77 
     78                     HfpClientDeviceBlock block = null;
     79                     if ((block = createBlockForDevice(device)) == null) {
     80                         Log.w(TAG, "Block already exists for device " + device + " ignoring.");
     81                     }
     82                 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
     83                     if (DBG) {
     84                         Log.d(TAG, "Disconnecting from " + device);
     85                     }
     86 
     87                     // Disconnect any inflight calls from the connection service.
     88                     synchronized (HfpClientConnectionService.this) {
     89                         HfpClientDeviceBlock block = mDeviceBlocks.remove(device);
     90                         if (block == null) {
     91                             Log.w(TAG, "Disconnect for device but no block " + device);
     92                             return;
     93                         }
     94                         block.cleanup();
     95                         // Block should be subsequently garbage collected
     96                         block = null;
     97                     }
     98                 }
     99             } else if (BluetoothHeadsetClient.ACTION_CALL_CHANGED.equals(action)) {
    100                 BluetoothHeadsetClientCall call =
    101                         intent.getParcelableExtra(BluetoothHeadsetClient.EXTRA_CALL);
    102                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    103                 HfpClientDeviceBlock block = findBlockForDevice(call.getDevice());
    104                 if (block == null) {
    105                     Log.w(TAG, "Call changed but no block for device " + device);
    106                     return;
    107                 }
    108 
    109                 // If we are not connected, then when we actually do get connected --
    110                 // the calls should
    111                 // be added (see ACTION_CONNECTION_STATE_CHANGED intent above).
    112                 block.handleCall(call);
    113             }
    114         }
    115     };
    116 
    117     @Override
    118     public void onCreate() {
    119         super.onCreate();
    120         if (DBG) {
    121             Log.d(TAG, "onCreate");
    122         }
    123         mAdapter = BluetoothAdapter.getDefaultAdapter();
    124         mTelecomManager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
    125         if (mTelecomManager != null) mTelecomManager.clearPhoneAccounts();
    126         mAdapter.getProfileProxy(this, mServiceListener, BluetoothProfile.HEADSET_CLIENT);
    127     }
    128 
    129     @Override
    130     public void onDestroy() {
    131         if (DBG) {
    132             Log.d(TAG, "onDestroy called");
    133         }
    134         // Close the profile.
    135         if (mHeadsetProfile != null) {
    136             mAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, mHeadsetProfile);
    137         }
    138 
    139         // Unregister the broadcast receiver.
    140         try {
    141             unregisterReceiver(mBroadcastReceiver);
    142         } catch (IllegalArgumentException ex) {
    143             Log.w(TAG, "Receiver was not registered.");
    144         }
    145 
    146         // Unregister the phone account. This should ideally happen when disconnection ensues but in
    147         // case the service crashes we may need to force clean.
    148         disconnectAll();
    149     }
    150 
    151     private synchronized void disconnectAll() {
    152         for (Iterator<Map.Entry<BluetoothDevice, HfpClientDeviceBlock>> it =
    153                 mDeviceBlocks.entrySet().iterator(); it.hasNext(); ) {
    154             it.next().getValue().cleanup();
    155             it.remove();
    156         }
    157     }
    158 
    159     @Override
    160     public int onStartCommand(Intent intent, int flags, int startId) {
    161         if (DBG) {
    162             Log.d(TAG, "onStartCommand " + intent);
    163         }
    164         // In order to make sure that the service is sticky (recovers from errors when HFP
    165         // connection is still active) and to stop it we need a special intent since stopService
    166         // only recreates it.
    167         if (intent != null && intent.getBooleanExtra(HeadsetClientService.HFP_CLIENT_STOP_TAG,
    168                 false)) {
    169             // Stop the service.
    170             stopSelf();
    171             return 0;
    172         } else {
    173             IntentFilter filter = new IntentFilter();
    174             filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
    175             filter.addAction(BluetoothHeadsetClient.ACTION_CALL_CHANGED);
    176             registerReceiver(mBroadcastReceiver, filter);
    177             return START_STICKY;
    178         }
    179     }
    180 
    181     // This method is called whenever there is a new incoming call (or right after BT connection).
    182     @Override
    183     public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerAccount,
    184             ConnectionRequest request) {
    185         if (DBG) {
    186             Log.d(TAG,
    187                     "onCreateIncomingConnection " + connectionManagerAccount + " req: " + request);
    188         }
    189 
    190         HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount);
    191         if (block == null) {
    192             Log.w(TAG, "HfpClient does not support having a connection manager");
    193             return null;
    194         }
    195 
    196         // We should already have a connection by this time.
    197         BluetoothHeadsetClientCall call =
    198                 request.getExtras().getParcelable(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
    199         return block.onCreateIncomingConnection(call);
    200     }
    201 
    202     // This method is called *only if* Dialer UI is used to place an outgoing call.
    203     @Override
    204     public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerAccount,
    205             ConnectionRequest request) {
    206         if (DBG) {
    207             Log.d(TAG, "onCreateOutgoingConnection " + connectionManagerAccount);
    208         }
    209         HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount);
    210         if (block == null) {
    211             Log.w(TAG, "HfpClient does not support having a connection manager");
    212             return null;
    213         }
    214 
    215         return block.onCreateOutgoingConnection(request.getAddress());
    216     }
    217 
    218     // This method is called when:
    219     // 1. Outgoing call created from the AG.
    220     // 2. Call transfer from AG -> HF (on connection when existed call present).
    221     @Override
    222     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerAccount,
    223             ConnectionRequest request) {
    224         if (DBG) {
    225             Log.d(TAG, "onCreateUnknownConnection " + connectionManagerAccount);
    226         }
    227         HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount);
    228         if (block == null) {
    229             Log.w(TAG, "HfpClient does not support having a connection manager");
    230             return null;
    231         }
    232 
    233         // We should already have a connection by this time.
    234         BluetoothHeadsetClientCall call =
    235                 request.getExtras().getParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
    236         return block.onCreateUnknownConnection(call);
    237     }
    238 
    239     @Override
    240     public void onConference(Connection connection1, Connection connection2) {
    241         if (DBG) {
    242             Log.d(TAG, "onConference " + connection1 + " " + connection2);
    243         }
    244 
    245         BluetoothDevice bd1 = ((HfpClientConnection) connection1).getDevice();
    246         BluetoothDevice bd2 = ((HfpClientConnection) connection2).getDevice();
    247         // We can only conference two connections on same device
    248         if (!Objects.equals(bd1, bd2)) {
    249             Log.e(TAG,
    250                     "Cannot conference calls from two different devices " + "bd1 " + bd1 + " bd2 "
    251                             + bd2 + " conn1 " + connection1 + "connection2 " + connection2);
    252             return;
    253         }
    254 
    255         HfpClientDeviceBlock block = findBlockForDevice(bd1);
    256         block.onConference(connection1, connection2);
    257     }
    258 
    259     private BluetoothDevice getDevice(PhoneAccountHandle handle) {
    260         PhoneAccount account = mTelecomManager.getPhoneAccount(handle);
    261         String btAddr = account.getAddress().getSchemeSpecificPart();
    262         return mAdapter.getRemoteDevice(btAddr);
    263     }
    264 
    265     BluetoothProfile.ServiceListener mServiceListener = new BluetoothProfile.ServiceListener() {
    266         @Override
    267         public void onServiceConnected(int profile, BluetoothProfile proxy) {
    268             if (DBG) {
    269                 Log.d(TAG, "onServiceConnected");
    270             }
    271             mHeadsetProfile = (BluetoothHeadsetClient) proxy;
    272 
    273             List<BluetoothDevice> devices = mHeadsetProfile.getConnectedDevices();
    274             if (devices == null) {
    275                 Log.w(TAG, "No connected or more than one connected devices found." + devices);
    276                 return;
    277             }
    278             for (BluetoothDevice device : devices) {
    279                 if (DBG) {
    280                     Log.d(TAG, "Creating phone account for device " + device);
    281                 }
    282 
    283                 // Creation of the block takes care of initializing the phone account and
    284                 // calls.
    285                 HfpClientDeviceBlock block = createBlockForDevice(device);
    286             }
    287         }
    288 
    289         @Override
    290         public void onServiceDisconnected(int profile) {
    291             if (DBG) {
    292                 Log.d(TAG, "onServiceDisconnected " + profile);
    293             }
    294             mHeadsetProfile = null;
    295             disconnectAll();
    296         }
    297     };
    298 
    299     // Block management functions
    300     synchronized HfpClientDeviceBlock createBlockForDevice(BluetoothDevice device) {
    301         Log.d(TAG, "Creating block for device " + device);
    302         if (mDeviceBlocks.containsKey(device)) {
    303             Log.e(TAG, "Device already exists " + device + " blocks " + mDeviceBlocks);
    304             return null;
    305         }
    306 
    307         HfpClientDeviceBlock block = new HfpClientDeviceBlock(this, device, mHeadsetProfile);
    308         mDeviceBlocks.put(device, block);
    309         return block;
    310     }
    311 
    312     synchronized HfpClientDeviceBlock findBlockForDevice(BluetoothDevice device) {
    313         Log.d(TAG, "Finding block for device " + device + " blocks " + mDeviceBlocks);
    314         return mDeviceBlocks.get(device);
    315     }
    316 
    317     synchronized HfpClientDeviceBlock findBlockForHandle(PhoneAccountHandle handle) {
    318         PhoneAccount account = mTelecomManager.getPhoneAccount(handle);
    319         String btAddr = account.getAddress().getSchemeSpecificPart();
    320         BluetoothDevice device = mAdapter.getRemoteDevice(btAddr);
    321         Log.d(TAG, "Finding block for handle " + handle + " device " + btAddr);
    322         return mDeviceBlocks.get(device);
    323     }
    324 
    325     // Util functions that may be used by various classes
    326     public static PhoneAccount createAccount(Context context, BluetoothDevice device) {
    327         Uri addr = Uri.fromParts(HfpClientConnectionService.HFP_SCHEME, device.getAddress(), null);
    328         PhoneAccountHandle handle =
    329                 new PhoneAccountHandle(new ComponentName(context, HfpClientConnectionService.class),
    330                         device.getAddress());
    331         PhoneAccount account =
    332                 new PhoneAccount.Builder(handle, "HFP " + device.toString()).setAddress(addr)
    333                         .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
    334                         .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
    335                         .build();
    336         if (DBG) {
    337             Log.d(TAG, "phoneaccount: " + account);
    338         }
    339         return account;
    340     }
    341 
    342     public static boolean hasHfpClientEcc(BluetoothHeadsetClient client, BluetoothDevice device) {
    343         Bundle features = client.getCurrentAgEvents(device);
    344         return features != null && features.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC,
    345                 false);
    346     }
    347 }
    348