App UWP, Cortana ed attivazione vocale
Windows 10 include l’assistente vocale Cortana, comparso per la prima volta in Windows Phone. Attraverso Cortana possiamo comandare il PC a voce, con tutta una serie di comandi predefiniti di sistema. Come sviluppatori Universal Windows Platform, inoltre, possiamo configurare tutta una serie di frasi che il sistema operativo può riconoscere. Quando lo fa, attiva la nostra app, e la nostra app può intercettare questo evento e reagire di conseguenza.
Vediamo passo passo come ottenere questo obiettivo.
Implementazione
La prima cosa da fare è modificare il file App.xaml.cs, e nell’override del metodo OnLaunched aggiungere le righe seguenti:
Task.Run(async () =>
{
var storageFile = await StorageFile.GetFileFromApplicationUriAsync
(new Uri("ms-appx:///VoiceCommands.xml"));
await VoiceCommandDefinitionManager.
InstallCommandDefinitionsFromStorageFileAsync(storageFile);
});
In pratica, viene utilizzato il file VoiceCommands.xml – ovviamente incluso nel progetto come Content e “Copy if newer” – e registrato a livello di sistema operativo. In questo modo, Windows 10 riconoscerà le frasi specificate nel file XML, anche quando la nostra app UWP è chiusa.
Come è composto questo file XML? Ecco un esempio:
<?xml version="1.0" encoding="utf-8" ?>
<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.1">
<CommandSet xml:lang="it-IT" Name="VoiceCommands">
<CommandPrefix> Nome App, </CommandPrefix>
<Example> Chi ha vinto la medaglia di bronzo ? </Example>
<Command Name="medal">
<Example> Chi ha vinto la medaglia di bronzo ? </Example>
<ListenFor> Chi ha vinto la medaglia di {medal} </ListenFor>
<Feedback> Chi ha vinto la medaglia di {medal} </Feedback>
<Navigate/>
</Command>
<PhraseList Label="medal">
<Item> bronzo </Item>
<Item> argento </Item>
<Item> oro </Item>
</PhraseList>
</CommandSet>
</VoiceCommands>
Grazie a questo XML, possiamo chiedere a Windows 10 ed alla nostra app chi ha vinto un certo tipo di medaglia. Il riconoscimento di questa frase – ripeto – avviene a livello di sistema operativo. Una volta eseguite le righe di codice C# indicate qui sopra, possiamo:
- Premere Win+C per avviare Cortana in modalità ascolto (oppure attivare Cortana con la voce pronunciando “Hey Cortana”, se questa funzionalità è stata preventivamente configurata ed attivata)
- Pronunciare ad esempio “Nome App, Chi ha vinto la medaglia di argento?”
Windows riconosce la frase ed avvia automaticamente l’app. Quando la nostra app viene attivata da un comando vocale come in questo caso, si passa dal metodo OnActivated della classe App (e non da OnLaunched) che si trova in App.xaml.cs.
protected override void OnActivated(IActivatedEventArgs e)
{
if (e.Kind == ActivationKind.VoiceCommand)
{
}
}
Il parametro e espone una proprietà Kind che ci dice il tipo di attivazione a cui siamo soggetti. Quando è di tipo ActivationKind.VoiceCommand, sappiamo che si tratta di un comando vocale. Giunti a questo punto, possono esserci due strade differenti:
- la nostra app è già aperta in foreground
- la nostra app è chiusa
Anche nel caso (1) l’esecuzione passa sempre e comunque da OnActivated. Nel caso (2), invece, la nostra app è completamente chiusa, perciò sta a noi decidere quale page visualizzare. Questo è il codice che ho utilizzato in una mia app reale:
protected override void OnActivated(IActivatedEventArgs e)
{
if (e.Kind == ActivationKind.VoiceCommand)
{
var commandArgs = e as VoiceCommandActivatedEventArgs;
var speechRecognitionResult = commandArgs.Result;
var textSpoken = speechRecognitionResult.Text;
if (this.rootFrame == null)
{
// App chiusa
rootFrame = new Frame();
Window.Current.Content = rootFrame;
this.rootFrame.Navigate(typeof(MainPage), textSpoken);
Window.Current.Activate();
}
// Invio un messaggio che verrà intercettato dal MainViewModel
// sia ad app già aperta, che appena avviata dal comando vocale
Messenger.Default.Send<TellMeMedalWinnerMessage>
(new TellMeMedalWinnerMessage(textSpoken));
}
}
Notare che ho spostato la dichiarazione dell’oggetto rootFrame a livello di classe, al contrario di quello che fa il template di base, che invece lo dichiara in modo locale al metodo OnLaunched.
Se l’oggetto rootFrame è null, significa che l’app era chiusa, quindi creo il frame, scateno la navigazione, imposto Window.Current.Content, etc. etc. Se invece rootFrame è diverso da null, significa che la mia app è già aperta. Per evitare di scrivere tutta la logica di riconoscimento in App.xaml.cs – davvero brutto – ho adottato questa soluzione: sia in un caso che nell’altro, grazie al Messenger di MvvmLight spedisco un messaggio di tipo TellMeMedalWinnerMessage. Questo è un passaggio molto importante. Il messaggio viene di conseguenza intercettato dal viewmodel che governa la UI, in questo modo:
// Nel costruttore
Messenger.Default.Register<TellMeMedalWinnerMessage>(this, GetMedalWinner);
// Funzione
private async void GetMedalWinner(TellMeMedalWinnerMessage message)
{
// invoco un servizio?
}
Nel costruttore del viewmodel registro quel messaggio, ed imposto il delegate da eseguire quando quel particolare messaggio viene ricevuto. La logica è tutta contenuta nel metodo GetMedalWinner: non fa altro che riceve in input un oggetto di tipo TellMeMedalWinnerMessage, che è una classe anemica implementata in questo modo:
class ScoreboardPositionMessage : BaseMessage
{
public string Phrase { get; set; }
public ScoreboardPositionMessage(string phrase)
{
this.Phrase = phrase;
}
}
Questa classe contiene solamente il testo della frase pronunciata dall’utente della nostra app. Adesso sta a noi decidere cosa farne. Possiamo elaborarla sul client Windows 10, oppure possiamo invocare un servizio remoto che elabora la stringa, consulta un database e restituisce un risultato al client.
Ci sono altre considerazioni da fare (grammatica, come gestire frasi libere oppure comandi più precisi, gestire più lingue): saranno tema di un prossimo post che scriverò nei prossimi giorni.
Alla prossima!!