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