blob: 7f0e27a96c0aa9aafc718f5acca827f7cc8dfc6a [file] [log] [blame]
Jason Monk7ce96b92015-02-02 11:27:58 -05001/*
2 * Copyright (C) 2011 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
17package com.android.settingslib.bluetooth;
18
19import android.bluetooth.BluetoothA2dp;
Sanket Agarwal1bec6a52015-10-21 18:23:27 -070020import android.bluetooth.BluetoothA2dpSink;
Jason Monk7ce96b92015-02-02 11:27:58 -050021import android.bluetooth.BluetoothDevice;
22import android.bluetooth.BluetoothHeadset;
Sanket Agarwalf8a1c912016-01-26 20:12:52 -080023import android.bluetooth.BluetoothHeadsetClient;
Jason Monk7ce96b92015-02-02 11:27:58 -050024import android.bluetooth.BluetoothMap;
25import android.bluetooth.BluetoothInputDevice;
26import android.bluetooth.BluetoothPan;
Joseph Pirozzo563c7002016-03-21 15:49:48 -070027import android.bluetooth.BluetoothPbapClient;
Jason Monk7ce96b92015-02-02 11:27:58 -050028import android.bluetooth.BluetoothProfile;
29import android.bluetooth.BluetoothUuid;
30import android.content.Context;
31import android.content.Intent;
32import android.os.ParcelUuid;
33import android.util.Log;
Joseph Pirozzo563c7002016-03-21 15:49:48 -070034import com.android.settingslib.R;
Jason Monk7ce96b92015-02-02 11:27:58 -050035import java.util.ArrayList;
36import java.util.Collection;
37import java.util.HashMap;
38import java.util.Map;
Jason Monk7ce96b92015-02-02 11:27:58 -050039
40/**
41 * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
42 * objects for the available Bluetooth profiles.
43 */
44public final class LocalBluetoothProfileManager {
45 private static final String TAG = "LocalBluetoothProfileManager";
46 private static final boolean DEBUG = Utils.D;
47 /** Singleton instance. */
48 private static LocalBluetoothProfileManager sInstance;
49
50 /**
51 * An interface for notifying BluetoothHeadset IPC clients when they have
52 * been connected to the BluetoothHeadset service.
53 * Only used by com.android.settings.bluetooth.DockService.
54 */
55 public interface ServiceListener {
56 /**
57 * Called to notify the client when this proxy object has been
58 * connected to the BluetoothHeadset service. Clients must wait for
59 * this callback before making IPC calls on the BluetoothHeadset
60 * service.
61 */
62 void onServiceConnected();
63
64 /**
65 * Called to notify the client that this proxy object has been
66 * disconnected from the BluetoothHeadset service. Clients must not
67 * make IPC calls on the BluetoothHeadset service after this callback.
68 * This callback will currently only occur if the application hosting
69 * the BluetoothHeadset service, but may be called more often in future.
70 */
71 void onServiceDisconnected();
72 }
73
74 private final Context mContext;
75 private final LocalBluetoothAdapter mLocalAdapter;
76 private final CachedBluetoothDeviceManager mDeviceManager;
77 private final BluetoothEventManager mEventManager;
78
79 private A2dpProfile mA2dpProfile;
Sanket Agarwal1bec6a52015-10-21 18:23:27 -070080 private A2dpSinkProfile mA2dpSinkProfile;
Jason Monk7ce96b92015-02-02 11:27:58 -050081 private HeadsetProfile mHeadsetProfile;
Sanket Agarwalf8a1c912016-01-26 20:12:52 -080082 private HfpClientProfile mHfpClientProfile;
Jason Monk7ce96b92015-02-02 11:27:58 -050083 private MapProfile mMapProfile;
84 private final HidProfile mHidProfile;
85 private OppProfile mOppProfile;
86 private final PanProfile mPanProfile;
Joseph Pirozzo563c7002016-03-21 15:49:48 -070087 private PbapClientProfile mPbapClientProfile;
Jason Monk7ce96b92015-02-02 11:27:58 -050088 private final PbapServerProfile mPbapProfile;
Joseph Pirozzo563c7002016-03-21 15:49:48 -070089 private final boolean mUsePbapPce;
Jason Monk7ce96b92015-02-02 11:27:58 -050090
91 /**
92 * Mapping from profile name, e.g. "HEADSET" to profile object.
93 */
94 private final Map<String, LocalBluetoothProfile>
95 mProfileNameMap = new HashMap<String, LocalBluetoothProfile>();
96
97 LocalBluetoothProfileManager(Context context,
98 LocalBluetoothAdapter adapter,
99 CachedBluetoothDeviceManager deviceManager,
100 BluetoothEventManager eventManager) {
101 mContext = context;
102
103 mLocalAdapter = adapter;
104 mDeviceManager = deviceManager;
105 mEventManager = eventManager;
Joseph Pirozzo563c7002016-03-21 15:49:48 -0700106 mUsePbapPce = mContext.getResources().getBoolean(R.bool.enable_pbap_pce_profile);
Jason Monk7ce96b92015-02-02 11:27:58 -0500107 // pass this reference to adapter and event manager (circular dependency)
108 mLocalAdapter.setProfileManager(this);
109 mEventManager.setProfileManager(this);
110
111 ParcelUuid[] uuids = adapter.getUuids();
112
113 // uuids may be null if Bluetooth is turned off
114 if (uuids != null) {
115 updateLocalProfiles(uuids);
116 }
117
118 // Always add HID and PAN profiles
119 mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this);
120 addProfile(mHidProfile, HidProfile.NAME,
121 BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
122
123 mPanProfile = new PanProfile(context);
124 addPanProfile(mPanProfile, PanProfile.NAME,
125 BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
126
127 if(DEBUG) Log.d(TAG, "Adding local MAP profile");
128 mMapProfile = new MapProfile(mContext, mLocalAdapter,
129 mDeviceManager, this);
130 addProfile(mMapProfile, MapProfile.NAME,
131 BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
132
133 //Create PBAP server profile, but do not add it to list of profiles
134 // as we do not need to monitor the profile as part of profile list
135 mPbapProfile = new PbapServerProfile(context);
136
137 if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete");
138 }
139
140 /**
141 * Initialize or update the local profile objects. If a UUID was previously
142 * present but has been removed, we print a warning but don't remove the
143 * profile object as it might be referenced elsewhere, or the UUID might
144 * come back and we don't want multiple copies of the profile objects.
145 * @param uuids
146 */
147 void updateLocalProfiles(ParcelUuid[] uuids) {
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700148 // A2DP SRC
Jason Monk7ce96b92015-02-02 11:27:58 -0500149 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
150 if (mA2dpProfile == null) {
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700151 if(DEBUG) Log.d(TAG, "Adding local A2DP SRC profile");
Jason Monk7ce96b92015-02-02 11:27:58 -0500152 mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this);
153 addProfile(mA2dpProfile, A2dpProfile.NAME,
154 BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
155 }
156 } else if (mA2dpProfile != null) {
157 Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing.");
158 }
159
Sanket Agarwalf8a1c912016-01-26 20:12:52 -0800160 // A2DP SINK
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700161 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)) {
162 if (mA2dpSinkProfile == null) {
163 if(DEBUG) Log.d(TAG, "Adding local A2DP Sink profile");
164 mA2dpSinkProfile = new A2dpSinkProfile(mContext, mLocalAdapter, mDeviceManager, this);
165 addProfile(mA2dpSinkProfile, A2dpSinkProfile.NAME,
166 BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
167 }
168 } else if (mA2dpSinkProfile != null) {
169 Log.w(TAG, "Warning: A2DP Sink profile was previously added but the UUID is now missing.");
170 }
171
Jason Monk7ce96b92015-02-02 11:27:58 -0500172 // Headset / Handsfree
173 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
174 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
175 if (mHeadsetProfile == null) {
176 if (DEBUG) Log.d(TAG, "Adding local HEADSET profile");
177 mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter,
178 mDeviceManager, this);
179 addProfile(mHeadsetProfile, HeadsetProfile.NAME,
180 BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
181 }
182 } else if (mHeadsetProfile != null) {
183 Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing.");
184 }
185
Sanket Agarwalf8a1c912016-01-26 20:12:52 -0800186 // Headset HF
187 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) {
188 if (mHfpClientProfile == null) {
189 if(DEBUG) Log.d(TAG, "Adding local HfpClient profile");
190 mHfpClientProfile =
191 new HfpClientProfile(mContext, mLocalAdapter, mDeviceManager, this);
192 addProfile(mHfpClientProfile, HfpClientProfile.NAME,
193 BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
194 }
195 } else if (mHfpClientProfile != null) {
196 Log.w(TAG,
197 "Warning: Hfp Client profile was previously added but the UUID is now missing.");
198 } else {
199 Log.d(TAG, "Handsfree Uuid not found.");
200 }
201
Jason Monk7ce96b92015-02-02 11:27:58 -0500202 // OPP
203 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
204 if (mOppProfile == null) {
205 if(DEBUG) Log.d(TAG, "Adding local OPP profile");
206 mOppProfile = new OppProfile();
207 // Note: no event handler for OPP, only name map.
208 mProfileNameMap.put(OppProfile.NAME, mOppProfile);
209 }
210 } else if (mOppProfile != null) {
211 Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
212 }
Joseph Pirozzo563c7002016-03-21 15:49:48 -0700213
214 //PBAP Client
215 if (mUsePbapPce) {
216 if (mPbapClientProfile == null) {
217 if(DEBUG) Log.d(TAG, "Adding local PBAP Client profile");
218 mPbapClientProfile = new PbapClientProfile(mContext, mLocalAdapter, mDeviceManager,
219 this);
220 addProfile(mPbapClientProfile, PbapClientProfile.NAME,
221 BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED);
222 }
223 } else if (mPbapClientProfile != null) {
224 Log.w(TAG,
225 "Warning: PBAP Client profile was previously added but the UUID is now missing.");
226 }
227
Jason Monk7ce96b92015-02-02 11:27:58 -0500228 mEventManager.registerProfileIntentReceiver();
229
Joseph Pirozzo563c7002016-03-21 15:49:48 -0700230 // There is no local SDP record for HID and Settings app doesn't control PBAP Server.
Jason Monk7ce96b92015-02-02 11:27:58 -0500231 }
232
233 private final Collection<ServiceListener> mServiceListeners =
234 new ArrayList<ServiceListener>();
235
236 private void addProfile(LocalBluetoothProfile profile,
237 String profileName, String stateChangedAction) {
238 mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile));
239 mProfileNameMap.put(profileName, profile);
240 }
241
242 private void addPanProfile(LocalBluetoothProfile profile,
243 String profileName, String stateChangedAction) {
244 mEventManager.addProfileHandler(stateChangedAction,
245 new PanStateChangedHandler(profile));
246 mProfileNameMap.put(profileName, profile);
247 }
248
249 public LocalBluetoothProfile getProfileByName(String name) {
250 return mProfileNameMap.get(name);
251 }
252
253 // Called from LocalBluetoothAdapter when state changes to ON
254 void setBluetoothStateOn() {
255 ParcelUuid[] uuids = mLocalAdapter.getUuids();
256 if (uuids != null) {
257 updateLocalProfiles(uuids);
258 }
259 mEventManager.readPairedDevices();
260 }
261
262 /**
263 * Generic handler for connection state change events for the specified profile.
264 */
265 private class StateChangedHandler implements BluetoothEventManager.Handler {
266 final LocalBluetoothProfile mProfile;
267
268 StateChangedHandler(LocalBluetoothProfile profile) {
269 mProfile = profile;
270 }
271
272 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
273 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
274 if (cachedDevice == null) {
275 Log.w(TAG, "StateChangedHandler found new device: " + device);
276 cachedDevice = mDeviceManager.addDevice(mLocalAdapter,
277 LocalBluetoothProfileManager.this, device);
278 }
279 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
280 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
281 if (newState == BluetoothProfile.STATE_DISCONNECTED &&
282 oldState == BluetoothProfile.STATE_CONNECTING) {
283 Log.i(TAG, "Failed to connect " + mProfile + " device");
284 }
285
286 cachedDevice.onProfileStateChanged(mProfile, newState);
287 cachedDevice.refresh();
288 }
289 }
290
291 /** State change handler for NAP and PANU profiles. */
292 private class PanStateChangedHandler extends StateChangedHandler {
293
294 PanStateChangedHandler(LocalBluetoothProfile profile) {
295 super(profile);
296 }
297
298 @Override
299 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
300 PanProfile panProfile = (PanProfile) mProfile;
301 int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0);
302 panProfile.setLocalRole(device, role);
303 super.onReceive(context, intent, device);
304 }
305 }
306
307 // called from DockService
308 public void addServiceListener(ServiceListener l) {
309 mServiceListeners.add(l);
310 }
311
312 // called from DockService
313 public void removeServiceListener(ServiceListener l) {
314 mServiceListeners.remove(l);
315 }
316
317 // not synchronized: use only from UI thread! (TODO: verify)
318 void callServiceConnectedListeners() {
319 for (ServiceListener l : mServiceListeners) {
320 l.onServiceConnected();
321 }
322 }
323
324 // not synchronized: use only from UI thread! (TODO: verify)
325 void callServiceDisconnectedListeners() {
326 for (ServiceListener listener : mServiceListeners) {
327 listener.onServiceDisconnected();
328 }
329 }
330
331 // This is called by DockService, so check Headset and A2DP.
332 public synchronized boolean isManagerReady() {
333 // Getting just the headset profile is fine for now. Will need to deal with A2DP
334 // and others if they aren't always in a ready state.
335 LocalBluetoothProfile profile = mHeadsetProfile;
336 if (profile != null) {
337 return profile.isProfileReady();
338 }
339 profile = mA2dpProfile;
340 if (profile != null) {
341 return profile.isProfileReady();
342 }
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700343 profile = mA2dpSinkProfile;
344 if (profile != null) {
345 return profile.isProfileReady();
346 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500347 return false;
348 }
349
350 public A2dpProfile getA2dpProfile() {
351 return mA2dpProfile;
352 }
353
Sanket Agarwalf8a1c912016-01-26 20:12:52 -0800354 public A2dpSinkProfile getA2dpSinkProfile() {
355 if ((mA2dpSinkProfile != null) && (mA2dpSinkProfile.isProfileReady())) {
356 return mA2dpSinkProfile;
357 } else {
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700358 return null;
Sanket Agarwalf8a1c912016-01-26 20:12:52 -0800359 }
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700360 }
361
Jason Monk7ce96b92015-02-02 11:27:58 -0500362 public HeadsetProfile getHeadsetProfile() {
363 return mHeadsetProfile;
364 }
365
Sanket Agarwalf8a1c912016-01-26 20:12:52 -0800366 public HfpClientProfile getHfpClientProfile() {
367 if ((mHfpClientProfile != null) && (mHfpClientProfile.isProfileReady())) {
368 return mHfpClientProfile;
369 } else {
370 return null;
371 }
372 }
373
Joseph Pirozzo563c7002016-03-21 15:49:48 -0700374 public PbapClientProfile getPbapClientProfile() {
375 return mPbapClientProfile;
376 }
377
Jason Monk7ce96b92015-02-02 11:27:58 -0500378 public PbapServerProfile getPbapProfile(){
379 return mPbapProfile;
380 }
381
382 public MapProfile getMapProfile(){
383 return mMapProfile;
384 }
385
386 /**
387 * Fill in a list of LocalBluetoothProfile objects that are supported by
388 * the local device and the remote device.
389 *
390 * @param uuids of the remote device
391 * @param localUuids UUIDs of the local device
392 * @param profiles The list of profiles to fill
393 * @param removedProfiles list of profiles that were removed
394 */
395 synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids,
396 Collection<LocalBluetoothProfile> profiles,
397 Collection<LocalBluetoothProfile> removedProfiles,
398 boolean isPanNapConnected, BluetoothDevice device) {
399 // Copy previous profile list into removedProfiles
400 removedProfiles.clear();
401 removedProfiles.addAll(profiles);
Joseph Pirozzo99fecc02016-04-07 13:43:49 -0700402 if (DEBUG) {
403 Log.d(TAG,"Current Profiles" + profiles.toString());
404 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500405 profiles.clear();
406
407 if (uuids == null) {
408 return;
409 }
410
411 if (mHeadsetProfile != null) {
412 if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) &&
413 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) ||
414 (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) &&
415 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) {
416 profiles.add(mHeadsetProfile);
417 removedProfiles.remove(mHeadsetProfile);
418 }
419 }
420
Joseph Pirozzo99fecc02016-04-07 13:43:49 -0700421 if ((mHfpClientProfile != null) &&
422 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) &&
423 BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree)) {
424 profiles.add(mHfpClientProfile);
425 removedProfiles.remove(mHfpClientProfile);
426 }
427
Jason Monk7ce96b92015-02-02 11:27:58 -0500428 if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) &&
429 mA2dpProfile != null) {
430 profiles.add(mA2dpProfile);
431 removedProfiles.remove(mA2dpProfile);
432 }
433
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700434 if (BluetoothUuid.containsAnyUuid(uuids, A2dpSinkProfile.SRC_UUIDS) &&
435 mA2dpSinkProfile != null) {
436 profiles.add(mA2dpSinkProfile);
437 removedProfiles.remove(mA2dpSinkProfile);
Joseph Pirozzo99fecc02016-04-07 13:43:49 -0700438 }
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700439
Jason Monk7ce96b92015-02-02 11:27:58 -0500440 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
441 mOppProfile != null) {
442 profiles.add(mOppProfile);
443 removedProfiles.remove(mOppProfile);
444 }
445
446 if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) ||
447 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) &&
448 mHidProfile != null) {
449 profiles.add(mHidProfile);
450 removedProfiles.remove(mHidProfile);
451 }
452
453 if(isPanNapConnected)
454 if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists.");
455 if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) &&
456 mPanProfile != null) || isPanNapConnected) {
457 profiles.add(mPanProfile);
458 removedProfiles.remove(mPanProfile);
459 }
460
461 if ((mMapProfile != null) &&
462 (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) {
463 profiles.add(mMapProfile);
464 removedProfiles.remove(mMapProfile);
465 mMapProfile.setPreferred(device, true);
466 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500467
Joseph Pirozzo563c7002016-03-21 15:49:48 -0700468 if (mUsePbapPce) {
469 profiles.add(mPbapClientProfile);
470 removedProfiles.remove(mPbapClientProfile);
471 profiles.remove(mPbapProfile);
472 removedProfiles.add(mPbapProfile);
473 }
Joseph Pirozzo99fecc02016-04-07 13:43:49 -0700474
475 if (DEBUG) {
476 Log.d(TAG,"New Profiles" + profiles.toString());
477 }
Joseph Pirozzo563c7002016-03-21 15:49:48 -0700478 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500479}