I am trying out Microsoft .NET MAUI that currently in Preview stage.
I try to make a small Android app that will use Google voice recognizer service as a way to let user navigate the app. Just a small demo to see what can I do with it. This is also my first time to actually write a Xamarin/MAUI project, so I am not really sure what I can actually do wit the platform.
The problem is that I would like to have this Google service to always on (without timeout) or auto-close then re-open when timeout. In short, I want user to never actually have to deal with this screen:
My intention is that the will be a background thread to keep asking user to say the command, only stop when user do, and the service will always ready to receive the speech.
However, I am unable to keep the above service always on or auto-close=>reopen when timeout.
I am search around and it seems that I cannot change the timeout of the service, so the only way is trying to auto-close=>reopen the service, but I don't know how to.
The below is my code, could you guy give me some direction with it?
1. The login page: only have username and password field, use will be asked to say the username. If it is exist, then asked to say password.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiDemo.LoginPage"
BackgroundColor="White">
<ContentPage.Content>
<StackLayout Margin="30" VerticalOptions="StartAndExpand">
<Label
x:Name="lblTitle"
HorizontalTextAlignment="Center"
FontSize="Large"
FontAttributes="Bold"
/>
<Label/>
<Button
x:Name="btnSpeak"
Text="Start"
Clicked="btnSpeak_Clicked"
FontAttributes="Bold"
BackgroundColor="DarkGreen"
/>
<Label/>
<Label
x:Name="lblUsername"
Text="Username"
FontAttributes="Bold"
/>
<Entry
x:Name="txtUsername"
TextColor="Black"
FontSize="18"
VerticalOptions="StartAndExpand"
HorizontalOptions="Fill"
IsReadOnly="True"
/>
<Label/>
<Label
x:Name="lblPassword"
Text="Password"
FontAttributes="Bold"
/>
<Entry
x:Name="txtPassword"
IsPassword="True"
TextColor="Black"
FontSize="18"
VerticalOptions="StartAndExpand"
HorizontalOptions="Fill"
IsReadOnly="True"
/>
<Label/>
<Label
x:Name="lblDisplayname"
Text="Name"
FontAttributes="Bold"
/>
<Label
x:Name="txtDisplayname"
/>
<Label/>
<Label
x:Name="lblMessage"
Text=""/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
using MauiDemo.Common;
using MauiDemo.Speech;
using Microsoft.Maui.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MauiDemo
{
public partial class LoginPage : ContentPage
{
private string _field = string.Empty;
private int _waitTime = 2000;
public List<Language> Languages { get; }
private SpeechToTextImplementation _speechRecongnitionInstance;
private struct VoiceMode
{
int Username = 1;
int Password = 2;
}
public LoginPage()
{
InitializeComponent();
this.lblTitle.Text = "Login" + App.Status;
CheckMicrophone();
CommonData.CurrentField = string.Empty;
try
{
_speechRecongnitionInstance = new SpeechToTextImplementation();
_speechRecongnitionInstance.Language = DefaultData.SettingLanguage;
}
catch (Exception ex)
{
DisplayAlert("Error", ex.Message, "OK");
}
MessagingCenter.Subscribe<ISpeechToText, string>(this, "STT", (sender, args) =>
{
ReceivedUsernameAsync(args);
});
MessagingCenter.Subscribe<ISpeechToText>(this, "Final", (sender) =>
{
btnSpeak.IsEnabled = true;
});
MessagingCenter.Subscribe<IMessageSender, string>(this, "STT", (sender, args) =>
{
SpeechToTextRecievedAsync(args);
});
isReceiveUsername = false;
isReceivePassword = false;
RequestUsername();
}
protected override void OnDisappearing()
{
CommonData.CurrentField = string.Empty;
base.OnDisappearing();
}
private async void btnSpeak_Clicked(Object sender, EventArgs e)
{
isReceiveUsername = false;
isReceivePassword = false;
await RequestUsername();
}
private async void SpeechToTextRecievedAsync(string args)
{
switch (_field)
{
case "Username":
await this.ReceivedUsernameAsync(args);
break;
case "Password":
await this.ReceivedPasswordAsync(args);
break;
}
}
bool isReceiveUsername = false;
bool isReceivePassword = false;
private async Task ReceivedUsernameAsync(string args)
{
txtUsername.Text = args.Replace(" ", string.Empty);
lblMessage.Text = string.Empty;
if (string.IsNullOrWhiteSpace(txtUsername.Text))
{
isReceiveUsername = false;
}
else
{
isReceiveUsername = true;
var checkUser = DefaultData.Users.Where(x => x.Username.ToLower().Equals(txtUsername.Text.ToLower()));
if (checkUser.Any())
{
await RequestPassword();
}
else
{
string message = CommonData.GetMessage(MessageCode.WrongUsername);
lblMessage.Text = message;
isReceiveUsername = false;
await RequestUsername(message);
}
}
}
private async Task ReceivedPasswordAsync(string args)
{
txtPassword.Text = args.Replace(" ", string.Empty);
lblMessage.Text = string.Empty;
if (string.IsNullOrWhiteSpace(txtPassword.Text))
{
isReceivePassword = false;
}
else
{
isReceivePassword = true;
var checkUser = DefaultData.Users.Where(x => x.Username.ToLower().Equals(txtUsername.Text.ToLower()) && x.Password.Equals(txtPassword.Text));
if (checkUser.Any())
{
_field = "";
lblDisplayname.Text = checkUser.FirstOrDefault().Displayname;
string msg = CommonData.GetMessage(MessageCode.LoginSuccess);
await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
msg
, crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
, speakRate: DefaultData.SettingSpeed
, pitch: DefaultData.SettingPitch
);
await Navigation.PushAsync(new MainPage());
}
else
{
string message = CommonData.GetMessage(MessageCode.WrongPassword);
lblMessage.Text = message;
isReceivePassword = false;
await RequestPassword(message);
}
}
}
private async Task RepeatVoiceUsername(string message)
{
do
{
//_speechRecongnitionInstance.StopSpeechToText();
//_speechRecongnitionInstance.StartSpeechToText();
await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
message
, crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
, speakRate: DefaultData.SettingSpeed
, pitch: DefaultData.SettingPitch
);
Thread.Sleep(_waitTime);
}
while (!isReceiveUsername);
}
private async Task RepeatVoicePassword(string message)
{
do
{
//_speechRecongnitionInstance.StopSpeechToText();
//_speechRecongnitionInstance.StartSpeechToText();
await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
message
, crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
, speakRate: DefaultData.SettingSpeed
, pitch: DefaultData.SettingPitch
);
Thread.Sleep(_waitTime);
}
while (!isReceivePassword);
}
private bool CheckMicrophone()
{
string rec = Android.Content.PM.PackageManager.FeatureMicrophone;
if (rec != "android.hardware.microphone")
{
// no microphone, no recording. Disable the button and output an alert
DisplayAlert("Error", CommonData.GetMessage(MessageCode.SettingSaveSuccess), "OK");
btnSpeak.IsEnabled = false;
return false;
}
return true;
}
private async Task RequestUsername(string message = "")
{
_field = "Username";
isReceiveUsername = false;
txtUsername.Text = string.Empty;
lblDisplayname.Text = string.Empty;
txtUsername.Focus();
message = (message.IsNullOrWhiteSpace() ? CommonData.GetMessage(MessageCode.InputUsername) : message);
Task.Run(() => RepeatVoiceUsername(message));
_speechRecongnitionInstance.StartSpeechToText(_field);
}
private async Task RequestPassword(string message = "")
{
_field = "Password";
isReceivePassword = false;
txtPassword.Text = string.Empty;
lblDisplayname.Text = string.Empty;
txtPassword.Focus();
message = (message.IsNullOrWhiteSpace() ? CommonData.GetMessage(MessageCode.InputPassword) : message);
Task.Run(() => RepeatVoicePassword(message));
_speechRecongnitionInstance.StartSpeechToText(_field);
}
}
}
2. The Speech Recognizer class:
using Android.App;
using Android.Content;
using Android.Speech;
using Java.Util;
using Plugin.CurrentActivity;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MauiDemo.Speech
{
public class SpeechToTextImplementation
{
public static AutoResetEvent autoEvent = new AutoResetEvent(false);
private readonly int VOICE = 10;
private Activity _activity;
private float _timeOut = 3;
private string _text;
public SpeechToTextImplementation()
{
_activity = CrossCurrentActivity.Current.Activity;
}
public SpeechToTextImplementation(string text)
{
_text = text;
_activity = CrossCurrentActivity.Current.Activity;
}
public string Language;
public void StartSpeechToText()
{
StartRecordingAndRecognizing();
}
public void StartSpeechToText(string text)
{
_text = text;
StartRecordingAndRecognizing();
}
private async void StartRecordingAndRecognizing()
{
string rec = global::Android.Content.PM.PackageManager.FeatureMicrophone;
if (rec == "android.hardware.microphone")
{
try
{
var locale = Locale.Default;
if (!string.IsNullOrWhiteSpace(Language))
{
locale = new Locale(Language);
}
Intent voiceIntent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
voiceIntent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);
voiceIntent.PutExtra(RecognizerIntent.ExtraPrompt, _text);
voiceIntent.PutExtra(RecognizerIntent.ExtraSpeechInputCompleteSilenceLengthMillis, _timeOut * 1000);
voiceIntent.PutExtra(RecognizerIntent.ExtraSpeechInputPossiblyCompleteSilenceLengthMillis, _timeOut * 1000);
voiceIntent.PutExtra(RecognizerIntent.ExtraSpeechInputMinimumLengthMillis, _timeOut * 1000);
voiceIntent.PutExtra(RecognizerIntent.ExtraMaxResults, 1);
voiceIntent.PutExtra(RecognizerIntent.ExtraLanguage, locale.ToString());
_activity.StartActivityForResult(voiceIntent, VOICE);
await Task.Run(() => { autoEvent.WaitOne(new TimeSpan(0, 2, 0)); });
}
catch (ActivityNotFoundException ex)
{
String appPackageName = "com.google.android.googlequicksearchbox";
try
{
Intent intent = new Intent(Intent.ActionView, global::Android.Net.Uri.Parse("market://details?id=" + appPackageName));
_activity.StartActivityForResult(intent, VOICE);
}
catch (ActivityNotFoundException e)
{
Intent intent = new Intent(Intent.ActionView, global::Android.Net.Uri.Parse("https://play.google.com/store/apps/details?id=" + appPackageName));
_activity.StartActivityForResult(intent, VOICE);
}
}
}
else
{
throw new Exception("No mic found");
}
}
public void StopSpeechToText()
{
// Do something here to close the service
}
}
}
3. MainActivity:
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Speech;
using MauiDemo.Common;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
namespace MauiDemo
{
[Activity(Label = "Maui Demo", Theme = "#style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
public class MainActivity : MauiAppCompatActivity, IMessageSender
{
private readonly int VOICE = 10;
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (requestCode == VOICE)
{
if (resultCode == Result.Ok)
{
var matches = data.GetStringArrayListExtra(RecognizerIntent.ExtraResults);
if (matches.Count != 0)
{
string textInput = matches[0];
MessagingCenter.Send<IMessageSender, string>(this, "STT", textInput);
}
else
{
MessagingCenter.Send<IMessageSender, string>(this, "STT", "");
}
}
}
base.OnActivityResult(requestCode, resultCode, data);
}
}
}
4. MainApplication
using Android.App;
using Android.OS;
using Android.Runtime;
using Microsoft.Maui;
using Microsoft.Maui.Hosting;
using Plugin.CurrentActivity;
using System;
namespace MauiDemo
{
[Application]
public class MainApplication : MauiApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
public override void OnCreate()
{
base.OnCreate();
CrossCurrentActivity.Current.Init(this);
}
public override void OnTerminate()
{
base.OnTerminate();
}
public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
{
CrossCurrentActivity.Current.Activity = activity;
}
public void OnActivityDestroyed(Activity activity)
{
}
public void OnActivityPaused(Activity activity)
{
}
public void OnActivityResumed(Activity activity)
{
CrossCurrentActivity.Current.Activity = activity;
}
public void OnActivitySaveInstanceState(Activity activity, Bundle outState)
{
}
public void OnActivityStarted(Activity activity)
{
CrossCurrentActivity.Current.Activity = activity;
}
public void OnActivityStopped(Activity activity)
{
}
}
}
After struggle for a few days without any success, I found a new way to do this thing by using SpeechRecognizer class, instead of using a Google service. With this, I am able to have a better control on the process.
To use SpeechRecognizer, I copied the code in "Create platform microphone services" for permission from this Microsoft page: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/azure-cognitive-services/speech-recognition
I have update my code as below:
Login page: currently is named Prototype2.
using MauiDemo.Common;
using MauiDemo.Speech;
using Microsoft.Maui.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MauiDemo.View
{
public partial class Prototype2 : ContentPage
{
private string _field = string.Empty;
private int _waitTime = 2000;
public List<Language> Languages { get; }
private SpeechToTextImplementation2 _speechRecognizer;
//private BackgroundWorker worker = new BackgroundWorker();
private struct VoiceMode
{
int Username = 1;
int Password = 2;
}
public Prototype2()
{
InitializeComponent();
this.lblTitle.Text = "Prototype2" + App.Status;
CheckMicrophone();
CommonData.CurrentField = string.Empty;
try
{
_speechRecognizer = new SpeechToTextImplementation2();
_speechRecognizer.Language = DefaultData.SettingLanguage;
}
catch (Exception ex)
{
DisplayAlert("Error", ex.Message, "OK");
}
MessagingCenter.Subscribe<ISpeechToText, string>(this, "STT", (sender, args) =>
{
ReceivedUsernameAsync(args);
});
MessagingCenter.Subscribe<ISpeechToText>(this, "Final", (sender) =>
{
btnSpeak.IsEnabled = true;
});
MessagingCenter.Subscribe<IMessageSender, string>(this, "STT", (sender, args) =>
{
SpeechToTextRecievedAsync(args);
});
isReceiveUsername = false;
isReceivePassword = false;
RequestUsername(true);
}
protected override void OnDisappearing()
{
CommonData.CurrentField = string.Empty;
base.OnDisappearing();
}
private async void btnSpeak_Clicked(Object sender, EventArgs e)
{
isReceiveUsername = false;
isReceivePassword = false;
await RequestUsername(true);
}
private async void SpeechToTextRecievedAsync(string args)
{
switch (_field)
{
case "Username":
await this.ReceivedUsernameAsync(args);
break;
case "Password":
await this.ReceivedPasswordAsync(args);
break;
}
}
bool isReceiveUsername = false;
bool isReceivePassword = false;
private async Task ReceivedUsernameAsync(string args)
{
txtUsername.Text = args.Replace(" ", string.Empty);
lblMessage.Text = string.Empty;
if (string.IsNullOrWhiteSpace(txtUsername.Text))
{
isReceiveUsername = false;
}
else
{
isReceiveUsername = true;
var checkUser = DefaultData.Users.Where(x => x.Username.ToLower().Equals(txtUsername.Text.ToLower()));
if (checkUser.Any())
{
await RequestPassword(true);
}
else
{
string message = CommonData.GetMessage(MessageCode.WrongUsername);
lblMessage.Text = message;
isReceiveUsername = false;
await RequestUsername(false, message);
}
}
}
private async Task ReceivedPasswordAsync(string args)
{
txtPassword.Text = args.Replace(" ", string.Empty);
lblMessage.Text = string.Empty;
if (string.IsNullOrWhiteSpace(txtPassword.Text))
{
isReceivePassword = false;
}
else
{
isReceivePassword = true;
var checkUser = DefaultData.Users.Where(x => x.Username.ToLower().Equals(txtUsername.Text.ToLower()) && x.Password.Equals(txtPassword.Text));
if (checkUser.Any())
{
_field = "";
lblDisplayname.Text = checkUser.FirstOrDefault().Displayname;
string msg = CommonData.GetMessage(MessageCode.LoginSuccess);
await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
msg
, crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
, speakRate: DefaultData.SettingSpeed
, pitch: DefaultData.SettingPitch
);
await Navigation.PushAsync(new MainPage());
}
else
{
string message = CommonData.GetMessage(MessageCode.WrongPassword);
lblMessage.Text = message;
isReceivePassword = false;
await RequestPassword(false, message);
}
}
}
private async Task RepeatVoiceUsername(string message)
{
do
{
await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
message
, crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
, speakRate: DefaultData.SettingSpeed
, pitch: DefaultData.SettingPitch
);
Thread.Sleep(_waitTime);
}
while (!isReceiveUsername);
}
private async Task RepeatVoicePassword(string message)
{
do
{
await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
message
, crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
, speakRate: DefaultData.SettingSpeed
, pitch: DefaultData.SettingPitch
);
Thread.Sleep(_waitTime);
}
while (!isReceivePassword);
}
private bool CheckMicrophone()
{
string rec = Android.Content.PM.PackageManager.FeatureMicrophone;
if (rec != "android.hardware.microphone")
{
// no microphone, no recording. Disable the button and output an alert
DisplayAlert("Error", CommonData.GetMessage(MessageCode.SettingSaveSuccess), "OK");
btnSpeak.IsEnabled = false;
return false;
}
return true;
}
private async Task RequestUsername(bool isRepeat, string message = "")
{
_field = "Username";
isReceiveUsername = false;
//txtUsername.Text = string.Empty;
//lblDisplayname.Text = string.Empty;
txtUsername.Focus();
message = (message.IsNullOrWhiteSpace() ? CommonData.GetMessage(MessageCode.InputUsername) : message);
if (isRepeat)
{
Task.Run(() => RepeatVoiceUsername(message));
}
else
{
await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
message
, crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
, speakRate: DefaultData.SettingSpeed
, pitch: DefaultData.SettingPitch
);
}
_speechRecognizer.StartListening();
}
private async Task RequestPassword(bool isRepeat, string message = "")
{
_field = "Password";
isReceivePassword = false;
//txtPassword.Text = string.Empty;
//lblDisplayname.Text = string.Empty;
txtPassword.Focus();
message = (message.IsNullOrWhiteSpace() ? CommonData.GetMessage(MessageCode.InputPassword) : message);
if (isRepeat)
{
Task.Run(() => RepeatVoicePassword(message));
}
else
{
await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
message
, crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
, speakRate: DefaultData.SettingSpeed
, pitch: DefaultData.SettingPitch
);
}
_speechRecognizer.StartListening();
}
}
}
New Microphone Service to handle the permission
using Android.App;
using Android.Content.PM;
using Android.OS;
using AndroidX.Core.App;
using Google.Android.Material.Snackbar;
using System.Threading.Tasks;
namespace MauiDemo.Speech
{
public class MicrophoneService
{
public const int RecordAudioPermissionCode = 1;
private TaskCompletionSource<bool> tcsPermissions;
string[] permissions = new string[] { Manifest.Permission.RecordAudio };
public MicrophoneService()
{
tcsPermissions = new TaskCompletionSource<bool>();
}
public Task<bool> GetPermissionAsync()
{
if ((int)Build.VERSION.SdkInt < 23)
{
tcsPermissions.TrySetResult(true);
}
else
{
var currentActivity = MainActivity.Instance;
if (ActivityCompat.CheckSelfPermission(currentActivity, Manifest.Permission.RecordAudio) != (int)Permission.Granted)
{
RequestMicPermissions();
}
else
{
tcsPermissions.TrySetResult(true);
}
}
return tcsPermissions.Task;
}
public void OnRequestPermissionResult(bool isGranted)
{
tcsPermissions.TrySetResult(isGranted);
}
void RequestMicPermissions()
{
if (ActivityCompat.ShouldShowRequestPermissionRationale(MainActivity.Instance, Manifest.Permission.RecordAudio))
{
Snackbar.Make(MainActivity.Instance.FindViewById(Android.Resource.Id.Content),
"Microphone permissions are required for speech transcription!",
Snackbar.LengthIndefinite)
.SetAction("Ok", v =>
{
((Activity)MainActivity.Instance).RequestPermissions(permissions, RecordAudioPermissionCode);
})
.Show();
}
else
{
ActivityCompat.RequestPermissions((Activity)MainActivity.Instance, permissions, RecordAudioPermissionCode);
}
}
}
}
New Speech=>Text class to use SpeechRecognizer: Mostly take frrm this How to increase the voice listen time in Google Recognizer Intent(Speech Recognition) Android
using Android;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Speech;
using AndroidX.Core.App;
using Java.Util;
using MauiDemo.Common;
using Microsoft.Maui.Controls;
using Plugin.CurrentActivity;
using System.Threading;
namespace MauiDemo.Speech
{
public class SpeechToTextImplementation2 : Java.Lang.Object, IRecognitionListener, IMessageSender
{
public static AutoResetEvent autoEvent = new AutoResetEvent(false);
private readonly int VOICE = 10;
private Activity _activity;
private float _timeOut = 3;
private SpeechRecognizer _speech;
private Intent _speechIntent;
public string Words;
public string Language;
private MicrophoneService micService;
public SpeechToTextImplementation2()
{
micService = new MicrophoneService();
_activity = CrossCurrentActivity.Current.Activity;
var locale = Locale.Default;
if (!string.IsNullOrWhiteSpace(Language))
{
locale = new Locale(Language);
}
_speech = SpeechRecognizer.CreateSpeechRecognizer(this._activity);
_speech.SetRecognitionListener(this);
_speechIntent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
_speechIntent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);
_speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputCompleteSilenceLengthMillis, _timeOut * 1000);
_speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputPossiblyCompleteSilenceLengthMillis, _timeOut * 1000);
_speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputMinimumLengthMillis, _timeOut * 1000);
_speechIntent.PutExtra(RecognizerIntent.ExtraMaxResults, 1);
_speechIntent.PutExtra(RecognizerIntent.ExtraLanguage, locale.ToString());
}
void RestartListening()
{
var locale = Locale.Default;
if (!string.IsNullOrWhiteSpace(Language))
{
locale = new Locale(Language);
}
_speech.Destroy();
_speech = SpeechRecognizer.CreateSpeechRecognizer(this._activity);
_speech.SetRecognitionListener(this);
_speechIntent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
_speechIntent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);
_speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputCompleteSilenceLengthMillis, _timeOut * 1000);
_speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputPossiblyCompleteSilenceLengthMillis, _timeOut * 1000);
_speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputMinimumLengthMillis, _timeOut * 1000);
_speechIntent.PutExtra(RecognizerIntent.ExtraMaxResults, 1);
_speechIntent.PutExtra(RecognizerIntent.ExtraLanguage, locale.ToString());
StartListening();
}
public async void StartListening()
{
bool isMicEnabled = await micService.GetPermissionAsync();
if (!isMicEnabled)
{
Words = "Please grant access to the microphone!";
return;
}
_speech.StartListening(_speechIntent);
}
public void StopListening()
{
_speech.StopListening();
}
public void OnBeginningOfSpeech()
{
}
public void OnBufferReceived(byte[] buffer)
{
}
public void OnEndOfSpeech()
{
}
public void OnError([GeneratedEnum] SpeechRecognizerError error)
{
Words = error.ToString();
MessagingCenter.Send<IMessageSender, string>(this, "Error", Words);
RestartListening();
}
public void OnEvent(int eventType, Bundle #params)
{
}
public void OnPartialResults(Bundle partialResults)
{
}
public void OnReadyForSpeech(Bundle #params)
{
}
public void OnResults(Bundle results)
{
var matches = results.GetStringArrayList(SpeechRecognizer.ResultsRecognition);
if (matches == null)
Words = "Null";
else
if (matches.Count != 0)
Words = matches[0];
else
Words = "";
MessagingCenter.Send<IMessageSender, string>(this, "STT", Words);
RestartListening();
}
public void OnRmsChanged(float rmsdB)
{
}
}
}
Update MainActivities for permission
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using Android.Speech;
using MauiDemo.Common;
using MauiDemo.Speech;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
namespace MauiDemo
{
[Activity(Label = "Maui Demo", Theme = "#style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
public class MainActivity : MauiAppCompatActivity, IMessageSender
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Instance = this;
micService = new MicrophoneService();
}
private readonly int VOICE = 10;
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (requestCode == VOICE)
{
if (resultCode == Result.Ok)
{
var matches = data.GetStringArrayListExtra(RecognizerIntent.ExtraResults);
if (matches.Count != 0)
{
string textInput = matches[0];
MessagingCenter.Send<IMessageSender, string>(this, "STT", textInput);
}
else
{
MessagingCenter.Send<IMessageSender, string>(this, "STT", "");
}
//SpeechToTextImplementation.autoEvent.Set();
}
}
base.OnActivityResult(requestCode, resultCode, data);
}
MicrophoneService micService;
internal static MainActivity Instance { get; private set; }
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
// ...
switch (requestCode)
{
case MicrophoneService.RecordAudioPermissionCode:
if (grantResults[0] == Permission.Granted)
{
micService.OnRequestPermissionResult(true);
}
else
{
micService.OnRequestPermissionResult(false);
}
break;
}
}
}
}
Feel free to check out the code, but I will not use this for anything serious because it does not runs properly yet.
Any opinion to improve for the code will be really appreciated, as I really want to get good with this MAUI platform.
Related
I'm trying to run some code in a new thread because I'm noticing a slowness on my device. It is compiling ok but at the app starting, it freezes and terminates with the message (MyApp is presenting errors constantly).
What I'm doing wrong?
using System.Threading.Tasks;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace MyApp
{
public partial class App : Application
{
public App()
{
InitializeComponent();
}
protected override void OnStart()
{
new System.Threading.Thread(new System.Threading.ThreadStart(() => {
TestaLogin();
})).Start();
}
private void TestaLogin()
{
try
{
Context mContext = Android.App.Application.Context;
ISharedPreferences pref = PreferenceManager.GetDefaultSharedPreferences(mContext);
string uid = pref.GetString("uid", "0");
string token = pref.GetString("token", "0");
if (uid == "0" || token == "0")
{
Device.BeginInvokeOnMainThread(() => { MainPage = new Login(); });
}
else
{
if (Logar(uid, token))
Device.BeginInvokeOnMainThread(() => { MainPage = new MainPage(uid); });
else
Device.BeginInvokeOnMainThread(() => { MainPage = new Login(); });
}
}
catch (Exception e)
{
throw e;
}
}
private bool Logar(string user, string pass)
{
try
{
using (WebClient client = new WebClient())
{
return client.DownloadString("https://www.example.com/mobile/login.php?u=" + user + "&t=" + pass) == "0" ? false : true;
}
}
catch
{
return false;
}
}
}
}
I also wanted to know if lines like this has to be inside the MainThread, I thought that because it's handling changing the page:
Device.BeginInvokeOnMainThread(() => { MainPage = new MainPage(uid); });
I coded an Android widget which works perfectly fine during debugging, but after the release, it seems like the AppWidget.cs-Program doesn't get called because I can only see what I created in the Layout.
This program is made for activating or deactivating a lamp which reacts when the program calls the site, for example: "http://192.168.0.52/ein" activates the lamp, "http://192.168.0.52/aus" deactivates the lamp, and "http://192.168.0.52/state" gets the current status ("on" or "off") of the lamp. Thank you in advance!
AppWidget.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using Android.App;
using Android.Appwidget;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
namespace OrigWidget
{
[BroadcastReceiver(Label = "IoT-Lampe")]
[IntentFilter(new string[] { "android.appwidget.action.APPWIDGET_UPDATE" })]
[MetaData("android.appwidget.provider", Resource = "#xml/appwidgetprovider")]
public class AppWidget : AppWidgetProvider
{
WebClient client;
RemoteViews widgetView;
AppWidgetManager awm;
Context c;
int[] aWid;
public override void OnUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
awm = appWidgetManager;
c = context;
aWid = appWidgetIds;
System.Threading.Timer t = new System.Threading.Timer(upd, null, 0, 5000);
}
private void upd(Object o)
{
var me = new ComponentName(c, Java.Lang.Class.FromType(typeof(AppWidget)).Name);
awm.UpdateAppWidget(me, BuildRemoteViews(c, aWid));
}
private RemoteViews BuildRemoteViews(Context context, int[] appWidgetIds)
{
widgetView = new RemoteViews(context.PackageName, Resource.Layout.Widget);
SetTextViewText(widgetView);
RegisterClicks(context, appWidgetIds, widgetView);
return widgetView;
}
private void SetTextViewText(RemoteViews widgetView)
{
Check();
}
public void Check()
{
client = new WebClient();
String htmlText = client.DownloadString("http://192.168.0.52/state");
client.Dispose();
if (htmlText == "on")
{
widgetView.SetInt(Resource.Id.an, "setBackgroundColor", Color.LimeGreen);
widgetView.SetInt(Resource.Id.aus, "setBackgroundColor", Color.Rgb(71, 66, 65));
}
else if (htmlText == "off")
{
widgetView.SetInt(Resource.Id.an, "setBackgroundColor", Color.Rgb(71, 66, 65));
widgetView.SetInt(Resource.Id.aus, "setBackgroundColor", Color.Red);
}
}
private static string AusClick = "AusClickTag";
private static string AnClick = "AnClickTag";
public override void OnReceive(Context context, Intent intent)
{
base.OnReceive(context, intent);
if (AnClick.Equals(intent.Action))
{
try
{
client = new WebClient();
String unnecessary = client.DownloadString("http://192.168.0.52/ein");
String nec = client.DownloadString("http://192.168.0.52/state");
client.Dispose();
}
catch (Exception) { }
}
if (AusClick.Equals(intent.Action))
{
try
{
client = new WebClient();
String unnecessary = client.DownloadString("http://192.168.0.52/aus");
client.Dispose();
}
catch (Exception) { }
}
}
private void RegisterClicks(Context context, int[] appWidgetIds, RemoteViews widgetView)
{
var intent = new Intent(context, typeof(AppWidget));
intent.SetAction(AppWidgetManager.ActionAppwidgetUpdate);
intent.PutExtra(AppWidgetManager.ExtraAppwidgetIds, appWidgetIds);
widgetView.SetOnClickPendingIntent(Resource.Id.an, GetPendingSelfIntent(context, AnClick));
widgetView.SetOnClickPendingIntent(Resource.Id.aus, GetPendingSelfIntent(context, AusClick));
}
private PendingIntent GetPendingSelfIntent(Context context, string action)
{
var intent = new Intent(context, typeof(AppWidget));
intent.SetAction(action);
return PendingIntent.GetBroadcast(context, 0, intent, 0);
}
}
}
I am trying to return a bool true if the user selects yes from AlertDialog and visa versa.
at the moment it always returns false. it seems like the bool "result" is never being set.
public bool AskForConfirmation(string messege, Context context)
{
bool result;
Android.Support.V7.App.AlertDialog.Builder dialog = new Android.Support.V7.App.AlertDialog.Builder(context);
dialog.SetPositiveButton("Yes", (sender, args) =>
{
result = true;
});
dialog.SetNegativeButton("No", (sender, args) =>
{
result = false;
}).SetMessage(messege).SetTitle("System Message");
dialog.Show();
return result;
}
And I call the method
this.RunOnUiThread(() =>
{
bool response = ioManager.AskForConfirmation("Message", this);
Console.WriteLine("Response is " + response);
});
You can create a Task-based dialog via a ManualResetEvent or a TaskCompletionSource so you can call it like this:
Usage via TaskCompletionSource Example:
try
{
var result = await DialogAsync.Show(this, "StackOverflow", "Does it rock?");
Log.Debug("SO", $"Dialog result: {result}");
}
catch (TaskCanceledException ex)
{
Log.Debug("SO", $"Dialog cancelled; backbutton, click outside dialog, system-initiated, .... ");
}
DialogAsync via TaskCompletionSource Example:
public class DialogAsync : Java.Lang.Object, IDialogInterfaceOnClickListener, IDialogInterfaceOnCancelListener
{
readonly TaskCompletionSource<bool?> taskCompletionSource = new TaskCompletionSource<bool?>();
public DialogAsync(IntPtr handle, Android.Runtime.JniHandleOwnership transfer) : base(handle, transfer) { }
public DialogAsync() { }
public void OnClick(IDialogInterface dialog, int which)
{
switch (which)
{
case -1:
SetResult(true);
break;
default:
SetResult(false);
break;
}
}
public void OnCancel(IDialogInterface dialog)
{
taskCompletionSource.SetCanceled();
}
void SetResult(bool? selection)
{
taskCompletionSource.SetResult(selection);
}
public async static Task<bool?> Show(Activity context, string title, string message)
{
using (var listener = new DialogAsync())
using (var dialog = new AlertDialog.Builder(context)
.SetPositiveButton("Yes", listener)
.SetNegativeButton("No", listener)
.SetOnCancelListener(listener)
.SetTitle(title)
.SetMessage(message))
{
dialog.Show();
return await listener.taskCompletionSource.Task;
}
}
}
Usage Via ManualResetEvent Example:
using (var cancellationTokenSource = new CancellationTokenSource())
{
var result = await DialogAsync.Show(this, "StackOverflow", "Does it rock?", cancellationTokenSource);
if (!cancellationTokenSource.Token.IsCancellationRequested)
{
Log.Debug("SO", $"Dialog result: {result}");
}
else
{
Log.Debug("SO", $"Dialog cancelled; backbutton, click outside dialog, system-initiated, .... ");
}
}
DialogAsync via ManualResetEvent Example:
public class DialogAsync : Java.Lang.Object, IDialogInterfaceOnClickListener, IDialogInterfaceOnCancelListener
{
readonly ManualResetEvent resetEvent = new ManualResetEvent(false);
CancellationTokenSource cancellationTokenSource;
bool? result;
public DialogAsync(IntPtr handle, Android.Runtime.JniHandleOwnership transfer) : base(handle, transfer) { }
public DialogAsync() { }
public void OnClick(IDialogInterface dialog, int which)
{
switch (which)
{
case -1:
SetResult(true);
break;
default:
SetResult(false);
break;
}
}
public void OnCancel(IDialogInterface dialog)
{
cancellationTokenSource.Cancel();
SetResult(null);
}
void SetResult(bool? selection)
{
result = selection;
resetEvent.Set();
}
public async static Task<bool?> Show(Activity context, string title, string message, CancellationTokenSource source)
{
using (var listener = new DialogAsync())
using (var dialog = new AlertDialog.Builder(context)
.SetPositiveButton("Yes", listener)
.SetNegativeButton("No", listener)
.SetOnCancelListener(listener)
.SetTitle(title)
.SetMessage(message))
{
listener.cancellationTokenSource = source;
context.RunOnUiThread(() => { dialog.Show(); });
await Task.Run(() => { listener.resetEvent.WaitOne(); }, source.Token);
return listener.result;
}
}
}
I can't seem to get my code work, although I tried several different approaches. Here is my preferred code snippet:
var client = await ApiClientProvider.GetApiClient();
var freeToPlayChampions = await client.GetChampionsAsync(true, Region);
var championsData = freeToPlayChampions.Select(x =>
client.GetStaticChampionByIdAsync(
(int)x.Id,
platformId: Region));
ConsoleTable.From(await Task.WhenAll(championsData)).Write();
When debugging I see that the code hangs on await Task.WhenAll(championsData). So i tried to make the code more easy:
var client = await ApiClientProvider.GetApiClient();
var freeToPlayChampions = await client.GetChampionsAsync(true, Region);
var table = new ConsoleTable();
foreach(var freeToPlayChampion in freeToPlayChampions)
{
var championsData = client.GetStaticChampionByIdAsync(
(int)freeToPlayChampion.Id,
platformId: Region);
table.AddRow(await championsData);
}
table.Write();
Unfortunately this hangs, as well. Again on the same code part, e.g. await championsData.
How can this 'easy' usage of async/await lead to an deadlock? Thanks in advance for help!
EDIT:
Here is the whole class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ConsoleTables;
using Mono.Options;
using RiotNet.Models;
using RiotShell.Properties;
namespace RiotShell
{
public class FreeToPlay : IShellCommand
{
public IEnumerable<string> Names { get; }
public OptionSet Options { get; }
public bool ShowHelp { get; private set; }
public string Region { get; private set; }
public FreeToPlay()
{
Names = new List<string>
{
"freetoplay",
"ftp"
};
Options = new OptionSet
{
{ "r|region=" , "The region to execute against", x => Region = x},
{ "h|help|?" , "Show help", x => ShowHelp = true }
};
}
public async Task Execute(IEnumerable<string> args)
{
if (ShowHelp)
{
Options.WriteOptionDescriptions(Console.Out);
return;
}
if (args.Any())
{
throw new Exception(Resources.TooManyArgumentsProvided);
}
if (Region == null)
{
throw new Exception(string.Format(Resources.RequiredOptionNotFound, "region"));
}
if (!PlatformId.All.Contains(Region))
{
throw new Exception(string.Format(Resources.InvalidRegion, Region));
}
var client = await ApiClientProvider.GetApiClient();
var freeToPlayChampions = await client.GetChampionsAsync(true, Region);
var championsData = freeToPlayChampions.Select(x =>
client.GetStaticChampionByIdAsync(
(int)x.Id,
platformId: Region));
ConsoleTable.From(await Task.WhenAll(championsData)).Write();
}
}
}
And here is the caller code, my main method:
using System;
using System.Threading.Tasks;
using RiotShell.Properties;
namespace RiotShell
{
public class Program
{
public static async Task Main()
{
while (true)
{
Console.Write(Resources.RiotShellLineString);
var input = Console.ReadLine();
try
{
var parsedArgs = InputParser.Parse(input);
(var command, var commandArgs) = ArgsToIShellCommandCaster.GetCommand(parsedArgs);
await command.Execute(commandArgs);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
}
Since it was wished, here the code for the ApiProvider:
using RiotNet;
using System.Threading.Tasks;
namespace RiotShell
{
public class ApiClientProvider
{
private static IRiotClient _client;
public static async Task<IRiotClient> GetApiClient()
{
if (_client != null)
{
_client.Settings.ApiKey = await KeyService.GetKey();
return _client;
}
_client = new RiotClient(new RiotClientSettings
{
ApiKey = await KeyService.GetKey()
});
return _client;
}
}
}
I have monitor the Application Allocation using Instrument. I have noticed that Memory is gradually increase. I just need release the memory which is used by HTTPClient.
I have tried using Dispose(),used using statement,used GC.collect; but those are not reducing the memory. It looks like not effective.
Below is code used in the program.
namespace MemoryManagementStudy
{
public partial class ViewController : UIViewController
{
protected ViewController(IntPtr handle) : base(handle)
{
}
NetworkStaticCall networkStaticCall;
public override void ViewDidLoad()
{
//lblStatusUpdate.Text = "Success Count: 0, Fail Count: 0 -- Last Update: " + DateTime.Now;
base.ViewDidLoad();
//string status = "";
networkStaticCall = new NetworkStaticCall();
networkStaticCall.init();
Timer timer = new Timer(1000 * 30);
timer.AutoReset = true;
timer.Elapsed += async (sender, e) => {
Console.WriteLine("Memory " + GC.GetTotalMemory(true));
await networkStaticCall.MakeSMWebService();
Console.WriteLine("Memory " + GC.GetTotalMemory(true));
};
timer.Start();
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
}
}
class NetworkStaticCall
{
//int successCount = 0;
//int failCount = 0;
private static HttpClient httpClient;
private static HttpResponseMessage httpResponseMessage;
private StringContent dataContent;
private string xmlString;
private static HttpClientHandler httpClientHandler;
private NSUrlSessionHandler nsUrlSessionHandler;
public void init()
{
httpClientHandler = new HttpClientHandler();
httpClientHandler.Credentials = new NetworkCredential("userName", "Password"); ;
httpClient = new HttpClient(httpClientHandler);
httpClient.DefaultRequestHeaders.Accept.Clear();
dataContent = new StringContent("Xml String Content",
Encoding.UTF8, "text/xml");
}
public async Task<string> MakeSMWebService()
{
Console.WriteLine("Call Processing");
try
{
httpClient.DefaultRequestHeaders.Accept.Clear();
httpResponseMessage = await httpClient.PostAsync("url", dataContent);
Console.WriteLine("Call success");
xmlString = await httpResponseMessage.Content.ReadAsStringAsync();
//successCount++;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return "";
}
}
}
We have tried https://stackoverflow.com/a/27830926 but it is not working.
I have tried WebRequest but it still increasing
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using UIKit;
namespace MemoryManagementStudy
{
public partial class ViewController : UIViewController
{
protected ViewController(IntPtr handle) : base(handle)
{
}
CustomWebRequest customWebRequest;
public override void ViewDidLoad()
{
customWebRequest = new CustomWebRequest();
lblStatusUpdate.Text = "Success Count: 0, Fail Count: 0 -- Last Update: " + DateTime.Now;
base.ViewDidLoad();
string status = "";
//networkStaticCall = new NetworkStaticCall();
//networkStaticCall.init();
//
Timer timer = new Timer(1000 * 60);
timer.AutoReset = true;
timer.Elapsed += async (sender, e) => {
status = await customWebRequest.Post();
InvokeOnMainThread(()=>{
lblStatusUpdate.Text =status + " -- Last Update: " + DateTime.Now;
});
GC.Collect();
};
timer.Start();
}
}
class CustomWebRequest{
string finalUrl = "URL";
//StreamReader reader;
int successCount = 0;
int failCount = 0;
async public Task<string> Post()
{
var request = WebRequest.Create(finalUrl);
request.Method = "GET";
request.Timeout = 10000;
try
{
using (var response = await request.GetResponseAsync())
{
using (var reader = new StreamReader(response.GetResponseStream()))
{
Console.WriteLine(reader.ReadToEnd());
}
}
successCount++;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
failCount++;
}
finally
{
if(request != null){
request.Abort();
request = null;
}
}
return string.Format("Success Count: {0}, Fail Count: {1}", successCount, failCount);
}
}
}