Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright 2018 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 package com.android.settingslib.media;
     17 
     18 import android.app.Notification;
     19 import android.bluetooth.BluetoothAdapter;
     20 import android.bluetooth.BluetoothDevice;
     21 import android.bluetooth.BluetoothProfile;
     22 import android.content.Context;
     23 import android.util.Log;
     24 
     25 import com.android.settingslib.bluetooth.A2dpProfile;
     26 import com.android.settingslib.bluetooth.BluetoothCallback;
     27 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
     28 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
     29 import com.android.settingslib.bluetooth.HearingAidProfile;
     30 import com.android.settingslib.bluetooth.LocalBluetoothManager;
     31 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
     32 
     33 import java.util.ArrayList;
     34 import java.util.List;
     35 
     36 /**
     37  * BluetoothMediaManager provide interface to get Bluetooth device list.
     38  */
     39 public class BluetoothMediaManager extends MediaManager implements BluetoothCallback,
     40         LocalBluetoothProfileManager.ServiceListener {
     41 
     42     private static final String TAG = "BluetoothMediaManager";
     43 
     44     private final DeviceAttributeChangeCallback mDeviceAttributeChangeCallback =
     45             new DeviceAttributeChangeCallback();
     46 
     47     private LocalBluetoothManager mLocalBluetoothManager;
     48     private LocalBluetoothProfileManager mProfileManager;
     49     private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager;
     50 
     51     private MediaDevice mLastAddedDevice;
     52     private MediaDevice mLastRemovedDevice;
     53 
     54     private boolean mIsA2dpProfileReady = false;
     55     private boolean mIsHearingAidProfileReady = false;
     56 
     57     BluetoothMediaManager(Context context, LocalBluetoothManager localBluetoothManager,
     58             Notification notification) {
     59         super(context, notification);
     60 
     61         mLocalBluetoothManager = localBluetoothManager;
     62         mProfileManager = mLocalBluetoothManager.getProfileManager();
     63         mCachedBluetoothDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
     64     }
     65 
     66     @Override
     67     public void startScan() {
     68         mLocalBluetoothManager.getEventManager().registerCallback(this);
     69         buildBluetoothDeviceList();
     70         dispatchDeviceListAdded();
     71         addServiceListenerIfNecessary();
     72     }
     73 
     74     private void addServiceListenerIfNecessary() {
     75         // The profile may not ready when calling startScan().
     76         // Device status are all disconnected since profiles are not ready to connected.
     77         // In this case, we observe onServiceConnected() in LocalBluetoothProfileManager.
     78         // When A2dpProfile or HearingAidProfile is connected will call buildBluetoothDeviceList()
     79         // again to find the connected devices.
     80         if (!mIsA2dpProfileReady || !mIsHearingAidProfileReady) {
     81             mProfileManager.addServiceListener(this);
     82         }
     83     }
     84 
     85     private void buildBluetoothDeviceList() {
     86         mMediaDevices.clear();
     87         addConnectableA2dpDevices();
     88         addConnectableHearingAidDevices();
     89     }
     90 
     91     private void addConnectableA2dpDevices() {
     92         final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
     93         if (a2dpProfile == null) {
     94             Log.w(TAG, "addConnectableA2dpDevices() a2dp profile is null!");
     95             return;
     96         }
     97 
     98         final List<BluetoothDevice> devices = a2dpProfile.getConnectableDevices();
     99 
    100         for (BluetoothDevice device : devices) {
    101             final CachedBluetoothDevice cachedDevice =
    102                     mCachedBluetoothDeviceManager.findDevice(device);
    103 
    104             if (cachedDevice == null) {
    105                 Log.w(TAG, "Can't found CachedBluetoothDevice : " + device.getName());
    106                 continue;
    107             }
    108 
    109             Log.d(TAG, "addConnectableA2dpDevices() device : " + cachedDevice.getName()
    110                     + ", is connected : " + cachedDevice.isConnected()
    111                     + ", is preferred : " + a2dpProfile.isPreferred(device));
    112 
    113             if (a2dpProfile.isPreferred(device)
    114                     && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) {
    115                 addMediaDevice(cachedDevice);
    116             }
    117         }
    118 
    119         mIsA2dpProfileReady = a2dpProfile.isProfileReady();
    120     }
    121 
    122     private void addConnectableHearingAidDevices() {
    123         final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile();
    124         if (hapProfile == null) {
    125             Log.w(TAG, "addConnectableHearingAidDevices() hap profile is null!");
    126             return;
    127         }
    128 
    129         final List<Long> devicesHiSyncIds = new ArrayList<>();
    130         final List<BluetoothDevice> devices = hapProfile.getConnectableDevices();
    131 
    132         for (BluetoothDevice device : devices) {
    133             final CachedBluetoothDevice cachedDevice =
    134                     mCachedBluetoothDeviceManager.findDevice(device);
    135 
    136             if (cachedDevice == null) {
    137                 Log.w(TAG, "Can't found CachedBluetoothDevice : " + device.getName());
    138                 continue;
    139             }
    140 
    141             Log.d(TAG, "addConnectableHearingAidDevices() device : " + cachedDevice.getName()
    142                     + ", is connected : " + cachedDevice.isConnected()
    143                     + ", is preferred : " + hapProfile.isPreferred(device));
    144 
    145             final long hiSyncId = hapProfile.getHiSyncId(device);
    146 
    147             // device with same hiSyncId should not be shown in the UI.
    148             // So do not add it into connectedDevices.
    149             if (!devicesHiSyncIds.contains(hiSyncId) && hapProfile.isPreferred(device)
    150                     && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) {
    151                 devicesHiSyncIds.add(hiSyncId);
    152                 addMediaDevice(cachedDevice);
    153             }
    154         }
    155 
    156         mIsHearingAidProfileReady = hapProfile.isProfileReady();
    157     }
    158 
    159     private void addMediaDevice(CachedBluetoothDevice cachedDevice) {
    160         MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice));
    161         if (mediaDevice == null) {
    162             mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice);
    163             cachedDevice.registerCallback(mDeviceAttributeChangeCallback);
    164             mLastAddedDevice = mediaDevice;
    165             mMediaDevices.add(mediaDevice);
    166         }
    167     }
    168 
    169     @Override
    170     public void stopScan() {
    171         mLocalBluetoothManager.getEventManager().unregisterCallback(this);
    172         unregisterDeviceAttributeChangeCallback();
    173     }
    174 
    175     private void unregisterDeviceAttributeChangeCallback() {
    176         for (MediaDevice device : mMediaDevices) {
    177             ((BluetoothMediaDevice) device).getCachedDevice()
    178                     .unregisterCallback(mDeviceAttributeChangeCallback);
    179         }
    180     }
    181 
    182     @Override
    183     public void onBluetoothStateChanged(int bluetoothState) {
    184         if (BluetoothAdapter.STATE_ON == bluetoothState) {
    185             buildBluetoothDeviceList();
    186             dispatchDeviceListAdded();
    187             addServiceListenerIfNecessary();
    188         } else if (BluetoothAdapter.STATE_OFF == bluetoothState) {
    189             final List<MediaDevice> removeDevicesList = new ArrayList<>();
    190             for (MediaDevice device : mMediaDevices) {
    191                 ((BluetoothMediaDevice) device).getCachedDevice()
    192                         .unregisterCallback(mDeviceAttributeChangeCallback);
    193                 removeDevicesList.add(device);
    194             }
    195             mMediaDevices.removeAll(removeDevicesList);
    196             dispatchDeviceListRemoved(removeDevicesList);
    197         }
    198     }
    199 
    200     @Override
    201     public void onAudioModeChanged() {
    202         dispatchDataChanged();
    203     }
    204 
    205     @Override
    206     public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
    207         if (isCachedDeviceConnected(cachedDevice)) {
    208             addMediaDevice(cachedDevice);
    209             dispatchDeviceAdded(cachedDevice);
    210         }
    211     }
    212 
    213     private boolean isCachedDeviceConnected(CachedBluetoothDevice cachedDevice) {
    214         final boolean isConnectedHearingAidDevice = cachedDevice.isConnectedHearingAidDevice();
    215         final boolean isConnectedA2dpDevice = cachedDevice.isConnectedA2dpDevice();
    216         Log.d(TAG, "isCachedDeviceConnected() cachedDevice : " + cachedDevice
    217                 + ", is hearing aid connected : " + isConnectedHearingAidDevice
    218                 + ", is a2dp connected : " + isConnectedA2dpDevice);
    219 
    220         return isConnectedHearingAidDevice || isConnectedA2dpDevice;
    221     }
    222 
    223     private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
    224         if (mLastAddedDevice != null
    225                 && MediaDeviceUtils.getId(cachedDevice) == mLastAddedDevice.getId()) {
    226             dispatchDeviceAdded(mLastAddedDevice);
    227         }
    228     }
    229 
    230     @Override
    231     public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
    232         if (!isCachedDeviceConnected(cachedDevice)) {
    233             removeMediaDevice(cachedDevice);
    234             dispatchDeviceRemoved(cachedDevice);
    235         }
    236     }
    237 
    238     private void removeMediaDevice(CachedBluetoothDevice cachedDevice) {
    239         final MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice));
    240         if (mediaDevice != null) {
    241             cachedDevice.unregisterCallback(mDeviceAttributeChangeCallback);
    242             mLastRemovedDevice = mediaDevice;
    243             mMediaDevices.remove(mediaDevice);
    244         }
    245     }
    246 
    247     void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) {
    248         if (mLastRemovedDevice != null
    249                 && MediaDeviceUtils.getId(cachedDevice) == mLastRemovedDevice.getId()) {
    250             dispatchDeviceRemoved(mLastRemovedDevice);
    251         }
    252     }
    253 
    254     @Override
    255     public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
    256             int bluetoothProfile) {
    257         Log.d(TAG, "onProfileConnectionStateChanged() device: " + cachedDevice
    258                 + ", state: " + state + ", bluetoothProfile: " + bluetoothProfile);
    259 
    260         updateMediaDeviceListIfNecessary(cachedDevice);
    261     }
    262 
    263     private void updateMediaDeviceListIfNecessary(CachedBluetoothDevice cachedDevice) {
    264         if (BluetoothDevice.BOND_NONE == cachedDevice.getBondState()) {
    265             removeMediaDevice(cachedDevice);
    266             dispatchDeviceRemoved(cachedDevice);
    267         } else {
    268             if (findMediaDevice(MediaDeviceUtils.getId(cachedDevice)) != null) {
    269                 dispatchDataChanged();
    270             }
    271         }
    272     }
    273 
    274     @Override
    275     public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
    276         Log.d(TAG, "onAclConnectionStateChanged() device: " + cachedDevice + ", state: " + state);
    277 
    278         updateMediaDeviceListIfNecessary(cachedDevice);
    279     }
    280 
    281     @Override
    282     public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
    283         Log.d(TAG, "onActiveDeviceChanged : device : "
    284                 + activeDevice + ", profile : " + bluetoothProfile);
    285 
    286         if (BluetoothProfile.HEARING_AID == bluetoothProfile) {
    287             if (activeDevice != null) {
    288                 dispatchConnectedDeviceChanged(MediaDeviceUtils.getId(activeDevice));
    289             }
    290         } else if (BluetoothProfile.A2DP == bluetoothProfile) {
    291             // When active device change to Hearing Aid,
    292             // BluetoothEventManager also send onActiveDeviceChanged() to notify that active device
    293             // of A2DP profile is null. To handle this case, check hearing aid device
    294             // is active device or not
    295             final MediaDevice activeHearingAidDevice = findActiveHearingAidDevice();
    296             final String id = activeDevice == null
    297                     ? activeHearingAidDevice == null
    298                     ? PhoneMediaDevice.ID : activeHearingAidDevice.getId()
    299                     : MediaDeviceUtils.getId(activeDevice);
    300             dispatchConnectedDeviceChanged(id);
    301         }
    302     }
    303 
    304     private MediaDevice findActiveHearingAidDevice() {
    305         final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
    306 
    307         if (hearingAidProfile != null) {
    308             final List<BluetoothDevice> activeDevices = hearingAidProfile.getActiveDevices();
    309             for (BluetoothDevice btDevice : activeDevices) {
    310                 if (btDevice != null) {
    311                     return findMediaDevice(MediaDeviceUtils.getId(btDevice));
    312                 }
    313             }
    314         }
    315         return null;
    316     }
    317 
    318     @Override
    319     public void onServiceConnected() {
    320         if (!mIsA2dpProfileReady || !mIsHearingAidProfileReady) {
    321             buildBluetoothDeviceList();
    322             dispatchDeviceListAdded();
    323         }
    324 
    325         //Remove the listener once a2dpProfile and hearingAidProfile are ready.
    326         if (mIsA2dpProfileReady && mIsHearingAidProfileReady) {
    327             mProfileManager.removeServiceListener(this);
    328         }
    329     }
    330 
    331     @Override
    332     public void onServiceDisconnected() {
    333 
    334     }
    335 
    336     /**
    337      * This callback is for update {@link BluetoothMediaDevice} summary when
    338      * {@link CachedBluetoothDevice} connection state is changed.
    339      */
    340     private class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback {
    341 
    342         @Override
    343         public void onDeviceAttributesChanged() {
    344             dispatchDataChanged();
    345         }
    346     }
    347 }
    348