[Windows Phone 8.1] Integrando nuestra aplicación con Cortana

Introducción

En el pasado //BUILD/ de Microsoft, Joe Belfiore vicepresidente del programa Windows Phone presento Cortana.
Cortana es el asistente virtual de Windows Phone y entre la enorme
cantidad de funcionalidades incluidas además de las que van incorporando
poco a poco (como por ejemplo, recomendaciones basadas en búsquedas de Foursquare) cabe destacar que es una plataforma sobre la que se pueden desarrollar aplicaciones de terceros. En este artículo vamos a centrarnos en analizar paso a paso como integrar una aplicación Windows Phone con Cortana.

¿Te apuntas?

Nuestra Aplicación

Para integrar una aplicación Windows Phone con Cortana lo primero que
necesitamos es… una aplicación!. Vamos a crear una aplicación sencilla
pero que nos sea válida para lograr nuestros objetivos. Nuestra
aplicación permitirá consultar cualquier clasificación de pilotos de
Formula 1 celebrada hasta la actual:

Añadimos las carpetas Views, ViewModels y Services además de las clases base necesarias para implementar el patrón MVVM de la misma forma que vimos en este artículo.
Como hemos comentado, nuestra aplicación permitirá consultar cualquier
clasificación de pilotos de Formula de cualquier año, necesitamos un
selector de año. Añadimos un botón en nuestra interfaz:

<Button />

Al botón le añadiremos un ListPickerFlyout, nos permitirá mostrar un listado de opciones al pulsar sobre el botón:

<Button>
     <Button.Flyout>
          <ListPickerFlyout
               Title="SELECCIONA AÑO">
               <ListPickerFlyout.ItemTemplate>
                    <DataTemplate>
                         <StackPanel>
                              <TextBlock Text="{Binding}" FontSize="{StaticResource TextStyleExtraLargeFontSize}"/>
                         </StackPanel>
                    </DataTemplate>
               </ListPickerFlyout.ItemTemplate>
          </ListPickerFlyout>
     </Button.Flyout>
</Button>

La interfaz de la página principal de nuestra aplicación es la siguiente:

El control ListPickerFlyout muestra una colección indicada mediante la propiedad ItemsSource, y el elemento seleccionado lo podemos obtener mediante la propiedad SelectedItem:

<Button
     HorizontalAlignment="Stretch" HorizontalContentAlignment="Left"
     Content="{Binding SelectedYear}">
     <Button.Flyout>
          <ListPickerFlyout
               Title="SELECCIONA AÑO" ItemsSource="{Binding Years}"
               SelectedItem="{Binding SelectedYear, Mode=TwoWay}">
               <ListPickerFlyout.ItemTemplate>
                    <DataTemplate>
                         <StackPanel>
                              <TextBlock Text="{Binding}" FontSize="{StaticResource TextStyleExtraLargeFontSize}"/>
                         </StackPanel>
                    </DataTemplate>
               </ListPickerFlyout.ItemTemplate>
          </ListPickerFlyout>
     </Button.Flyout>
</Button>

Al pulsar el botón:

¿Pero la propiedad Years bindeada a la propiedad ItemsSource de donde obtiene la información?. En nuestra viewmodel:

private ObservableCollection<string> _years;
private string _selectedYear;
 
public ObservableCollection<string> Years
{
     get { return _years; }
     set { _years = value; }
}
 
private void LoadYears()
{
     for (var i = 1950; i <= DateTime.Now.Year; i++)
     {
          Years.Add(i.ToString());
     }
}
 
public string SelectedYear
{
     get { return _selectedYear; }
     set
     {
          _selectedYear = value;
          RaisePropertyChanged();
     }
}
Creamos una colección de años que usaremos para bindear al control ListPickerFlyout. La información, los años seleccionables, los rellenamos con el sencillo método LoadYears
que podéis ver en las líneas superiores. Una vez seleccionado un año
del que obtener la clasificación de pilotos, necesitamos lanzar la
búsqueda:
<Page.BottomAppBar>
     <CommandBar>
          <AppBarButton Label="buscar" Icon="Find" Command="{Binding SearchCommand}" />
     </CommandBar>
</Page.BottomAppBar>
Añadimos una CommandBar con un botón que nos permita realizar la búsqueda. En la viewmodel:
private ICommand _searchCommand;
 
public ICommand SearchCommand
{
     get { return _searchCommand = _searchCommand ?? new DelegateCommand(SearchCommandDelegate); }
}
 
public void SearchCommandDelegate()
{
     AppFrame.Navigate(typeof(SearchView), _selectedYear);
}

Añadimos un comando de modo que al ejecutarse, navega a una nueva vista
pasando como parámetro el año seleccionado. En la vista a la que
navegamos tendremos un listado:

<GridView    
     ItemsSource="{Binding DriverStanding}"
     ItemTemplate="{StaticResource DriverTemplate}"
     ItemsPanel="{StaticResource ItemPanelTemplate}"
     SelectionMode="None"
     IsItemClickEnabled="True">

Con la plantilla que definirá el aspecto de cada elemento de la lista:

<DataTemplate x:Key="DriverTemplate">
     <Grid Margin="5" Width="300">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Grid>
                <Ellipse Height="50" Width="50" Fill="Red"/>
                <TextBlock Text="{Binding Position}" FontSize="28" HorizontalAlignment="Center"
                           VerticalAlignment="Center" Foreground="White" />
            </Grid>
            <StackPanel Grid.Column="1" Margin="15, 0">
                <TextBlock Text="{Binding Driver.CompleteName}" FontSize="24" />
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Points}" Foreground="Red" />
                    <TextBlock Text=" Points" />
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Wins}" Foreground="Red" />
                    <TextBlock Text=" Wins" />
                </StackPanel>
            </StackPanel>
     </Grid>
</DataTemplate>
     
<ItemsPanelTemplate x:Key="ItemPanelTemplate">
     <StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>

Y por su puesto, su correspondiente viewmodel donde sobrescribimos el evento OnNavigatedTo
que se lanzará cada vez que entremos a la página para obtener el
parámetro de navegación (el año del que deseamos obtener la
clasificación) y lanzamos la carga de la información:

public override async System.Threading.Tasks.Task OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
{
     var season = args.Parameter.ToString();
     await LoadStandingsData(season);
}

La carga de la información accederá a un servicio que nos permite
obtener las clasificaciones de la Formula 1 por año y volcará la
información en una colección disponible como propiedad públicada
bindeada a la lista:

private async Task LoadStandingsData(string season)
{
     var driverStandings = await _standingService.GetSeasonDriverStandingsCollectionAsync(season);
     var drivers = driverStandings.StandingsLists.First().DriverStandings;
 
     foreach (var driver in drivers)
     {
          DriverStanding.Add(driver);
     }
}

El resultado:

En la captura superior podemos ver el resultado de la búsqueda de la
clasificación de pilotos del año 2006. Un ejemplo sencillo pero
interesante que podría ganar mucho realizando la integración con
Cortana.

Cortana

Inspirado por un famoso personaje de Halo que era un asistente
digital personal para el protagonista del juego, Master Chief, nos llega
un asistente de voz personal a Windows Phone.

NOTA: Tras la voz de Cortana esta Jen Taylor la misma mujer que dio voz al personaje en el videojuego.

En otras plataformas contaban con Siri (Apple) o Google Now (Google), asistentes de voz más versátiles que el sistema de reconocimiento de voz con el que contábamos hasta ahora en Windows Phone.

Cortana llega para ir más alla que todo lo existente hasta ahora.

¿Cómo?

Cortana no se limitará a un sistema que reaccionará ante comandos de
voz, si no que tomará múltiples fuentes para contar con la mayor
cantidad de información posible para interaccionar de la forma más
precisa posible.

La primera vez que usemos Cortana nos realizará algunas preguntas
básicas sobre nosotros. A partir de ese momento, Cortana mirara en
contactos, lugares habituales, intereses… todo lo necesario para
aprender lo máximo posible de nosotros mismos.

NOTA: El nivel de acceso de Cortana a nuestros datos es configurable.

Asi que Cortana no se limita a responder órdenes o realizar búsquedas
básicas como lugares, el tiempo o resultados deportivos, además puede
establecer recordatorios como recordarle a tu madre que tal le va el
nuevo Windows Phone que le has regalado por ejemplo.

Por si fuese poco, otra gran diferencia de Cortana con otros asistentes digitales es la posibilidad de interactuar con aplicaciones de terceros.

Integración con Cortana

Vamos a integrar nuestra aplicación con Cortana.

¿Qué quiere decir esto?

Sencillo, desde Cortana permitiremos preguntar por “Formula One standings for 2006″
y que en lugar de realizar una búsqueda en Bing, abra nuestra
aplicación, realice la búsqueda correspondiente y muestre la información
completa de la clasificación de pilotos de Formula 1 del año 2006. De
esta forma, por un lado ofrecemos una experiencia cada vez más completa e
integrada al usuario desde Cortana y además logramos otro punto de
entrada y un mayor uso de nuestra aplicación.

Para integrar nuestra aplicación con Cortana realizaremos tres sencillos pasos:

1º Creando la definición de comandos de voz (VCD)

El usuario puede mediante voz tratar con Cortana para activar la
aplicación y ejecutar una acción concreta. Por ejemplo, el usuario que
usa la aplicación Formula 1 Standings podría mantener presionado el
botón Buscar para iniciar Cortana y decir: “Formula 1
Standings for 2006″. Esto activará la aplicación Formula 1 Standings,
que, seguidamente, navegará a la página de búsqueda realizando la
acción. El primer paso para conseguir esto sera crear un archivo VCD (Definición de comando de voz). El archivo VCD es un documento XML en el que definimos todos los comandos de voz que el usuario puede utilizar para lanzar acciones en la aplicación.

Creamos el archivo VCD. En Visual Studio, hacemos clic derecho en el proyecto y seleccionamos Agregar->Nuevo elemento y luego Archivo de texto. Tras agregar el archivo de texto debemos asignarle el nombre que consideremos oportuno, en nuestro ejemplo se le ha llamado VoiceCommandDefinitions y lo más importante debemos cambiar la exntesión a .xml. Además, En la ventana de Propiedades del archivo xml establecemos la propiedad Acción de compilación a Contenido y a continuación Copiar en el directorio de salida en Copiar si es posterior. Nuestra definición de comando de voz es la siguiente:

<?xml version="1.0" encoding="utf-8"?>
  <CommandSet xml:lang="en-us">
    <CommandPrefix>formula one standing for</CommandPrefix>
    <Example>Search Formula One Standings</Example>
    <Command Name="for">
      <Example>2005</Example>
      <ListenFor>[for] {dictatedSearchTerms}</ListenFor>
      <Feedback>checking that out on Ergast API for you, give me a second</Feedback>
      <Navigate Target="SearchView.xaml"/>
    </Command>
    <PhraseTopic Label="dictatedSearchTerms" Scenario="Natural Language">
   </PhraseTopic>
</CommandSet>
</VoiceCommands>

Todos los comandos de voz tienen:

  1. Una frase de ejemplo que muestra como lanzar el comando.
  2. Las palabras que se reconocerán para lanzar el comando.
  3. El texto que Cortana mostrará al reconocer el comando.
  4. La pantalla a la que navegará la aplicación al reconocer el comando.

Analicemos nuestra definición de comandos de voz paso a paso:

  • <CommandPrefix></CommandPrefix> Puede ser el nombre de la aplicación o la frase que el usuario utilizará para lanzar nuestra aplicación.
  • <Example></Example> Ejemplo de más alto nivel indicando al usuario un ejemplo delo que puede hacer.
  • <Command></Command> Unidad lógica de lo
    que el usuario quiere hacer. Contiene lo que el usuario dice, lo que
    Cortana responde y lo que Cortana hace.
  • <ListenFor></ListenFor> Una o más frases.
  • <Feedback></Feedback> Feedback visual trasmitido por Cortana hacia al usuario tras reconocer un comando.
  • <Navigate></Navigate> Acción a realizar tras reconocer el comando. Es opcional en apps no-Silverlight.

2º Registrar el XML del VCD en el inicio de la App

Durante la ejecución de la aplicación, registraremos el conjunto de comandos contenidos en el archivo VCD:

async Task InstallVoice()
{
     var storageFile =
          await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///VoiceCommandDefinitions.xml"));
 
     await VoiceCommandManager.InstallCommandSetsFromStorageFileAsync(storageFile);
}

Utilizamos el método GetFileFromApplicationUriAsync que nos permitirá acceder a recursos de la aplicación utilizando URIs “ms-appx://” o “ms-appdata://”.
En este caso lo usamos para acceder al archivo xml del VCD. A
continuación, utilizamos la clase estática VoiceCommandManager que nos
permite acceder o instalar conjunto de definiciones de comandos de voz
mediante archivo VCD. En este caso, procedemos a la instalación
utilizando el método InstallCommandSetsFromStorageFileAsync.

NOTA: Para que la instalación de voces se pueda realizar debemos añadir el micrófono como una capacidad de la aplicación.

3º Gestionar la activación de comandos de voz

Cuando el usuario utilizando Cortana ejecuta una de nuestros comandos de voz en nuestra aplicación se lanza el evento OnActivated.
Realmente este evento forma una parte importante del ciclo de vida de
las aplicaciones Windows Phone. Sin embargo, en este caso, al lanzarse
el evento por motivo de un comando de voz recibimos un parámetro de tipo
VoiceCommand.

Tras una pequeña validación, navegaremos a la página principal pasando
como parámetro la información recibida desde Cortana con el objeto de
tipo SpeechRecognitionResult:

protected override void OnActivated(IActivatedEventArgs args)
{
     if (args.Kind == ActivationKind.VoiceCommand)
     {
          var voiceArgs = (IVoiceCommandActivatedEventArgs)args;
          var result = voiceArgs.Result;
 
          var frame = Window.Current.Content as Frame;
          frame.Navigate(typeof(SearchView), result);
          Window.Current.Content = frame;
 
          Window.Current.Activate();
      }
 
      base.OnActivated(args);
}

Con la información recibida desde Cortana, gestionaremos el contenido en una clase (la viewmodel de la página principal) y redigiremos al usuario a la vista correspondiente con la información esperada:

En la viewmodel de la vista a la que navegamos, en el método sobrescrito OnNavigatedTo,
capturaremos el parámetro con la información enviada por Cortana,
obtendremos el valor de la propiedad Text que indica el valor indicado
por el usuario, en nuestro ejemplo el año del que desea ver la
clasificación de pilotos y se lo pasamos a un método encargado de
gestionar el comando de voz:

public override async System.Threading.Tasks.Task OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
{
     var season = string.Empty;

     if (args.NavigationMode == NavigationMode.New)
     {
          var voiceResult = args.Parameter as SpeechRecognitionResult;

          if (voiceResult != null)
               season = voiceResult.Text;
          else
               season = args.Parameter.ToString();
     }

     if (!string.IsNullOrEmpty(season))
          await LoadStandingsData(season);
}

Hasta aqui todo preparado. Puedes descargar el ejemplo completo a continuación:

¿Probamos?

Comenzamos abriendo Cortana:

Podemos consultar que aplicaciones estan integradas y que podemos decir preguntando “What can i say?”:

Vemos que nuestra aplicación de ejemplo que nos permite realizar
consultas sobre las clasificaciones de la formula 1 esta disponible. Si
pulsamos sobre ella:

Vemos un ejemplo de lo que podemos consultar. Realizamos una consulta:

Cortana interpreta el comando, detecta nuestra aplicación, muestra un
mensaje de feedback al usuario de lo que se esta realizando y lanza
nuestra aplicación:

El resultado final:

Como hemos comentado logramos otro punto de entrada hacia nuestra
aplicación asi como ofrecer una experiencia mucho más completa e
integrada desde Cortana. Comparemos el resultado a la búsqueda sin
existir nuestra aplicación:

El video:

Consideraciones en el diseño

En el proceso de integración de nuestra aplicación con Cortana debemos tener en cuenta una serie de consideraciones:

  • Usa comandos sencillos en los elementos <ListenFor> del archivo VCD.
  • Analiza con calma los comandos de voz. Se cuidadoso con el coste y beneficio de cada palabra.
  • Adapta la interfaz y la respuesta otorgada segun la petición del usuario.
  • Manten al usuario siempre informado de todo lo que esta ocurriendo.

Más información

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *