blob: 6b94916f8e9713a2dd88d5859ea23ad8cac3ef05 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/media/prefs/capture_device_ranking.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
MATCHER(VideoCaptureDeviceInfoEq, "") {
return std::get<0>(arg).descriptor.device_id ==
std::get<1>(arg).descriptor.device_id;
}
MATCHER(MediaStreamDeviceInfoEq, "") {
return std::get<0>(arg).id == std::get<1>(arg).id &&
std::get<0>(arg).type == std::get<1>(arg).type;
}
template <typename T>
std::vector<T>::const_iterator GetIterForInfo(const std::vector<T>& infos,
const T& info) {
for (auto iter = infos.begin(); iter < infos.end(); ++iter) {
if (media_prefs::internal::DeviceInfoToUniqueId(*iter) ==
media_prefs::internal::DeviceInfoToUniqueId(info)) {
return iter;
}
}
return infos.end();
}
} // namespace
class CaptureDeviceRankingTest : public testing::Test {
void SetUp() override { media_prefs::RegisterUserPrefs(prefs_.registry()); }
protected:
void UpdateAudioRanking(
const media::AudioDeviceDescription& preferred_device,
const std::vector<media::AudioDeviceDescription>& infos) {
media_prefs::UpdateAudioDevicePreferenceRanking(
prefs_, GetIterForInfo(infos, preferred_device), infos);
}
void UpdateVideoRanking(
const media::VideoCaptureDeviceInfo& preferred_device,
const std::vector<media::VideoCaptureDeviceInfo>& infos) {
media_prefs::UpdateVideoDevicePreferenceRanking(
prefs_, GetIterForInfo(infos, preferred_device), infos);
}
void ExpectAudioRanking(
std::vector<media::AudioDeviceDescription> infos,
const std::vector<media::AudioDeviceDescription>& expected_output) {
media_prefs::PreferenceRankAudioDeviceInfos(prefs_, infos);
EXPECT_THAT(infos, expected_output);
}
void ExpectVideoRanking(
std::vector<media::VideoCaptureDeviceInfo> infos,
const std::vector<media::VideoCaptureDeviceInfo>& expected_output) {
media_prefs::PreferenceRankVideoDeviceInfos(prefs_, infos);
EXPECT_THAT(
infos, testing::Pointwise(VideoCaptureDeviceInfoEq(), expected_output));
}
TestingPrefServiceSimple prefs_;
};
TEST_F(CaptureDeviceRankingTest, PreferenceRankVideoDeviceInfos) {
const media::VideoCaptureDeviceInfo kIntegratedCamera(
{/*display_name=*/"Integrated Camera",
/*device_id=*/"integrated_camera"});
const media::VideoCaptureDeviceInfo kUsbCamera({/*display_name=*/"USB Camera",
/*device_id=*/"usb_camera"});
const media::VideoCaptureDeviceInfo kDevice3({/*display_name=*/"Device 3",
/*device_id=*/"device_3"});
const media::VideoCaptureDeviceInfo kDeviceLost(
{/*display_name=*/"Device Lost",
/*device_id=*/"device_lost"});
const media::VideoCaptureDeviceInfo kDeviceExtra(
{/*display_name=*/"Device Extra",
/*device_id=*/"device_extra"});
UpdateVideoRanking(
/*preferred_device=*/kIntegratedCamera,
/*infos=*/{kDeviceLost, kDevice3, kIntegratedCamera, kUsbCamera});
UpdateVideoRanking(
/*preferred_device=*/kUsbCamera,
/*infos=*/{kDevice3, kIntegratedCamera, kUsbCamera});
ExpectVideoRanking(
/*infos=*/{kDevice3, kIntegratedCamera, kUsbCamera},
/*expected_output=*/{kUsbCamera, kIntegratedCamera, kDevice3});
// Remove USB camera and select the integrated camera. Integrated
// should stay below USB in the ranking because the USB camera isn't
// present.
UpdateVideoRanking(/*preferred_device=*/kIntegratedCamera,
/*infos=*/{kDevice3, kIntegratedCamera});
ExpectVideoRanking(
/*infos=*/{kDeviceExtra, kDevice3, kIntegratedCamera},
/*expected_output=*/{kIntegratedCamera, kDevice3, kDeviceExtra});
// Bring back USB camera. The USB camera should be ranked as the most
// preferred device even without an update.
ExpectVideoRanking(
/*infos=*/{kDeviceExtra, kDevice3, kIntegratedCamera, kUsbCamera},
/*expected_output=*/{kUsbCamera, kIntegratedCamera, kDevice3,
kDeviceExtra});
}
TEST_F(CaptureDeviceRankingTest, PreferenceRankAudioDeviceInfos) {
const media::AudioDeviceDescription kIntegratedMic(
{/*device_name=*/"Integrated Mic",
/*unique_id=*/"integrated_mic",
/*group_id=*/"integrated_group"});
const media::AudioDeviceDescription kUsbMic({
/*device_name=*/"USB Mic",
/*unique_id=*/"usb_mic",
/*group_id=*/"usb_group",
});
const media::AudioDeviceDescription kDefaultMic({
/*device_name=*/"",
/*unique_id=*/media::AudioDeviceDescription::kDefaultDeviceId,
/*group_id=*/"default_group",
});
const media::AudioDeviceDescription kDevice3({/*device_name=*/"Device 3",
/*unique_id=*/"device_3",
/*group_id=*/"device_group"});
const media::AudioDeviceDescription kDeviceLost(
{/*device_name=*/"Device Lost",
/*unique_id=*/"device_lost",
/*group_id=*/"device_group"});
const media::AudioDeviceDescription kDeviceExtra(
{/*device_name=*/"Device Extra",
/*unique_id=*/"device_extra",
/*group_id=*/"device_group"});
UpdateAudioRanking(
/*preferred_device=*/kIntegratedMic,
/*infos=*/{kDeviceLost, kDevice3, kIntegratedMic, kUsbMic});
UpdateAudioRanking(/*preferred_device=*/kUsbMic,
/*infos=*/{kDevice3, kIntegratedMic, kUsbMic});
ExpectAudioRanking(
/*infos=*/{kDevice3, kIntegratedMic, kUsbMic},
/*expected_output=*/{kUsbMic, kIntegratedMic, kDevice3});
// Remove USB mic and select the integrated mic. Integrated
// should stay below USB in the ranking because the USB mic isn't
// present.
UpdateAudioRanking(/*preferred_device=*/kIntegratedMic,
/*infos=*/{kDevice3, kIntegratedMic});
ExpectAudioRanking(
/*infos=*/{kDeviceExtra, kDevice3, kIntegratedMic},
/*expected_output=*/{kIntegratedMic, kDevice3, kDeviceExtra});
// Bring back USB mic. The USB mic should be ranked as the most
// preferred device even without an update.
ExpectAudioRanking(
/*infos=*/{kDeviceExtra, kDevice3, kIntegratedMic, kUsbMic},
/*expected_output=*/{kUsbMic, kIntegratedMic, kDevice3, kDeviceExtra});
// Include the default mic, which has an empty name.
UpdateAudioRanking(/*preferred_device=*/kDefaultMic, /*infos=*/{
kUsbMic, kIntegratedMic, kDefaultMic, kDevice3});
ExpectAudioRanking(
/*infos=*/{kUsbMic, kIntegratedMic, kDefaultMic, kDevice3},
/*expected_output=*/{kDefaultMic, kUsbMic, kIntegratedMic, kDevice3});
}
TEST_F(CaptureDeviceRankingTest, PreferenceRankDefaultAudioDevice) {
const media::AudioDeviceDescription kIntegratedMic(
{/*device_name=*/"Integrated Mic",
/*unique_id=*/"integrated_mic",
/*group_id=*/"integrated_group"});
const media::AudioDeviceDescription kUsbMic({
/*device_name=*/"USB Mic",
/*unique_id=*/"usb_mic",
/*group_id=*/"usb_group",
});
const media::AudioDeviceDescription kDefaultMic({
/*device_name=*/"",
/*unique_id=*/media::AudioDeviceDescription::kDefaultDeviceId,
/*group_id=*/"default_group",
});
// Include the default mic, which has an empty name. Then expect that
// preference ranking works correctly for the other device info types.
UpdateAudioRanking(/*preferred_device=*/kDefaultMic,
/*infos=*/{kUsbMic, kIntegratedMic, kDefaultMic});
ExpectAudioRanking(
/*infos=*/{kUsbMic, kDefaultMic, kIntegratedMic},
/*expected_output=*/{kDefaultMic, kUsbMic, kIntegratedMic});
{
const blink::WebMediaDeviceInfo kUsbDevice(
/*device_id=*/"usb_device",
/*label=*/"USB Device",
/*group_id=*/"usb_group");
const blink::WebMediaDeviceInfo kDefaultDevice(
/*device_id=*/media::AudioDeviceDescription::kDefaultDeviceId,
/*label=*/"Default",
/*group_id=*/"default_group");
std::vector infos = {kUsbDevice, kDefaultDevice};
media_prefs::PreferenceRankAudioDeviceInfos(prefs_, infos);
std::vector expected = {kDefaultDevice, kUsbDevice};
EXPECT_EQ(infos, expected);
}
{
const blink::MediaStreamDevice kUsbDevice(
/*type=*/blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
/*id=*/"usb_device",
/*name=*/"USB Device");
const blink::MediaStreamDevice kDefaultDevice(
/*type=*/blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
/*id=*/media::AudioDeviceDescription::kDefaultDeviceId,
/*name=*/"Default");
std::vector infos = {kUsbDevice, kDefaultDevice};
media_prefs::PreferenceRankAudioDeviceInfos(prefs_, infos);
std::vector expected = {kDefaultDevice, kUsbDevice};
EXPECT_EQ(infos, expected);
}
}
TEST_F(CaptureDeviceRankingTest, InitializeAudioDeviceInfosRanking) {
const media::AudioDeviceDescription kIntegratedMic(
{/*device_name=*/"Integrated Mic",
/*unique_id=*/"integrated_mic",
/*group_id=*/"integrated_group"});
const media::AudioDeviceDescription kUsbMic({
/*device_name=*/"USB Mic",
/*unique_id=*/"usb_mic",
/*group_id=*/"usb_group",
});
prefs_.registry()->RegisterStringPref(
prefs::kDefaultAudioCaptureDeviceDeprecated, "");
// The default device is set to "" so the output should be unmodified.
ExpectAudioRanking({kIntegratedMic, kUsbMic}, {kIntegratedMic, kUsbMic});
// The default device isn't in the passed device list, so the output should be
// unmodified.
prefs_.SetString(prefs::kDefaultAudioCaptureDeviceDeprecated, "not_found_id");
ExpectAudioRanking({kIntegratedMic, kUsbMic}, {kIntegratedMic, kUsbMic});
// The default device is USB mic so the device ranking gets initialized to put
// that first.
prefs_.SetString(prefs::kDefaultAudioCaptureDeviceDeprecated,
kUsbMic.unique_id);
ExpectAudioRanking({kIntegratedMic, kUsbMic}, {kUsbMic, kIntegratedMic});
// The device ranking pref now has a value, so use that instead of the default
// device pref.
UpdateAudioRanking(kIntegratedMic, {kIntegratedMic, kUsbMic});
ExpectAudioRanking({kUsbMic, kIntegratedMic}, {kIntegratedMic, kUsbMic});
}
TEST_F(CaptureDeviceRankingTest, InitializeVideoDeviceInfosRanking) {
const media::VideoCaptureDeviceInfo kIntegratedCamera(
{/*display_name=*/"Integrated Camera",
/*device_id=*/"integrated_camera"});
const media::VideoCaptureDeviceInfo kUsbCamera({/*display_name=*/"USB Camera",
/*device_id=*/"usb_camera"});
prefs_.registry()->RegisterStringPref(
prefs::kDefaultVideoCaptureDeviceDeprecated, "");
// The default device is set to "" so the output should be unmodified.
ExpectVideoRanking({kIntegratedCamera, kUsbCamera},
{kIntegratedCamera, kUsbCamera});
// The default device isn't in the passed device list, so the output should be
// unmodified.
prefs_.SetString(prefs::kDefaultVideoCaptureDeviceDeprecated, "not_found_id");
ExpectVideoRanking({kIntegratedCamera, kUsbCamera},
{kIntegratedCamera, kUsbCamera});
// The default device is usb camera so the device ranking gets Initialized to
// put that first.
prefs_.SetString(prefs::kDefaultVideoCaptureDeviceDeprecated,
kUsbCamera.descriptor.device_id);
ExpectVideoRanking({kIntegratedCamera, kUsbCamera},
{kUsbCamera, kIntegratedCamera});
// The device ranking pref now has a value, so use that instead of the default
// device pref.
UpdateVideoRanking(kIntegratedCamera, {kIntegratedCamera, kUsbCamera});
ExpectVideoRanking({kUsbCamera, kIntegratedCamera},
{kIntegratedCamera, kUsbCamera});
}
class CaptureDeviceRankingWebMediaDeviceTest
: public CaptureDeviceRankingTest,
public testing::WithParamInterface<blink::mojom::MediaDeviceType> {
protected:
void UpdateDeviceRanking(
blink::mojom::MediaDeviceType device_type,
const blink::WebMediaDeviceInfo& preferred_device,
const std::vector<blink::WebMediaDeviceInfo>& infos) {
if (device_type == blink::mojom::MediaDeviceType::kMediaAudioInput) {
media_prefs::UpdateAudioDevicePreferenceRanking(
prefs_, GetIterForInfo(infos, preferred_device), infos);
} else if (device_type == blink::mojom::MediaDeviceType::kMediaVideoInput) {
media_prefs::UpdateVideoDevicePreferenceRanking(
prefs_, GetIterForInfo(infos, preferred_device), infos);
} else {
FAIL() << "Called with an unsupported type: " << device_type;
}
}
void ExpectDeviceRanking(
blink::mojom::MediaDeviceType device_type,
std::vector<blink::WebMediaDeviceInfo> infos,
const std::vector<blink::WebMediaDeviceInfo>& expected_output) {
if (device_type == blink::mojom::MediaDeviceType::kMediaAudioInput) {
media_prefs::PreferenceRankAudioDeviceInfos(prefs_, infos);
EXPECT_EQ(infos, expected_output);
} else if (device_type == blink::mojom::MediaDeviceType::kMediaVideoInput) {
media_prefs::PreferenceRankVideoDeviceInfos(prefs_, infos);
EXPECT_EQ(infos, expected_output);
} else {
FAIL() << "Called with an unsupported type: " << device_type;
}
}
};
INSTANTIATE_TEST_SUITE_P(
WebMediaDeviceAudioVideoInput,
CaptureDeviceRankingWebMediaDeviceTest,
::testing::Values(blink::mojom::MediaDeviceType::kMediaAudioInput,
blink::mojom::MediaDeviceType::kMediaVideoInput));
TEST_P(CaptureDeviceRankingWebMediaDeviceTest, PreferenceWebMediaDeviceInfos) {
const blink::WebMediaDeviceInfo kIntegratedDevice(
/*device_id=*/"integrated_device",
/*label=*/"Integrated Device",
/*group_id=*/"integrated_group");
const blink::WebMediaDeviceInfo kUsbDevice(
/*device_id=*/"usb_device",
/*label=*/"USB Device",
/*group_id=*/"usb_group");
const blink::WebMediaDeviceInfo kDefaultDevice(
/*device_id=*/media::AudioDeviceDescription::kDefaultDeviceId,
/*label=*/"Default",
/*group_id=*/"default_group");
const blink::WebMediaDeviceInfo kDevice3(
/*device_id=*/"device_3",
/*label=*/"Device 3",
/*group_id=*/"device_group");
const blink::WebMediaDeviceInfo kDeviceLost(
/*device_id=*/"device_lost",
/*label=*/"Device Lost",
/*group_id=*/"device_group");
const blink::WebMediaDeviceInfo kDeviceExtra(
/*device_id=*/"device_extra",
/*label=*/"Device Extra",
/*group_id=*/"device_group");
const blink::mojom::MediaDeviceType device_type = GetParam();
UpdateDeviceRanking(
device_type,
/*preferred_device=*/kIntegratedDevice,
/*infos=*/{kDeviceLost, kDevice3, kIntegratedDevice, kUsbDevice});
UpdateDeviceRanking(device_type,
/*preferred_device=*/kUsbDevice,
/*infos=*/{kDevice3, kIntegratedDevice, kUsbDevice});
ExpectDeviceRanking(
device_type,
/*infos=*/{kDevice3, kIntegratedDevice, kUsbDevice},
/*expected_output=*/{kUsbDevice, kIntegratedDevice, kDevice3});
// Remove USB device and select the integrated device. Integrated
// should stay below USB in the ranking because the USB device isn't
// present.
UpdateDeviceRanking(device_type,
/*preferred_device=*/kIntegratedDevice,
/*infos=*/{kDevice3, kIntegratedDevice});
ExpectDeviceRanking(
device_type,
/*infos=*/{kDeviceExtra, kDevice3, kIntegratedDevice},
/*expected_output=*/{kIntegratedDevice, kDevice3, kDeviceExtra});
// Bring back USB device. The USB device should be ranked as the most
// preferred device even without an update.
ExpectDeviceRanking(
device_type,
/*infos=*/{kDeviceExtra, kDevice3, kIntegratedDevice, kUsbDevice},
/*expected_output=*/
{kUsbDevice, kIntegratedDevice, kDevice3, kDeviceExtra});
// Include the default device.
UpdateDeviceRanking(
device_type, /*preferred_device=*/kDefaultDevice,
/*infos=*/{kUsbDevice, kIntegratedDevice, kDefaultDevice, kDevice3});
ExpectDeviceRanking(
device_type,
/*infos=*/{kUsbDevice, kIntegratedDevice, kDefaultDevice, kDevice3},
/*expected_output=*/
{kDefaultDevice, kUsbDevice, kIntegratedDevice, kDevice3});
}
class CaptureDeviceRankingMediaStreamDeviceTest
: public CaptureDeviceRankingTest,
public testing::WithParamInterface<blink::mojom::MediaStreamType> {
protected:
void UpdateDeviceRanking(blink::mojom::MediaStreamType device_type,
const blink::MediaStreamDevice& preferred_device,
const std::vector<blink::MediaStreamDevice>& infos) {
if (device_type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
media_prefs::UpdateAudioDevicePreferenceRanking(
prefs_, GetIterForInfo(infos, preferred_device), infos);
} else if (device_type ==
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
media_prefs::UpdateVideoDevicePreferenceRanking(
prefs_, GetIterForInfo(infos, preferred_device), infos);
} else {
FAIL() << "Called with an unsupported type: " << device_type;
}
}
void ExpectDeviceRanking(
blink::mojom::MediaStreamType device_type,
std::vector<blink::MediaStreamDevice> infos,
const std::vector<blink::MediaStreamDevice>& expected_output) {
if (device_type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
media_prefs::PreferenceRankAudioDeviceInfos(prefs_, infos);
EXPECT_THAT(infos, testing::Pointwise(MediaStreamDeviceInfoEq(),
expected_output));
} else if (device_type ==
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
media_prefs::PreferenceRankVideoDeviceInfos(prefs_, infos);
EXPECT_THAT(infos, testing::Pointwise(MediaStreamDeviceInfoEq(),
expected_output));
} else {
FAIL() << "Called with an unsupported type: " << device_type;
}
}
};
INSTANTIATE_TEST_SUITE_P(
MediaStreamDeviceAudioVideoInput,
CaptureDeviceRankingMediaStreamDeviceTest,
::testing::Values(blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
TEST_P(CaptureDeviceRankingMediaStreamDeviceTest,
PreferenceMediaStreamDevices) {
const blink::mojom::MediaStreamType device_type = GetParam();
const blink::MediaStreamDevice kIntegratedDevice(
/*type=*/device_type,
/*id=*/"integrated_device",
/*name=*/"Integrated Device");
const blink::MediaStreamDevice kUsbDevice(
/*type=*/device_type,
/*id=*/"usb_device",
/*name=*/"USB Device");
const blink::MediaStreamDevice kDefaultDevice(
/*type=*/device_type,
/*id=*/media::AudioDeviceDescription::kDefaultDeviceId,
/*name=*/"Default");
const blink::MediaStreamDevice kDevice3(
/*type=*/device_type,
/*id=*/"device_3",
/*name=*/"Device 3");
const blink::MediaStreamDevice kDeviceLost(
/*type=*/device_type,
/*id=*/"device_lost",
/*name=*/"Device Lost");
const blink::MediaStreamDevice kDeviceExtra(
/*type=*/device_type,
/*id=*/"device_extra",
/*name=*/"Device Extra");
UpdateDeviceRanking(
device_type,
/*preferred_device=*/kIntegratedDevice,
/*infos=*/{kDeviceLost, kDevice3, kIntegratedDevice, kUsbDevice});
UpdateDeviceRanking(device_type,
/*preferred_device=*/kUsbDevice,
/*infos=*/{kDevice3, kIntegratedDevice, kUsbDevice});
ExpectDeviceRanking(
device_type,
/*infos=*/{kDevice3, kIntegratedDevice, kUsbDevice},
/*expected_output=*/{kUsbDevice, kIntegratedDevice, kDevice3});
// Remove USB device and select the integrated device. Integrated
// should stay below USB in the ranking because the USB device isn't
// present.
UpdateDeviceRanking(device_type,
/*preferred_device=*/kIntegratedDevice,
/*infos=*/{kDevice3, kIntegratedDevice});
ExpectDeviceRanking(
device_type,
/*infos=*/{kDeviceExtra, kDevice3, kIntegratedDevice},
/*expected_output=*/{kIntegratedDevice, kDevice3, kDeviceExtra});
// Bring back USB device. The USB device should be ranked as the most
// preferred device even without an update.
ExpectDeviceRanking(
device_type,
/*infos=*/{kDeviceExtra, kDevice3, kIntegratedDevice, kUsbDevice},
/*expected_output=*/
{kUsbDevice, kIntegratedDevice, kDevice3, kDeviceExtra});
// Include the default device.
UpdateDeviceRanking(
device_type, /*preferred_device=*/kDefaultDevice,
/*infos=*/{kUsbDevice, kIntegratedDevice, kDefaultDevice, kDevice3});
ExpectDeviceRanking(
device_type,
/*infos=*/{kUsbDevice, kIntegratedDevice, kDefaultDevice, kDevice3},
/*expected_output=*/
{kDefaultDevice, kUsbDevice, kIntegratedDevice, kDevice3});
}