Intermédiaire

Intégrer son application Windows 10 au sein de Cortana

5258 Cortana vous propose non seulement de lancer votre application par la voix comme nous l’avons vu précédemment, mais aussi d’y intégrer directement votre contenu. Cette fonctionnalité vous permet donc de mettre en avant votre contenu directement au sein du système d’exploitation. Nous allons voir, au cours de cet article, comment faire en sorte d’intégrer votre contenu en réponse à une phrase prononcée par l’utilisateur à Cortana.

Je précise qu’il est nécessaire pour comprendre cet article d’avoir lu la première partie car cette dernière pose les bases de Cortana et notamment des fichiers de commande.

Il est important de noter que cette façon de s’interfacer avec Cortana est plus complexe que celle abordée lors de notre premier article. En effet, dans ce cas, il va être nécessaire de passer par un BackgroundAgent pour dialoguer avec Cortana. L’architecture de cette application sera donc plus complexe et il sera nécessaire d’avoir des bases un peu plus importantes en ce qui concerne le développement d’applications Windows 10 (W10).

Intégration simple (une commande)

Pour ce premier scénario, nous allons faire réagir Cortana à la phrase « SOAT, affiche mes séries ». Dans un premier temps, nous allons, à la suite de cette commande, lancer l’application au premier plan. Cette approche nous situera dans le scénario simple du précédent article, mais abordera donc une nouvelle manière de procéder.

Pour commencer, nous allons donc créer notre fichier VCD.xml qui regroupera nos commandes, à la racine de notre nouveau projet d’applications universelles (que j’ai nommé dans mon cas Cortana.Integrated). Ce fichier aura le code suivant :

<?xml version="1.0" encoding="utf-8" ?>
<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.1">
  <CommandSet xml:lang="fr-fr" Name="CortanaIntegratedCommandSet_fr-fr">
    <CommandPrefix> Soat, </CommandPrefix>
    <Example> affiche les séries </Example>

    <Command Name="launchSeries">
      <Example> affiche les séries  </Example>
      <ListenFor> affiche les séries </ListenFor>
      <Feedback> Affichage des séries </Feedback>
      <VoiceCommandService Target="CortanaVoiceCommandService"/>
    </Command>

  </CommandSet>
</VoiceCommands>

On s’aperçoit donc que la seule ligne qui change par rapport à notre précédent fichier est celle qui contenait la balise « Navigate », qui est remplacée par la balise VoiceCommandService.

Cette dernière permet donc d’indiquer à Cortana que, lorsque cette commande est déclenchée, il va falloir exécuter le service CortanaVoiceCommandService qui aura été au préalablement enregistré au sein de notre manifeste d’application.

Pour créer notre service, il va dans un premier temps falloir créer un « Windows Runtime Component » qui contiendra notre service. J’ai appelé le mien Cortana.Integrated.BackgroundAgent, et j’y ai créé une classe CortanaVoiceCommandService qui implémente IBackgroundTask. Cette classe se compose du code suivant :

public class CortanaVoiceCommandService : IBackgroundTask
    {
        private BackgroundTaskDeferral serviceDeferral;
        VoiceCommandServiceConnection voiceServiceConnection;

        public async void Run(IBackgroundTaskInstance taskInstance)
        {
            this.serviceDeferral = taskInstance.GetDeferral();

            taskInstance.Canceled += (sender, reason) => serviceDeferral?.Complete();

            var triggerDetails =
              taskInstance.TriggerDetails as AppServiceTriggerDetails;

            if (triggerDetails != null &&
              triggerDetails.Name == "CortanaVoiceIntegration")
            {
                try
                {
                    voiceServiceConnection =
                      VoiceCommandServiceConnection.FromAppServiceTriggerDetails(triggerDetails);

                    voiceServiceConnection.VoiceCommandCompleted += (sender, reason) => serviceDeferral?.Complete();

                    LaunchAppInForeground();
                }
                catch { }
            }
        }

        private void VoiceCommandCompleted(VoiceCommandServiceConnection sender, VoiceCommandCompletedEventArgs args)
        {
            if (this.serviceDeferral != null)
            {
                this.serviceDeferral.Complete();
            }
        }

        private async void LaunchAppInForeground()
        {
            var userMessage = new VoiceCommandUserMessage();
            userMessage.SpokenMessage = "Lancement de l'application";

            var response = VoiceCommandResponse.CreateResponse(userMessage);

            response.AppLaunchArgument = "launchSeries";

            await voiceServiceConnection.RequestAppLaunchAsync(response);
        }

    }

Si l’on détaille un petit peu ce code, on repère trois fonctions que nous allons détailler ci-dessous.

Run : cette fonction est le point d’entrée de notre service. En effet, lorsque le CortanaVoiceCommandService est appelé, c’est cette fonction qui est appelée. Cette dernière récupère l’instance en cours du service dans un premier temps. Puis nous y ajoutons un handler au cas où notre service crasherait. Enfin, nous récupérons les détails du lancement de notre service au sein de la variable triggerDetails, ce qui nous permettra de déclencher le bon scénario.
Ensuite, nous récupérons la connexion au service, nous nous abonnons à l’évènement completed de cette connexion de manière à pouvoir terminer notre instance une fois notre scénario. Pour cela, nous utilisons la fonction VoiceCommandCompleted sur laquelle nous reviendrons dans quelques instants.

Enfin, nous lançons notre application au premier plan grâce à la fonction LaunchAppInForeground.

Revenons désormais sur la fonction VoiceCommandCompleted : cette dernière permet de signifier à l’instance de notre service que tout est terminé et que la tâche peut être terminée correctement. C’est une étape essentielle.

Enfin, la fonction LaunchAppInForeground : il s’agit de la fonction qui sera chargé de lancer notre application au premier plan, comme son nom l’indique. Pour ce faire, le code commence tout d’abord par créer un message de retour à Cortana du type VoiceCommandUserMessage qui permet de renvoyer également un message d’attente à l’utilisateur.

Nous définissons alors le SpokenMessage, qui sera le texte prononcé par Cortana pour annoncer la bonne exécution de notre commande. Puis nous créons une réponse grâce à ce message.

Nous ajoutons ensuite un argument de lancement à notre réponse, qui sera un argument reçu par notre application et qui nous permettra donc, lors du lancement de notre application, d’appliquer le bon scénario. Enfin, nous informons la connexion à notre service que nous demandons le lancement de notre application au premier plan, avec pour paramètre notre réponse précédemment créée. L’appel à cette fonction déclenchera également l’évènement Completed de notre connexion.

Nous avons désormais notre service qui est prêt à fonctionner, il nous reste à préparer notre application. Pour cela deux étapes sont nécessaires :

  • Il est dans un premier temps indispensable de lier notre service à notre application pour que ce dernier puisse s’exécuter correctement. Pour cela, nous allons dans un premier temps rajouter une référence vers notre service au sein de notre application, puis nous allons indiquer au sein de notre manifeste l’existence de notre service avec le code suivant qui devra être placé au sein de la balise Extensions, qui est elle-même au sein de la balise Application :
      <Extensions>
        <uap:Extension Category="windows.appService" EntryPoint="Cortana.Integrated.CortanaAgent.CortanaVoiceCommandService">
          <uap:AppService Name="CortanaVoiceIntegration" />
        </uap:Extension>
      </Extensions>

L’EntryPoint de votre extension est donc le namespace de votre classe représentant votre service, suivi du nom de cette classe. Le nom de l’AppService, quant à lui, est celui que vous indiquerez au sein de votre fichier VCD pour l’attribut Target de votre balise VoiceCommandService.

  • Dans un second temps, il va nous falloir enregistrer notre fichier VCD au sein de Cortana. Pour ce faire, je vous invite à retrouver la classe CortanaHelper que nous avions créée lors du précédent article, puis d’exécuter la fonction Register au sein de la fonction OnLaunched de votre fichier App.xaml.cs.
    Une fois exécuté une première fois, en demandant à Cortana « Que puis-je dire », vous devriez voir apparaître votre application.

que-puis-je-dire

De manière à pouvoir debugger Cortana, il est nécessaire de modifier la manière dont l’application est lancée lors du debug. Pour ce faire, faites un clic-droit sur votre projet d’application au sein de Visual Studio et accédez aux propriétés, puis allez dans l’onglet Debug et cochez la case « Do not launch, but debug my code when it starts ». Ainsi, vous pourrez lancer votre application en debug, mais cette dernière ne sera pas lancée. Vous pourrez alors mettre un point d’arrêt au sein de votre VoiceCommandService de manière à pouvoir debugger cette fonctionnalité.

Pour les besoins de ce tutoriel, j’ai créé deux pages dans l’application : SeriePage et SeriesPages (de la même manière que dans la précédente partie de ce tutoriel).

Désormais, il va falloir préparer notre application pour pouvoir la lancer via Cortana. Pour cela, il va falloir surcharger la fonction OnActivated au sein de notre fichier App.xaml.cs car c’est cette dernière qui sera le point d’entrée lors du lancement de l’application via Cortana.

Le code de cette fonction sera donc :

protected override void OnActivated(IActivatedEventArgs args)
        {
            Type pageToNavigate = typeof(MainPage);
            object param = null;

            if (args.Kind == ActivationKind.Protocol)
            {
                var commandArgs = args as ProtocolActivatedEventArgs;
                if(commandArgs != null)
                {
                    var decoder = new WwwFormUrlDecoder(commandArgs.Uri.Query);
                    var navigationParam = decoder.GetFirstValueByName("LaunchContext");
                }
            }

            Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame == null)
            {
                rootFrame = new Frame();
                rootFrame.NavigationFailed += OnNavigationFailed;

                Window.Current.Content = rootFrame;
            }

            rootFrame.Navigate(pageToNavigate, param);
            Window.Current.Activate();
        }

Ce code reprend en grande partie le code proposé lors de la première partie, la partie modifiée, et qui nous intéresse, étant la suivante :

if (args.Kind == ActivationKind.Protocol)
{
var commandArgs = args as ProtocolActivatedEventArgs;
       if(commandArgs != null)
       {
       	var decoder = new WwwFormUrlDecoder(commandArgs.Uri.Query);
              var launchParam = decoder.GetFirstValueByName("LaunchContext");

              switch (launchParam)
              {
              	case "launchSeries":
                    		pageToNavigate = typeof(SeriesPage);
								break;
								}
				}
}

Détaillons un peu son fonctionnement, nous commençons donc par vérifier si il s’agit d’une activation via un protocole. En effet, le lancement de l’application via Cortana s’effectue via un protocole. Nous castons donc ensuite l’argument de la fonction OnActivated en ProtocolActivatedEventArgs. Puis nous allons récupérer les paramètres transmis lors du lancement de l’application et les décodés. Enfin pour finir, nous allons récupérer l’argument qui nous intéresse : LaunchContext. Ensuite, nous lançons le bon scénario, dans notre cas, il s’agit de lancer la page de listing des séries.

Nous avons parlé plus haut du fait que Cortana lançait l’application via un protocole. Il s’agit du protocole personnalAssistantLaunch, et pour que votre application puisse être lancée, il faut rajouter la ligne suivante au sein de votre manifeste d’application dans la catégorie Extensions :

 <uap:Extension Category="windows.personalAssistantLaunch" /> 

Si vous oubliez cette ligne au sein du manifeste alors votre application ne pourra pas être lancée via votre service Cortana.

Et voilà, maintenant vous pouvez demander à cortana « SOAT affiche mes séries » et l’application se lancera sur la page des séries. C’est une belle fonctionnalité, mais ne préféreriez-vous pas voir s’afficher directement vos séries au sein de Cortana sans avoir à lancer votre application ?

Intégration avec paramètres

Nous allons donc voir dans cette partie comment afficher directement des informations au sein de Cortana sans avoir à lancer votre application. Pour ce faire, nous allons devoir modifier essentiellement notre service de manière à retourner une liste de séries à ce dernier. Il faudra également modifier notre application de manière à rediriger vers la bonne page au clic sur une série.

Pour commencer, nous allons donc modifier la réponse à la commande « SOAT affiche mes séries » au sein de notre service.

Nous allons donc créer la fonction ShowSeries qui sera appelée lorsque la commande launchSeries est déclenchée. Voici le code de cette fonction :

private async Task ShowSeries()
        {
            //Création d'un message de réponse temporaire permettant de ne plus couper la tâche au bout de XX secondes par défaut
            var userProgressMessage = new VoiceCommandUserMessage();
            userProgressMessage.DisplayMessage = userProgressMessage.SpokenMessage = "Nous récupérons vos séries";

            VoiceCommandResponse response_temp = VoiceCommandResponse.CreateResponse(userProgressMessage);
            await voiceServiceConnection.ReportProgressAsync(response_temp);

            //Création de la liste de résultats à afficher par Cortana
            var destinationsContentTiles = new List<VoiceCommandContentTile>();

            //Création d'une tuile pour chaque série
            foreach (var serie in _series)
            {
                var tile = new VoiceCommandContentTile();
                tile.ContentTileType = VoiceCommandContentTileType.TitleOnly;
                tile.AppLaunchArgument = serie.Key;
                tile.Title = serie.Value;

                destinationsContentTiles.Add(tile);
            }


            //Message de résultat
            var userReprompt = new VoiceCommandUserMessage();
            userReprompt.DisplayMessage = "Vos séries";
            userReprompt.SpokenMessage = "Voici vos séries";

            var response = VoiceCommandResponse.CreateResponse(userReprompt, destinationsContentTiles);
            await voiceServiceConnection.ReportSuccessAsync(response);
        }

Le code est décomposé de la manière suivante :

  • Nous créons un premier message temporaire pour l’utilisateur de manière à le faire patienter. De plus, ce message permet de ne pas fermer prématurément le service Cortana au bout de 5 secondes.

  • Puis, nous créons une liste de « tuiles », qui correspondront à nos séries. Dans cette version, j’ai volontairement créé des tuiles avec une image de manière à montrer le fonctionnement, mais vous avez plusieurs templates qui vous permettent, par exemple, d’ajouter un sous-titre ou un sous-sous-titre sur ces tuiles, et vous pouvez aussi ne pas définir d’image pour ces tuiles.

  • Une fois ceci fait, nous préparons un nouveau message de réponse pour l’utilisateur qui viendra remplacer le message temporaire. Nous définissons donc un texte à afficher (DisplayMessage), un texte à dicter par Cortana (SpokenMessage) et nous créons la réponse avec ce message et la liste de nos tuiles. Nous informons ensuite notre connexion à Cortana que nous avons réussi à afficher les résultats et que la tâche est terminée.

En demandant à Cortana « SOAT affiche mes séries », vous devriez alors avoir le résultat suivant :

liste-cortana

Et désormais, si vous cliquez sur une série, vous devriez pouvoir lancer votre application. Passons donc désormais à la gestion de l’ouverture de la bonne série lors du clic sur l’une d’elles. Il va nous falloir modifier notre fonction OnActivated, nous allons donc remplacer l’ancien code :

switch (launchParam)
{
		case "launchSeries":
						pageToNavigate = typeof(SeriesPage);
		 break;
}

Par ce nouveau code :

if (launchParam.StartsWith("serie-"))
{
		pageToNavigate = typeof(SeriePage);
					 param = launchParam.Remove(0, 6);
}
else {
		pageToNavigate = typeof(SeriesPage);
}

Ainsi, nous pouvons rediriger l’utilisateur vers la série demandée.

Activation avec un paramètre

Nous pouvons également interagir avec Cortana en arrière-plan via un paramètre. Cette utilisation peut par exemple permettre de demander des confirmations à l’utilisateur ou autre.

Pour ce faire, nous allons commencer par rajouter au sein de notre fichier de commande une nouvelle commande du type « SOAT, affiche moi la série XXXX ». De plus, nous allons donc rajouter des paramètres de manière à pouvoir faire fonctionner cette commande.

Voici donc à quoi ressemblera cette commande au sein de notre fichier VCD.xml (qui ressemble beaucoup à la commande que nous avons réalisée au sein de la première partie de notre article) :

    <Command Name="launchSerie">
      <Example> affiche la série homeland </Example>
      <ListenFor> affiche la série {serie} </ListenFor>
      <Feedback> Affichage de la série Homeland </Feedback>
      <VoiceCommandService Target="CortanaVoiceIntegration"/>
    </Command>

    <PhraseList Label="serie">
      <Item> Homeland </Item>
      <Item> Jessica Jones </Item>
      <Item> Breaking Bad </Item>
      <Item> American Dad </Item>
      <Item> Heroes </Item>
      <Item> Heroes Reborn </Item>
    </PhraseList>

On peut donc voir que cela ne diffère pas réellement d’une commande classique de lancement d’application sans intégration en arrière-plan, à l’exception près que nous ne naviguons pas directement vers l’application, mais faisons appel à notre service Cortana.

Nous devrons donc ajouter au sein de notre service une gestion pour le cas de notre commande « launchSerie », grâce au code suivant qui remplace au sein de la fonction Run l’appel de la fonction qui permet d’afficher les séries à l’utilisateur :

switch (voiceCommand.CommandName)
                    {
                        case "launchSeries":
                            await ShowSeries();
                            break;

                        case "launchSerie":
                            await ShowSerie(voiceCommand);
                            break;
                    }	

Enfin, la fonction ShowSerie aura donc la forme suivante :

private async Task ShowSerie(VoiceCommand command)
        {
            //Création d'un message de réponse temporaire permettant de ne plus couper la tâche au bout de .5 secondes par défaut mais au bout de 5 secondes
            var userProgressMessage = new VoiceCommandUserMessage();
            userProgressMessage.DisplayMessage = userProgressMessage.SpokenMessage = "Nous récupérons vos séries";

            VoiceCommandResponse response_temp = VoiceCommandResponse.CreateResponse(userProgressMessage);
            await voiceServiceConnection.ReportProgressAsync(response_temp);

            string serie = command.Properties["serie"][0];

            var tempSerie = _series.FirstOrDefault(x => x.Value == serie);

            if(_series.ContainsValue(serie))
            {
                var userMessage = new VoiceCommandUserMessage();
                userMessage.SpokenMessage = "Affichage de la série";

                var tempResponse = VoiceCommandResponse.CreateResponse(userMessage);

                tempResponse.AppLaunchArgument = "serie-" + _series.First(x => x.Value == serie).Key;

                await voiceServiceConnection.RequestAppLaunchAsync(tempResponse);
            }
	}
	

En décomposant un peu ce code, on peut donc s’apercevoir que, dans un premier temps, on affiche un message temporaire à l’utilisateur, lui indiquant que nous réalisons quelques actions. Ce message est notamment nécessaire si jamais l’exécution de votre tâche d’arrière-plan dure plus de 0.5s, car la tâche sera stoppée par le système au-délà de cette durée.

En affichant un message de progression à l’utilisateur, on prolonge cette durée à 5 secondes.

Ensuite, nous récupérons la série correspondante à ce qu’a indiqué l’utilisateur et nous lançons l’application avec en paramètre la série désirée.

Voilà, nous avons désormais une application qui est capable de s’interfacer avec Cortana pour afficher directement des séries sans pour autant lancer l’application au premier plan, mais qui est également capable de récupérer des paramètres directement au sein de la tâche d’arrière-plan.

Conclusion

Dans le prochain, et dernier, article de cette série, nous verrons que nous pouvons faire interagir l’utilisateur directement au sein de Cortana. A bientôt pour de nouvelles aventures !

Le code source est disponible ici (il s’agit des projets Cortana.Integrated et Cortana.Integrated.CortanaAgent).

© SOAT
Toute reproduction interdite sans autorisation de la société SOAT

Nombre de vue : 183

AJOUTER UN COMMENTAIRE