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.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          */
     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          */
     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 
     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      */
    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 
    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 
    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 
    203     LocalBluetoothProfile getProfileByName(String name) {
    204         return mProfileNameMap.get(name);
    205     }
    206 
    207     // Called from LocalBluetoothAdapter when state changes to ON
    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 
    222         StateChangedHandler(LocalBluetoothProfile profile) {
    223             mProfile = profile;
    224         }
    225 
    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 
    248         PanStateChangedHandler(LocalBluetoothProfile profile) {
    249             super(profile);
    250         }
    251 
    252         @Override
    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
    262     void addServiceListener(ServiceListener l) {
    263         mServiceListeners.add(l);
    264     }
    265 
    266     // called from DockService
    267     void removeServiceListener(ServiceListener l) {
    268         mServiceListeners.remove(l);
    269     }
    270 
    271     // not synchronized: use only from UI thread! (TODO: verify)
    272     void callServiceConnectedListeners() {
    273         for (ServiceListener l : mServiceListeners) {
    274             l.onServiceConnected();
    275         }
    276     }
    277 
    278     // not synchronized: use only from UI thread! (TODO: verify)
    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.
    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 
    300     A2dpProfile getA2dpProfile() {
    301         return mA2dpProfile;
    302     }
    303 
    304     HeadsetProfile getHeadsetProfile() {
    305         return mHeadsetProfile;
    306     }
    307 
    308     PbapServerProfile getPbapProfile(){
    309         return mPbapProfile;
    310     }
    311 
    312     /**
    313      * Fill in a list of LocalBluetoothProfile objects that are supported by
    314      * the local device and the remote device.
    315      *
    316      * @param uuids of the remote device
    317      * @param localUuids UUIDs of the local device
    318      * @param profiles The list of profiles to fill
    319      * @param removedProfiles list of profiles that were removed
    320      */
    321     synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids,
    322             Collection<LocalBluetoothProfile> profiles,
    323             Collection<LocalBluetoothProfile> removedProfiles,
    324             boolean isPanNapConnected, BluetoothDevice device) {
    325         // Copy previous profile list into removedProfiles
    326         removedProfiles.clear();
    327         removedProfiles.addAll(profiles);
    328         profiles.clear();
    329 
    330         if (uuids == null) {
    331             return;
    332         }
    333 
    334         if (mHeadsetProfile != null) {
    335             if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) &&
    336                     BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) ||
    337                     (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) &&
    338                             BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) {
    339                 profiles.add(mHeadsetProfile);
    340                 removedProfiles.remove(mHeadsetProfile);
    341             }
    342         }
    343 
    344         if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) &&
    345             mA2dpProfile != null) {
    346             profiles.add(mA2dpProfile);
    347             removedProfiles.remove(mA2dpProfile);
    348         }
    349 
    350         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
    351             mOppProfile != null) {
    352             profiles.add(mOppProfile);
    353             removedProfiles.remove(mOppProfile);
    354         }
    355 
    356         if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) ||
    357              BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) &&
    358             mHidProfile != null) {
    359             profiles.add(mHidProfile);
    360             removedProfiles.remove(mHidProfile);
    361         }
    362 
    363         if(isPanNapConnected)
    364             if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists.");
    365         if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) &&
    366             mPanProfile != null) || isPanNapConnected) {
    367             profiles.add(mPanProfile);
    368             removedProfiles.remove(mPanProfile);
    369         }
    370 
    371         if ((mMapProfile != null) &&
    372             (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) {
    373             profiles.add(mMapProfile);
    374             removedProfiles.remove(mMapProfile);
    375             mMapProfile.setPreferred(device, true);
    376         }
    377     }
    378 
    379 }
    380