diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 7124096e31b88..2ae6f92f8c17a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -171,7 +171,6 @@ public class BluetoothEventManager { callback.onProfileConnectionStateChanged(device, state, bluetoothProfile); } } - mDeviceManager.onProfileConnectionStateChanged(device, state, bluetoothProfile); } private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index a2e30df9ac5cc..b4e82e912f79d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -29,6 +29,8 @@ import android.os.SystemClock; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.VisibleForTesting; + import com.android.settingslib.R; import java.util.ArrayList; @@ -36,8 +38,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import androidx.annotation.VisibleForTesting; - /** * CachedBluetoothDevice represents a remote Bluetooth device. It contains * attributes of the device (such as the address, name, RSSI, etc.) and @@ -54,11 +54,12 @@ public class CachedBluetoothDevice implements Comparable private final Context mContext; private final BluetoothAdapter mLocalAdapter; private final LocalBluetoothProfileManager mProfileManager; - private final BluetoothDevice mDevice; + BluetoothDevice mDevice; private long mHiSyncId; // Need this since there is no method for getting RSSI - private short mRssi; - + short mRssi; + // mProfiles and mRemovedProfiles does not do swap() between main and sub device. It is + // because current sub device is only for HearingAid and its profile is the same. private final List mProfiles = Collections.synchronizedList(new ArrayList<>()); @@ -69,7 +70,7 @@ public class CachedBluetoothDevice implements Comparable // Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP private boolean mLocalNapRoleConnected; - private boolean mJustDiscovered; + boolean mJustDiscovered; private int mMessageRejectionCount; @@ -92,6 +93,8 @@ public class CachedBluetoothDevice implements Comparable private boolean mIsActiveDeviceA2dp = false; private boolean mIsActiveDeviceHeadset = false; private boolean mIsActiveDeviceHearingAid = false; + // Group second device for Hearing Aid + private CachedBluetoothDevice mSubDevice; CachedBluetoothDevice(Context context, LocalBluetoothProfileManager profileManager, BluetoothDevice device) { @@ -1064,4 +1067,28 @@ public class CachedBluetoothDevice implements Comparable return hearingAidProfile != null && hearingAidProfile.getConnectionStatus(mDevice) == BluetoothProfile.STATE_CONNECTED; } + + public CachedBluetoothDevice getSubDevice() { + return mSubDevice; + } + + public void setSubDevice(CachedBluetoothDevice subDevice) { + mSubDevice = subDevice; + } + + public void switchSubDeviceContent() { + // Backup from main device + BluetoothDevice tmpDevice = mDevice; + short tmpRssi = mRssi; + boolean tmpJustDiscovered = mJustDiscovered; + // Set main device from sub device + mDevice = mSubDevice.mDevice; + mRssi = mSubDevice.mRssi; + mJustDiscovered = mSubDevice.mJustDiscovered; + // Set sub device from backup + mSubDevice.mDevice = tmpDevice; + mSubDevice.mRssi = tmpRssi; + mSubDevice.mJustDiscovered = tmpJustDiscovered; + fetchActiveDevices(); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index 21cf0c27a8d52..f7f65893efb53 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -18,8 +18,6 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHearingAid; -import android.bluetooth.BluetoothProfile; import android.content.Context; import android.util.Log; @@ -27,12 +25,8 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.Set; /** * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices. @@ -45,20 +39,14 @@ public class CachedBluetoothDeviceManager { private final LocalBluetoothManager mBtManager; @VisibleForTesting - final List mCachedDevices = - new ArrayList(); - // Contains the list of hearing aid devices that should not be shown in the UI. + final List mCachedDevices = new ArrayList(); @VisibleForTesting - final List mHearingAidDevicesNotAddedInCache - = new ArrayList(); - // Maintains a list of devices which are added in mCachedDevices and have hiSyncIds. - @VisibleForTesting - final Map mCachedDevicesMapForHearingAids - = new HashMap(); + HearingAidDeviceManager mHearingAidDeviceManager; CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) { mContext = context; mBtManager = localBtManager; + mHearingAidDeviceManager = new HearingAidDeviceManager(localBtManager, mCachedDevices); } public synchronized Collection getCachedDevicesCopy() { @@ -92,12 +80,13 @@ public class CachedBluetoothDeviceManager { if (cachedDevice.getDevice().equals(device)) { return cachedDevice; } - } - for (CachedBluetoothDevice notCachedDevice : mHearingAidDevicesNotAddedInCache) { - if (notCachedDevice.getDevice().equals(device)) { - return notCachedDevice; + // Check sub devices if it exists + CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); + if (subDevice != null && subDevice.getDevice().equals(device)) { + return subDevice; } } + return null; } @@ -111,24 +100,10 @@ public class CachedBluetoothDeviceManager { LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager(); CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, profileManager, device); - if (profileManager.getHearingAidProfile() != null - && profileManager.getHearingAidProfile().getHiSyncId(newDevice.getDevice()) - != BluetoothHearingAid.HI_SYNC_ID_INVALID) { - newDevice.setHiSyncId(profileManager.getHearingAidProfile() - .getHiSyncId(newDevice.getDevice())); - } - // Just add one of the hearing aids from a pair in the list that is shown in the UI. - if (isPairAddedInCache(newDevice.getHiSyncId())) { - synchronized (this) { - mHearingAidDevicesNotAddedInCache.add(newDevice); - } - } else { - synchronized (this) { + mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice); + synchronized (this) { + if (!mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) { mCachedDevices.add(newDevice); - if (newDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID - && !mCachedDevicesMapForHearingAids.containsKey(newDevice.getHiSyncId())) { - mCachedDevicesMapForHearingAids.put(newDevice.getHiSyncId(), newDevice); - } mBtManager.getEventManager().dispatchDeviceAdded(newDevice); } } @@ -136,50 +111,19 @@ public class CachedBluetoothDeviceManager { return newDevice; } - /** - * Returns true if the one of the two hearing aid devices is already cached for UI. - * - * @param long hiSyncId - * @return {@code True} if one of the two hearing aid devices is is already cached for UI. - */ - private synchronized boolean isPairAddedInCache(long hiSyncId) { - if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) { - return false; - } - if(mCachedDevicesMapForHearingAids.containsKey(hiSyncId)) { - return true; - } - return false; - } - /** * Returns device summary of the pair of the hearing aid passed as the parameter. * * @param CachedBluetoothDevice device - * @return Device summary, or if the pair does not exist or if its not a hearing aid, + * @return Device summary, or if the pair does not exist or if it is not a hearing aid, * then {@code null}. */ - public synchronized String getHearingAidPairDeviceSummary(CachedBluetoothDevice device) { - String pairDeviceSummary = null; - if (device.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) { - for (CachedBluetoothDevice hearingAidDevice : mHearingAidDevicesNotAddedInCache) { - if (hearingAidDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID - && hearingAidDevice.getHiSyncId() == device.getHiSyncId()) { - pairDeviceSummary = hearingAidDevice.getConnectionSummary(); - } - } + public synchronized String getSubDeviceSummary(CachedBluetoothDevice device) { + CachedBluetoothDevice subDevice = device.getSubDevice(); + if (subDevice != null && subDevice.isConnected()) { + return subDevice.getConnectionSummary(); } - return pairDeviceSummary; - } - - /** - * Adds the 2nd hearing aid in a pair in a list that maintains the hearing aids that are - * not dispalyed in the UI. - * - * @param CachedBluetoothDevice device - */ - public synchronized void addDeviceNotaddedInMap(CachedBluetoothDevice device) { - mHearingAidDevicesNotAddedInCache.add(device); + return null; } /** @@ -187,28 +131,8 @@ public class CachedBluetoothDeviceManager { * Hearing Aid Service is connected and the HiSyncId's are now available. * @param LocalBluetoothProfileManager profileManager */ - public synchronized void updateHearingAidsDevices(LocalBluetoothProfileManager profileManager) { - HearingAidProfile profileProxy = profileManager.getHearingAidProfile(); - if (profileProxy == null) { - log("updateHearingAidsDevices: getHearingAidProfile() is null"); - return; - } - final Set syncIdChangedSet = new HashSet(); - for (CachedBluetoothDevice cachedDevice : mCachedDevices) { - if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) { - continue; - } - - long newHiSyncId = profileProxy.getHiSyncId(cachedDevice.getDevice()); - - if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) { - cachedDevice.setHiSyncId(newHiSyncId); - syncIdChangedSet.add(newHiSyncId); - } - } - for (Long syncId : syncIdChangedSet) { - onHiSyncIdChanged(syncId); - } + public synchronized void updateHearingAidsDevices() { + mHearingAidDeviceManager.updateHearingAidsDevices(); } /** @@ -232,15 +156,21 @@ public class CachedBluetoothDeviceManager { } public synchronized void clearNonBondedDevices() { - - mCachedDevicesMapForHearingAids.entrySet().removeIf(entries - -> entries.getValue().getBondState() == BluetoothDevice.BOND_NONE); - + clearNonBondedSubDevices(); mCachedDevices.removeIf(cachedDevice -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE); + } - mHearingAidDevicesNotAddedInCache.removeIf(hearingAidDevice - -> hearingAidDevice.getBondState() == BluetoothDevice.BOND_NONE); + private void clearNonBondedSubDevices() { + for (int i = mCachedDevices.size() - 1; i >= 0; i--) { + CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); + if (subDevice != null + && subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) { + // Sub device exists and it is not bonded + cachedDevice.setSubDevice(null); + } + } } public synchronized void onScanningStateChanged(boolean started) { @@ -250,10 +180,10 @@ public class CachedBluetoothDeviceManager { for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); cachedDevice.setJustDiscovered(false); - } - for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) { - CachedBluetoothDevice notCachedDevice = mHearingAidDevicesNotAddedInCache.get(i); - notCachedDevice.setJustDiscovered(false); + final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); + if (subDevice != null) { + subDevice.setJustDiscovered(false); + } } } @@ -277,162 +207,45 @@ public class CachedBluetoothDeviceManager { if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) { for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); + if (subDevice != null) { + if (subDevice.getBondState() != BluetoothDevice.BOND_BONDED) { + cachedDevice.setSubDevice(null); + } + } if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) { cachedDevice.setJustDiscovered(false); mCachedDevices.remove(i); - if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID - && mCachedDevicesMapForHearingAids.containsKey(cachedDevice.getHiSyncId())) - { - mCachedDevicesMapForHearingAids.remove(cachedDevice.getHiSyncId()); - } - } - } - for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) { - CachedBluetoothDevice notCachedDevice = mHearingAidDevicesNotAddedInCache.get(i); - if (notCachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) { - notCachedDevice.setJustDiscovered(false); - mHearingAidDevicesNotAddedInCache.remove(i); } } } } public synchronized void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, - int bluetoothProfile) { + int bluetoothProfile) { for (CachedBluetoothDevice cachedDevice : mCachedDevices) { boolean isActive = Objects.equals(cachedDevice, activeDevice); cachedDevice.onActiveDeviceChanged(isActive, bluetoothProfile); } } - public synchronized void onHiSyncIdChanged(long hiSyncId) { - int firstMatchedIndex = -1; - - for (int i = mCachedDevices.size() - 1; i >= 0; i--) { - CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); - if (cachedDevice.getHiSyncId() == hiSyncId) { - if (firstMatchedIndex != -1) { - /* Found the second one */ - int indexToRemoveFromUi; - CachedBluetoothDevice deviceToRemoveFromUi; - - // Since the hiSyncIds have been updated for a connected pair of hearing aids, - // we remove the entry of one the hearing aids from the UI. Unless the - // hiSyncId get updated, the system does not know it is a hearing aid, so we add - // both the hearing aids as separate entries in the UI first, then remove one - // of them after the hiSyncId is populated. We will choose the device that - // is not connected to be removed. - if (cachedDevice.isConnected()) { - indexToRemoveFromUi = firstMatchedIndex; - deviceToRemoveFromUi = mCachedDevices.get(firstMatchedIndex); - mCachedDevicesMapForHearingAids.put(hiSyncId, cachedDevice); - } else { - indexToRemoveFromUi = i; - deviceToRemoveFromUi = cachedDevice; - mCachedDevicesMapForHearingAids.put(hiSyncId, - mCachedDevices.get(firstMatchedIndex)); - } - - mCachedDevices.remove(indexToRemoveFromUi); - mHearingAidDevicesNotAddedInCache.add(deviceToRemoveFromUi); - log("onHiSyncIdChanged: removed from UI device=" + deviceToRemoveFromUi - + ", with hiSyncId=" + hiSyncId); - mBtManager.getEventManager().dispatchDeviceRemoved(deviceToRemoveFromUi); - break; - } else { - mCachedDevicesMapForHearingAids.put(hiSyncId, cachedDevice); - firstMatchedIndex = i; - } - } - } - } - - private CachedBluetoothDevice getHearingAidOtherDevice(CachedBluetoothDevice thisDevice, - long hiSyncId) { - if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) { - return null; - } - - // Searched the lists for the other side device with the matching hiSyncId. - for (CachedBluetoothDevice notCachedDevice : mHearingAidDevicesNotAddedInCache) { - if ((hiSyncId == notCachedDevice.getHiSyncId()) && - (!Objects.equals(notCachedDevice, thisDevice))) { - return notCachedDevice; - } - } - - CachedBluetoothDevice cachedDevice = mCachedDevicesMapForHearingAids.get(hiSyncId); - if (!Objects.equals(cachedDevice, thisDevice)) { - return cachedDevice; - } - return null; - } - - private void hearingAidSwitchDisplayDevice(CachedBluetoothDevice toDisplayDevice, - CachedBluetoothDevice toHideDevice, long hiSyncId) - { - log("hearingAidSwitchDisplayDevice: toDisplayDevice=" + toDisplayDevice - + ", toHideDevice=" + toHideDevice); - - // Remove the "toHideDevice" device from the UI. - mHearingAidDevicesNotAddedInCache.add(toHideDevice); - mCachedDevices.remove(toHideDevice); - mBtManager.getEventManager().dispatchDeviceRemoved(toHideDevice); - - // Add the "toDisplayDevice" device to the UI. - mHearingAidDevicesNotAddedInCache.remove(toDisplayDevice); - mCachedDevices.add(toDisplayDevice); - mCachedDevicesMapForHearingAids.put(hiSyncId, toDisplayDevice); - mBtManager.getEventManager().dispatchDeviceAdded(toDisplayDevice); - } - - public synchronized void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, - int state, int bluetoothProfile) { - if (bluetoothProfile == BluetoothProfile.HEARING_AID - && cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID - && cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) { - - long hiSyncId = cachedDevice.getHiSyncId(); - - CachedBluetoothDevice otherDevice = getHearingAidOtherDevice(cachedDevice, hiSyncId); - if (otherDevice == null) { - // no other side device. Nothing to do. - return; - } - - if (state == BluetoothProfile.STATE_CONNECTED && - mHearingAidDevicesNotAddedInCache.contains(cachedDevice)) { - hearingAidSwitchDisplayDevice(cachedDevice, otherDevice, hiSyncId); - } else if (state == BluetoothProfile.STATE_DISCONNECTED - && otherDevice.isConnected()) { - CachedBluetoothDevice mapDevice = mCachedDevicesMapForHearingAids.get(hiSyncId); - if ((mapDevice != null) && (Objects.equals(cachedDevice, mapDevice))) { - hearingAidSwitchDisplayDevice(otherDevice, cachedDevice, hiSyncId); - } - } - } + public synchronized boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice + cachedDevice, int state) { + return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice, + state); } public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) { - final long hiSyncId = device.getHiSyncId(); - - if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) return; - - for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) { - CachedBluetoothDevice cachedDevice = mHearingAidDevicesNotAddedInCache.get(i); - if (cachedDevice.getHiSyncId() == hiSyncId) { - // TODO: Look for more cleanups on unpairing the device. - mHearingAidDevicesNotAddedInCache.remove(i); - if (device == cachedDevice) continue; - log("onDeviceUnpaired: Unpair device=" + cachedDevice); - cachedDevice.unpair(); - } - } - - CachedBluetoothDevice mappedDevice = mCachedDevicesMapForHearingAids.get(hiSyncId); - if ((mappedDevice != null) && (!Objects.equals(device, mappedDevice))) { - log("onDeviceUnpaired: Unpair mapped device=" + mappedDevice); - mappedDevice.unpair(); + CachedBluetoothDevice mainDevice = mHearingAidDeviceManager.findMainDevice(device); + CachedBluetoothDevice subDevice = device.getSubDevice(); + if (subDevice != null) { + // Main device is unpaired, to unpair sub device + subDevice.unpair(); + device.setSubDevice(null); + } else if (mainDevice != null) { + // Sub device unpaired, to unpair main device + mainDevice.unpair(); + mainDevice.setSubDevice(null); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java new file mode 100644 index 0000000000000..20ece69d7281b --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * HearingAidDeviceManager manages the set of remote HearingAid Bluetooth devices. + */ +public class HearingAidDeviceManager { + private static final String TAG = "HearingAidDeviceManager"; + private static final boolean DEBUG = BluetoothUtils.D; + + private final LocalBluetoothManager mBtManager; + private final List mCachedDevices; + HearingAidDeviceManager(LocalBluetoothManager localBtManager, + List CachedDevices) { + mBtManager = localBtManager; + mCachedDevices = CachedDevices; + } + + void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) { + long hiSyncId = getHiSyncId(newDevice.getDevice()); + if (isValidHiSyncId(hiSyncId)) { + // Once hiSyncId is valid, assign hiSyncId + newDevice.setHiSyncId(hiSyncId); + } + } + + private long getHiSyncId(BluetoothDevice device) { + LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager(); + HearingAidProfile profileProxy = profileManager.getHearingAidProfile(); + if (profileProxy != null) { + return profileProxy.getHiSyncId(device); + } + return BluetoothHearingAid.HI_SYNC_ID_INVALID; + } + + boolean setSubDeviceIfNeeded(CachedBluetoothDevice newDevice) { + final long hiSyncId = newDevice.getHiSyncId(); + if (isValidHiSyncId(hiSyncId)) { + final CachedBluetoothDevice hearingAidDevice = getCachedDevice(hiSyncId); + // Just add one of the hearing aids from a pair in the list that is shown in the UI. + // Once there is another device with the same hiSyncId, to add new device as sub + // device. + if (hearingAidDevice != null) { + hearingAidDevice.setSubDevice(newDevice); + return true; + } + } + return false; + } + + private boolean isValidHiSyncId(long hiSyncId) { + return hiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID; + } + + private CachedBluetoothDevice getCachedDevice(long hiSyncId) { + for (int i = mCachedDevices.size() - 1; i >= 0; i--) { + CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + if (cachedDevice.getHiSyncId() == hiSyncId) { + return cachedDevice; + } + } + return null; + } + + // To collect all HearingAid devices and call #onHiSyncIdChanged to group device by HiSyncId + void updateHearingAidsDevices() { + final Set newSyncIdSet = new HashSet(); + for (CachedBluetoothDevice cachedDevice : mCachedDevices) { + // Do nothing if HiSyncId has been assigned + if (!isValidHiSyncId(cachedDevice.getHiSyncId())) { + final long newHiSyncId = getHiSyncId(cachedDevice.getDevice()); + // Do nothing if there is no HiSyncId on Bluetooth device + if (isValidHiSyncId(newHiSyncId)) { + cachedDevice.setHiSyncId(newHiSyncId); + newSyncIdSet.add(newHiSyncId); + } + } + } + for (Long syncId : newSyncIdSet) { + onHiSyncIdChanged(syncId); + } + } + + // Group devices by hiSyncId + @VisibleForTesting + void onHiSyncIdChanged(long hiSyncId) { + int firstMatchedIndex = -1; + + for (int i = mCachedDevices.size() - 1; i >= 0; i--) { + CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + if (cachedDevice.getHiSyncId() != hiSyncId) { + continue; + } + if (firstMatchedIndex == -1) { + // Found the first one + firstMatchedIndex = i; + continue; + } + // Found the second one + int indexToRemoveFromUi; + CachedBluetoothDevice subDevice; + CachedBluetoothDevice mainDevice; + // Since the hiSyncIds have been updated for a connected pair of hearing aids, + // we remove the entry of one the hearing aids from the UI. Unless the + // hiSyncId get updated, the system does not know it is a hearing aid, so we add + // both the hearing aids as separate entries in the UI first, then remove one + // of them after the hiSyncId is populated. We will choose the device that + // is not connected to be removed. + if (cachedDevice.isConnected()) { + mainDevice = cachedDevice; + indexToRemoveFromUi = firstMatchedIndex; + subDevice = mCachedDevices.get(firstMatchedIndex); + } else { + mainDevice = mCachedDevices.get(firstMatchedIndex); + indexToRemoveFromUi = i; + subDevice = cachedDevice; + } + + mainDevice.setSubDevice(subDevice); + mCachedDevices.remove(indexToRemoveFromUi); + log("onHiSyncIdChanged: removed from UI device =" + subDevice + + ", with hiSyncId=" + hiSyncId); + mBtManager.getEventManager().dispatchDeviceRemoved(subDevice); + break; + } + } + + // @return {@code true}, the event is processed inside the method. It is for updating + // hearing aid device on main-sub relationship when receiving connected or disconnected. + // @return {@code false}, it is not hearing aid device or to process it same as other profiles + boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice, + int state) { + switch (state) { + case BluetoothProfile.STATE_CONNECTED: + onHiSyncIdChanged(cachedDevice.getHiSyncId()); + CachedBluetoothDevice mainDevice = findMainDevice(cachedDevice); + if (mainDevice != null){ + if (mainDevice.isConnected()) { + // When main device exists and in connected state, receiving sub device + // connection. To refresh main device UI + mainDevice.refresh(); + return true; + } else { + // When both Hearing Aid devices are disconnected, receiving sub device + // connection. To switch content and dispatch to notify UI change + mBtManager.getEventManager().dispatchDeviceRemoved(mainDevice); + mainDevice.switchSubDeviceContent(); + mainDevice.refresh(); + // It is necessary to do remove and add for updating the mapping on + // preference and device + mBtManager.getEventManager().dispatchDeviceAdded(mainDevice); + return true; + } + } + break; + case BluetoothProfile.STATE_DISCONNECTED: + mainDevice = findMainDevice(cachedDevice); + if (mainDevice != null) { + // When main device exists, receiving sub device disconnection + // To update main device UI + mainDevice.refresh(); + return true; + } + CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); + if (subDevice != null && subDevice.isConnected()) { + // Main device is disconnected and sub device is connected + // To copy data from sub device to main device + mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); + cachedDevice.switchSubDeviceContent(); + cachedDevice.refresh(); + // It is necessary to do remove and add for updating the mapping on + // preference and device + mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice); + return true; + } + break; + } + return false; + } + + CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) { + for (CachedBluetoothDevice cachedDevice : mCachedDevices) { + if (isValidHiSyncId(cachedDevice.getHiSyncId())) { + CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); + if (subDevice != null && subDevice.equals(device)) { + return cachedDevice; + } + } + } + return null; + } + + private void log(String msg) { + if (DEBUG) { + Log.d(TAG, msg); + } + } +} \ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index 8bc0acf5f824b..adb5ab34b5c13 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -71,7 +71,7 @@ public class HearingAidProfile implements LocalBluetoothProfile { } // Check current list of CachedDevices to see if any are Hearing Aid devices. - mDeviceManager.updateHearingAidsDevices(mProfileManager); + mDeviceManager.updateHearingAidsDevices(); mIsProfileReady=true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java index 9653972414ee8..29c6d719641a1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -40,7 +40,6 @@ import android.util.Log; import androidx.annotation.VisibleForTesting; -import com.android.internal.R; import com.android.internal.util.CollectionUtils; import java.util.ArrayList; @@ -289,21 +288,21 @@ public class LocalBluetoothProfileManager { (newState == BluetoothProfile.STATE_CONNECTED)) { // Check if the HiSyncID has being initialized if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) { - long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice()); - if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) { cachedDevice.setHiSyncId(newHiSyncId); - mDeviceManager.onHiSyncIdChanged(newHiSyncId); } } } - cachedDevice.onProfileStateChanged(mProfile, newState); - cachedDevice.refresh(); // Dispatch profile changed after device update - mEventManager.dispatchProfileConnectionStateChanged(cachedDevice, newState, - mProfile.getProfileId()); + if (!(cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID + && mDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice, + newState))) { + cachedDevice.refresh(); + mEventManager.dispatchProfileConnectionStateChanged(cachedDevice, newState, + mProfile.getProfileId()); + } } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java index 14bfb2798183c..b54d2561cd180 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java @@ -96,8 +96,5 @@ public class BluetoothEventManagerTest { verify(mBluetoothCallback).onProfileConnectionStateChanged(mCachedBluetoothDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP); - - verify(mCachedDeviceManager).onProfileConnectionStateChanged(mCachedBluetoothDevice, - BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java index 62b5688fc9e68..9c75491472179 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java @@ -18,7 +18,9 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothClass; @@ -80,6 +82,7 @@ public class CachedBluetoothDeviceManagerTest { private CachedBluetoothDevice mCachedDevice2; private CachedBluetoothDevice mCachedDevice3; private CachedBluetoothDeviceManager mCachedDeviceManager; + private HearingAidDeviceManager mHearingAidDeviceManager; private Context mContext; @Before @@ -105,13 +108,12 @@ public class CachedBluetoothDeviceManagerTest { when(mA2dpProfile.isProfileReady()).thenReturn(true); when(mPanProfile.isProfileReady()).thenReturn(true); when(mHearingAidProfile.isProfileReady()).thenReturn(true); + doAnswer((invocation) -> mHearingAidProfile). + when(mLocalProfileManager).getHearingAidProfile(); mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager); - mCachedDevice1 = spy( - new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1)); - mCachedDevice2 = spy( - new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2)); - mCachedDevice3 = spy( - new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3)); + mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1)); + mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2)); + mCachedDevice3 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3)); } /** @@ -132,6 +134,76 @@ public class CachedBluetoothDeviceManagerTest { assertThat(mCachedDeviceManager.findDevice(mDevice2)).isEqualTo(cachedDevice2); } + /** + * Test to verify getSubDevice(), new device has the same HiSyncId. + */ + @Test + public void addDevice_sameHiSyncId_validSubDevice() { + doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1); + doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice2); + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); + + assertThat(cachedDevice1.getSubDevice()).isEqualTo(cachedDevice2); + } + + /** + * Test to verify getSubDevice(), new device has the different HiSyncId. + */ + @Test + public void addDevice_differentHiSyncId_validSubDevice() { + doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1); + doAnswer((invocation) -> HISYNCID2).when(mHearingAidProfile).getHiSyncId(mDevice2); + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); + + assertThat(cachedDevice1.getSubDevice()).isNull(); + } + + /** + * Test to verify addDevice(), new device has the same HiSyncId. + */ + @Test + public void addDevice_sameHiSyncId_validCachedDevices_mainDevicesAdded_subDevicesNotAdded() { + doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1); + doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice2); + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); + + Collection devices = mCachedDeviceManager.getCachedDevicesCopy(); + assertThat(devices).contains(cachedDevice1); + assertThat(devices).doesNotContain(cachedDevice2); + } + + /** + * Test to verify addDevice(), new device has the different HiSyncId. + */ + @Test + public void addDevice_differentHiSyncId_validCachedDevices_bothAdded() { + doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1); + doAnswer((invocation) -> HISYNCID2).when(mHearingAidProfile).getHiSyncId(mDevice2); + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); + + Collection devices = mCachedDeviceManager.getCachedDevicesCopy(); + assertThat(devices).contains(cachedDevice1); + assertThat(devices).contains(cachedDevice2); + } + + /** + * Test to verify findDevice(), new device has the same HiSyncId. + */ + @Test + public void findDevice_sameHiSyncId_foundBothDevice() { + doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1); + doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice2); + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); + + assertThat(mCachedDeviceManager.findDevice(mDevice1)).isEqualTo(cachedDevice1); + assertThat(mCachedDeviceManager.findDevice(mDevice2)).isEqualTo(cachedDevice2); + } + /** * Test to verify getName(). */ @@ -190,453 +262,89 @@ public class CachedBluetoothDeviceManagerTest { } /** - * Test to verify clearNonBondedDevices() for hearing aids. + * Test to verify clearNonBondedDevices() for hearing aids sub device. */ @Test - public void clearNonBondedDevices_HearingAids_nonBondedHAsClearedFromCachedDevicesMap() { + public void clearNonBondedDevices_nonBondedSubDevice() { when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE); + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); + cachedDevice1.setSubDevice(cachedDevice2); - mCachedDevice1.setHiSyncId(HISYNCID1); - mCachedDevice2.setHiSyncId(HISYNCID2); - mCachedDeviceManager.mCachedDevicesMapForHearingAids.put(HISYNCID1, mCachedDevice1); - mCachedDeviceManager.mCachedDevicesMapForHearingAids.put(HISYNCID2, mCachedDevice2); - + assertThat(cachedDevice1.getSubDevice()).isEqualTo(cachedDevice2); mCachedDeviceManager.clearNonBondedDevices(); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) - .doesNotContain(mCachedDevice2); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) - .contains(mCachedDevice1); + assertThat(cachedDevice1.getSubDevice()).isNull(); } /** - * Test to verify onHiSyncIdChanged() for hearing aid devices with same HiSyncId. + * Test to verify OnDeviceUnpaired() for main hearing Aid device unpair. */ @Test - public void onHiSyncIdChanged_sameHiSyncId_populateInDifferentLists() { - CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); - assertThat(cachedDevice1).isNotNull(); - CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); - assertThat(cachedDevice2).isNotNull(); - - // Since both devices do not have hiSyncId, they should be added in mCachedDevices. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty(); - - cachedDevice1.setHiSyncId(HISYNCID1); - cachedDevice2.setHiSyncId(HISYNCID1); - mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); - - // Since both devices have the same hiSyncId, one should remain in mCachedDevices - // and the other should be removed from mCachedDevices and get added in - // mHearingAidDevicesNotAddedInCache. The one that is in mCachedDevices should also be - // added in mCachedDevicesMapForHearingAids. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).hasSize(1); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1); - Collection devices = mCachedDeviceManager.getCachedDevicesCopy(); - assertThat(devices).contains(cachedDevice2); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids) - .containsKey(HISYNCID1); - } - - /** - * Test to verify onHiSyncIdChanged() for 2 hearing aid devices with same HiSyncId but one - * device is connected and other is disconnected. The connected device should be chosen. - */ - @Test - public void onHiSyncIdChanged_sameHiSyncIdAndOneConnected_chooseConnectedDevice() { - CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); - assertThat(cachedDevice1).isNotNull(); - CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); - assertThat(cachedDevice2).isNotNull(); - cachedDevice1.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); - cachedDevice2.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); - - /* Device 1 is connected and Device 2 is disconnected */ - when(mHearingAidProfile.getConnectionStatus(mDevice1)). - thenReturn(BluetoothProfile.STATE_CONNECTED); - when(mHearingAidProfile.getConnectionStatus(mDevice2)). - thenReturn(BluetoothProfile.STATE_DISCONNECTED); - - // Since both devices do not have hiSyncId, they should be added in mCachedDevices. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty(); - - cachedDevice1.setHiSyncId(HISYNCID1); - cachedDevice2.setHiSyncId(HISYNCID1); - mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); - - // Only the connected device, device 1, should be visible to UI. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids). - containsExactly(HISYNCID1, cachedDevice1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache). - containsExactly(cachedDevice2); - } - - /** - * Test to verify onHiSyncIdChanged() for hearing aid devices with different HiSyncId. - */ - @Test - public void onHiSyncIdChanged_differentHiSyncId_populateInSameList() { - CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); - assertThat(cachedDevice1).isNotNull(); - CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); - assertThat(cachedDevice2).isNotNull(); - - // Since both devices do not have hiSyncId, they should be added in mCachedDevices. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty(); - - cachedDevice1.setHiSyncId(HISYNCID1); - cachedDevice2.setHiSyncId(HISYNCID2); - mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); - mCachedDeviceManager.onHiSyncIdChanged(HISYNCID2); - - // Since both devices do not have same hiSyncId, they should remain in mCachedDevices and - // also be added in mCachedDevicesMapForHearingAids. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(2); - Collection devices = mCachedDeviceManager.getCachedDevicesCopy(); - assertThat(devices).contains(cachedDevice2); - assertThat(devices).contains(cachedDevice1); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) - .contains(cachedDevice1); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) - .contains(cachedDevice2); - } - - /** - * Test to verify onProfileConnectionStateChanged() for single hearing aid device connection. - */ - @Test - public void onProfileConnectionStateChanged_singleDeviceConnected_visible() { - CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); - assertThat(cachedDevice1).isNotNull(); - cachedDevice1.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); - - // Since both devices do not have hiSyncId, they should be added in mCachedDevices. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty(); - - cachedDevice1.setHiSyncId(HISYNCID1); - mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); - - // Connect the Device 1 - mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice1, - BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID); - - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids). - containsExactly(HISYNCID1, cachedDevice1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); - - // Disconnect the Device 1 - mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice1, - BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.HEARING_AID); - - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids). - containsExactly(HISYNCID1, cachedDevice1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); - } - - /** - * Test to verify onProfileConnectionStateChanged() for two hearing aid devices where both - * devices are disconnected and they get connected. - */ - @Test - public void onProfileConnectionStateChanged_twoDevicesConnected_oneDeviceVisible() { - CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); - assertThat(cachedDevice1).isNotNull(); - CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); - assertThat(cachedDevice2).isNotNull(); - cachedDevice1.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); - cachedDevice2.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + public void onDeviceUnpaired_unpairMainDevice() { when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - - // Since both devices do not have hiSyncId, they should be added in mCachedDevices. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty(); - - cachedDevice1.setHiSyncId(HISYNCID1); - cachedDevice2.setHiSyncId(HISYNCID1); - mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); - - // There should be one cached device but can be either one. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); - - // Connect the Device 1 - mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice1, - BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID); - - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids). - containsExactly(HISYNCID1, cachedDevice1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice2); - assertThat(mCachedDeviceManager.mCachedDevices).contains(cachedDevice1); - - when(mHearingAidProfile.getConnectionStatus(mDevice1)). - thenReturn(BluetoothProfile.STATE_CONNECTED); - when(mHearingAidProfile.getConnectionStatus(mDevice2)). - thenReturn(BluetoothProfile.STATE_DISCONNECTED); - - // Connect the Device 2 - mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice2, - BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID); - - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).hasSize(1); - assertThat(mCachedDeviceManager.mCachedDevices).hasSize(1); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1); - } - - /** - * Test to verify onProfileConnectionStateChanged() for two hearing aid devices where both - * devices are connected and they get disconnected. - */ - @Test - public void onProfileConnectionStateChanged_twoDevicesDisconnected_oneDeviceVisible() { CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); - assertThat(cachedDevice1).isNotNull(); CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); - assertThat(cachedDevice2).isNotNull(); - cachedDevice1.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); - cachedDevice2.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); - - when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - when(mHearingAidProfile.getConnectionStatus(mDevice1)). - thenReturn(BluetoothProfile.STATE_CONNECTED); - when(mHearingAidProfile.getConnectionStatus(mDevice2)). - thenReturn(BluetoothProfile.STATE_CONNECTED); - - // Since both devices do not have hiSyncId, they should be added in mCachedDevices. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty(); - cachedDevice1.setHiSyncId(HISYNCID1); cachedDevice2.setHiSyncId(HISYNCID1); - mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); - - /* Disconnect the Device 1 */ - mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice1, - BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.HEARING_AID); - - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice2); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1); - assertThat(mCachedDeviceManager.mCachedDevices).contains(cachedDevice2); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids) - .containsExactly(HISYNCID1, cachedDevice2); - - when(mHearingAidProfile.getConnectionStatus(mDevice1)). - thenReturn(BluetoothProfile.STATE_DISCONNECTED); - when(mHearingAidProfile.getConnectionStatus(mDevice2)). - thenReturn(BluetoothProfile.STATE_CONNECTED); - - /* Disconnect the Device 2 */ - mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice2, - BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.HEARING_AID); - - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).hasSize(1); - assertThat(mCachedDeviceManager.mCachedDevices).hasSize(1); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1); - } - - /** - * Test to verify OnDeviceUnpaired() for a paired hearing Aid device pair. - */ - @Test - public void onDeviceUnpaired_bothHearingAidsPaired_removesItsPairFromList() { - CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); - assertThat(cachedDevice1).isNotNull(); - CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); - assertThat(cachedDevice2).isNotNull(); - - cachedDevice1.setHiSyncId(HISYNCID1); - cachedDevice2.setHiSyncId(HISYNCID1); - mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); - - // Check if one device is in mCachedDevices and one in mHearingAidDevicesNotAddedInCache. - Collection devices = mCachedDeviceManager.getCachedDevicesCopy(); - assertThat(devices).contains(cachedDevice2); - assertThat(devices).doesNotContain(cachedDevice1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache) - .doesNotContain(cachedDevice2); + cachedDevice1.setSubDevice(cachedDevice2); // Call onDeviceUnpaired for the one in mCachedDevices. mCachedDeviceManager.onDeviceUnpaired(cachedDevice2); - - // Check if its pair is removed from mHearingAidDevicesNotAddedInCache. - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache) - .doesNotContain(cachedDevice1); + verify(mDevice1).removeBond(); } /** - * Test to verify OnDeviceUnpaired() for paired hearing Aid devices which are not a pair. + * Test to verify OnDeviceUnpaired() for sub hearing Aid device unpair. */ @Test - public void onDeviceUnpaired_bothHearingAidsNotPaired_doesNotRemoveAnyDeviceFromList() { + public void onDeviceUnpaired_unpairSubDevice() { + when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); - assertThat(cachedDevice1).isNotNull(); CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); - assertThat(cachedDevice2).isNotNull(); - CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3); - assertThat(cachedDevice2).isNotNull(); + cachedDevice1.setSubDevice(cachedDevice2); - cachedDevice1.setHiSyncId(HISYNCID1); - cachedDevice2.setHiSyncId(HISYNCID1); - cachedDevice3.setHiSyncId(HISYNCID2); - mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); - mCachedDeviceManager.onHiSyncIdChanged(HISYNCID2); - - // Check if one device is in mCachedDevices and one in mHearingAidDevicesNotAddedInCache. - Collection devices = mCachedDeviceManager.getCachedDevicesCopy(); - assertThat(devices).contains(cachedDevice2); - assertThat(devices).contains(cachedDevice3); - assertThat(devices).doesNotContain(cachedDevice1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache) - .doesNotContain(cachedDevice2); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache) - .doesNotContain(cachedDevice3); - - // Call onDeviceUnpaired for the one in mCachedDevices with no pair. - mCachedDeviceManager.onDeviceUnpaired(cachedDevice3); - - // Check if no list is changed. - devices = mCachedDeviceManager.getCachedDevicesCopy(); - assertThat(devices).contains(cachedDevice2); - assertThat(devices).contains(cachedDevice3); - assertThat(devices).doesNotContain(cachedDevice1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache) - .doesNotContain(cachedDevice2); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache) - .doesNotContain(cachedDevice3); + // Call onDeviceUnpaired for the one in mCachedDevices. + mCachedDeviceManager.onDeviceUnpaired(cachedDevice1); + verify(mDevice2).removeBond(); } /** - * Test to verify addDevice() for hearing aid devices with same HiSyncId. + * Test to verify getSubDeviceSummary() for disconnected sub device */ @Test - public void addDevice_hearingAidDevicesWithSameHiSyncId_populateInDifferentLists() { - doAnswer((invocation) -> mHearingAidProfile).when(mLocalProfileManager) - .getHearingAidProfile(); - doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1); - doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice2); + public void getSubDeviceSummary_SubDeviceDisconnected() { + when(mCachedDevice2.isConnected()).thenReturn(false); + mCachedDevice1.setSubDevice(mCachedDevice2); + mCachedDeviceManager.getSubDeviceSummary(mCachedDevice1); - CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); - assertThat(cachedDevice1).isNotNull(); - // The first hearing aid device should be populated in mCachedDevice and - // mCachedDevicesMapForHearingAids. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) - .contains(cachedDevice1); - - CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); - assertThat(cachedDevice2).isNotNull(); - // The second hearing aid device should be populated in mHearingAidDevicesNotAddedInCache. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).hasSize(1); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1); + verify(mCachedDevice2, never()).getConnectionSummary(); } /** - * Test to verify addDevice() for hearing aid devices with different HiSyncId. + * Test to verify getSubDeviceSummary() for connected sub device */ @Test - public void addDevice_hearingAidDevicesWithDifferentHiSyncId_populateInSameList() { - doAnswer((invocation) -> mHearingAidProfile).when(mLocalProfileManager) - .getHearingAidProfile(); - doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1); - doAnswer((invocation) -> HISYNCID2).when(mHearingAidProfile).getHiSyncId(mDevice2); - CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); - assertThat(cachedDevice1).isNotNull(); - // The first hearing aid device should be populated in mCachedDevice and - // mCachedDevicesMapForHearingAids. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) - .contains(cachedDevice1); + public void getSubDeviceSummary_SubDeviceConnected() { + when(mCachedDevice2.isConnected()).thenReturn(true); + mCachedDevice1.setSubDevice(mCachedDevice2); + mCachedDeviceManager.getSubDeviceSummary(mCachedDevice1); - CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); - assertThat(cachedDevice2).isNotNull(); - // The second hearing aid device should also be populated in mCachedDevice - // and mCachedDevicesMapForHearingAids as its not a pair of the first one. - assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(2); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) - .contains(cachedDevice1); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) - .contains(cachedDevice2); - } - - /** - * Test to verify getHearingAidPairDeviceSummary() for hearing aid devices with same HiSyncId. - */ - @Test - public void getHearingAidPairDeviceSummary_bothHearingAidsPaired_returnsSummaryOfPair() { - mCachedDevice1.setHiSyncId(HISYNCID1); - mCachedDevice2.setHiSyncId(HISYNCID1); - mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); - mCachedDeviceManager.mHearingAidDevicesNotAddedInCache.add(mCachedDevice2); - doAnswer((invocation) -> DEVICE_SUMMARY_1).when(mCachedDevice1).getConnectionSummary(); - doAnswer((invocation) -> DEVICE_SUMMARY_2).when(mCachedDevice2).getConnectionSummary(); - - assertThat(mCachedDeviceManager.getHearingAidPairDeviceSummary(mCachedDevice1)) - .isEqualTo(DEVICE_SUMMARY_2); - } - - /** - * Test to verify getHearingAidPairDeviceSummary() for hearing aid devices with different - * HiSyncId. - */ - @Test - public void getHearingAidPairDeviceSummary_bothHearingAidsNotPaired_returnsNull() { - mCachedDevice1.setHiSyncId(HISYNCID1); - mCachedDevice2.setHiSyncId(HISYNCID2); - mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); - mCachedDeviceManager.mHearingAidDevicesNotAddedInCache.add(mCachedDevice2); - doAnswer((invocation) -> DEVICE_SUMMARY_1).when(mCachedDevice1).getConnectionSummary(); - doAnswer((invocation) -> DEVICE_SUMMARY_2).when(mCachedDevice2).getConnectionSummary(); - - assertThat(mCachedDeviceManager.getHearingAidPairDeviceSummary(mCachedDevice1)) - .isEqualTo(null); + verify(mCachedDevice2).getConnectionSummary(); } /** * Test to verify updateHearingAidsDevices(). */ @Test - public void updateHearingAidDevices_hiSyncIdAvailable_setsHiSyncId() { - doAnswer((invocation) -> mHearingAidProfile).when(mLocalProfileManager) - .getHearingAidProfile(); - doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1); - doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice2); - mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); - mCachedDeviceManager.mCachedDevices.add(mCachedDevice2); - mCachedDeviceManager.updateHearingAidsDevices(mLocalProfileManager); + public void updateHearingAidDevices_directToHearingAidDeviceManager() { + mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mLocalBluetoothManager, + mCachedDeviceManager.mCachedDevices)); + mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager; + mCachedDeviceManager.updateHearingAidsDevices(); - // Assert that the mCachedDevice1 and mCachedDevice2 have an updated HiSyncId. - assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1); - assertThat(mCachedDevice2.getHiSyncId()).isEqualTo(HISYNCID1); + verify(mHearingAidDeviceManager).updateHearingAidsDevices(); } /** diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index f6201dd0b5f11..7f9a5afba5fa2 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -48,6 +48,10 @@ public class CachedBluetoothDeviceTest { private final static String DEVICE_ALIAS = "TestAlias"; private final static String DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF"; private final static String DEVICE_ALIAS_NEW = "TestAliasNew"; + private final static short RSSI_1 = 10; + private final static short RSSI_2 = 11; + private final static boolean JUSTDISCOVERED_1 = true; + private final static boolean JUSTDISCOVERED_2 = false; @Mock private LocalBluetoothProfileManager mProfileManager; @Mock @@ -60,6 +64,8 @@ public class CachedBluetoothDeviceTest { private HearingAidProfile mHearingAidProfile; @Mock private BluetoothDevice mDevice; + @Mock + private BluetoothDevice mSubDevice; private CachedBluetoothDevice mCachedDevice; private ShadowAudioManager mShadowAudioManager; private Context mContext; @@ -657,4 +663,39 @@ public class CachedBluetoothDeviceTest { doReturn(status).when(profile).getConnectionStatus(mDevice); mCachedDevice.onProfileStateChanged(profile, status); } + + @Test + public void getSubDevice_setSubDevice() { + CachedBluetoothDevice subCachedDevice = new CachedBluetoothDevice(mContext, mProfileManager, + mSubDevice); + mCachedDevice.setSubDevice(subCachedDevice); + + assertThat(mCachedDevice.getSubDevice()).isEqualTo(subCachedDevice); + } + + @Test + public void switchSubDeviceContent() { + CachedBluetoothDevice subCachedDevice = new CachedBluetoothDevice(mContext, mProfileManager, + mSubDevice); + mCachedDevice.mRssi = RSSI_1; + mCachedDevice.mJustDiscovered = JUSTDISCOVERED_1; + subCachedDevice.mRssi = RSSI_2; + subCachedDevice.mJustDiscovered = JUSTDISCOVERED_2; + mCachedDevice.setSubDevice(subCachedDevice); + + assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_1); + assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1); + assertThat(mCachedDevice.mDevice).isEqualTo(mDevice); + assertThat(subCachedDevice.mRssi).isEqualTo(RSSI_2); + assertThat(subCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2); + assertThat(subCachedDevice.mDevice).isEqualTo(mSubDevice); + mCachedDevice.switchSubDeviceContent(); + + assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2); + assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2); + assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice); + assertThat(subCachedDevice.mRssi).isEqualTo(RSSI_1); + assertThat(subCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1); + assertThat(subCachedDevice.mDevice).isEqualTo(mDevice); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java new file mode 100644 index 0000000000000..cb1b12d04f839 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; +import android.content.Context; + +import com.android.settingslib.SettingsLibRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsLibRobolectricTestRunner.class) +public class HearingAidDeviceManagerTest { + private final static long HISYNCID1 = 10; + private final static long HISYNCID2 = 11; + private final static String DEVICE_NAME_1 = "TestName_1"; + private final static String DEVICE_NAME_2 = "TestName_2"; + private final static String DEVICE_ALIAS_1 = "TestAlias_1"; + private final static String DEVICE_ALIAS_2 = "TestAlias_2"; + private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11"; + private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22"; + private final BluetoothClass DEVICE_CLASS = + new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE); + @Mock + private LocalBluetoothProfileManager mLocalProfileManager; + @Mock + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private BluetoothEventManager mBluetoothEventManager; + @Mock + private HearingAidProfile mHearingAidProfile; + @Mock + private BluetoothDevice mDevice1; + @Mock + private BluetoothDevice mDevice2; + private CachedBluetoothDevice mCachedDevice1; + private CachedBluetoothDevice mCachedDevice2; + private CachedBluetoothDeviceManager mCachedDeviceManager; + private HearingAidDeviceManager mHearingAidDeviceManager; + private Context mContext; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1); + when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2); + when(mDevice1.getName()).thenReturn(DEVICE_NAME_1); + when(mDevice2.getName()).thenReturn(DEVICE_NAME_2); + when(mDevice1.getAliasName()).thenReturn(DEVICE_ALIAS_1); + when(mDevice2.getAliasName()).thenReturn(DEVICE_ALIAS_2); + when(mDevice1.getBluetoothClass()).thenReturn(DEVICE_CLASS); + when(mDevice2.getBluetoothClass()).thenReturn(DEVICE_CLASS); + when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager); + when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager); + when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); + + mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager); + mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mLocalBluetoothManager, + mCachedDeviceManager.mCachedDevices)); + mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1)); + mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2)); + } + + /** + * Test initHearingAidDeviceIfNeeded, a valid HiSyncId will be assigned + */ + @Test + public void initHearingAidDeviceIfNeeded_validHiSyncId_verifyHiSyncId() { + when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1); + + assertThat(mCachedDevice1.getHiSyncId()).isNotEqualTo(HISYNCID1); + mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1); + + assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1); + } + + /** + * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId will not be assigned + */ + @Test + public void initHearingAidDeviceIfNeeded_invalidHiSyncId_notToSetHiSyncId() { + when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn( + BluetoothHearingAid.HI_SYNC_ID_INVALID); + mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1); + + verify(mCachedDevice1, never()).setHiSyncId(anyLong()); + } + + /** + * Test setSubDeviceIfNeeded, a device with same HiSyncId will be set as sub device + */ + @Test + public void setSubDeviceIfNeeded_sameHiSyncId_setSubDevice() { + mCachedDevice1.setHiSyncId(HISYNCID1); + mCachedDevice2.setHiSyncId(HISYNCID1); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2); + + assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2); + } + + /** + * Test setSubDeviceIfNeeded, a device with different HiSyncId will not be set as sub device + */ + @Test + public void setSubDeviceIfNeeded_differentHiSyncId_notSetSubDevice() { + mCachedDevice1.setHiSyncId(HISYNCID1); + mCachedDevice2.setHiSyncId(HISYNCID2); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2); + + assertThat(mCachedDevice1.getSubDevice()).isNull(); + } + + /** + * Test updateHearingAidsDevices, to link two devices with the same HiSyncId. + * When first paired devices is connected and second paired device is disconnected, first + * paired device would be set as main device and second device will be removed from + * CachedDevices list. + */ + @Test + public void updateHearingAidsDevices_firstPairedDevicesConnected_verifySubDevice() { + when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1); + when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1); + when(mCachedDevice1.isConnected()).thenReturn(true); + when(mCachedDevice2.isConnected()).thenReturn(false); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice2); + + assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice2)).isTrue(); + assertThat(mCachedDevice1.getSubDevice()).isNull(); + mHearingAidDeviceManager.updateHearingAidsDevices(); + + assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice2)).isFalse(); + assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2); + } + + /** + * Test updateHearingAidsDevices, to link two devices with the same HiSyncId. + * When second paired devices is connected and first paired device is disconnected, second + * paired device would be set as main device and first device will be removed from + * CachedDevices list. + */ + @Test + public void updateHearingAidsDevices_secondPairedDeviceConnected_verifySubDevice() { + when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1); + when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1); + when(mCachedDevice1.isConnected()).thenReturn(false); + when(mCachedDevice2.isConnected()).thenReturn(true); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice2); + + assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice1)).isTrue(); + assertThat(mCachedDevice2.getSubDevice()).isNull(); + mHearingAidDeviceManager.updateHearingAidsDevices(); + + assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice1)).isFalse(); + assertThat(mCachedDevice2.getSubDevice()).isEqualTo(mCachedDevice1); + } + + /** + * Test updateHearingAidsDevices, to link two devices with the same HiSyncId. + * When both devices are connected, to build up main and sub relationship and to remove sub + * device from CachedDevices list. + */ + @Test + public void updateHearingAidsDevices_BothConnected_verifySubDevice() { + when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1); + when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1); + when(mCachedDevice1.isConnected()).thenReturn(true); + when(mCachedDevice2.isConnected()).thenReturn(true); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice2); + + assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice2)).isTrue(); + assertThat(mCachedDevice1.getSubDevice()).isNull(); + mHearingAidDeviceManager.updateHearingAidsDevices(); + + assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice2)).isFalse(); + assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2); + } + + /** + * Test updateHearingAidsDevices, dispatch callback + */ + @Test + public void updateHearingAidsDevices_dispatchDeviceRemovedCallback() { + when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1); + when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice2); + mHearingAidDeviceManager.updateHearingAidsDevices(); + + verify(mBluetoothEventManager).dispatchDeviceRemoved(mCachedDevice1); + } + + /** + * Test updateHearingAidsDevices, do nothing when HiSyncId is invalid + */ + @Test + public void updateHearingAidsDevices_invalidHiSyncId_doNothing() { + when(mHearingAidProfile.getHiSyncId(mDevice1)). + thenReturn(BluetoothHearingAid.HI_SYNC_ID_INVALID); + when(mHearingAidProfile.getHiSyncId(mDevice2)). + thenReturn(BluetoothHearingAid.HI_SYNC_ID_INVALID); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice2); + mHearingAidDeviceManager.updateHearingAidsDevices(); + + verify(mHearingAidDeviceManager, never()).onHiSyncIdChanged(anyLong()); + } + + /** + * Test onProfileConnectionStateChangedIfProcessed. + * When first hearing aid device is connected, to process it same as other generic devices. + * No need to process it. + */ + @Test + public void onProfileConnectionStateChanged_connected_singleDevice_returnFalse() { + when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1); + + assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed( + mCachedDevice1, BluetoothProfile.STATE_CONNECTED)).isFalse(); + } + + /** + * Test onProfileConnectionStateChangedIfProcessed. + * When a new hearing aid device is connected, to set it as sub device by onHiSyncIdChanged(). + * And, to verify new device is not in CachedDevices list. + */ + @Test + public void onProfileConnectionStateChanged_connected_newDevice_verifySubDevice() { + when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice1.isConnected()).thenReturn(true); + when(mCachedDevice2.isConnected()).thenReturn(true); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice2); + + assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice2)).isTrue(); + assertThat(mCachedDevice1.getSubDevice()).isNull(); + mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1, + BluetoothProfile.STATE_CONNECTED); + + assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice2)).isFalse(); + assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2); + verify(mHearingAidDeviceManager).onHiSyncIdChanged(anyLong()); + } + + /** + * Test onProfileConnectionStateChangedIfProcessed. + * When sub device is disconnected, do nothing and return False for main device connected event + */ + @Test + public void + onProfileConnectionStateChanged_connected_mainDevice_subDeviceDisconnected_returnFalse() { + when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice2.isConnected()).thenReturn(false); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDevice1.setSubDevice(mCachedDevice2); + + assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed( + mCachedDevice1, BluetoothProfile.STATE_CONNECTED)).isFalse(); + verify(mHearingAidDeviceManager).onHiSyncIdChanged(anyLong()); + } + + /** + * Test onProfileConnectionStateChangedIfProcessed. + * When main device is connected, do main device refresh() for sub device connected event + */ + @Test + public void + onProfileConnectionStateChanged_connected_subDevice_mainDeviceConnected_verifyRefresh() { + when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice1.isConnected()).thenReturn(true); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDevice1.setSubDevice(mCachedDevice2); + + assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed( + mCachedDevice2, BluetoothProfile.STATE_CONNECTED)).isTrue(); + verify(mHearingAidDeviceManager).onHiSyncIdChanged(anyLong()); + verify(mCachedDevice1).refresh(); + } + + /** + * Test onProfileConnectionStateChangedIfProcessed. + * When main device is disconnected, to verify switch() result for sub device connected + * event + */ + @Test + public void onProfileConnectionStateChanged_connected_subDevice_mainDeviceDisconnected_switch() + { + when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice1.isConnected()).thenReturn(false); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDevice1.setSubDevice(mCachedDevice2); + + assertThat(mCachedDevice1.mDevice).isEqualTo(mDevice1); + assertThat(mCachedDevice2.mDevice).isEqualTo(mDevice2); + assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed( + mCachedDevice2, BluetoothProfile.STATE_CONNECTED)).isTrue(); + + assertThat(mCachedDevice1.mDevice).isEqualTo(mDevice2); + assertThat(mCachedDevice2.mDevice).isEqualTo(mDevice1); + verify(mHearingAidDeviceManager).onHiSyncIdChanged(anyLong()); + verify(mCachedDevice1).refresh(); + } + + /** + * Test onProfileConnectionStateChangedIfProcessed. + * When sub device is connected, to verify switch() result for main device disconnected + * event + */ + @Test + public void onProfileConnectionStateChanged_disconnected_mainDevice_subDeviceConnected_switch() + { + when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice2.isConnected()).thenReturn(true); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDevice1.setSubDevice(mCachedDevice2); + + assertThat(mCachedDevice1.mDevice).isEqualTo(mDevice1); + assertThat(mCachedDevice2.mDevice).isEqualTo(mDevice2); + assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed( + mCachedDevice1, BluetoothProfile.STATE_DISCONNECTED)).isTrue(); + + assertThat(mCachedDevice1.mDevice).isEqualTo(mDevice2); + assertThat(mCachedDevice2.mDevice).isEqualTo(mDevice1); + verify(mCachedDevice1).refresh(); + } + + /** + * Test onProfileConnectionStateChangedIfProcessed. + * When sub device is disconnected, do nothing and return False for main device disconnected + * event + */ + @Test + public void + onProfileConnectionStateChanged_disconnected_mainDevice_subDeviceDisconnected_returnFalse() { + when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice2.isConnected()).thenReturn(false); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDevice1.setSubDevice(mCachedDevice2); + + assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed( + mCachedDevice1, BluetoothProfile.STATE_DISCONNECTED)).isFalse(); + } + + /** + * Test onProfileConnectionStateChangedIfProcessed. + * Refresh main device UI for sub device disconnected event + */ + @Test + public void onProfileConnectionStateChanged_disconnected_subDevice_verifyRefresh() { + when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDevice1.setSubDevice(mCachedDevice2); + + assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed( + mCachedDevice2, BluetoothProfile.STATE_DISCONNECTED)).isTrue(); + verify(mCachedDevice1).refresh(); + } + + @Test + public void findMainDevice() { + when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1); + when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDevice1.setSubDevice(mCachedDevice2); + + assertThat(mHearingAidDeviceManager.findMainDevice(mCachedDevice2)). + isEqualTo(mCachedDevice1); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java index 6f4c29286c531..4c7f33818b61f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java @@ -55,6 +55,8 @@ import java.util.List; @RunWith(SettingsLibRobolectricTestRunner.class) @Config(shadows = {ShadowBluetoothAdapter.class}) public class LocalBluetoothProfileManagerTest { + private final static long HISYNCID = 10; + @Mock private CachedBluetoothDeviceManager mDeviceManager; @Mock @@ -79,6 +81,7 @@ public class LocalBluetoothProfileManagerTest { mContext, null)); mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); when(mDeviceManager.findDevice(mDevice)).thenReturn(mCachedBluetoothDevice); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice); mProfileManager = new LocalBluetoothProfileManager(mContext, mLocalBluetoothAdapter, mDeviceManager, mEventManager); } @@ -186,13 +189,14 @@ public class LocalBluetoothProfileManagerTest { /** * Verify BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED with uuid intent will dispatch to - * profile connection state changed callback + * CachedBluetoothDeviceManager method */ @Test - public void stateChangedHandler_receiveHAPConnectionStateChanged_shouldDispatchCallback() { + public void stateChangedHandler_receiveHAPConnectionStateChanged_shouldDispatchDeviceManager() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.HEARING_AID})); mProfileManager.updateLocalProfiles(); + when(mCachedBluetoothDevice.getHiSyncId()).thenReturn(HISYNCID); mIntent = new Intent(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); @@ -201,8 +205,8 @@ public class LocalBluetoothProfileManagerTest { mContext.sendBroadcast(mIntent); - verify(mEventManager).dispatchProfileConnectionStateChanged(mCachedBluetoothDevice, - BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID); + verify(mDeviceManager).onProfileConnectionStateChangedIfProcessed(mCachedBluetoothDevice, + BluetoothProfile.STATE_CONNECTED); } /**