Merge "Fix audio routing can not work after the phone get rebooted" into udc-dev

This commit is contained in:
Jason Hsu
2023-03-23 05:18:57 +00:00
committed by Android (Google) Code Review
8 changed files with 585 additions and 17 deletions

View File

@@ -51,7 +51,8 @@ public class CachedBluetoothDeviceManager {
public CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
mContext = context;
mBtManager = localBtManager;
mHearingAidDeviceManager = new HearingAidDeviceManager(localBtManager, mCachedDevices);
mHearingAidDeviceManager = new HearingAidDeviceManager(context, localBtManager,
mCachedDevices);
mCsipDeviceManager = new CsipDeviceManager(localBtManager, mCachedDevices);
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2023 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.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Constant values used to configure hearing aid audio routing.
*
* {@link HearingAidAudioRoutingHelper}
*/
public final class HearingAidAudioRoutingConstants {
public static final int[] CALL_ROUTING_ATTRIBUTES = new int[] {
// Stands for STRATEGY_PHONE
AudioAttributes.USAGE_VOICE_COMMUNICATION,
};
public static final int[] MEDIA_ROUTING_ATTRIBUTES = new int[] {
// Stands for STRATEGY_MEDIA, including USAGE_GAME, USAGE_ASSISTANT,
// USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, USAGE_ASSISTANCE_SONIFICATION
AudioAttributes.USAGE_MEDIA
};
public static final int[] RINGTONE_ROUTING_ATTRIBUTE = new int[] {
// Stands for STRATEGY_SONIFICATION, including USAGE_ALARM
AudioAttributes.USAGE_NOTIFICATION_RINGTONE
};
public static final int[] SYSTEM_SOUNDS_ROUTING_ATTRIBUTES = new int[] {
// Stands for STRATEGY_SONIFICATION_RESPECTFUL, including USAGE_NOTIFICATION_EVENT
AudioAttributes.USAGE_NOTIFICATION,
// Stands for STRATEGY_ACCESSIBILITY
AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
// Stands for STRATEGY_DTMF
AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
};
@Retention(RetentionPolicy.SOURCE)
@IntDef({
RoutingValue.AUTO,
RoutingValue.HEARING_DEVICE,
RoutingValue.DEVICE_SPEAKER,
})
public @interface RoutingValue {
int AUTO = 0;
int HEARING_DEVICE = 1;
int DEVICE_SPEAKER = 2;
}
public static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright (C) 2023 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.content.Context;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.audiopolicy.AudioProductStrategy;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* A helper class to configure the routing strategy for hearing aids.
*/
public class HearingAidAudioRoutingHelper {
private final AudioManager mAudioManager;
public HearingAidAudioRoutingHelper(Context context) {
mAudioManager = context.getSystemService(AudioManager.class);
}
/**
* Gets the list of {@link AudioProductStrategy} referred by the given list of usage values
* defined in {@link AudioAttributes}
*/
public List<AudioProductStrategy> getSupportedStrategies(int[] attributeSdkUsageList) {
final List<AudioAttributes> audioAttrList = new ArrayList<>(attributeSdkUsageList.length);
for (int attributeSdkUsage : attributeSdkUsageList) {
audioAttrList.add(new AudioAttributes.Builder().setUsage(attributeSdkUsage).build());
}
final List<AudioProductStrategy> allStrategies = getAudioProductStrategies();
final List<AudioProductStrategy> supportedStrategies = new ArrayList<>();
for (AudioProductStrategy strategy : allStrategies) {
for (AudioAttributes audioAttr : audioAttrList) {
if (strategy.supportsAudioAttributes(audioAttr)) {
supportedStrategies.add(strategy);
}
}
}
return supportedStrategies.stream().distinct().collect(Collectors.toList());
}
/**
* Sets the preferred device for the given strategies.
*
* @param supportedStrategies A list of {@link AudioProductStrategy} used to configure audio
* routing
* @param hearingDevice {@link AudioDeviceAttributes} of the device to be changed in audio
* routing
* @param routingValue one of value defined in
* {@link HearingAidAudioRoutingConstants.RoutingValue}, denotes routing
* destination.
* @return {code true} if the routing value successfully configure
*/
public boolean setPreferredDeviceRoutingStrategies(
List<AudioProductStrategy> supportedStrategies, AudioDeviceAttributes hearingDevice,
@HearingAidAudioRoutingConstants.RoutingValue int routingValue) {
boolean status;
switch (routingValue) {
case HearingAidAudioRoutingConstants.RoutingValue.AUTO:
status = removePreferredDeviceForStrategies(supportedStrategies);
return status;
case HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE:
status = removePreferredDeviceForStrategies(supportedStrategies);
status &= setPreferredDeviceForStrategies(supportedStrategies, hearingDevice);
return status;
case HearingAidAudioRoutingConstants.RoutingValue.DEVICE_SPEAKER:
status = removePreferredDeviceForStrategies(supportedStrategies);
status &= setPreferredDeviceForStrategies(supportedStrategies,
HearingAidAudioRoutingConstants.DEVICE_SPEAKER_OUT);
return status;
default:
throw new IllegalArgumentException("Unexpected routingValue: " + routingValue);
}
}
/**
* Gets the matched hearing device {@link AudioDeviceAttributes} for {@code device}.
*
* <p>Will also try to match the {@link CachedBluetoothDevice#getSubDevice()} of {@code device}
*
* @param device the {@link CachedBluetoothDevice} need to be hearing aid device
* @return the requested AudioDeviceAttributes or {@code null} if not match
*/
@Nullable
public AudioDeviceAttributes getMatchedHearingDeviceAttributes(CachedBluetoothDevice device) {
if (device == null || !device.isHearingAidDevice()) {
return null;
}
AudioDeviceInfo[] audioDevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
for (AudioDeviceInfo audioDevice : audioDevices) {
// ASHA for TYPE_HEARING_AID, HAP for TYPE_BLE_HEADSET
if (audioDevice.getType() == AudioDeviceInfo.TYPE_HEARING_AID
|| audioDevice.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) {
if (matchAddress(device, audioDevice)) {
return new AudioDeviceAttributes(audioDevice);
}
}
}
return null;
}
private boolean matchAddress(CachedBluetoothDevice device, AudioDeviceInfo audioDevice) {
final String audioDeviceAddress = audioDevice.getAddress();
final CachedBluetoothDevice subDevice = device.getSubDevice();
final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
return device.getAddress().equals(audioDeviceAddress)
|| (subDevice != null && subDevice.getAddress().equals(audioDeviceAddress))
|| (!memberDevices.isEmpty() && memberDevices.stream().anyMatch(
m -> m.getAddress().equals(audioDeviceAddress)));
}
private boolean setPreferredDeviceForStrategies(List<AudioProductStrategy> strategies,
AudioDeviceAttributes audioDevice) {
boolean status = true;
for (AudioProductStrategy strategy : strategies) {
status &= mAudioManager.setPreferredDeviceForStrategy(strategy, audioDevice);
}
return status;
}
private boolean removePreferredDeviceForStrategies(List<AudioProductStrategy> strategies) {
boolean status = true;
for (AudioProductStrategy strategy : strategies) {
status &= mAudioManager.removePreferredDeviceForStrategy(strategy);
}
return status;
}
@VisibleForTesting
public List<AudioProductStrategy> getAudioProductStrategies() {
return AudioManager.getAudioProductStrategies();
}
}

View File

@@ -18,6 +18,11 @@ package com.android.settingslib.bluetooth;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.content.ContentResolver;
import android.content.Context;
import android.media.AudioDeviceAttributes;
import android.media.audiopolicy.AudioProductStrategy;
import android.provider.Settings;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -33,12 +38,25 @@ public class HearingAidDeviceManager {
private static final String TAG = "HearingAidDeviceManager";
private static final boolean DEBUG = BluetoothUtils.D;
private final ContentResolver mContentResolver;
private final LocalBluetoothManager mBtManager;
private final List<CachedBluetoothDevice> mCachedDevices;
HearingAidDeviceManager(LocalBluetoothManager localBtManager,
private final HearingAidAudioRoutingHelper mRoutingHelper;
HearingAidDeviceManager(Context context, LocalBluetoothManager localBtManager,
List<CachedBluetoothDevice> CachedDevices) {
mContentResolver = context.getContentResolver();
mBtManager = localBtManager;
mCachedDevices = CachedDevices;
mRoutingHelper = new HearingAidAudioRoutingHelper(context);
}
@VisibleForTesting
HearingAidDeviceManager(Context context, LocalBluetoothManager localBtManager,
List<CachedBluetoothDevice> cachedDevices, HearingAidAudioRoutingHelper routingHelper) {
mContentResolver = context.getContentResolver();
mBtManager = localBtManager;
mCachedDevices = cachedDevices;
mRoutingHelper = routingHelper;
}
void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) {
@@ -192,12 +210,11 @@ public class HearingAidDeviceManager {
case BluetoothProfile.STATE_CONNECTED:
onHiSyncIdChanged(cachedDevice.getHiSyncId());
CachedBluetoothDevice mainDevice = findMainDevice(cachedDevice);
if (mainDevice != null){
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
@@ -207,9 +224,15 @@ public class HearingAidDeviceManager {
// It is necessary to do remove and add for updating the mapping on
// preference and device
mBtManager.getEventManager().dispatchDeviceAdded(mainDevice);
return true;
// Only need to set first device of a set. AudioDeviceInfo for
// GET_DEVICES_OUTPUTS will not change device.
setAudioRoutingConfig(cachedDevice);
}
return true;
}
// Only need to set first device of a set. AudioDeviceInfo for GET_DEVICES_OUTPUTS
// will not change device.
setAudioRoutingConfig(cachedDevice);
break;
case BluetoothProfile.STATE_DISCONNECTED:
mainDevice = findMainDevice(cachedDevice);
@@ -232,13 +255,83 @@ public class HearingAidDeviceManager {
// It is necessary to do remove and add for updating the mapping on
// preference and device
mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice);
return true;
}
// Only need to clear when last device of a set get disconnected
clearAudioRoutingConfig();
break;
}
return false;
}
private void setAudioRoutingConfig(CachedBluetoothDevice device) {
AudioDeviceAttributes hearingDeviceAttributes =
mRoutingHelper.getMatchedHearingDeviceAttributes(device);
if (hearingDeviceAttributes == null) {
Log.w(TAG, "Can not find expected AudioDeviceAttributes for hearing device: "
+ device.getDevice().getAnonymizedAddress());
return;
}
final int callRoutingValue = Settings.Secure.getInt(mContentResolver,
Settings.Secure.HEARING_AID_CALL_ROUTING,
HearingAidAudioRoutingConstants.RoutingValue.AUTO);
final int mediaRoutingValue = Settings.Secure.getInt(mContentResolver,
Settings.Secure.HEARING_AID_MEDIA_ROUTING,
HearingAidAudioRoutingConstants.RoutingValue.AUTO);
final int ringtoneRoutingValue = Settings.Secure.getInt(mContentResolver,
Settings.Secure.HEARING_AID_RINGTONE_ROUTING,
HearingAidAudioRoutingConstants.RoutingValue.AUTO);
final int systemSoundsRoutingValue = Settings.Secure.getInt(mContentResolver,
Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
HearingAidAudioRoutingConstants.RoutingValue.AUTO);
setPreferredDeviceRoutingStrategies(
HearingAidAudioRoutingConstants.CALL_ROUTING_ATTRIBUTES,
hearingDeviceAttributes, callRoutingValue);
setPreferredDeviceRoutingStrategies(
HearingAidAudioRoutingConstants.MEDIA_ROUTING_ATTRIBUTES,
hearingDeviceAttributes, mediaRoutingValue);
setPreferredDeviceRoutingStrategies(
HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTE,
hearingDeviceAttributes, ringtoneRoutingValue);
setPreferredDeviceRoutingStrategies(
HearingAidAudioRoutingConstants.SYSTEM_SOUNDS_ROUTING_ATTRIBUTES,
hearingDeviceAttributes, systemSoundsRoutingValue);
}
private void clearAudioRoutingConfig() {
// Don't need to pass hearingDevice when we want to reset it (set to AUTO).
setPreferredDeviceRoutingStrategies(
HearingAidAudioRoutingConstants.CALL_ROUTING_ATTRIBUTES,
/* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
setPreferredDeviceRoutingStrategies(
HearingAidAudioRoutingConstants.MEDIA_ROUTING_ATTRIBUTES,
/* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
setPreferredDeviceRoutingStrategies(
HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTE,
/* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
setPreferredDeviceRoutingStrategies(
HearingAidAudioRoutingConstants.SYSTEM_SOUNDS_ROUTING_ATTRIBUTES,
/* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
}
private void setPreferredDeviceRoutingStrategies(int[] attributeSdkUsageList,
AudioDeviceAttributes hearingDevice,
@HearingAidAudioRoutingConstants.RoutingValue int routingValue) {
final List<AudioProductStrategy> supportedStrategies =
mRoutingHelper.getSupportedStrategies(attributeSdkUsageList);
final boolean status = mRoutingHelper.setPreferredDeviceRoutingStrategies(
supportedStrategies, hearingDevice, routingValue);
if (!status) {
Log.w(TAG, "routingStrategies: " + supportedStrategies.toString() + "routingValue: "
+ routingValue + " fail to configure AudioProductStrategy");
}
}
CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) {
for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
if (isValidHiSyncId(cachedDevice.getHiSyncId())) {

View File

@@ -361,6 +361,7 @@ public class LocalBluetoothProfileManager {
cachedDevice.setHearingAidInfo(infoBuilder.build());
}
}
HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice);
}

View File

@@ -403,7 +403,7 @@ public class CachedBluetoothDeviceManagerTest {
*/
@Test
public void updateHearingAidDevices_directToHearingAidDeviceManager() {
mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mLocalBluetoothManager,
mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager,
mCachedDeviceManager.mCachedDevices));
mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager;
mCachedDeviceManager.updateHearingAidsDevices();

View File

@@ -0,0 +1,165 @@
/*
* Copyright (C) 2023 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.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.audiopolicy.AudioProductStrategy;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** Tests for {@link HearingAidAudioRoutingHelper}. */
@RunWith(RobolectricTestRunner.class)
public class HearingAidAudioRoutingHelperTest {
@Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Spy
private final Context mContext = ApplicationProvider.getApplicationContext();
private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
private static final String NOT_EXPECT_DEVICE_ADDRESS = "11:B2:B2:B2:B2:B2";
@Mock
private AudioProductStrategy mAudioStrategy;
@Spy
private AudioManager mAudioManager = mContext.getSystemService(AudioManager.class);
@Mock
private AudioDeviceInfo mAudioDeviceInfo;
@Mock
private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock
private CachedBluetoothDevice mSubCachedBluetoothDevice;
private AudioDeviceAttributes mHearingDeviceAttribute;
private HearingAidAudioRoutingHelper mHelper;
@Before
public void setUp() {
doReturn(mAudioManager).when(mContext).getSystemService(AudioManager.class);
when(mAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HEARING_AID);
when(mAudioDeviceInfo.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).thenReturn(
new AudioDeviceInfo[]{mAudioDeviceInfo});
when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
AudioManager.STREAM_MUSIC))
.thenReturn((new AudioAttributes.Builder()).build());
mHearingDeviceAttribute = new AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_HEARING_AID,
TEST_DEVICE_ADDRESS);
mHelper = spy(new HearingAidAudioRoutingHelper(mContext));
doReturn(List.of(mAudioStrategy)).when(mHelper).getAudioProductStrategies();
}
@Test
public void setPreferredDeviceRoutingStrategies_valueAuto_callRemoveStrategy() {
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
mHearingDeviceAttribute,
HearingAidAudioRoutingConstants.RoutingValue.AUTO);
verify(mAudioManager, atLeastOnce()).removePreferredDeviceForStrategy(mAudioStrategy);
}
@Test
public void setPreferredDeviceRoutingStrategies_valueHearingDevice_callSetStrategy() {
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
mHearingDeviceAttribute,
HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE);
verify(mAudioManager, atLeastOnce()).setPreferredDeviceForStrategy(mAudioStrategy,
mHearingDeviceAttribute);
}
@Test
public void setPreferredDeviceRoutingStrategies_valueDeviceSpeaker_callSetStrategy() {
final AudioDeviceAttributes speakerDevice = new AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
mHearingDeviceAttribute,
HearingAidAudioRoutingConstants.RoutingValue.DEVICE_SPEAKER);
verify(mAudioManager, atLeastOnce()).setPreferredDeviceForStrategy(mAudioStrategy,
speakerDevice);
}
@Test
public void getMatchedHearingDeviceAttributes_mainHearingDevice_equalAddress() {
when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
mCachedBluetoothDevice).getAddress();
assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
}
@Test
public void getMatchedHearingDeviceAttributes_subHearingDevice_equalAddress() {
when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getAddress()).thenReturn(NOT_EXPECT_DEVICE_ADDRESS);
when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice);
when(mSubCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
when(mSubCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
mCachedBluetoothDevice).getAddress();
assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
}
@Test
public void getMatchedHearingDeviceAttributes_memberHearingDevice_equalAddress() {
when(mSubCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
when(mSubCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
final Set<CachedBluetoothDevice> memberDevices = new HashSet<CachedBluetoothDevice>();
memberDevices.add(mSubCachedBluetoothDevice);
when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getAddress()).thenReturn(NOT_EXPECT_DEVICE_ADDRESS);
when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(memberDevices);
final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
mCachedBluetoothDevice).getAddress();
assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
}
}

View File

@@ -18,7 +18,12 @@ package com.android.settingslib.bluetooth;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -29,18 +34,32 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.audiopolicy.AudioProductStrategy;
import android.os.Parcel;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class HearingAidDeviceManagerTest {
@Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
private final static long HISYNCID1 = 10;
private final static long HISYNCID2 = 11;
private final static String DEVICE_NAME_1 = "TestName_1";
@@ -51,6 +70,15 @@ public class HearingAidDeviceManagerTest {
private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
private final BluetoothClass DEVICE_CLASS =
createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
private CachedBluetoothDevice mCachedDevice1;
private CachedBluetoothDevice mCachedDevice2;
private CachedBluetoothDeviceManager mCachedDeviceManager;
private HearingAidDeviceManager mHearingAidDeviceManager;
private AudioDeviceAttributes mHearingDeviceAttribute;
private final Context mContext = ApplicationProvider.getApplicationContext();
@Spy
private HearingAidAudioRoutingHelper mHelper = new HearingAidAudioRoutingHelper(mContext);
@Mock
private LocalBluetoothProfileManager mLocalProfileManager;
@Mock
@@ -60,14 +88,12 @@ public class HearingAidDeviceManagerTest {
@Mock
private HearingAidProfile mHearingAidProfile;
@Mock
private AudioProductStrategy mAudioStrategy;
@Mock
private BluetoothDevice mDevice1;
@Mock
private BluetoothDevice mDevice2;
private CachedBluetoothDevice mCachedDevice1;
private CachedBluetoothDevice mCachedDevice2;
private CachedBluetoothDeviceManager mCachedDeviceManager;
private HearingAidDeviceManager mHearingAidDeviceManager;
private Context mContext;
private BluetoothClass createBtClass(int deviceClass) {
Parcel p = Parcel.obtain();
@@ -81,8 +107,6 @@ public class HearingAidDeviceManagerTest {
@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);
@@ -94,10 +118,18 @@ public class HearingAidDeviceManagerTest {
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
AudioManager.STREAM_MUSIC))
.thenReturn((new AudioAttributes.Builder()).build());
doReturn(List.of(mAudioStrategy)).when(mHelper).getSupportedStrategies(any(int[].class));
mHearingDeviceAttribute = new AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_HEARING_AID,
DEVICE_ADDRESS_1);
mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mLocalBluetoothManager,
mCachedDeviceManager.mCachedDevices));
mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager,
mCachedDeviceManager.mCachedDevices, mHelper));
mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1));
mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2));
}
@@ -445,6 +477,44 @@ public class HearingAidDeviceManagerTest {
verify(mCachedDevice1).refresh();
}
@Test
public void onProfileConnectionStateChanged_connected_callSetStrategies() {
when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
mHearingDeviceAttribute);
mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
BluetoothProfile.STATE_CONNECTED);
verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
eq(List.of(mAudioStrategy)), any(AudioDeviceAttributes.class), anyInt());
}
@Test
public void onProfileConnectionStateChanged_disconnected_callSetStrategiesWithAutoValue() {
when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
mHearingDeviceAttribute);
mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
BluetoothProfile.STATE_DISCONNECTED);
verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
eq(List.of(mAudioStrategy)), /* hearingDevice= */ isNull(),
eq(HearingAidAudioRoutingConstants.RoutingValue.AUTO));
}
@Test
public void onProfileConnectionStateChanged_unpairing_callSetStrategiesWithAutoValue() {
when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
mHearingDeviceAttribute);
when(mCachedDevice1.getUnpairing()).thenReturn(true);
mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
BluetoothProfile.STATE_DISCONNECTED);
verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
eq(List.of(mAudioStrategy)), /* hearingDevice= */ isNull(),
eq(HearingAidAudioRoutingConstants.RoutingValue.AUTO));
}
@Test
public void findMainDevice() {
when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1);