LocalBluetoothProfileManager.java revision 631768d81469f83552de05bf0ec839d1cd6eab04
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
17package com.android.settingslib.bluetooth;
18
19import android.bluetooth.BluetoothA2dp;
20import android.bluetooth.BluetoothA2dpSink;
21import android.bluetooth.BluetoothDevice;
22import android.bluetooth.BluetoothHeadset;
23import android.bluetooth.BluetoothHeadsetClient;
24import android.bluetooth.BluetoothMap;
25import android.bluetooth.BluetoothMapClient;
26import android.bluetooth.BluetoothInputDevice;
27import android.bluetooth.BluetoothPan;
28import android.bluetooth.BluetoothPbapClient;
29import android.bluetooth.BluetoothProfile;
30import android.bluetooth.BluetoothUuid;
31import android.content.Context;
32import android.content.Intent;
33import android.os.ParcelUuid;
34import android.util.Log;
35import com.android.settingslib.R;
36import java.util.ArrayList;
37import java.util.Collection;
38import java.util.HashMap;
39import java.util.Map;
40
41/**
42 * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
43 * objects for the available Bluetooth profiles.
44 */
45public final 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         */
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         */
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
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     */
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
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
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
275    public LocalBluetoothProfile getProfileByName(String name) {
276        return mProfileNameMap.get(name);
277    }
278
279    // Called from LocalBluetoothAdapter when state changes to ON
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
294        StateChangedHandler(LocalBluetoothProfile profile) {
295            mProfile = profile;
296        }
297
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
320        PanStateChangedHandler(LocalBluetoothProfile profile) {
321            super(profile);
322        }
323
324        @Override
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
334    public void addServiceListener(ServiceListener l) {
335        mServiceListeners.add(l);
336    }
337
338    // called from DockService
339    public void removeServiceListener(ServiceListener l) {
340        mServiceListeners.remove(l);
341    }
342
343    // not synchronized: use only from UI thread! (TODO: verify)
344    void callServiceConnectedListeners() {
345        for (ServiceListener l : mServiceListeners) {
346            l.onServiceConnected();
347        }
348    }
349
350    // not synchronized: use only from UI thread! (TODO: verify)
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.
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
376    public A2dpProfile getA2dpProfile() {
377        return mA2dpProfile;
378    }
379
380    public A2dpSinkProfile getA2dpSinkProfile() {
381        if ((mA2dpSinkProfile != null) && (mA2dpSinkProfile.isProfileReady())) {
382            return mA2dpSinkProfile;
383        } else {
384            return null;
385        }
386    }
387
388    public HeadsetProfile getHeadsetProfile() {
389        return mHeadsetProfile;
390    }
391
392    public HfpClientProfile getHfpClientProfile() {
393        if ((mHfpClientProfile != null) && (mHfpClientProfile.isProfileReady())) {
394            return mHfpClientProfile;
395        } else {
396          return null;
397        }
398    }
399
400    public PbapClientProfile getPbapClientProfile() {
401        return mPbapClientProfile;
402    }
403
404    public PbapServerProfile getPbapProfile(){
405        return mPbapProfile;
406    }
407
408    public MapProfile getMapProfile(){
409        return mMapProfile;
410    }
411
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     */
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