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