UWP - Integration Cortana with your app

UWP - Integration Cortana with your app

之前寫過幾篇相關 Voice Commands 與 Speech 的文章,例如:

SpeechRecognizerUI /SpeechRecognizer / Voice Command,讓App懂你的話–1

/ Voice Command,讓App懂你的話–2 與 Text-to-Speech (TTS)讓應用程式讀出內容

本篇要介紹的是如何讓你的 App 與 Cortana 整合,并且介紹相關 VCD 1.2 的結構。

 

[重要概念]

1. 實做一個 App Service 提供 Cortana 執行用;

2. App 在 OnLaunch() 時將定義的 VCD 加入 VoiceCommandDefinitionManager
    (或是在 App 中再加入也是可以);

3. App 的 OnActived() 加入處理來自 Cortana 回傳的 command 與 parameters;

3. 對 Cortana 說 “what can I say”,它會列出已加入 VoiceCommandDefinitionManager 的
    App 與 範例語句;

 

Voice Command 1.2 結構説明

     voice commands 被使用來啓動 App 或特定的 action / command。例如:對著 Cortana 說 "Contoso Widgets, show best sellers" 來開啓對應 App 被顯示對應的内容。因此,往下大略説明這個 schema 需要注意的地方。

<?xml version="1.0" encoding="utf-8" ?>
<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.2">
<CommandSet Name="BusControl_en" xml:lang="en-us">
    <!-- AppName 與 CommandPrefix 選一個使用,使用 AppName 會影響 ListenFor 的 RequireAppName 屬性 -->
    <AppName>Bus Control</AppName>
    <Example>Where have Bus</Example>
    <Command Name="ShowNearBusList">
      <Example>Where have Bus</Example>
      <!-- 1 to 10 ListenFor elements -->
      <ListenFor RequireAppName="BeforePhrase">{where} have Bus</ListenFor>
      <Feedback>Search ...</Feedback>
      <!-- Navigate 與 VoiceCommandService 選一個 -->
      <VoiceCommandService Target="BusVoiceCommandService" />
    </Command>
    <!-- more Commands -->
    <PhraseList Label="where">
      <Item>where</Item>
      <Item>here</Item>
      <Item>there</Item>
    </PhraseList>
    <!-- more PhraseList -->
    <!--<PhraseTopic Label="" Scenario="Search" />-->
  </CommandSet>
</VoiceCommands>

 

element 説明:

Element Description
VoiceCommands Required. The root element of a VCD file. Contains between 1 and 15 CommandSet elements, each of which represents the voice commands for a single language.
CommandSet

Required child element of the VoiceCommands element. A container for all the voice commands that an app will accept in the language specified by the required xml:lang attribute.

Attributes:
xml:lang:must be unique in the VoiceCommand document. (single, specific language, specified in language name form)

Name:The Name attribute is optional and can be any arbitrary string;  however, the Name attribute is required in order to reference and update a CommandSet element's PhraseList  programmatically.

The CommandSet element contains the following child elements:
CommandPrefix (0 or 1), Example (exactly 1), Command (1 to 100), PhraseList elements (0 to 10), and PhraseTopic elements (0 to 10).

These child elements must occur in the order listed.

Mutually exclusive CommandPrefix

Optional child element of the CommandSet element. If present, must be the first child element of the CommandSet element.

Specifies a user-friendly name for an app that a user can speak when giving a voice command.

This is useful for apps with names that are long or are difficult to pronounce. (如果你的 App 很難念或是無法正確拼音,可考慮使用這個 element)

Avoid using prefixes that conflict with other voice-enabled experiences.

AppName

Optional child element of the CommandSet element. If present, must be the first child element of the CommandSet element.

Replaces CommandPrefix and supports the RequireAppName attribute and {builtin:AppName} phrase of the ListenFor element.

Specifies a user-friendly name for an app that a user can speak when giving a voice command.

This is useful for apps with names that are long or are difficult to pronounce. (如果你的 App 很難念或是無法正確拼音,可考慮使用這個 element)

Avoid using prefixes that conflict with other voice-enabled experiences.

Command

Required child element of the CommandSet element.

Takes the Name attribute. Defines an app action that users can initiate by speaking and what users can say to initiate the action.

Each Command element can be associated with a specific page in your app.

Contains the following required child elements:

Example (exactly 1), ListenFor (1 to 10), Feedback (exactly 1), and Navigate (exactly 1).

These child elements must occur in the order listed.

Example

Required child of both the CommandSet element and the Command element.

Gives a representative example of what a user can say for a CommandSet as a whole, and for an individual command.

These examples will be will be visible to a user while viewing the What can I say screen on the phone.

This screen appears when a user presses and holds the Search button and says, "Help" or "What can I say?", or taps See more.

Examples should not include the name or prefix of the application, as this is handled automatically.

ListenFor

Required (1 to 10) child element of the Command element.

Contains a word or phrase that your app will recognize for this command.

This may include or be a reference to a PhraseList (or PhraseTopic) element's Label attribute, which appears in the ListenFor element enclosed in curly braces, for example: {myList}, or {myTopic}.

The content of any ListenFor elements can be recognized to activate the command.

An optional RequireAppName attribute can be specified to indicate whether the value of the AppName element can be prepended, appended, or used inline with the ListenFor element.

This attribute supports four values:

  • BeforePhrase

    The user must say the AppName before the ListenFor phrase.

  • AfterPhrase

    The user must say "In|On|Using|With" AppName after the ListenFor phrase.

  • BeforeOrAfterPhrase

    The user must say the AppName before or after the ListenFor phrase.

  • ExplicitlySpecified

    The AppName is explicitly referenced in the ListenFor, using {builtin:AppName}. The user is not required to say the AppName before or after the ListenFor phrase.

Use brackets around a word or words that are optional.(使用 [] 的字或是詞都是可選用的)

That is, the word or words can be spoken, but are not necessary for a match. For example, <ListenFor>[Show] {options}</ListenFor>.

You can set up wildcard functionality by including an asterisk character inside a pair of curly braces, such as <ListenFor> Find {*} </ListenFor>. (可以使用萬用字元 {*} 代表要處理任何用戶說的字或詞,例如用戶說 Find XXXX,如果要匹配找到内容需要另外定義 SpeechRecognitionResult.Text)

In this example, the voice command will match as long as the user speaks "Find", optionally followed by any other word or phrase.

If the voice command for a wildcard-enabled ListenFor element is matched, the SpeechRecognitionResult.Text property will contain the string "…" in the same position as the wildcard.

Feedback

Required child element of the Command element.

Specifies the text that will be displayed and read back to the user when the command is recognized.

If the Feedback element includes a reference to a Label attribute of a PhraseList (or PhraseTopic) element, then every ListenFor element in the containing Command element must also reference the same Label attribute of the PhraseList (or PhraseTopic) element.

Mutually exclusive Navigate

Required child element of the Command element, unless the Command element has a VoiceCommandService child element.

The Target attribute is optional and is typically used to specify the page that the app should navigate to when it launches.

You can obtain the value of the Target attribute (or the empty string if you omit the Target attribute) from the SpeechRecognitionSemanticInterpretation.Properties dictionary using the "NavigationTarget" key.

VoiceCommandService

Required child element of the Command element, unless the Command element has a Navigate child element.

This element specifies that the voice command is handled through an app service (see Windows.ApplicationModel.AppService) with feedback displayed on the Cortana canvas.

The Target attribute is mandatory and must match the value of the Name attribute of the AppService element in the app package manifest.

PhraseList

Optional child of the CommandSet element.

One CommandSet element can contain no more than 2,000 Item elements, and 2,000 Item elements is the combined total limit across all PhraseList elements in a CommandSet.

Each Item specifies a word or phrase that can be recognized to initiate the command that references the PhraseList.

The Items content may be programmatically updated from within your application.

A PhraseList requires the Label attribute, the value of which may appear—enclosed by curly braces—inside ListenFor or Feedback elements, and is used to reference the PhraseList. (例如:<ListenFor>{flag}</ListenFor>,中的 flag 就是 Label)

PhraseList has an optional Disambiguate attribute (default true), which specifies whether this PhraseList will produce user disambiguation when multiple items from the list are simultaneously recognized.

When false, this PhraseList will also be unusable from within Feedback elements and will not produce parameters for your application.

That's useful for phrases that are alternative ways of saying the same thing, but do not require any specific action.

In your app, to find out which phrase from the list was spoken, you can access the SpeechRecognitionSemanticInterpretation.Properties dictionary using a key with the same value as the Label of the PhraseList.

    Item

Optional child of the PhraseList element.

One of multiple words or phrases that can be recognized to initiate a command.

A CommandSet can contain no more than 2,000 Item elements across all of its child PhraseList elements.

PhraseTopic

Optional child of the CommandSet element.

Specifies a topic for large vocabulary recognition.

The topic may specify a single (0 or 1) Scenario attribute and several (0 to 20) Subject child elements for the scenario, which may be used to improve the relevance of the recognition achieved.

A PhraseTopic requires the Label attribute, the value of which may appear—enclosed by curly braces—inside ListenFor or Feedback elements, and is used to reference the PhraseTopic.  (例如:<ListenFor>{flag}</ListenFor>,中的 flag 就是 Label)

The Scenario attribute (default "Dictation") specifies the desired scenario for this PhraseTopic, which may optimize the underlying speech recognition of voice commands using the PhraseTopic to produce results that are better-suited to the desired context of the command. Valid values are "Natural Language", "Search", "Short Message", "Dictation", "Commands", and "Form Filling".

The Subject child elements specify a subject specific to the Scenario attribute of the parent PhraseTopic to further refine the relevance of speech recognition results within spoken commands using the PhraseTopic.

Subjects will be evaluated in the order provided and, when appropriate, later-specified subjects will constrain earlier-specified ones.

Valid inner text values are "Date/Time", "Addresses", "City/State", "Person Names", "Movies", "Music", and "Phone Number". For example: <Subject>Phone Number</Subject>

In your app, to find out the content spoken in the subset of a ListenFor element represented by a PhraseTopic reference, you can access the SpeechRecognitionSemanticInterpretation.Properties dictionary using a key with the same value as the Label of the PhraseTopic.

(如果提供 voice commands 針對特定主題的話,可以是參考這個 element 加以設定,例如:App 主要提供某些 搜尋内容的功能,或是有辨識輸入(default)…等)


上述有用到一些符號例如 {} 與 [],往下補充說明:

Special character Description
{} Contains the value of the Label attribute for the PhraseList or PhraseTopic to reference, for example: {myList}, or {myTopic}.
Used within a ListenFor or Feedback element.
A PhraseList or PhraseTopic reference in a Feedback element must match a corresponding reference in a ListenFor element in the same command.
[] Designates that the enclosed word or phrase is optional.
The enclosed word or phrase may be spoken but is not required to be recognized to initiate the command.
For example, if the contents of a ListenFor element are "[start] [begin] new game", the user can speak "start new game" or "new game" or "begin new game" (or even "start begin new game") to initiate the command.
Each bracketed element is independently optional, but they must be spoken in the correct order to be recognized.
So, in the "new game" example, "start begin new game" would work, but "begin start new game" would not work because of the order in which they were declared.

 

上述的内容主要來自<Voice Command Definition (VCD) elements and attributes v1.2>的定義,關於使用上的重點已經標上了黃色與底綫。

理解整個 Voice Command 新結構之後,往下説明如何操作 Windows.ApplicationModel.VoiceCommands namespace 來藉由 Cortana 產生與用戶的互動。

 

重要元素關聯:

1. VoiceCommandDefinitionManager 負責載入 VCD files。

2. VoiceCommandServiceConnection 提供 background app service 可以與 Cortana 互動(接受或回應)。

3. VoiceCommandContentTile,VoiceCommandUserMessage 負責定義要提供那些内容顯示在 Cortana canvas 上面來與用戶互動。

4. VoiceCommandResponse 負責裝載 VoiceCommmandContentTile 或 VoiceCommandUserMessage 來傳遞給 Cortana。

 

VoiceCommandDefinitionManager

     static class 負責允許從 VCD(Voice command definition) 安裝 command sets,與取得已經安裝的 command sets。重要方法與屬性如下:

Type Name Description
Method InstallCommandDefinitionsFromStorageFileAsync Installs the CommandSet elements in a Voice Command Definition (VCD) file.
Property InstalledCommandDefinitions Read-only. A dictionary that contains all installed command sets that have a Name attribute set in the Voice Command Definition (VCD) file.

 

VoiceCommandServiceConnection

     提供 background app service 與 Cortana 連接的接口,負責接受 Cortana 傳入的 voice command 與 當下的 message,更可以回應内容到 Cortana Canvans 上。

重要方法與屬性如下:

Type Name Description
Event VoiceCommandCompleted 當 voice command 被完成與 background app service 被 terminate 時會觸發。代表與 Cortana 完成了所有的交易。
Method FromAppServiceTriggerDetails background app service 在執行時,必須藉由該方法取得由 Cortana 傳入的 AppServiceTriggerDetails 并且轉換成 VoiceCommandServiceConnection。
  GetVoiceCommandAsync 截取用戶 submit 給 Cortana 的 voice 或 文字,剛好符合的 voice command。
  ReportFailureAsync Sends a response to Cortana indicating the voice command has failed.
  ReportProgressAsync Sends a response to Cortana indicating voice command is being processed.
  ReportSuccessAsync Sends a response to Cortana indicating the voice command has succeeded.
  RequestAppLaunchAsync Sends a response to Cortana indicating the command should be handled by the app in the foreground.
傳送給 Cortana 之後,Cortana canvas 會呈現 app service 所指定的 URL 要執行。
  RequestConfirmationAsync Sends a response to Cortana indicating the voice command requires confirmation.
傳送給 Cortana 之後,Cortana canvas 會呈現 app service 提供的訊息讓用戶確認。

使用這個方法會得到一個 VoiceCommandConfirmationResult,重要屬性如下:
Property Description
Confirmed Read-only. Gets the response to the question specified by the background app and shown on the Cortana confirmation screen. (Boolean)
  RequestDisambiguationAsync Sends a response to Cortana indicating the voice command returned more than one result and requires the user to select one.
傳送給 Cortana 之後,Cortana canvas 會呈現 app service 提供的選項讓用戶選擇。

使用這個方法會得到一個 VoiceCommandDisambiguationResult,重要屬性如下:
Property Description
SelectedItem Read-only. The item selected from the list of items displayed on the Cortana disambiguation screen.
Item 是 VoiceCommandContentTile  物件。
Property Language Read-only. 取得目前 voice command 的 locale。

上面有介紹幾個 methods:RequestAppLaunchAsyncRequestConfirmationAsyncRequestDisambiguationAsync 提供 background app service 與 User 藉由 Cortana 互動取得結果進一步在 app service 收到訊息繼續往下處理。

 

VoiceCommand

     取得由 Cortana 傳入給 background app 的資訊,例如:  speech or text。其 command 必須來自 Voice Command Definition (VCD) file 的宣告。

重要屬性如下:

Property Description
CommandName Read-only. Gets the value of the Name attribute for the Command element declared in the Voice Command Definition (VCD) file.
Properties Read-only. Gets a dictionary containing the semantic properties of the recognized phrase in the voice command.
例如:
在 VCDs 定义了 <PhraseList />  的 Label 属性就会变成了 Properties 的 key。
SpeechRecognitionResult Read-only. Gets the SpeechRecognitionResult object representing the results returned by the voice command.

 

VoiceCommandContentTile

    An asset, containing image, text, and link data, provided by the background app service for display on the Cortana canvas. 可以理解上述介紹的那 3 個 requests

正式需要這個 VoiceCmmandContentTile 來協助。可以使用的屬性如下:

Name Description
AppContext Read/write. Gets or sets supplementary information the background app service can associate with the content tile.
AppLaunchArgument Read/write. Gets or sets a string as a launch parameter that can be associated with the response by the background app service.
ContentTileType

Read/write. Gets or sets the layout template used to display the content tile on the Cortana canvas.
VoiceCommandContentTileType

Member Value Description
TitleOnly 0 Only item title.
TitleWithText 1 Item title with up to three lines of text.
TitleWith68x68Icon 2 Item title with small, square image.
TitleWith68x68IconAndText 3 Item title, up to three lines of text, and small square image.
TitleWith68x92Icon 4 Item title with tall image.
TitleWith68x92IconAndText 5 Item title, up to three lines of text, and tall image.
TitleWith280x140Icon 6 Item title with wide image.
TitleWith280x140IconAndText 7 Item title, up to two lines of text, and wide image.
Image Read/write. Gets or sets an image the background app service can associate with the content tile.
TextLine1 Read/write. Gets or sets the first line of text the background app service can associate with the content tile.
TextLine2 Read/write. Gets or sets the second line of text the background app service can associate with the content tile.
TextLine3 Read/write. Gets or sets the third line of text the background app service can associate with the content tile.
Title Read/write. Gets or sets the title the background app service can associate with the content tile.

VoiceCommandResponse 的 MaxSupportedVoiceCommandContentTiles 提供了最大可用 VoiceCommandContentTile 的數量,而這個數量的多寡其實是受限于

Cortana canvas 提供給 feedback screen 的 size,VoiceCommandResponse 針對 RequestProgressAsync,RequestConfirmationAsync,RequestDisambiguationAsync 也會有影響。

 

VoiceCommandUserMessage

     除了 VoiceCommandContentTile 可以提供畫面内容外,VoiceCommandUserMessage 則是提供 message 顯示在 Cortana canvas 上。

而這個 message 可能是:

重要屬性如下:

Property Description
DisplayMessage Read/write.  Gets or sets the message that is shown on the Cortana canvas.
SpokenMessage Read/write. The message that is spoken by Cortana.

 

VoiceCommandResponse

    讓 background app service 爲了 progress,confirmation,disambiguation,completion,或是 failure screen displayed 傳送 response。

VoiceCommandContentTile  與 VoiceCommandUserMessage 定義要呈現的内容,那 VoiceCommandResponse 就是爲了讓 background app service 裝載并傳送它們給 Cortana。

主要的方法與屬性如下:

Type Name Description
Method CreateResponse(VoiceCommandUserMessage) Creates a VoiceCommandResponse object used in calls to ReportProgressAsync, ReportSuccessAsync or ReportFailureAsync.
  CreateResponse(VoiceCommandUserMessage,IIterable(VoiceCommandContentTile)) Creates a VoiceCommandResponse object used in calls to ReportProgressAsync, ReportSuccessAsync or ReportFailureAsync.
  CreateResponseForPrompt(VoiceCommandUserMessage,VoiceCommandUserMessage) Creates a VoiceCommandResponse object used in calls to RequestConfirmationAsync or RequestDisambiguationAsync.
  CreateResponseForPrompt(VoiceCommandUserMessage,VoiceCommandUserMessage,IIterable(VoiceCommandContentTile)) Creates a VoiceCommandResponse object used in calls to RequestConfirmationAsync or RequestDisambiguationAsync.
Property AppLaunchArgument Read/write. Gets or sets a string as a launch parameter that can be associated with the response by the background app service.
  MaxSupportedVoiceCommandContentTiles Read-only. Gets the maximum number of content tiles the background app service can display on the Cortana canvas.
  Message Read/write. The initial message that is spoken by Cortana and shown on the Cortana canvas.
  RepeatMessage Read/write. The secondary message (for disambiguation and confirmation screens only) that is spoken by Cortana and shown on the Cortana canvas, if a response was not understood.
  VoiceCommandContentTiles Read-only. The collection of assets, containing image and text data, provided by the background app service for display on the Cortana canvas.

[注意] background app service 無法直接使用 VoiceCommandResponse 要求 Cortana 呈現内容在 Cortana canvas,

              需要由 Cortana 觸發 background app service 讓它可以取得  VoiceCommandServiceConnection 才可以使用

 

介紹相關的元素之後,往下藉由一個簡單的程式碼說明怎麽讓 AppService 與 Cortana 整合。

  • 目前沒有正體中文的 Cortana ,改以 簡體中文(切換地區中國,下載 Speech 語系包,設定簡體中文爲第一語系)爲例子。

A. 定義 Voice Command Definition (VCD) files。

<?xml version="1.0" encoding="utf-8" ?>
<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.2">
  <CommandSet Name="BusControl_cn" xml:lang="zh-cn">
    <AppName>公交车控制器</AppName>    
    <Example>哪里有公交车</Example>
    <Command Name="SearchWhereBusList">
      <Example>哪里有公交车</Example>
      <ListenFor RequireAppName="BeforePhrase">{where}有公交车</ListenFor>
      <Feedback>搜寻{where}有公交车</Feedback>
      <VoiceCommandService Target="com.poumason.uwp.VCBusService"/>
    </Command>
    <Command Name="CheckStartBusStation">
      <Example>还有发车吗?</Example>
      <ListenFor RequireAppName="ExplicitlySpecified">{builtin:AppName}还有发车吗</ListenFor>
      <Feedback>确认中...</Feedback>
      <VoiceCommandService Target="com.poumason.uwp.VCBusService"/>
    </Command>
    <Command Name="OpenApp">
      <Example>开启公交车控制器</Example>
      <ListenFor>开启公交车控制器</ListenFor>
      <ListenFor RequireAppName="ExplicitlySpecified">显示[我]{builtin:AppName}的公交车</ListenFor>
      <Feedback>开启中</Feedback>
      <Navigate Target="showNow" />
    </Command>
    <PhraseList Label="where">
      <Item>哪里</Item>
      <Item>附近</Item>
      <Item>最近</Item>
    </PhraseList>
  </CommandSet>
</VoiceCommands>
private async void OnLoadVCDFiles(object sender, RoutedEventArgs e)
{
    // 加入定义好的 Voice Command Definition file
    var vcdFolder = await Package.Current.InstalledLocation.GetFolderAsync("Commands");
    var vcdfile = await vcdFolder.GetFileAsync("Command1.xml");
    var installedCS = VoiceCommandDefinitionManager.InstalledCommandDefinitions;
    await VoiceCommandDefinitionManager.InstallCommandDefinitionsFromStorageFileAsync(vcdfile);
}

 

B. 建立一個負責處理 voice command 内容的  viewmodel。

/// <summary>
/// 以公车资讯为例,当作可使用的 ViewModel
/// </summary>
public class BusData
{
    public String Name { get; set; }
 
    public Double Lat { get; set; }
 
    public Double Lon { get; set; }
 
    public String GoId { get; set; }
 
    public String BackId { get; set; }
 
    public BusData(String param)
    {
        String[] paramValue = param.Split(',');
        if (paramValue.Length == 6)
        {
            Name = paramValue[0];
            Lat = Double.Parse(paramValue[1]);
            Lon = Double.Parse(paramValue[2]);
            GoId = paramValue[4];
            BackId = paramValue[5];
        }
    }
}

 

C. 建立 background app service 處理 Cortana 送入的内容:

public async void Run(IBackgroundTaskInstance taskInstance)
{
    serviceDeferral = taskInstance.GetDeferral();
    taskInstance.Canceled += TaskInstance_Canceled;
 
    // Get trigger details , it from Cortana
    var triggerDetails = taskInstance.TriggerDetails as AppServiceTriggerDetails;
    if (triggerDetails != null && triggerDetails.Name == "com.poumason.uwp.VCBusService")
    {
        // 取得 VoiceCommandServiceConnection
        vcConnection = VoiceCommandServiceConnection.FromAppServiceTriggerDetails(triggerDetails);
        vcConnection.VoiceCommandCompleted += VcConnection_VoiceCommandCompleted;
 
        VoiceCommand vCommand = await vcConnection.GetVoiceCommandAsync();
        // 判断 command name
        switch (vCommand.CommandName)
        {
            case "SearchWhereBusList":
                foreach (var item in vCommand.Properties)
                {
                    Debug.WriteLine(String.Format("{0}={1}", item.Key, item.Value));
                }
                await HandleSearchWhereBusList();
                break;
            case "CheckStartBusStation":
                ShowHaveOtherBusConfirm();
                break;
            default:
                // 需要注册这个处理,以相容那些不再使用的 VCDs.
                LaunchAppInForeground();
                break;
        }
    }
}

在处理收到的 VoiceCommand 时,在 Properties 中包装了为 <PhraseList /> 的 Label 属性对应的值,藉由它就可以取得用户所发出的字,属于在 <PhraseList /> 中的  <Item />。

C-1. 提供 Confirm 的互動:

private async void ShowHaveOtherBusConfirm()
{
    List<VoiceCommandContentTile> vcTiles = new List<VoiceCommandContentTile>();
    vcTiles.Add(new VoiceCommandContentTile
    {
        Title = "目前已经没有公车了,是否继续查询?",
        ContentTileType = VoiceCommandContentTileType.TitleWithText,
        Image = await StorageFile.GetFileFromApplicationUriAsync(
                    new Uri("ms-appx:///BusSearchVCService/Images/GreyTile.png"))
    });
 
    // Create a VoiceCommandUserMessage for the initial question.
    VoiceCommandUserMessage userMsg = new VoiceCommandUserMessage();
    userMsg.DisplayMessage = userMsg.SpokenMessage = "请选择";
    // Create a VoiceCommandUserMessage for the second question,
    // in case Cortana needs to reprompt.
    VoiceCommandUserMessage repeatMsg = new VoiceCommandUserMessage();
    repeatMsg.DisplayMessage = repeatMsg.SpokenMessage = "请重新选择";
 
    VoiceCommandResponse response = VoiceCommandResponse.CreateResponseForPrompt(userMsg, repeatMsg, vcTiles);
 
    var vcConfirm = await vcConnection.RequestConfirmationAsync(response);
    if (vcConfirm != null)
    {
        if (vcConfirm.Confirmed)
        {
            await HandleSearchWhereBusList();
        }
        else
        {
            VoiceCommandUserMessage cancelMsg = new VoiceCommandUserMessage();
            cancelMsg.DisplayMessage = repeatMsg.SpokenMessage = "非常抱歉";
            VoiceCommandResponse response1 = VoiceCommandResponse.CreateResponse(cancelMsg);
            vcConnection.ReportFailureAsync(response1);
        }
    }
}

C-2. 提供 Selection 的 互動:

private async Task HandleSearchWhereBusList()
{
    await ShowProgressScreen("处理中 ...");
 
    // 提供 VoiceCommenContentTile 给用户选择
    List<BusData> result = await new SearchService().SearchBusList();
    result = result.Take(5).ToList();
    List<VoiceCommandContentTile> vcTiles = new List<VoiceCommandContentTile>();
    int i = 0;
    foreach (var item in result)
    {
        vcTiles.Add(new VoiceCommandContentTile
        {
            Title = item.Name,
            TextLine1 = String.Format("{0}_{1}", item.Name, i),
            ContentTileType = VoiceCommandContentTileType.TitleWithText,
            Image = await StorageFile.GetFileFromApplicationUriAsync(
                new Uri("ms-appx:///BusSearchVCService/Images/GreyTile.png")),
            AppContext = item
        });
        i += 1;
    }
 
    // Create a VoiceCommandUserMessage for the initial question.
    VoiceCommandUserMessage userMsg = new VoiceCommandUserMessage();
    userMsg.DisplayMessage = userMsg.SpokenMessage = "请选择";
    // Create a VoiceCommandUserMessage for the second question,
    // in case Cortana needs to reprompt.
    VoiceCommandUserMessage repeatMsg = new VoiceCommandUserMessage();
    repeatMsg.DisplayMessage = repeatMsg.SpokenMessage = "请重新选择";
 
    //如果要使用 RequestDisambiguationAsync 需要使用 CreateResponseForPrompt
    VoiceCommandResponse response = VoiceCommandResponse.CreateResponseForPrompt(userMsg, repeatMsg, vcTiles);            
    // 显示多个选择
    var vcResult = await vcConnection.RequestDisambiguationAsync(response);
    // 取得选择结果
    BusData selectedItem = vcResult.SelectedItem.AppContext as BusData;
    VoiceCommandUserMessage resultMsg = new VoiceCommandUserMessage();
    resultMsg.DisplayMessage = resultMsg.SpokenMessage = "选择:" + selectedItem.Name;
    VoiceCommandResponse response1 = VoiceCommandResponse.CreateResponse(resultMsg);
    await vcConnection.ReportSuccessAsync(response1);
}

C-3. 提供  AppLauch 的互動:

private async void LaunchAppInForeground()
{
    var userMessage = new VoiceCommandUserMessage();
    userMessage.SpokenMessage = "开启 App 中...请稍后";
    var response = VoiceCommandResponse.CreateResponse(userMessage);
    response.AppLaunchArgument = "";
    await vcConnection.RequestAppLaunchAsync(response);
}

 

D. 處理 foreground app 接受 Cortana 的 parameters:

protected override void OnActivated(IActivatedEventArgs args)
{
    base.OnActivated(args);
    if (args.Kind == ActivationKind.VoiceCommand)
    {
        // The arguments can represent many different activation types. Cast it so we can get the
        // parameters we care about out.
        var commandArgs = args as VoiceCommandActivatedEventArgs;
        SpeechRecognitionResult speechRecognitionResult = commandArgs.Result;
 
        String commandName;
        // find command name
        foreach (var item in speechRecognitionResult.RulePath)
        {
            Debug.WriteLine(item);
            commandName = item; //speechRecognitionResult.RulePath[0];
        }
 
        String commandMode;
        // find Command Mode or other <PhraseList> item
        foreach (var item in speechRecognitionResult.SemanticInterpretation.Properties)
        {
            Debug.WriteLine(item);
            commandMode = item.Value.FirstOrDefault();
        }
    }
}

 

[範例程式]

https://github.com/poumason/DotblogsSampleCode

請 clone 這個 GitHub 的内容,參考範例《IntegrateCortanaApp》。謝謝。

======

該篇讀完之後我發現與 Cortana 的整合非常方便啊,而且定义好的话直接透过在 Win10 的搜寻框输入符合 Command 的字串一样可以搜寻。

衹是說如果在 Win 10 設備上搜尋任然沒有辦法有像過去提供的 Search Extensions 方便,

但是藉由 voice command 與 background app service 確實擴充了使用的情境。希望該篇對大家有所幫助,謝謝。

 

References

Launch a background app with voice commands in Cortana

Using Cortana to interact with your customers (10 by 10)

Integration of an application to Cortana & Article-CortanaExtensibility

Adding Cortana to your UWP App with Visual Studio 2015

Adding Cortana to your Windows 10 App

Microsoft/Windows-universal-samples

Windows 10–Adding Cortana and Voice Control to the UWP/AllJoyn/IoT Core/Lightbulb Demo

Cortana and your app—helping people get things done

Voice Command Definition (VCD) elements and attributes v1.2

JustinXinLiu/SwipeableSplitView