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