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