Fix audio routing can not work after the phone get rebooted
Root Cause: setPreferredDeviceForStrategy() need to be called when hearing device get connected. Solution: * Call setPreferredDeviceForStrategy() when HearingAidDeviceManager#onProfileConnectionStateChangedIfProcessed() * Extract the common functions into SettingsLib Bug: 269122580 Test: make RunSettingsRoboTests ROBOTEST_FILTER="(CachedBluetoothDeviceManagerTest|HearingAidAudioRoutingHelperTest|HearingAidDeviceManagerTest)" Change-Id: I622040384f56f61c65f0daac45735789d637b703
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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, "");
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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())) {
|
||||
|
||||
@@ -361,6 +361,7 @@ public class LocalBluetoothProfileManager {
|
||||
cachedDevice.setHearingAidInfo(infoBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user