halkyon/packages/halkyon_app/test/speech/voice_input_app_test.dart
Tizian 「ティツィアーン」 c36bd83c6a
feat(learn,todo,meditate): dictate through the phone's voice input app (ADR 0038, D18)
On Android the microphone now opens the app handling
RECOGNIZE_SPEECH (FUTO Voice Input, Google's), the default the person
chose in Android, and puts the text it returns where the cursor stood,
as Dictation does; cancelled, nothing. Only where none is installed, or
it can't be opened, does speech_to_text listen in line as before.

halkyon_app: VoiceInputApp (one listen, an answer once closed) on the
channel app.halkyon/voice_input, which DeviceSpeechInput asks first at
every listen, answering SpeechStart.finished so that Dictation doesn't
listen again; speech_to_text isn't touched on that route, so our app
asks for no microphone. SpeechInput.listen and Dictation.start take the
prompt the voice input app shows (the field's hint).

Each app: VoiceInput.kt (queryIntentActivities, startActivityForResult,
the first of EXTRA_RESULTS) attached in MainActivity, which hands it
activity results, and RECOGNIZE_SPEECH among the manifest's queries.
The privacy pages of Learn and Meditate say so.

Our own Whisper and Meditate's dream recordings are dropped (D18).

Whats-New: The microphone now dictates through your phone's voice input app, such as FUTO Voice Input.
Whats-New-De: Das Mikrofon diktiert jetzt über die Spracheingabe-App deines Telefons, etwa FUTO Voice Input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 11:48:42 +01:00

98 lines
3.3 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_app/halkyon_app.dart';
/// The device's voice input app through Android's `RECOGNIZE_SPEECH`
/// intent (Halkyon Meditate's ADR 0038, D18), asked through each app's
/// `VoiceInput.kt` on `app.halkyon/voice_input`, its channel answering as
/// the activity does.
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late List<MethodCall> calls;
void answer(Object? Function(MethodCall call) answers) {
calls = [];
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(ChannelVoiceInputApp.channel, (call) async {
calls.add(call);
return answers(call);
});
}
tearDown(() {
debugDefaultTargetPlatformOverride = null;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(ChannelVoiceInputApp.channel, null);
});
const app = ChannelVoiceInputApp();
group('installed', () {
test('where Android has an app for the intent', () async {
answer((call) => call.method == 'installed');
expect(await app.installed(), isTrue);
});
test('not where it has none', () async {
answer((call) => false);
expect(await app.installed(), isFalse);
});
test('not where the activity fails or knows no such channel', () async {
answer((call) => throw PlatformException(code: 'failed'));
expect(await app.installed(), isFalse);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(ChannelVoiceInputApp.channel, null);
expect(await app.installed(), isFalse);
});
test('never off Android, without asking', () async {
answer((call) => true);
for (final platform in [
TargetPlatform.iOS,
TargetPlatform.linux,
TargetPlatform.macOS,
TargetPlatform.windows,
]) {
debugDefaultTargetPlatformOverride = platform;
expect(await app.installed(), isFalse, reason: '$platform');
}
expect(calls, isEmpty);
});
});
group('listening', () {
test('asks in the language given, with the prompt, and hands back the '
'words', () async {
answer((call) => 'a house by the sea');
expect(
await app.listen(language: 'de-DE', prompt: 'Was hast du geträumt?'),
'a house by the sea',
);
final listen = calls.single;
expect(listen.method, 'listen');
expect(listen.arguments, {
'language': 'de-DE',
'prompt': 'Was hast du geträumt?',
});
});
test('cancelled, hands back nothing', () async {
answer((call) => '');
expect(await app.listen(language: null), '');
expect(calls.single.arguments, {'language': null, 'prompt': null});
});
test(
'where the app could not be opened, says so without throwing',
() async {
answer((call) => throw PlatformException(code: 'noApp'));
expect(await app.listen(language: 'en'), isNull);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(ChannelVoiceInputApp.channel, null);
expect(await app.listen(language: 'en'), isNull);
},
);
});
}