blob: 6226b23c0ab2951211f7bb49e409400e0f6b66f4 [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;
Jason Monk7ce96b92015-02-02 11:27:58 -050027import android.bluetooth.BluetoothProfile;
28import android.bluetooth.BluetoothUuid;
29import android.content.Context;
30import android.content.Intent;
31import android.os.ParcelUuid;
32import android.util.Log;
Jason Monk7ce96b92015-02-02 11:27:58 -050033import java.util.ArrayList;
34import java.util.Collection;
35import java.util.HashMap;
36import java.util.Map;
Jason Monk7ce96b92015-02-02 11:27:58 -050037
38/**
39 * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
40 * objects for the available Bluetooth profiles.
41 */
42public final class LocalBluetoothProfileManager {
43 private static final String TAG = "LocalBluetoothProfileManager";
44 private static final boolean DEBUG = Utils.D;
45 /** Singleton instance. */
46 private static LocalBluetoothProfileManager sInstance;
47
48 /**
49 * An interface for notifying BluetoothHeadset IPC clients when they have
50 * been connected to the BluetoothHeadset service.
51 * Only used by com.android.settings.bluetooth.DockService.
52 */
53 public interface ServiceListener {
54 /**
55 * Called to notify the client when this proxy object has been
56 * connected to the BluetoothHeadset service. Clients must wait for
57 * this callback before making IPC calls on the BluetoothHeadset
58 * service.
59 */
60 void onServiceConnected();
61
62 /**
63 * Called to notify the client that this proxy object has been
64 * disconnected from the BluetoothHeadset service. Clients must not
65 * make IPC calls on the BluetoothHeadset service after this callback.
66 * This callback will currently only occur if the application hosting
67 * the BluetoothHeadset service, but may be called more often in future.
68 */
69 void onServiceDisconnected();
70 }
71
72 private final Context mContext;
73 private final LocalBluetoothAdapter mLocalAdapter;
74 private final CachedBluetoothDeviceManager mDeviceManager;
75 private final BluetoothEventManager mEventManager;
76
77 private A2dpProfile mA2dpProfile;
Sanket Agarwal1bec6a52015-10-21 18:23:27 -070078 private A2dpSinkProfile mA2dpSinkProfile;
Jason Monk7ce96b92015-02-02 11:27:58 -050079 private HeadsetProfile mHeadsetProfile;
Sanket Agarwalf8a1c912016-01-26 20:12:52 -080080 private HfpClientProfile mHfpClientProfile;
Jason Monk7ce96b92015-02-02 11:27:58 -050081 private MapProfile mMapProfile;
82 private final HidProfile mHidProfile;
83 private OppProfile mOppProfile;
84 private final PanProfile mPanProfile;
85 private final PbapServerProfile mPbapProfile;
86
87 /**
88 * Mapping from profile name, e.g. "HEADSET" to profile object.
89 */
90 private final Map<String, LocalBluetoothProfile>
91 mProfileNameMap = new HashMap<String, LocalBluetoothProfile>();
92
93 LocalBluetoothProfileManager(Context context,
94 LocalBluetoothAdapter adapter,
95 CachedBluetoothDeviceManager deviceManager,
96 BluetoothEventManager eventManager) {
97 mContext = context;
98
99 mLocalAdapter = adapter;
100 mDeviceManager = deviceManager;
101 mEventManager = eventManager;
102 // pass this reference to adapter and event manager (circular dependency)
103 mLocalAdapter.setProfileManager(this);
104 mEventManager.setProfileManager(this);
105
106 ParcelUuid[] uuids = adapter.getUuids();
107
108 // uuids may be null if Bluetooth is turned off
109 if (uuids != null) {
110 updateLocalProfiles(uuids);
111 }
112
113 // Always add HID and PAN profiles
114 mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this);
115 addProfile(mHidProfile, HidProfile.NAME,
116 BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
117
118 mPanProfile = new PanProfile(context);
119 addPanProfile(mPanProfile, PanProfile.NAME,
120 BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
121
122 if(DEBUG) Log.d(TAG, "Adding local MAP profile");
123 mMapProfile = new MapProfile(mContext, mLocalAdapter,
124 mDeviceManager, this);
125 addProfile(mMapProfile, MapProfile.NAME,
126 BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
127
128 //Create PBAP server profile, but do not add it to list of profiles
129 // as we do not need to monitor the profile as part of profile list
130 mPbapProfile = new PbapServerProfile(context);
131
132 if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete");
133 }
134
135 /**
136 * Initialize or update the local profile objects. If a UUID was previously
137 * present but has been removed, we print a warning but don't remove the
138 * profile object as it might be referenced elsewhere, or the UUID might
139 * come back and we don't want multiple copies of the profile objects.
140 * @param uuids
141 */
142 void updateLocalProfiles(ParcelUuid[] uuids) {
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700143 // A2DP SRC
Jason Monk7ce96b92015-02-02 11:27:58 -0500144 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
145 if (mA2dpProfile == null) {
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700146 if(DEBUG) Log.d(TAG, "Adding local A2DP SRC profile");
Jason Monk7ce96b92015-02-02 11:27:58 -0500147 mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this);
148 addProfile(mA2dpProfile, A2dpProfile.NAME,
149 BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
150 }
151 } else if (mA2dpProfile != null) {
152 Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing.");
153 }
154
Sanket Agarwalf8a1c912016-01-26 20:12:52 -0800155 // A2DP SINK
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700156 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)) {
157 if (mA2dpSinkProfile == null) {
158 if(DEBUG) Log.d(TAG, "Adding local A2DP Sink profile");
159 mA2dpSinkProfile = new A2dpSinkProfile(mContext, mLocalAdapter, mDeviceManager, this);
160 addProfile(mA2dpSinkProfile, A2dpSinkProfile.NAME,
161 BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
162 }
163 } else if (mA2dpSinkProfile != null) {
164 Log.w(TAG, "Warning: A2DP Sink profile was previously added but the UUID is now missing.");
165 }
166
Jason Monk7ce96b92015-02-02 11:27:58 -0500167 // Headset / Handsfree
168 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
169 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
170 if (mHeadsetProfile == null) {
171 if (DEBUG) Log.d(TAG, "Adding local HEADSET profile");
172 mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter,
173 mDeviceManager, this);
174 addProfile(mHeadsetProfile, HeadsetProfile.NAME,
175 BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
176 }
177 } else if (mHeadsetProfile != null) {
178 Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing.");
179 }
180
Sanket Agarwalf8a1c912016-01-26 20:12:52 -0800181 // Headset HF
182 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) {
183 if (mHfpClientProfile == null) {
184 if(DEBUG) Log.d(TAG, "Adding local HfpClient profile");
185 mHfpClientProfile =
186 new HfpClientProfile(mContext, mLocalAdapter, mDeviceManager, this);
187 addProfile(mHfpClientProfile, HfpClientProfile.NAME,
188 BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
189 }
190 } else if (mHfpClientProfile != null) {
191 Log.w(TAG,
192 "Warning: Hfp Client profile was previously added but the UUID is now missing.");
193 } else {
194 Log.d(TAG, "Handsfree Uuid not found.");
195 }
196
Jason Monk7ce96b92015-02-02 11:27:58 -0500197 // OPP
198 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
199 if (mOppProfile == null) {
200 if(DEBUG) Log.d(TAG, "Adding local OPP profile");
201 mOppProfile = new OppProfile();
202 // Note: no event handler for OPP, only name map.
203 mProfileNameMap.put(OppProfile.NAME, mOppProfile);
204 }
205 } else if (mOppProfile != null) {
206 Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
207 }
208 mEventManager.registerProfileIntentReceiver();
209
210 // There is no local SDP record for HID and Settings app doesn't control PBAP
211 }
212
213 private final Collection<ServiceListener> mServiceListeners =
214 new ArrayList<ServiceListener>();
215
216 private void addProfile(LocalBluetoothProfile profile,
217 String profileName, String stateChangedAction) {
218 mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile));
219 mProfileNameMap.put(profileName, profile);
220 }
221
222 private void addPanProfile(LocalBluetoothProfile profile,
223 String profileName, String stateChangedAction) {
224 mEventManager.addProfileHandler(stateChangedAction,
225 new PanStateChangedHandler(profile));
226 mProfileNameMap.put(profileName, profile);
227 }
228
229 public LocalBluetoothProfile getProfileByName(String name) {
230 return mProfileNameMap.get(name);
231 }
232
233 // Called from LocalBluetoothAdapter when state changes to ON
234 void setBluetoothStateOn() {
235 ParcelUuid[] uuids = mLocalAdapter.getUuids();
236 if (uuids != null) {
237 updateLocalProfiles(uuids);
238 }
239 mEventManager.readPairedDevices();
240 }
241
242 /**
243 * Generic handler for connection state change events for the specified profile.
244 */
245 private class StateChangedHandler implements BluetoothEventManager.Handler {
246 final LocalBluetoothProfile mProfile;
247
248 StateChangedHandler(LocalBluetoothProfile profile) {
249 mProfile = profile;
250 }
251
252 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
253 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
254 if (cachedDevice == null) {
255 Log.w(TAG, "StateChangedHandler found new device: " + device);
256 cachedDevice = mDeviceManager.addDevice(mLocalAdapter,
257 LocalBluetoothProfileManager.this, device);
258 }
259 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
260 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
261 if (newState == BluetoothProfile.STATE_DISCONNECTED &&
262 oldState == BluetoothProfile.STATE_CONNECTING) {
263 Log.i(TAG, "Failed to connect " + mProfile + " device");
264 }
265
266 cachedDevice.onProfileStateChanged(mProfile, newState);
267 cachedDevice.refresh();
268 }
269 }
270
271 /** State change handler for NAP and PANU profiles. */
272 private class PanStateChangedHandler extends StateChangedHandler {
273
274 PanStateChangedHandler(LocalBluetoothProfile profile) {
275 super(profile);
276 }
277
278 @Override
279 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
280 PanProfile panProfile = (PanProfile) mProfile;
281 int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0);
282 panProfile.setLocalRole(device, role);
283 super.onReceive(context, intent, device);
284 }
285 }
286
287 // called from DockService
288 public void addServiceListener(ServiceListener l) {
289 mServiceListeners.add(l);
290 }
291
292 // called from DockService
293 public void removeServiceListener(ServiceListener l) {
294 mServiceListeners.remove(l);
295 }
296
297 // not synchronized: use only from UI thread! (TODO: verify)
298 void callServiceConnectedListeners() {
299 for (ServiceListener l : mServiceListeners) {
300 l.onServiceConnected();
301 }
302 }
303
304 // not synchronized: use only from UI thread! (TODO: verify)
305 void callServiceDisconnectedListeners() {
306 for (ServiceListener listener : mServiceListeners) {
307 listener.onServiceDisconnected();
308 }
309 }
310
311 // This is called by DockService, so check Headset and A2DP.
312 public synchronized boolean isManagerReady() {
313 // Getting just the headset profile is fine for now. Will need to deal with A2DP
314 // and others if they aren't always in a ready state.
315 LocalBluetoothProfile profile = mHeadsetProfile;
316 if (profile != null) {
317 return profile.isProfileReady();
318 }
319 profile = mA2dpProfile;
320 if (profile != null) {
321 return profile.isProfileReady();
322 }
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700323 profile = mA2dpSinkProfile;
324 if (profile != null) {
325 return profile.isProfileReady();
326 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500327 return false;
328 }
329
330 public A2dpProfile getA2dpProfile() {
331 return mA2dpProfile;
332 }
333
Sanket Agarwalf8a1c912016-01-26 20:12:52 -0800334 public A2dpSinkProfile getA2dpSinkProfile() {
335 if ((mA2dpSinkProfile != null) && (mA2dpSinkProfile.isProfileReady())) {
336 return mA2dpSinkProfile;
337 } else {
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700338 return null;
Sanket Agarwalf8a1c912016-01-26 20:12:52 -0800339 }
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700340 }
341
Jason Monk7ce96b92015-02-02 11:27:58 -0500342 public HeadsetProfile getHeadsetProfile() {
343 return mHeadsetProfile;
344 }
345
Sanket Agarwalf8a1c912016-01-26 20:12:52 -0800346 public HfpClientProfile getHfpClientProfile() {
347 if ((mHfpClientProfile != null) && (mHfpClientProfile.isProfileReady())) {
348 return mHfpClientProfile;
349 } else {
350 return null;
351 }
352 }
353
Jason Monk7ce96b92015-02-02 11:27:58 -0500354 public PbapServerProfile getPbapProfile(){
355 return mPbapProfile;
356 }
357
358 public MapProfile getMapProfile(){
359 return mMapProfile;
360 }
361
362 /**
363 * Fill in a list of LocalBluetoothProfile objects that are supported by
364 * the local device and the remote device.
365 *
366 * @param uuids of the remote device
367 * @param localUuids UUIDs of the local device
368 * @param profiles The list of profiles to fill
369 * @param removedProfiles list of profiles that were removed
370 */
371 synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids,
372 Collection<LocalBluetoothProfile> profiles,
373 Collection<LocalBluetoothProfile> removedProfiles,
374 boolean isPanNapConnected, BluetoothDevice device) {
375 // Copy previous profile list into removedProfiles
376 removedProfiles.clear();
377 removedProfiles.addAll(profiles);
378 profiles.clear();
379
380 if (uuids == null) {
381 return;
382 }
383
384 if (mHeadsetProfile != null) {
385 if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) &&
386 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) ||
387 (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) &&
388 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) {
389 profiles.add(mHeadsetProfile);
390 removedProfiles.remove(mHeadsetProfile);
391 }
392 }
393
394 if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) &&
395 mA2dpProfile != null) {
396 profiles.add(mA2dpProfile);
397 removedProfiles.remove(mA2dpProfile);
398 }
399
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700400 if (BluetoothUuid.containsAnyUuid(uuids, A2dpSinkProfile.SRC_UUIDS) &&
401 mA2dpSinkProfile != null) {
402 profiles.add(mA2dpSinkProfile);
403 removedProfiles.remove(mA2dpSinkProfile);
404 }
405
Jason Monk7ce96b92015-02-02 11:27:58 -0500406 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
407 mOppProfile != null) {
408 profiles.add(mOppProfile);
409 removedProfiles.remove(mOppProfile);
410 }
411
412 if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) ||
413 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) &&
414 mHidProfile != null) {
415 profiles.add(mHidProfile);
416 removedProfiles.remove(mHidProfile);
417 }
418
419 if(isPanNapConnected)
420 if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists.");
421 if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) &&
422 mPanProfile != null) || isPanNapConnected) {
423 profiles.add(mPanProfile);
424 removedProfiles.remove(mPanProfile);
425 }
426
427 if ((mMapProfile != null) &&
428 (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) {
429 profiles.add(mMapProfile);
430 removedProfiles.remove(mMapProfile);
431 mMapProfile.setPreferred(device, true);
432 }
433 }
434
435}