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.BluetoothInputDevice;
26 import android.bluetooth.BluetoothPan;
27 import android.bluetooth.BluetoothPbapClient;
28 import android.bluetooth.BluetoothProfile;
29 import android.bluetooth.BluetoothUuid;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.os.ParcelUuid;
33 import android.util.Log;
34 import com.android.settingslib.R;
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.HashMap;
38 import java.util.Map;
39 
40 /**
41  * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
42  * objects for the available Bluetooth profiles.
43  */
44 public 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          */
onServiceConnected()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          */
onServiceDisconnected()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;
80     private A2dpSinkProfile mA2dpSinkProfile;
81     private HeadsetProfile mHeadsetProfile;
82     private HfpClientProfile mHfpClientProfile;
83     private MapProfile mMapProfile;
84     private final HidProfile mHidProfile;
85     private OppProfile mOppProfile;
86     private final PanProfile mPanProfile;
87     private PbapClientProfile mPbapClientProfile;
88     private final PbapServerProfile mPbapProfile;
89     private final boolean mUsePbapPce;
90 
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 
LocalBluetoothProfileManager(Context context, LocalBluetoothAdapter adapter, CachedBluetoothDeviceManager deviceManager, BluetoothEventManager eventManager)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;
106         mUsePbapPce = mContext.getResources().getBoolean(R.bool.enable_pbap_pce_profile);
107         // 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      */
updateLocalProfiles(ParcelUuid[] uuids)147     void updateLocalProfiles(ParcelUuid[] uuids) {
148         // A2DP SRC
149         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
150             if (mA2dpProfile == null) {
151                 if(DEBUG) Log.d(TAG, "Adding local A2DP SRC profile");
152                 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 
160         // A2DP SINK
161         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 
172         // 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 
186         // 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 
202         // 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         }
213 
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 
228         mEventManager.registerProfileIntentReceiver();
229 
230         // There is no local SDP record for HID and Settings app doesn't control PBAP Server.
231     }
232 
233     private final Collection<ServiceListener> mServiceListeners =
234             new ArrayList<ServiceListener>();
235 
addProfile(LocalBluetoothProfile profile, String profileName, String stateChangedAction)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 
addPanProfile(LocalBluetoothProfile profile, String profileName, String stateChangedAction)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 
getProfileByName(String name)249     public LocalBluetoothProfile getProfileByName(String name) {
250         return mProfileNameMap.get(name);
251     }
252 
253     // Called from LocalBluetoothAdapter when state changes to ON
setBluetoothStateOn()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 
StateChangedHandler(LocalBluetoothProfile profile)268         StateChangedHandler(LocalBluetoothProfile profile) {
269             mProfile = profile;
270         }
271 
onReceive(Context context, Intent intent, BluetoothDevice device)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 
PanStateChangedHandler(LocalBluetoothProfile profile)294         PanStateChangedHandler(LocalBluetoothProfile profile) {
295             super(profile);
296         }
297 
298         @Override
onReceive(Context context, Intent intent, BluetoothDevice device)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
addServiceListener(ServiceListener l)308     public void addServiceListener(ServiceListener l) {
309         mServiceListeners.add(l);
310     }
311 
312     // called from DockService
removeServiceListener(ServiceListener l)313     public void removeServiceListener(ServiceListener l) {
314         mServiceListeners.remove(l);
315     }
316 
317     // not synchronized: use only from UI thread! (TODO: verify)
callServiceConnectedListeners()318     void callServiceConnectedListeners() {
319         for (ServiceListener l : mServiceListeners) {
320             l.onServiceConnected();
321         }
322     }
323 
324     // not synchronized: use only from UI thread! (TODO: verify)
callServiceDisconnectedListeners()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.
isManagerReady()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         }
343         profile = mA2dpSinkProfile;
344         if (profile != null) {
345             return profile.isProfileReady();
346         }
347         return false;
348     }
349 
getA2dpProfile()350     public A2dpProfile getA2dpProfile() {
351         return mA2dpProfile;
352     }
353 
getA2dpSinkProfile()354     public A2dpSinkProfile getA2dpSinkProfile() {
355         if ((mA2dpSinkProfile != null) && (mA2dpSinkProfile.isProfileReady())) {
356             return mA2dpSinkProfile;
357         } else {
358             return null;
359         }
360     }
361 
getHeadsetProfile()362     public HeadsetProfile getHeadsetProfile() {
363         return mHeadsetProfile;
364     }
365 
getHfpClientProfile()366     public HfpClientProfile getHfpClientProfile() {
367         if ((mHfpClientProfile != null) && (mHfpClientProfile.isProfileReady())) {
368             return mHfpClientProfile;
369         } else {
370           return null;
371         }
372     }
373 
getPbapClientProfile()374     public PbapClientProfile getPbapClientProfile() {
375         return mPbapClientProfile;
376     }
377 
getPbapProfile()378     public PbapServerProfile getPbapProfile(){
379         return mPbapProfile;
380     }
381 
getMapProfile()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      */
updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, Collection<LocalBluetoothProfile> profiles, Collection<LocalBluetoothProfile> removedProfiles, boolean isPanNapConnected, BluetoothDevice device)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);
402         if (DEBUG) {
403             Log.d(TAG,"Current Profiles" + profiles.toString());
404         }
405         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 
421         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 
428         if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) &&
429             mA2dpProfile != null) {
430             profiles.add(mA2dpProfile);
431             removedProfiles.remove(mA2dpProfile);
432         }
433 
434         if (BluetoothUuid.containsAnyUuid(uuids, A2dpSinkProfile.SRC_UUIDS) &&
435                 mA2dpSinkProfile != null) {
436                 profiles.add(mA2dpSinkProfile);
437                 removedProfiles.remove(mA2dpSinkProfile);
438         }
439 
440         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         }
467 
468         if (mUsePbapPce) {
469             profiles.add(mPbapClientProfile);
470             removedProfiles.remove(mPbapClientProfile);
471             profiles.remove(mPbapProfile);
472             removedProfiles.add(mPbapProfile);
473         }
474 
475         if (DEBUG) {
476             Log.d(TAG,"New Profiles" + profiles.toString());
477         }
478     }
479 }
480