File: system_live_caption_service_browsertest.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (549 lines) | stat: -rw-r--r-- 22,205 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
// Copyright 2022 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/ash/accessibility/live_caption/system_live_caption_service.h"

#include <memory>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "chrome/browser/accessibility/live_caption/live_caption_controller_factory.h"
#include "chrome/browser/ash/accessibility/live_caption/system_live_caption_service_factory.h"
#include "chrome/browser/ash/login/session/user_session_initializer.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_test_util.h"
#include "chrome/browser/speech/cros_speech_recognition_service_factory.h"
#include "chrome/browser/speech/fake_speech_recognition_service.h"
#include "chrome/browser/speech/fake_speech_recognizer.h"
#include "chrome/browser/speech/speech_recognition_client_browser_interface.h"
#include "chrome/browser/speech/speech_recognition_client_browser_interface_factory.h"
#include "chrome/browser/speech/speech_recognizer_delegate.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/live_caption/caption_bubble_controller.h"
#include "components/live_caption/live_caption_controller.h"
#include "components/live_caption/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/soda/constants.h"
#include "components/soda/soda_installer.h"
#include "content/public/test/browser_test.h"
#include "media/audio/audio_system.h"
#include "media/base/audio_parameters.h"
#include "media/base/media_switches.h"
#include "media/mojo/mojom/speech_recognition_service.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;

namespace ash {

namespace {
static constexpr int kDefaultSampleRateMs = 16000;
static constexpr int kDefaultPollingTimesHz = 10;
static constexpr char kAlternativeLiveCaptionLanguageName[] = "es-ES";
static constexpr char kDefaultLiveCaptionLanguageName[] = "en-US";
}  // namespace

// We need to swap out the device audio system for a fake one.
class MockAudioSystem : public media::AudioSystem {
 public:
  MockAudioSystem() = default;
  ~MockAudioSystem() override = default;
  MockAudioSystem(const MockAudioSystem&) = delete;
  MockAudioSystem& operator=(const MockAudioSystem&) = delete;

  // media::AudioSystem overrides:
  MOCK_METHOD2(GetInputStreamParameters,
               void(const std::string& device_id,
                    OnAudioParamsCallback callback));
  MOCK_METHOD2(GetOutputStreamParameters,
               void(const std::string& device_id,
                    OnAudioParamsCallback on_params_cb));
  MOCK_METHOD1(HasInputDevices, void(OnBoolCallback on_has_devices_cb));
  MOCK_METHOD1(HasOutputDevices, void(OnBoolCallback on_has_devices_cb));
  MOCK_METHOD2(GetDeviceDescriptions,
               void(bool for_input,
                    OnDeviceDescriptionsCallback on_descriptions_cp));
  MOCK_METHOD2(GetAssociatedOutputDeviceID,
               void(const std::string& input_device_id,
                    OnDeviceIdCallback on_device_id_cb));
  MOCK_METHOD2(GetInputDeviceInfo,
               void(const std::string& input_device_id,
                    OnInputDeviceInfoCallback on_input_device_info_cb));
};

// Creates and returns a stub audio system that reports a reasonable default for
// audio device parameters.
std::unique_ptr<media::AudioSystem> CreateStubAudioSystem() {
  const media::AudioParameters params(
      media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
      media::ChannelLayoutConfig::Stereo(), kDefaultSampleRateMs,
      kDefaultSampleRateMs / (kDefaultPollingTimesHz * 2));

  std::unique_ptr<MockAudioSystem> stub_audio_system =
      std::make_unique<MockAudioSystem>();
  EXPECT_CALL(*stub_audio_system, GetInputStreamParameters(_, _))
      .WillRepeatedly(
          [params](auto, MockAudioSystem::OnAudioParamsCallback cb) {
            std::move(cb).Run(params);
          });

  return stub_audio_system;
}

// Runs the system live caption service backed by a fake audio system and SODA
// installation.
class SystemLiveCaptionServiceTest
    : public InProcessBrowserTest,
      public speech::FakeSpeechRecognitionService::Observer {
 public:
  SystemLiveCaptionServiceTest() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kOnDeviceSpeechRecognition,
                              media::kLiveCaptionMultiLanguage},
        /*disabled_features=*/{});
  }

  ~SystemLiveCaptionServiceTest() override = default;
  SystemLiveCaptionServiceTest(const SystemLiveCaptionServiceTest&) = delete;
  SystemLiveCaptionServiceTest& operator=(const SystemLiveCaptionServiceTest&) =
      delete;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(switches::kIgnoreUserProfileMappingForTests);
  }

  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();
    primary_profile_ = browser()->profile();

    // Create an additional profile. We will verify that its caption bubble is
    // inactive, since only the primary profile should be processing system
    // audio.
    ProfileManager* profile_manager = g_browser_process->profile_manager();
    const base::FilePath profile_path =
        profile_manager->GenerateNextProfileDirectoryPath();
    secondary_profile_ =
        &profiles::testing::CreateProfileSync(profile_manager, profile_path);
    CHECK(secondary_profile_);

    // Replace our CrosSpeechRecognitionService with a fake one.
    fake_speech_recognition_service_ =
        CrosSpeechRecognitionServiceFactory::GetInstanceForTest()
            ->SetTestingSubclassFactoryAndUse(
                primary_profile_, base::BindOnce([](content::BrowserContext*) {
                  return std::make_unique<
                      speech::FakeSpeechRecognitionService>();
                }));
    fake_speech_recognition_service_->AddObserver(this);

    // Pass in an inert audio system backend.
    SystemLiveCaptionServiceFactory::GetInstance()
        ->GetForProfile(primary_profile_)
        ->set_audio_system_factory_for_testing(
            base::BindRepeating(&CreateStubAudioSystem));

    // Don't actually try to download SODA.
    speech::SodaInstaller::GetInstance()->NeverDownloadSodaForTesting();

    // Use English as our caption language.
    primary_profile_->GetPrefs()->SetString(prefs::kLiveCaptionLanguageCode,
                                            kDefaultLiveCaptionLanguageName);
  }

  const std::string GetLanguageCode() const {
    return primary_profile_->GetPrefs()->GetString(
        prefs::kLiveCaptionLanguageCode);
  }

  void SetLanguagePref(const std::string& language) {
    primary_profile_->GetPrefs()->SetString(prefs::kLiveCaptionLanguageCode,
                                            language);
    base::RunLoop().RunUntilIdle();
  }

  void SetLiveCaptionsPref(bool enabled) {
    primary_profile_->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled,
                                             enabled);
    base::RunLoop().RunUntilIdle();
  }

  void NotifyNonChromeOutputStarted() {
    SystemLiveCaptionServiceFactory::GetInstance()
        ->GetForProfile(primary_profile_)
        ->OnNonChromeOutputStarted();
    base::RunLoop().RunUntilIdle();
  }

  ::captions::CaptionBubbleController* GetCaptionBubbleController(
      Profile* profile) const {
    return ::captions::LiveCaptionControllerFactory::GetInstance()
        ->GetForProfile(profile)
        ->caption_bubble_controller_for_testing();
  }

  // Emit the given text from our fake speech recognition service.
  void EmulateRecognizedSpeech(const std::string& text) {
    ASSERT_TRUE(current_audio_fetcher_);
    current_audio_fetcher_->SendSpeechRecognitionResult(
        media::SpeechRecognitionResult(text, /*is_final=*/false));
    base::RunLoop().RunUntilIdle();
  }

  // Meet the preconditions for live captioning so that our logic-under-test
  // starts executing.
  void StartLiveCaptioning() {
    SetLiveCaptionsPref(/*enabled=*/true);
    speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(
        speech::GetLanguageCode(GetLanguageCode()));
    speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
    // Events must propagate, so we wait after install.
    base::RunLoop().RunUntilIdle();
    NotifyNonChromeOutputStarted();
    base::RunLoop().RunUntilIdle();
  }

  // FakeSpeechRecognitionService::Observer
  void OnRecognizerBound(
      speech::FakeSpeechRecognizer* bound_recognizer) override {
    const std::optional<std::string>& optional_language =
        bound_recognizer->recognition_options()->language;

    // All valid recognizers in this test will have a language.
    if (!optional_language) {
      return;
    }

    if (optional_language.value() == GetLanguageCode()) {
      current_audio_fetcher_ = bound_recognizer->GetWeakPtr();
    }
  }

  // Unowned.
  raw_ptr<Profile, DanglingUntriaged> primary_profile_;
  raw_ptr<Profile, DanglingUntriaged> secondary_profile_;

  // current_audio_fetcher_ is a speech recognizer fake that is used to assert
  // correct behavior when a session is started by the SystemLiveCaptionService.
  // When a session is started `OnRecognizerBound` is invoked which will
  // populate the current_audio_fetcher_ with the correct audio fetcher.  If
  // this pointer is null then that means that the SystemLiveCapitonService
  // has yet to start a session, so if we want to assert that a session
  // hasn't been started yet thus far in a test we can expect this
  // pointer to be null.
  base::WeakPtr<speech::FakeSpeechRecognizer> current_audio_fetcher_ = nullptr;

  raw_ptr<speech::FakeSpeechRecognitionService, DanglingUntriaged>
      fake_speech_recognition_service_;

  base::test::ScopedFeatureList scoped_feature_list_;
};

// Tests that system audio is processed only when all our preconditions are
// satisfied.
IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, Triggering) {
  // We should be waiting for the feature to be enabled and for SODA to be
  // installed.
  EXPECT_FALSE(current_audio_fetcher_);

  // Enable feature.
  SetLiveCaptionsPref(/*enabled=*/true);

  // We should still be waiting for SODA to be installed.
  EXPECT_FALSE(current_audio_fetcher_);

  // Fake successful language pack install.
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(
      speech::GetLanguageCode(GetLanguageCode()));
  base::RunLoop().RunUntilIdle();

  // We should be waiting for the base binary too.
  EXPECT_FALSE(current_audio_fetcher_);

  // Fake successful base binary install.
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
  base::RunLoop().RunUntilIdle();
  // The client should be created at this point.
  ASSERT_TRUE(current_audio_fetcher_);
  EXPECT_FALSE(current_audio_fetcher_->is_capturing_audio());

  // Start audio.
  // Set audio output running.
  NotifyNonChromeOutputStarted();

  // Should now be processing system audio.
  EXPECT_TRUE(current_audio_fetcher_->is_capturing_audio());

  // Now turn off live captioning.
  SetLiveCaptionsPref(/*enabled=*/false);

  // This should stop audio fetching.
  EXPECT_FALSE(current_audio_fetcher_);
}

// Test that feature is gated on successful SODA install.
IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, SodaError) {
  // Enable feature so that we start listening for SODA install status.
  SetLiveCaptionsPref(/*enabled=*/true);

  // Fake successful base binary install but failed language install.
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
  speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting(
      speech::GetLanguageCode(GetLanguageCode()));
  base::RunLoop().RunUntilIdle();

  // Our language is not yet installed, so we shouldn't be processing audio.
  EXPECT_FALSE(current_audio_fetcher_);
}

// Tests that our feature listens to the correct SODA language.
IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, SodaIrrelevantError) {
  // Set audio output running
  NotifyNonChromeOutputStarted();

  // Enable feature so that we start listening for SODA install status.
  SetLiveCaptionsPref(/*enabled=*/true);

  // Fake successful base binary install.
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
  base::RunLoop().RunUntilIdle();

  // Fake failed install of an unrelated language.
  speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting(
      speech::LanguageCode::kJaJp);
  base::RunLoop().RunUntilIdle();

  // Our language is not yet installed, so we shouldn't be processing audio.
  // Therefore the current_audio_fetcher_ should be null.
  EXPECT_FALSE(current_audio_fetcher_);

  // Fake successful install of our language.
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(
      speech::GetLanguageCode(GetLanguageCode()));
  base::RunLoop().RunUntilIdle();
  // Tell the caption service audio is running again. This is needed since we
  // don't actually go to a fake cras audio system in this test.
  NotifyNonChromeOutputStarted();
  // We should have ignored the unrelated error.
  ASSERT_TRUE(current_audio_fetcher_);
  EXPECT_TRUE(current_audio_fetcher_->is_capturing_audio());
}

// Test that captions are only dispatched for the primary profile.
IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, DispatchToProfile) {
  StartLiveCaptioning();

  // Capture fake audio.
  EmulateRecognizedSpeech("System audio caption");
  ASSERT_TRUE(current_audio_fetcher_);
  EXPECT_TRUE(current_audio_fetcher_->is_capturing_audio());

  // Transcribed speech should be displayed from the primary profile.
  auto* primary_bubble = GetCaptionBubbleController(primary_profile_);
  ASSERT_NE(nullptr, primary_bubble);
  EXPECT_TRUE(primary_bubble->IsWidgetVisibleForTesting());
  EXPECT_FALSE(primary_bubble->IsGenericErrorMessageVisibleForTesting());
  EXPECT_EQ("System audio caption",
            primary_bubble->GetBubbleLabelTextForTesting());

  // Transcribed speech should _not_ be shown for any other profiles.
  EXPECT_EQ(nullptr, GetCaptionBubbleController(secondary_profile_));
}

IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, StartStopStart) {
  StartLiveCaptioning();

  // Capture fake audio.
  EmulateRecognizedSpeech("System audio caption");
  ASSERT_TRUE(current_audio_fetcher_);
  EXPECT_TRUE(current_audio_fetcher_->is_capturing_audio());

  // Transcribed speech should be displayed from the primary profile.
  // The added captions are all added as non-finals, so they over-write not
  // append.
  auto* primary_bubble = GetCaptionBubbleController(primary_profile_);
  ASSERT_NE(nullptr, primary_bubble);
  EXPECT_TRUE(primary_bubble->IsWidgetVisibleForTesting());
  EXPECT_FALSE(primary_bubble->IsGenericErrorMessageVisibleForTesting());
  EXPECT_EQ("System audio caption",
            primary_bubble->GetBubbleLabelTextForTesting());

  // Stop
  SystemLiveCaptionServiceFactory::GetInstance()
      ->GetForProfile(primary_profile_)
      ->OnNonChromeOutputStopped();
  EmulateRecognizedSpeech(" more after stop ");
  EXPECT_EQ(" more after stop ",
            primary_bubble->GetBubbleLabelTextForTesting());
  // Idle stop.
  base::RunLoop().RunUntilIdle();

  // Start again.
  SystemLiveCaptionServiceFactory::GetInstance()
      ->GetForProfile(primary_profile_)
      ->OnNonChromeOutputStarted();
  EmulateRecognizedSpeech(" and yet more ");

  EXPECT_EQ(" and yet more ", primary_bubble->GetBubbleLabelTextForTesting());
  // Transcribed speech should _not_ be shown for any other profiles.
  EXPECT_EQ(nullptr, GetCaptionBubbleController(secondary_profile_));
}

// Test that we can cease transcription by closing the bubble UI.
IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, EarlyStopping) {
  StartLiveCaptioning();

  // Fake some speech.
  EmulateRecognizedSpeech("System audio caption");

  // Bubble UI should be active to show transcribed speech.
  auto* primary_bubble = GetCaptionBubbleController(primary_profile_);
  ASSERT_NE(nullptr, primary_bubble);

  // Emulate closing bubble UI.
  primary_bubble->CloseActiveModelForTesting();

  // Fake detection of more speech, to which the bubble should respond by
  // requesting an early stop.
  EmulateRecognizedSpeech("More system audio captions");

  // The speech recognition service should have received the early stop request.
  // The client will be deleted.
  ASSERT_FALSE(current_audio_fetcher_);
}

// Test that the UI is closed when transcription is complete.
IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, EndOfStream) {
  StartLiveCaptioning();
  ASSERT_TRUE(current_audio_fetcher_);

  // Fake some speech.
  EmulateRecognizedSpeech("System audio caption");

  // Bubble UI should be active to show transcribed speech.
  auto* primary_bubble = GetCaptionBubbleController(primary_profile_);
  ASSERT_NE(nullptr, primary_bubble);
  EXPECT_TRUE(primary_bubble->IsWidgetVisibleForTesting());

  // Emulate end of audio stream.
  current_audio_fetcher_->MarkDone();
  base::RunLoop().RunUntilIdle();

  // Bubble should not be shown since there is no more audio.
  primary_bubble = GetCaptionBubbleController(primary_profile_);
  ASSERT_NE(nullptr, primary_bubble);
  EXPECT_FALSE(primary_bubble->IsWidgetVisibleForTesting());
}

// Test that an error message is shown if something goes wrong.
IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, ServiceError) {
  StartLiveCaptioning();
  ASSERT_TRUE(current_audio_fetcher_);

  // Fake some speech.
  EmulateRecognizedSpeech("System audio caption");

  // Bubble UI should be active to show transcribed speech.
  auto* primary_bubble = GetCaptionBubbleController(primary_profile_);
  ASSERT_NE(nullptr, primary_bubble);
  EXPECT_TRUE(primary_bubble->IsWidgetVisibleForTesting());
  EXPECT_FALSE(primary_bubble->IsGenericErrorMessageVisibleForTesting());

  // Emulate recognition error.
  current_audio_fetcher_->SendSpeechRecognitionError();
  base::RunLoop().RunUntilIdle();

  // Bubble should still be shown and should display error text.
  primary_bubble = GetCaptionBubbleController(primary_profile_);
  ASSERT_NE(nullptr, primary_bubble);
  EXPECT_TRUE(primary_bubble->IsWidgetVisibleForTesting());
  EXPECT_TRUE(primary_bubble->IsGenericErrorMessageVisibleForTesting());
}

// Tests that the System Live Caption Service uses the correct language as set
// by the kLiveCaptionLanguageCode preference.
IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, UsesCorrectLanguage) {
  SetLanguagePref(kAlternativeLiveCaptionLanguageName);
  StartLiveCaptioning();
  ASSERT_TRUE(current_audio_fetcher_);

  // retrieve the recognition options struct passed to the recognition service.
  // we use this to assert that the correct language was passed to the service.
  const media::mojom::SpeechRecognitionOptions* recognition_options =
      current_audio_fetcher_->recognition_options();

  // Should now be processing system audio.
  EXPECT_TRUE(current_audio_fetcher_->is_capturing_audio());

  // Assert language is correct.
  ASSERT_NE(recognition_options, nullptr);
  EXPECT_EQ(std::string(kAlternativeLiveCaptionLanguageName),
            recognition_options->language.value());
}

// When a language changes in the middle of a session the service must switch
// out the speech recognition client for a new one with the selected language.
// This tests that while there are non chrome outputs running that the session
// restarts automatically.
IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest,
                       SwitchesLanguageCorrectly) {
  StartLiveCaptioning();
  ASSERT_TRUE(current_audio_fetcher_);

  // retrieve the recognition options struct passed to the recognition service.
  // we use this to assert that the correct language was passed to the service.
  const media::mojom::SpeechRecognitionOptions* recognition_options =
      current_audio_fetcher_->recognition_options();

  // Should now be processing system audio.
  EXPECT_TRUE(current_audio_fetcher_->is_capturing_audio());

  // Assert language is correct.
  ASSERT_NE(recognition_options, nullptr);
  ASSERT_TRUE(recognition_options->language.has_value());
  EXPECT_EQ(std::string(kDefaultLiveCaptionLanguageName),
            recognition_options->language.value());

  // This should restart the recognizer with the correct language.  The
  // language pack will be installed by the live caption controller and then
  // the SODA Installer will notify the SystemLiveCaptionService.
  SetLanguagePref(kAlternativeLiveCaptionLanguageName);

  // For this test case we want to switch while output is running so that we
  // restart the session without explicitly calling OnNonChromeOutputStarted.
  SystemLiveCaptionServiceFactory::GetInstance()
      ->GetForProfile(primary_profile_)
      ->set_num_non_chrome_output_streams_for_testing(
          /*num_output_streams=*/1);

  // Until SODA installs we should do nothing.  The Client will be created at
  // this point so we can assert that the current audio fetcher is not capturing
  // audio.
  EXPECT_FALSE(current_audio_fetcher_);

  // Emulate successful SODA installation from LiveCaptionController.
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(
      speech::GetLanguageCode(kAlternativeLiveCaptionLanguageName));
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(current_audio_fetcher_);
  EXPECT_TRUE(current_audio_fetcher_->is_capturing_audio());

  // We destroy the old options struct when resetting the speech recogntion
  // client.
  recognition_options = current_audio_fetcher_->recognition_options();

  ASSERT_NE(recognition_options, nullptr);
  ASSERT_TRUE(recognition_options->language.has_value());
  EXPECT_EQ(std::string(kAlternativeLiveCaptionLanguageName),
            recognition_options->language.value());
}

}  // namespace ash