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