Home | History | Annotate | Download | only in bluetooth
      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.BluetoothMapClient;
     26 import android.bluetooth.BluetoothInputDevice;
     27 import android.bluetooth.BluetoothPan;
     28 import android.bluetooth.BluetoothPbapClient;
     29 import android.bluetooth.BluetoothProfile;
     30 import android.bluetooth.BluetoothUuid;
     31 import android.content.Context;
     32 import android.content.Intent;
     33 import android.os.ParcelUuid;
     34 import android.util.Log;
     35 import com.android.internal.R;
     36 import java.util.ArrayList;
     37 import java.util.Collection;
     38 import java.util.HashMap;
     39 import java.util.Map;
     40 
     41 /**
     42  * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
     43  * objects for the available Bluetooth profiles.
     44  */
     45 public 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