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