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;
17 
18 import android.bluetooth.BluetoothAdapter;
19 import android.bluetooth.BluetoothDevice;
20 import android.bluetooth.BluetoothManager;
21 import android.bluetooth.BluetoothProfile;
22 import android.content.ComponentName;
23 import android.content.Intent;
24 import android.net.Uri;
25 import android.os.ParcelUuid;
26 import android.telecom.Connection;
27 import android.telecom.ConnectionRequest;
28 import android.telecom.ConnectionService;
29 import android.telecom.PhoneAccount;
30 import android.telecom.PhoneAccountHandle;
31 import android.telecom.TelecomManager;
32 import android.util.Log;
33 
34 import com.android.bluetooth.btservice.AdapterService;
35 import com.android.bluetooth.pbapclient.PbapClientService;
36 
37 import java.util.Arrays;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Objects;
43 
44 public class HfpClientConnectionService extends ConnectionService {
45     private static final String TAG = HfpClientConnectionService.class.getSimpleName();
46 
47     public static final String HFP_SCHEME = "hfpc";
48 
49     private TelecomManager mTelecomManager;
50 
51     private HeadsetClientServiceInterface mServiceInterface = new HeadsetClientServiceInterface();
52 
53     private final Map<BluetoothDevice, HfpClientDeviceBlock> mDeviceBlocks = new HashMap<>();
54 
55     // --------------------------------------------------------------------------------------------//
56     // SINGLETON MANAGEMENT                                                                       //
57     // --------------------------------------------------------------------------------------------//
58 
59     private static final Object INSTANCE_LOCK = new Object();
60     private static HfpClientConnectionService sHfpClientConnectionService;
61 
setInstance(HfpClientConnectionService instance)62     private void setInstance(HfpClientConnectionService instance) {
63         synchronized (INSTANCE_LOCK) {
64             sHfpClientConnectionService = instance;
65         }
66     }
67 
getInstance()68     private static HfpClientConnectionService getInstance() {
69         synchronized (INSTANCE_LOCK) {
70             return sHfpClientConnectionService;
71         }
72     }
73 
clearInstance()74     private void clearInstance() {
75         synchronized (INSTANCE_LOCK) {
76             if (sHfpClientConnectionService == this) {
77                 setInstance(null);
78             }
79         }
80     }
81 
82     // --------------------------------------------------------------------------------------------//
83     // MESSAGES FROM HEADSET CLIENT SERVICE                                                       //
84     // --------------------------------------------------------------------------------------------//
85 
86     /** Send a device connection state changed event to this service */
onConnectionStateChanged( BluetoothDevice device, int newState, int oldState)87     public static void onConnectionStateChanged(
88             BluetoothDevice device, int newState, int oldState) {
89         HfpClientConnectionService service = getInstance();
90         if (service == null) {
91             Log.e(TAG, "onConnectionStateChanged: HFP Client Connection Service not started");
92             return;
93         }
94         service.onConnectionStateChangedInternal(device, newState, oldState);
95     }
96 
97     /** Send a device call state changed event to this service */
onCallChanged(BluetoothDevice device, HfpClientCall call)98     public static void onCallChanged(BluetoothDevice device, HfpClientCall call) {
99         HfpClientConnectionService service = getInstance();
100         if (service == null) {
101             Log.e(TAG, "onCallChanged: HFP Client Connection Service not started");
102             return;
103         }
104         service.onCallChangedInternal(device, call);
105     }
106 
107     /** Send a device audio state changed event to this service */
onAudioStateChanged(BluetoothDevice device, int newState, int oldState)108     public static void onAudioStateChanged(BluetoothDevice device, int newState, int oldState) {
109         HfpClientConnectionService service = getInstance();
110         if (service == null) {
111             Log.e(TAG, "onAudioStateChanged: HFP Client Connection Service not started");
112             return;
113         }
114         service.onAudioStateChangedInternal(device, newState, oldState);
115     }
116 
117     // --------------------------------------------------------------------------------------------//
118     // HANDLE MESSAGES FROM HEADSET CLIENT SERVICE                                                //
119     // --------------------------------------------------------------------------------------------//
120 
onConnectionStateChangedInternal( BluetoothDevice device, int newState, int oldState)121     private void onConnectionStateChangedInternal(
122             BluetoothDevice device, int newState, int oldState) {
123         if (newState == BluetoothProfile.STATE_CONNECTED) {
124             Log.d(TAG, "Established connection with " + device);
125 
126             HfpClientDeviceBlock block = createBlockForDevice(device);
127             if (block == null) {
128                 Log.w(TAG, "Block already exists for device= " + device + ", ignoring.");
129             }
130         } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
131             Log.d(TAG, "Disconnecting from " + device);
132 
133             // Disconnect any inflight calls from the connection service.
134             synchronized (HfpClientConnectionService.this) {
135                 HfpClientDeviceBlock block = mDeviceBlocks.remove(device);
136                 if (block == null) {
137                     Log.w(TAG, "Disconnect for device but no block, device=" + device);
138                     return;
139                 }
140                 block.cleanup();
141             }
142         }
143         AdapterService adapterService = AdapterService.getAdapterService();
144         if (adapterService != null && adapterService.getRemoteDevices() != null) {
145             adapterService
146                     .getRemoteDevices()
147                     .handleHeadsetClientConnectionStateChanged(device, oldState, newState);
148         }
149         adapterService.notifyProfileConnectionStateChangeToGatt(
150                 BluetoothProfile.HEADSET_CLIENT, oldState, newState);
151         if (PbapClientService.getPbapClientService() != null) {
152             PbapClientService.getPbapClientService()
153                     .handleHeadsetClientConnectionStateChanged(device, oldState, newState);
154         }
155         if (adapterService != null) {
156             adapterService.updateProfileConnectionAdapterProperties(
157                     device, BluetoothProfile.HEADSET_CLIENT, newState, oldState);
158         }
159     }
160 
onCallChangedInternal(BluetoothDevice device, HfpClientCall call)161     private void onCallChangedInternal(BluetoothDevice device, HfpClientCall call) {
162         HfpClientDeviceBlock block = findBlockForDevice(device);
163         if (block == null) {
164             Log.w(TAG, "Call changed but no block for device=" + device);
165             return;
166         }
167 
168         // If we are not connected, then when we actually do get connected the calls should be added
169         // (see ACTION_CONNECTION_STATE_CHANGED intent above).
170         block.handleCall(call);
171     }
172 
onAudioStateChangedInternal(BluetoothDevice device, int newState, int oldState)173     private void onAudioStateChangedInternal(BluetoothDevice device, int newState, int oldState) {
174         HfpClientDeviceBlock block = findBlockForDevice(device);
175         if (block == null) {
176             Log.w(TAG, "Device audio state changed but no block for device=" + device);
177             return;
178         }
179         block.onAudioStateChange(newState, oldState);
180     }
181 
182     // --------------------------------------------------------------------------------------------//
183     // SERVICE SETUP AND TEAR DOWN                                                                //
184     // --------------------------------------------------------------------------------------------//
185 
186     @Override
onCreate()187     public void onCreate() {
188         super.onCreate();
189         Log.d(TAG, "onCreate");
190         mTelecomManager = getSystemService(TelecomManager.class);
191         if (mTelecomManager != null) mTelecomManager.clearPhoneAccounts();
192 
193         List<BluetoothDevice> devices = mServiceInterface.getConnectedDevices();
194         if (devices != null) {
195             for (BluetoothDevice device : devices) {
196                 createBlockForDevice(device);
197             }
198         }
199 
200         setInstance(this);
201     }
202 
203     @Override
onDestroy()204     public void onDestroy() {
205         Log.d(TAG, "onDestroy called");
206 
207         // Unregister the phone account. This should ideally happen when disconnection ensues but in
208         // case the service crashes we may need to force clean.
209         disconnectAll();
210 
211         clearInstance();
212     }
213 
disconnectAll()214     private synchronized void disconnectAll() {
215         for (Iterator<Map.Entry<BluetoothDevice, HfpClientDeviceBlock>> it =
216                         mDeviceBlocks.entrySet().iterator();
217                 it.hasNext(); ) {
218             it.next().getValue().cleanup();
219             it.remove();
220         }
221     }
222 
223     @Override
onStartCommand(Intent intent, int flags, int startId)224     public int onStartCommand(Intent intent, int flags, int startId) {
225         Log.d(TAG, "onStartCommand " + intent);
226         // In order to make sure that the service is sticky (recovers from errors when HFP
227         // connection is still active) and to stop it we need a special intent since stopService
228         // only recreates it.
229         if (intent != null
230                 && intent.getBooleanExtra(HeadsetClientService.HFP_CLIENT_STOP_TAG, false)) {
231             // Stop the service.
232             stopSelf();
233             return 0;
234         }
235         return START_STICKY;
236     }
237 
238     // --------------------------------------------------------------------------------------------//
239     // TELECOM CONNECTION SERVICE FUNCTIONS                                                       //
240     // --------------------------------------------------------------------------------------------//
241 
242     // This method is called whenever there is a new incoming call (or right after BT connection).
243     @Override
onCreateIncomingConnection( PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)244     public Connection onCreateIncomingConnection(
245             PhoneAccountHandle connectionManagerAccount, ConnectionRequest request) {
246         Log.d(TAG, "onCreateIncomingConnection " + connectionManagerAccount + " req: " + request);
247 
248         HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount);
249         if (block == null) {
250             Log.w(TAG, "HfpClient does not support having a connection manager");
251             return null;
252         }
253 
254         // We should already have a connection by this time.
255         ParcelUuid callUuid =
256                 request.getExtras().getParcelable(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
257         HfpClientConnection connection =
258                 block.onCreateIncomingConnection((callUuid != null ? callUuid.getUuid() : null));
259         return connection;
260     }
261 
262     // This method is called *only if* Dialer UI is used to place an outgoing call.
263     @Override
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)264     public Connection onCreateOutgoingConnection(
265             PhoneAccountHandle connectionManagerAccount, ConnectionRequest request) {
266         Log.d(TAG, "onCreateOutgoingConnection " + connectionManagerAccount);
267         HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount);
268         if (block == null) {
269             Log.w(TAG, "HfpClient does not support having a connection manager");
270             return null;
271         }
272         HfpClientConnection connection = block.onCreateOutgoingConnection(request.getAddress());
273         return connection;
274     }
275 
276     // This method is called when:
277     // 1. Outgoing call created from the AG.
278     // 2. Call transfer from AG -> HF (on connection when existed call present).
279     @Override
onCreateUnknownConnection( PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)280     public Connection onCreateUnknownConnection(
281             PhoneAccountHandle connectionManagerAccount, ConnectionRequest request) {
282         Log.d(TAG, "onCreateUnknownConnection " + connectionManagerAccount);
283         HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount);
284         if (block == null) {
285             Log.w(TAG, "HfpClient does not support having a connection manager");
286             return null;
287         }
288 
289         // We should already have a connection by this time.
290         ParcelUuid callUuid =
291                 request.getExtras().getParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
292         HfpClientConnection connection =
293                 block.onCreateUnknownConnection((callUuid != null ? callUuid.getUuid() : null));
294         return connection;
295     }
296 
297     @Override
onConference(Connection connection1, Connection connection2)298     public void onConference(Connection connection1, Connection connection2) {
299         Log.d(TAG, "onConference " + connection1 + " " + connection2);
300 
301         BluetoothDevice bd1 = ((HfpClientConnection) connection1).getDevice();
302         BluetoothDevice bd2 = ((HfpClientConnection) connection2).getDevice();
303         // We can only conference two connections on same device
304         if (!Objects.equals(bd1, bd2)) {
305             Log.e(
306                     TAG,
307                     "Cannot conference calls from two different devices "
308                             + "bd1 "
309                             + bd1
310                             + " bd2 "
311                             + bd2
312                             + " conn1 "
313                             + connection1
314                             + "connection2 "
315                             + connection2);
316             return;
317         }
318 
319         HfpClientDeviceBlock block = findBlockForDevice(bd1);
320         block.onConference(connection1, connection2);
321     }
322 
323     // --------------------------------------------------------------------------------------------//
324     // DEVICE MANAGEMENT                                                                          //
325     // --------------------------------------------------------------------------------------------//
326 
getDevice(PhoneAccountHandle handle)327     private BluetoothDevice getDevice(PhoneAccountHandle handle) {
328         BluetoothAdapter adapter = getSystemService(BluetoothManager.class).getAdapter();
329         PhoneAccount account = mTelecomManager.getPhoneAccount(handle);
330         if (account == null) {
331             return null;
332         }
333         String btAddr = account.getAddress().getSchemeSpecificPart();
334         return adapter.getRemoteDevice(btAddr);
335     }
336 
337     // Block management functions
createBlockForDevice(BluetoothDevice device)338     synchronized HfpClientDeviceBlock createBlockForDevice(BluetoothDevice device) {
339         Log.d(TAG, "Creating block for device " + device);
340         if (mDeviceBlocks.containsKey(device)) {
341             Log.e(TAG, "Device already exists " + device + " blocks " + mDeviceBlocks);
342             return null;
343         }
344 
345         HfpClientDeviceBlock block =
346                 HfpClientDeviceBlock.Factory.build(device, this, mServiceInterface);
347         mDeviceBlocks.put(device, block);
348         return block;
349     }
350 
findBlockForDevice(BluetoothDevice device)351     synchronized HfpClientDeviceBlock findBlockForDevice(BluetoothDevice device) {
352         Log.d(TAG, "Finding block for device " + device + " blocks " + mDeviceBlocks);
353         return mDeviceBlocks.get(device);
354     }
355 
findBlockForHandle(PhoneAccountHandle handle)356     synchronized HfpClientDeviceBlock findBlockForHandle(PhoneAccountHandle handle) {
357         BluetoothDevice device = getDevice(handle);
358         Log.d(TAG, "Finding block for handle " + handle + " device " + device);
359         return mDeviceBlocks.get(device);
360     }
361 
createAccount(BluetoothDevice device)362     PhoneAccount createAccount(BluetoothDevice device) {
363         Uri addr = Uri.fromParts(HfpClientConnectionService.HFP_SCHEME, device.getAddress(), null);
364         PhoneAccountHandle handle =
365                 new PhoneAccountHandle(
366                         new ComponentName(this, HfpClientConnectionService.class),
367                         device.getAddress());
368 
369         int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER;
370         if (getApplicationContext()
371                 .getResources()
372                 .getBoolean(
373                         com.android.bluetooth.R.bool
374                                 .hfp_client_connection_service_support_emergency_call)) {
375             // Need to have an emergency call capability to place emergency call
376             capabilities |= PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS;
377         }
378 
379         PhoneAccount account =
380                 new PhoneAccount.Builder(handle, "HFP " + device.toString())
381                         .setAddress(addr)
382                         .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
383                         .setCapabilities(capabilities)
384                         .build();
385         Log.d(TAG, "phoneaccount: " + account);
386         return account;
387     }
388 
getDeviceBlocks()389     private Map<BluetoothDevice, HfpClientDeviceBlock> getDeviceBlocks() {
390         return mDeviceBlocks;
391     }
392 
393     /** Dump the state of the HfpClientConnectionService and internal objects */
dump(StringBuilder sb)394     public static void dump(StringBuilder sb) {
395         HfpClientConnectionService instance = getInstance();
396         sb.append("  HfpClientConnectionService:\n");
397 
398         if (instance == null) {
399             sb.append("    null");
400         } else {
401             sb.append("    Devices:\n");
402             for (HfpClientDeviceBlock block : instance.getDeviceBlocks().values()) {
403                 sb.append("      " + block.toString() + "\n");
404             }
405         }
406     }
407 }
408