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