• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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             } else if (BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED.equals(action)) {
114                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
115                 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
116                         BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED);
117                 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
118                         BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED);
119                 HfpClientDeviceBlock block = findBlockForDevice(device);
120                 if (block == null) {
121                     Log.w(TAG, "Device audio state changed but no block for device");
122                     return;
123                 }
124                 block.onAudioStateChange(newState, oldState);
125             }
126         }
127     };
128 
129     @Override
onCreate()130     public void onCreate() {
131         super.onCreate();
132         if (DBG) {
133             Log.d(TAG, "onCreate");
134         }
135         mAdapter = BluetoothAdapter.getDefaultAdapter();
136         mTelecomManager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
137         if (mTelecomManager != null) mTelecomManager.clearPhoneAccounts();
138         mAdapter.getProfileProxy(this, mServiceListener, BluetoothProfile.HEADSET_CLIENT);
139     }
140 
141     @Override
onDestroy()142     public void onDestroy() {
143         if (DBG) {
144             Log.d(TAG, "onDestroy called");
145         }
146         // Close the profile.
147         if (mHeadsetProfile != null) {
148             mAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, mHeadsetProfile);
149         }
150 
151         // Unregister the broadcast receiver.
152         try {
153             unregisterReceiver(mBroadcastReceiver);
154         } catch (IllegalArgumentException ex) {
155             Log.w(TAG, "Receiver was not registered.");
156         }
157 
158         // Unregister the phone account. This should ideally happen when disconnection ensues but in
159         // case the service crashes we may need to force clean.
160         disconnectAll();
161     }
162 
disconnectAll()163     private synchronized void disconnectAll() {
164         for (Iterator<Map.Entry<BluetoothDevice, HfpClientDeviceBlock>> it =
165                 mDeviceBlocks.entrySet().iterator(); it.hasNext(); ) {
166             it.next().getValue().cleanup();
167             it.remove();
168         }
169     }
170 
171     @Override
onStartCommand(Intent intent, int flags, int startId)172     public int onStartCommand(Intent intent, int flags, int startId) {
173         if (DBG) {
174             Log.d(TAG, "onStartCommand " + intent);
175         }
176         // In order to make sure that the service is sticky (recovers from errors when HFP
177         // connection is still active) and to stop it we need a special intent since stopService
178         // only recreates it.
179         if (intent != null && intent.getBooleanExtra(HeadsetClientService.HFP_CLIENT_STOP_TAG,
180                 false)) {
181             // Stop the service.
182             stopSelf();
183             return 0;
184         } else {
185             IntentFilter filter = new IntentFilter();
186             filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
187             filter.addAction(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED);
188             filter.addAction(BluetoothHeadsetClient.ACTION_CALL_CHANGED);
189             registerReceiver(mBroadcastReceiver, filter);
190             return START_STICKY;
191         }
192     }
193 
194     // This method is called whenever there is a new incoming call (or right after BT connection).
195     @Override
onCreateIncomingConnection(PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)196     public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerAccount,
197             ConnectionRequest request) {
198         if (DBG) {
199             Log.d(TAG,
200                     "onCreateIncomingConnection " + connectionManagerAccount + " req: " + request);
201         }
202 
203         HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount);
204         if (block == null) {
205             Log.w(TAG, "HfpClient does not support having a connection manager");
206             return null;
207         }
208 
209         // We should already have a connection by this time.
210         BluetoothHeadsetClientCall call =
211                 request.getExtras().getParcelable(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
212         HfpClientConnection connection = block.onCreateIncomingConnection(call);
213         connection.setHfpClientConnectionService(this);
214         return connection;
215     }
216 
217     // This method is called *only if* Dialer UI is used to place an outgoing call.
218     @Override
onCreateOutgoingConnection(PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)219     public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerAccount,
220             ConnectionRequest request) {
221         if (DBG) {
222             Log.d(TAG, "onCreateOutgoingConnection " + connectionManagerAccount);
223         }
224         HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount);
225         if (block == null) {
226             Log.w(TAG, "HfpClient does not support having a connection manager");
227             return null;
228         }
229         HfpClientConnection connection = block.onCreateOutgoingConnection(request.getAddress());
230         connection.setHfpClientConnectionService(this);
231         return connection;
232     }
233 
234     // This method is called when:
235     // 1. Outgoing call created from the AG.
236     // 2. Call transfer from AG -> HF (on connection when existed call present).
237     @Override
onCreateUnknownConnection(PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)238     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerAccount,
239             ConnectionRequest request) {
240         if (DBG) {
241             Log.d(TAG, "onCreateUnknownConnection " + connectionManagerAccount);
242         }
243         HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount);
244         if (block == null) {
245             Log.w(TAG, "HfpClient does not support having a connection manager");
246             return null;
247         }
248 
249         // We should already have a connection by this time.
250         BluetoothHeadsetClientCall call =
251                 request.getExtras().getParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
252         HfpClientConnection connection = block.onCreateUnknownConnection(call);
253         connection.setHfpClientConnectionService(this);
254         return connection;
255     }
256 
257     @Override
onConference(Connection connection1, Connection connection2)258     public void onConference(Connection connection1, Connection connection2) {
259         if (DBG) {
260             Log.d(TAG, "onConference " + connection1 + " " + connection2);
261         }
262 
263         BluetoothDevice bd1 = ((HfpClientConnection) connection1).getDevice();
264         BluetoothDevice bd2 = ((HfpClientConnection) connection2).getDevice();
265         // We can only conference two connections on same device
266         if (!Objects.equals(bd1, bd2)) {
267             Log.e(TAG,
268                     "Cannot conference calls from two different devices " + "bd1 " + bd1 + " bd2 "
269                             + bd2 + " conn1 " + connection1 + "connection2 " + connection2);
270             return;
271         }
272 
273         HfpClientDeviceBlock block = findBlockForDevice(bd1);
274         block.onConference(connection1, connection2);
275     }
276 
getDevice(PhoneAccountHandle handle)277     private BluetoothDevice getDevice(PhoneAccountHandle handle) {
278         PhoneAccount account = mTelecomManager.getPhoneAccount(handle);
279         String btAddr = account.getAddress().getSchemeSpecificPart();
280         return mAdapter.getRemoteDevice(btAddr);
281     }
282 
283     BluetoothProfile.ServiceListener mServiceListener = new BluetoothProfile.ServiceListener() {
284         @Override
285         public void onServiceConnected(int profile, BluetoothProfile proxy) {
286             if (DBG) {
287                 Log.d(TAG, "onServiceConnected");
288             }
289             mHeadsetProfile = (BluetoothHeadsetClient) proxy;
290 
291             List<BluetoothDevice> devices = mHeadsetProfile.getConnectedDevices();
292             if (devices == null) {
293                 Log.w(TAG, "No connected or more than one connected devices found." + devices);
294                 return;
295             }
296             for (BluetoothDevice device : devices) {
297                 if (DBG) {
298                     Log.d(TAG, "Creating phone account for device " + device);
299                 }
300 
301                 // Creation of the block takes care of initializing the phone account and
302                 // calls.
303                 HfpClientDeviceBlock block = createBlockForDevice(device);
304             }
305         }
306 
307         @Override
308         public void onServiceDisconnected(int profile) {
309             if (DBG) {
310                 Log.d(TAG, "onServiceDisconnected " + profile);
311             }
312             mHeadsetProfile = null;
313             disconnectAll();
314         }
315     };
316 
317     // Block management functions
createBlockForDevice(BluetoothDevice device)318     synchronized HfpClientDeviceBlock createBlockForDevice(BluetoothDevice device) {
319         Log.d(TAG, "Creating block for device " + device);
320         if (mDeviceBlocks.containsKey(device)) {
321             Log.e(TAG, "Device already exists " + device + " blocks " + mDeviceBlocks);
322             return null;
323         }
324 
325         HfpClientDeviceBlock block = new HfpClientDeviceBlock(this, device, mHeadsetProfile);
326         mDeviceBlocks.put(device, block);
327         return block;
328     }
329 
findBlockForDevice(BluetoothDevice device)330     synchronized HfpClientDeviceBlock findBlockForDevice(BluetoothDevice device) {
331         Log.d(TAG, "Finding block for device " + device + " blocks " + mDeviceBlocks);
332         return mDeviceBlocks.get(device);
333     }
334 
findBlockForHandle(PhoneAccountHandle handle)335     synchronized HfpClientDeviceBlock findBlockForHandle(PhoneAccountHandle handle) {
336         PhoneAccount account = mTelecomManager.getPhoneAccount(handle);
337         String btAddr = account.getAddress().getSchemeSpecificPart();
338         BluetoothDevice device = mAdapter.getRemoteDevice(btAddr);
339         Log.d(TAG, "Finding block for handle " + handle + " device " + btAddr);
340         return mDeviceBlocks.get(device);
341     }
342 
343     // Util functions that may be used by various classes
createAccount(Context context, BluetoothDevice device)344     public static PhoneAccount createAccount(Context context, BluetoothDevice device) {
345         Uri addr = Uri.fromParts(HfpClientConnectionService.HFP_SCHEME, device.getAddress(), null);
346         PhoneAccountHandle handle =
347                 new PhoneAccountHandle(new ComponentName(context, HfpClientConnectionService.class),
348                         device.getAddress());
349 
350         int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER;
351         if (context.getApplicationContext().getResources().getBoolean(
352                 com.android.bluetooth.R.bool
353                 .hfp_client_connection_service_support_emergency_call)) {
354             // Need to have an emergency call capability to place emergency call
355             capabilities |= PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS;
356         }
357 
358         PhoneAccount account =
359                 new PhoneAccount.Builder(handle, "HFP " + device.toString()).setAddress(addr)
360                         .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
361                         .setCapabilities(capabilities)
362                         .build();
363         if (DBG) {
364             Log.d(TAG, "phoneaccount: " + account);
365         }
366         return account;
367     }
368 
hasHfpClientEcc(BluetoothHeadsetClient client, BluetoothDevice device)369     public static boolean hasHfpClientEcc(BluetoothHeadsetClient client, BluetoothDevice device) {
370         Bundle features = client.getCurrentAgEvents(device);
371         return features != null && features.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC,
372                 false);
373     }
374 }
375