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