• 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.settings.bluetooth;
18 
19 import android.bluetooth.BluetoothA2dp;
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.BluetoothHeadset;
22 import android.bluetooth.BluetoothInputDevice;
23 import android.bluetooth.BluetoothPan;
24 import android.bluetooth.BluetoothPbap;
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 android.os.Handler;
32 import android.os.Message;
33 
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.HashMap;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.List;
40 
41 /**
42  * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
43  * objects for the available Bluetooth profiles.
44  */
45 final class LocalBluetoothProfileManager {
46     private static final String TAG = "LocalBluetoothProfileManager";
47 
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 {@link 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 HeadsetProfile mHeadsetProfile;
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 
LocalBluetoothProfileManager(Context context, LocalBluetoothAdapter adapter, CachedBluetoothDeviceManager deviceManager, BluetoothEventManager eventManager)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        //Create PBAP server profile, but do not add it to list of profiles
123        // as we do not need to monitor the profile as part of profile list
124         mPbapProfile = new PbapServerProfile(context);
125 
126         Log.d(TAG, "LocalBluetoothProfileManager construction complete");
127     }
128 
129     /**
130      * Initialize or update the local profile objects. If a UUID was previously
131      * present but has been removed, we print a warning but don't remove the
132      * profile object as it might be referenced elsewhere, or the UUID might
133      * come back and we don't want multiple copies of the profile objects.
134      * @param uuids
135      */
updateLocalProfiles(ParcelUuid[] uuids)136     void updateLocalProfiles(ParcelUuid[] uuids) {
137         // A2DP
138         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
139             if (mA2dpProfile == null) {
140                 Log.d(TAG, "Adding local A2DP profile");
141                 mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this);
142                 addProfile(mA2dpProfile, A2dpProfile.NAME,
143                         BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
144             }
145         } else if (mA2dpProfile != null) {
146             Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing.");
147         }
148 
149         // Headset / Handsfree
150         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
151             BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
152             if (mHeadsetProfile == null) {
153                 Log.d(TAG, "Adding local HEADSET profile");
154                 mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter,
155                         mDeviceManager, this);
156                 addProfile(mHeadsetProfile, HeadsetProfile.NAME,
157                         BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
158             }
159         } else if (mHeadsetProfile != null) {
160             Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing.");
161         }
162 
163         // OPP
164         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
165             if (mOppProfile == null) {
166                 Log.d(TAG, "Adding local OPP profile");
167                 mOppProfile = new OppProfile();
168                 // Note: no event handler for OPP, only name map.
169                 mProfileNameMap.put(OppProfile.NAME, mOppProfile);
170             }
171         } else if (mOppProfile != null) {
172             Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
173         }
174         mEventManager.registerProfileIntentReceiver();
175 
176         // There is no local SDP record for HID and Settings app doesn't control PBAP
177     }
178 
179     private final Collection<ServiceListener> mServiceListeners =
180             new ArrayList<ServiceListener>();
181 
addProfile(LocalBluetoothProfile profile, String profileName, String stateChangedAction)182     private void addProfile(LocalBluetoothProfile profile,
183             String profileName, String stateChangedAction) {
184         mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile));
185         mProfileNameMap.put(profileName, profile);
186     }
187 
addPanProfile(LocalBluetoothProfile profile, String profileName, String stateChangedAction)188     private void addPanProfile(LocalBluetoothProfile profile,
189             String profileName, String stateChangedAction) {
190         mEventManager.addProfileHandler(stateChangedAction,
191                 new PanStateChangedHandler(profile));
192         mProfileNameMap.put(profileName, profile);
193     }
194 
getProfileByName(String name)195     LocalBluetoothProfile getProfileByName(String name) {
196         return mProfileNameMap.get(name);
197     }
198 
199     // Called from LocalBluetoothAdapter when state changes to ON
setBluetoothStateOn()200     void setBluetoothStateOn() {
201         ParcelUuid[] uuids = mLocalAdapter.getUuids();
202         if (uuids != null) {
203             updateLocalProfiles(uuids);
204         }
205         mEventManager.readPairedDevices();
206     }
207 
208     /**
209      * Generic handler for connection state change events for the specified profile.
210      */
211     private class StateChangedHandler implements BluetoothEventManager.Handler {
212         final LocalBluetoothProfile mProfile;
213 
StateChangedHandler(LocalBluetoothProfile profile)214         StateChangedHandler(LocalBluetoothProfile profile) {
215             mProfile = profile;
216         }
217 
onReceive(Context context, Intent intent, BluetoothDevice device)218         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
219             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
220             if (cachedDevice == null) {
221                 Log.w(TAG, "StateChangedHandler found new device: " + device);
222                 cachedDevice = mDeviceManager.addDevice(mLocalAdapter,
223                         LocalBluetoothProfileManager.this, device);
224             }
225             int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
226             int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
227             if (newState == BluetoothProfile.STATE_DISCONNECTED &&
228                     oldState == BluetoothProfile.STATE_CONNECTING) {
229                 Log.i(TAG, "Failed to connect " + mProfile + " device");
230             }
231 
232             cachedDevice.onProfileStateChanged(mProfile, newState);
233             cachedDevice.refresh();
234         }
235     }
236 
237     /** State change handler for NAP and PANU profiles. */
238     private class PanStateChangedHandler extends StateChangedHandler {
239 
PanStateChangedHandler(LocalBluetoothProfile profile)240         PanStateChangedHandler(LocalBluetoothProfile profile) {
241             super(profile);
242         }
243 
244         @Override
onReceive(Context context, Intent intent, BluetoothDevice device)245         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
246             PanProfile panProfile = (PanProfile) mProfile;
247             int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0);
248             panProfile.setLocalRole(device, role);
249             super.onReceive(context, intent, device);
250         }
251     }
252 
253     // called from DockService
addServiceListener(ServiceListener l)254     void addServiceListener(ServiceListener l) {
255         mServiceListeners.add(l);
256     }
257 
258     // called from DockService
removeServiceListener(ServiceListener l)259     void removeServiceListener(ServiceListener l) {
260         mServiceListeners.remove(l);
261     }
262 
263     // not synchronized: use only from UI thread! (TODO: verify)
callServiceConnectedListeners()264     void callServiceConnectedListeners() {
265         for (ServiceListener l : mServiceListeners) {
266             l.onServiceConnected();
267         }
268     }
269 
270     // not synchronized: use only from UI thread! (TODO: verify)
callServiceDisconnectedListeners()271     void callServiceDisconnectedListeners() {
272         for (ServiceListener listener : mServiceListeners) {
273             listener.onServiceDisconnected();
274         }
275     }
276 
277     // This is called by DockService, so check Headset and A2DP.
isManagerReady()278     public synchronized boolean isManagerReady() {
279         // Getting just the headset profile is fine for now. Will need to deal with A2DP
280         // and others if they aren't always in a ready state.
281         LocalBluetoothProfile profile = mHeadsetProfile;
282         if (profile != null) {
283             return profile.isProfileReady();
284         }
285         profile = mA2dpProfile;
286         if (profile != null) {
287             return profile.isProfileReady();
288         }
289         return false;
290     }
291 
getA2dpProfile()292     A2dpProfile getA2dpProfile() {
293         return mA2dpProfile;
294     }
295 
getHeadsetProfile()296     HeadsetProfile getHeadsetProfile() {
297         return mHeadsetProfile;
298     }
299 
getPbapProfile()300     PbapServerProfile getPbapProfile(){
301         return mPbapProfile;
302     }
303 
304 
305     /**
306      * Fill in a list of LocalBluetoothProfile objects that are supported by
307      * the local device and the remote device.
308      *
309      * @param uuids of the remote device
310      * @param localUuids UUIDs of the local device
311      * @param profiles The list of profiles to fill
312      * @param removedProfiles list of profiles that were removed
313      */
updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, Collection<LocalBluetoothProfile> profiles, Collection<LocalBluetoothProfile> removedProfiles, boolean isPanNapConnected)314     synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids,
315             Collection<LocalBluetoothProfile> profiles,
316             Collection<LocalBluetoothProfile> removedProfiles,
317             boolean isPanNapConnected) {
318         // Copy previous profile list into removedProfiles
319         removedProfiles.clear();
320         removedProfiles.addAll(profiles);
321         profiles.clear();
322 
323         if (uuids == null) {
324             return;
325         }
326 
327         if (mHeadsetProfile != null) {
328             if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) &&
329                     BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) ||
330                     (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) &&
331                             BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) {
332                 profiles.add(mHeadsetProfile);
333                 removedProfiles.remove(mHeadsetProfile);
334             }
335         }
336 
337         if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) &&
338             mA2dpProfile != null) {
339             profiles.add(mA2dpProfile);
340             removedProfiles.remove(mA2dpProfile);
341         }
342 
343         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
344             mOppProfile != null) {
345             profiles.add(mOppProfile);
346             removedProfiles.remove(mOppProfile);
347         }
348 
349         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) &&
350             mHidProfile != null) {
351             profiles.add(mHidProfile);
352             removedProfiles.remove(mHidProfile);
353         }
354 
355         if(isPanNapConnected)
356             Log.d(TAG, "Valid PAN-NAP connection exists.");
357         if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) &&
358             mPanProfile != null) || isPanNapConnected) {
359             profiles.add(mPanProfile);
360             removedProfiles.remove(mPanProfile);
361         }
362     }
363 
364 }
365