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.BluetoothInputDevice;
     23 import android.bluetooth.BluetoothPan;
     24 import android.bluetooth.BluetoothProfile;
     25 import android.bluetooth.BluetoothUuid;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.os.ParcelUuid;
     29 import android.util.Log;
     30 
     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 final class LocalBluetoothProfileManager {
     41     private static final String TAG = "LocalBluetoothProfileManager";
     42 
     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 {@link 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 final HidProfile mHidProfile;
     78     private OppProfile mOppProfile;
     79     private final PanProfile mPanProfile;
     80 
     81     /**
     82      * Mapping from profile name, e.g. "HEADSET" to profile object.
     83      */
     84     private final Map<String, LocalBluetoothProfile>
     85             mProfileNameMap = new HashMap<String, LocalBluetoothProfile>();
     86 
     87     LocalBluetoothProfileManager(Context context,
     88             LocalBluetoothAdapter adapter,
     89             CachedBluetoothDeviceManager deviceManager,
     90             BluetoothEventManager eventManager) {
     91         mContext = context;
     92 
     93         mLocalAdapter = adapter;
     94         mDeviceManager = deviceManager;
     95         mEventManager = eventManager;
     96         // pass this reference to adapter and event manager (circular dependency)
     97         mLocalAdapter.setProfileManager(this);
     98         mEventManager.setProfileManager(this);
     99 
    100         ParcelUuid[] uuids = adapter.getUuids();
    101 
    102         // uuids may be null if Bluetooth is turned off
    103         if (uuids != null) {
    104             updateLocalProfiles(uuids);
    105         }
    106 
    107         // Always add HID and PAN profiles
    108         mHidProfile = new HidProfile(context, mLocalAdapter);
    109         addProfile(mHidProfile, HidProfile.NAME,
    110                 BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
    111 
    112         mPanProfile = new PanProfile(context);
    113         addPanProfile(mPanProfile, PanProfile.NAME,
    114                 BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
    115 
    116         Log.d(TAG, "LocalBluetoothProfileManager construction complete");
    117     }
    118 
    119     /**
    120      * Initialize or update the local profile objects. If a UUID was previously
    121      * present but has been removed, we print a warning but don't remove the
    122      * profile object as it might be referenced elsewhere, or the UUID might
    123      * come back and we don't want multiple copies of the profile objects.
    124      * @param uuids
    125      */
    126     void updateLocalProfiles(ParcelUuid[] uuids) {
    127         // A2DP
    128         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
    129             if (mA2dpProfile == null) {
    130                 Log.d(TAG, "Adding local A2DP profile");
    131                 mA2dpProfile = new A2dpProfile(mContext);
    132                 addProfile(mA2dpProfile, A2dpProfile.NAME,
    133                         BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
    134             }
    135         } else if (mA2dpProfile != null) {
    136             Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing.");
    137         }
    138 
    139         // Headset / Handsfree
    140         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
    141             BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
    142             if (mHeadsetProfile == null) {
    143                 Log.d(TAG, "Adding local HEADSET profile");
    144                 mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter,
    145                         mDeviceManager, this);
    146                 addProfile(mHeadsetProfile, HeadsetProfile.NAME,
    147                         BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
    148             }
    149         } else if (mHeadsetProfile != null) {
    150             Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing.");
    151         }
    152 
    153         // OPP
    154         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
    155             if (mOppProfile == null) {
    156                 Log.d(TAG, "Adding local OPP profile");
    157                 mOppProfile = new OppProfile();
    158                 // Note: no event handler for OPP, only name map.
    159                 mProfileNameMap.put(OppProfile.NAME, mOppProfile);
    160             }
    161         } else if (mOppProfile != null) {
    162             Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
    163         }
    164         mEventManager.registerProfileIntentReceiver();
    165 
    166         // There is no local SDP record for HID and Settings app doesn't control PBAP
    167     }
    168 
    169     private final Collection<ServiceListener> mServiceListeners =
    170             new ArrayList<ServiceListener>();
    171 
    172     private void addProfile(LocalBluetoothProfile profile,
    173             String profileName, String stateChangedAction) {
    174         mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile));
    175         mProfileNameMap.put(profileName, profile);
    176     }
    177 
    178     private void addPanProfile(LocalBluetoothProfile profile,
    179             String profileName, String stateChangedAction) {
    180         mEventManager.addProfileHandler(stateChangedAction,
    181                 new PanStateChangedHandler(profile));
    182         mProfileNameMap.put(profileName, profile);
    183     }
    184 
    185     LocalBluetoothProfile getProfileByName(String name) {
    186         return mProfileNameMap.get(name);
    187     }
    188 
    189     // Called from LocalBluetoothAdapter when state changes to ON
    190     void setBluetoothStateOn() {
    191         ParcelUuid[] uuids = mLocalAdapter.getUuids();
    192         if (uuids != null) {
    193             updateLocalProfiles(uuids);
    194         }
    195         mEventManager.readPairedDevices();
    196     }
    197 
    198     /**
    199      * Generic handler for connection state change events for the specified profile.
    200      */
    201     private class StateChangedHandler implements BluetoothEventManager.Handler {
    202         final LocalBluetoothProfile mProfile;
    203 
    204         StateChangedHandler(LocalBluetoothProfile profile) {
    205             mProfile = profile;
    206         }
    207 
    208         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
    209             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
    210             if (cachedDevice == null) {
    211                 Log.w(TAG, "StateChangedHandler found new device: " + device);
    212                 cachedDevice = mDeviceManager.addDevice(mLocalAdapter,
    213                         LocalBluetoothProfileManager.this, device);
    214             }
    215             int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
    216             int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
    217             if (newState == BluetoothProfile.STATE_DISCONNECTED &&
    218                     oldState == BluetoothProfile.STATE_CONNECTING) {
    219                 Log.i(TAG, "Failed to connect " + mProfile + " device");
    220             }
    221 
    222             cachedDevice.onProfileStateChanged(mProfile, newState);
    223             cachedDevice.refresh();
    224         }
    225     }
    226 
    227     /** State change handler for NAP and PANU profiles. */
    228     private class PanStateChangedHandler extends StateChangedHandler {
    229 
    230         PanStateChangedHandler(LocalBluetoothProfile profile) {
    231             super(profile);
    232         }
    233 
    234         @Override
    235         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
    236             PanProfile panProfile = (PanProfile) mProfile;
    237             int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0);
    238             panProfile.setLocalRole(device, role);
    239             super.onReceive(context, intent, device);
    240         }
    241     }
    242 
    243     // called from DockService
    244     void addServiceListener(ServiceListener l) {
    245         mServiceListeners.add(l);
    246     }
    247 
    248     // called from DockService
    249     void removeServiceListener(ServiceListener l) {
    250         mServiceListeners.remove(l);
    251     }
    252 
    253     // not synchronized: use only from UI thread! (TODO: verify)
    254     void callServiceConnectedListeners() {
    255         for (ServiceListener l : mServiceListeners) {
    256             l.onServiceConnected();
    257         }
    258     }
    259 
    260     // not synchronized: use only from UI thread! (TODO: verify)
    261     void callServiceDisconnectedListeners() {
    262         for (ServiceListener listener : mServiceListeners) {
    263             listener.onServiceDisconnected();
    264         }
    265     }
    266 
    267     // This is called by DockService, so check Headset and A2DP.
    268     public synchronized boolean isManagerReady() {
    269         // Getting just the headset profile is fine for now. Will need to deal with A2DP
    270         // and others if they aren't always in a ready state.
    271         LocalBluetoothProfile profile = mHeadsetProfile;
    272         if (profile != null) {
    273             return profile.isProfileReady();
    274         }
    275         profile = mA2dpProfile;
    276         if (profile != null) {
    277             return profile.isProfileReady();
    278         }
    279         return false;
    280     }
    281 
    282     A2dpProfile getA2dpProfile() {
    283         return mA2dpProfile;
    284     }
    285 
    286     HeadsetProfile getHeadsetProfile() {
    287         return mHeadsetProfile;
    288     }
    289 
    290     /**
    291      * Fill in a list of LocalBluetoothProfile objects that are supported by
    292      * the local device and the remote device.
    293      *
    294      * @param uuids of the remote device
    295      * @param localUuids UUIDs of the local device
    296      * @param profiles The list of profiles to fill
    297      * @param removedProfiles list of profiles that were removed
    298      */
    299     synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids,
    300             Collection<LocalBluetoothProfile> profiles,
    301             Collection<LocalBluetoothProfile> removedProfiles) {
    302         // Copy previous profile list into removedProfiles
    303         removedProfiles.clear();
    304         removedProfiles.addAll(profiles);
    305         profiles.clear();
    306 
    307         if (uuids == null) {
    308             return;
    309         }
    310 
    311         if (mHeadsetProfile != null) {
    312             if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) &&
    313                     BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) ||
    314                     (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) &&
    315                             BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) {
    316                 profiles.add(mHeadsetProfile);
    317                 removedProfiles.remove(mHeadsetProfile);
    318             }
    319         }
    320 
    321         if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) &&
    322             mA2dpProfile != null) {
    323             profiles.add(mA2dpProfile);
    324             removedProfiles.remove(mA2dpProfile);
    325         }
    326 
    327         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
    328             mOppProfile != null) {
    329             profiles.add(mOppProfile);
    330             removedProfiles.remove(mOppProfile);
    331         }
    332 
    333         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) &&
    334             mHidProfile != null) {
    335             profiles.add(mHidProfile);
    336             removedProfiles.remove(mHidProfile);
    337         }
    338 
    339         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) &&
    340             mPanProfile != null) {
    341             profiles.add(mPanProfile);
    342             removedProfiles.remove(mPanProfile);
    343         }
    344     }
    345 }
    346