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