从 Cortana 中的后台应用到前台应用的深层链接

警告

自 Windows 10 2020 年 5 月更新(版本 2004,codename“20H1”)起,不再支持此功能。

从 Cortana 中的后台应用提供深层链接,以便在特定状态或上下文中将应用启动到前台

注意

启动前台应用时,Cortana 和后台应用服务都会终止

深层链接默认显示在 Cortana 完成屏幕上,如下所示(“转到 AdventureWorks”),但你可以在其他各种屏幕上显示深层链接

即将开始的行程的 Cortana 后台应用完成的屏幕截图

概述

用户可以使用以下方式通过 Cortana 访问你的应用

本文将介绍深层链接。

当 Cortana 和应用服务充当全功能应用的网关(而不要求用户通过“开始”菜单启动应用)时,或者需要提供对应用中更丰富细节和功能的访问(无法通过 Cortana 做到这一点)时,深层链接非常有用。 深层链接是提高可用性和推广应用的另一种方式。

可通过三种方式提供深层链接:

  • 在各种 Cortana 屏幕上提供“转到 <应用>”链接
  • 在各种 Cortana 屏幕上的内容磁贴中嵌入链接
  • 以编程方式从后台应用服务启动前台应用。

Cortana 在大多数屏幕上的内容卡下方都会显示一个“转到 <应用>”深层链接

后台应用完成屏幕上 Cortana“转到应用”深层链接的屏幕截图。

可为此链接提供启动参数,用于在与应用服务类似的上下文中打开应用。 如果不提供启动参数,则会在主屏幕上启动应用。

此示例摘自 AdventureWorks 示例的 AdventureWorksVoiceCommandService.cs,在其中,我们将指定的目标 (destination) 字符串传递给 SendCompletionMessageForDestination 方法,该方法检索所有匹配的行程并提供应用的深层链接

首先,我们创建一个由 Cortana 讲出的并显示在 Cortana 画布上的 VoiceCommandUserMessage (userMessage)。 然后创建一个 VoiceCommandContentTile 列表对象,用于在画布上显示结果卡的集合

然后将这两个对象传递给 VoiceCommandResponse 对象 (response) 的 CreateResponse 方法。 然后将响应对象的 AppLaunchArgument 属性值设置为传递给此函数的 destination 的值。 当用户点击 Cortana 画布上的内容磁贴时,参数值将通过响应对象传递给应用。

最后,调用 VoiceCommandServiceConnectionReportSuccessAsync 方法

/// <summary>
/// Show details for a single trip, if the trip can be found. 
/// This demonstrates a simple response flow in Cortana.
/// </summary>
/// <param name="destination">The destination specified in the voice command.</param>
private async Task SendCompletionMessageForDestination(string destination)
{
...
    IEnumerable<Model.Trip> trips = store.Trips.Where(p => p.Destination == destination);

    var userMessage = new VoiceCommandUserMessage();
    var destinationsContentTiles = new List<VoiceCommandContentTile>();
...
    var response = VoiceCommandResponse.CreateResponse(userMessage, destinationsContentTiles);

    if (trips.Count() > 0)
    {
        response.AppLaunchArgument = destination;
    }

    await voiceServiceConnection.ReportSuccessAsync(response);
}

可将深层链接添加到各种 Cortana 屏幕上的内容卡中

Cortana 画布的屏幕截图,用于结束 Cortana 后台应用流,使用 AdventureWorks 即将推出的行程和交接AdventureWorks“即将到来的旅行”与转接屏幕

与使用“转到 <应用>”链接时一样,你可以提供启动参数,以便在与应用服务类似的上下文中打开应用。 如果不提供启动参数,则内容磁贴不会链接到应用。

此示例摘自 AdventureWorks 示例的 AdventureWorksVoiceCommandService.cs,在其中,我们将指定的目标传递给 SendCompletionMessageForDestination 方法,该方法检索所有匹配的行程并提供包含应用深层链接的内容卡

首先,我们创建一个由 Cortana 讲出的并显示在 Cortana 画布上的 VoiceCommandUserMessage (userMessage)。 然后创建一个 VoiceCommandContentTile 列表对象,用于在画布上显示结果卡的集合

然后将这两个对象传递给 VoiceCommandResponse 对象 (response) 的 CreateResponse 方法。 然后将 AppLaunchArgument 属性值设置为语音命令中目标的值

最后,调用 VoiceCommandServiceConnectionReportSuccessAsync 方法。 在此处,我们将两个具有不同 AppLaunchArgument 参数值的内容磁贴,添加到 VoiceCommandServiceConnection 对象的 ReportSuccessAsync 调用中使用的 VoiceCommandContentTile 列表

/// <summary>
/// Show details for a single trip, if the trip can be found. 
/// This demonstrates a simple response flow in Cortana.
/// </summary>
/// <param name="destination">The destination specified in the voice command.</param>
private async Task SendCompletionMessageForDestination(string destination)
{
    // If this operation is expected to take longer than 0.5 seconds, the task must
    // supply a progress response to Cortana before starting the operation, and
    // updates must be provided at least every 5 seconds.
    string loadingTripToDestination = string.Format(
               cortanaResourceMap.GetValue("LoadingTripToDestination", cortanaContext).ValueAsString,
               destination);
    await ShowProgressScreen(loadingTripToDestination);
    Model.TripStore store = new Model.TripStore();
    await store.LoadTrips();

    // Query for the specified trip. 
    // The destination should be in the phrase list. However, there might be  
    // multiple trips to the destination. We pick the first.
    IEnumerable<Model.Trip> trips = store.Trips.Where(p => p.Destination == destination);

    var userMessage = new VoiceCommandUserMessage();
    var destinationsContentTiles = new List<VoiceCommandContentTile>();
    if (trips.Count() == 0)
    {
        string foundNoTripToDestination = string.Format(
               cortanaResourceMap.GetValue("FoundNoTripToDestination", cortanaContext).ValueAsString,
               destination);
        userMessage.DisplayMessage = foundNoTripToDestination;
        userMessage.SpokenMessage = foundNoTripToDestination;
    }
    else
    {
        // Set plural or singular title.
        string message = "";
        if (trips.Count() > 1)
        {
            message = cortanaResourceMap.GetValue("PluralUpcomingTrips", cortanaContext).ValueAsString;
        }
        else
        {
            message = cortanaResourceMap.GetValue("SingularUpcomingTrip", cortanaContext).ValueAsString;
        }
        userMessage.DisplayMessage = message;
        userMessage.SpokenMessage = message;

        // Define a tile for each destination.
        foreach (Model.Trip trip in trips)
        {
            int i = 1;
            
            var destinationTile = new VoiceCommandContentTile();

            destinationTile.ContentTileType = VoiceCommandContentTileType.TitleWith68x68IconAndText;
            destinationTile.Image = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///AdventureWorks.VoiceCommands/Images/GreyTile.png"));

            destinationTile.AppLaunchArgument = trip.Destination;
            destinationTile.Title = trip.Destination;
            if (trip.StartDate != null)
            {
                destinationTile.TextLine1 = trip.StartDate.Value.ToString(dateFormatInfo.LongDatePattern);
            }
            else
            {
                destinationTile.TextLine1 = trip.Destination + " " + i;
            }

            destinationsContentTiles.Add(destinationTile);
            i++;
        }
    }

    var response = VoiceCommandResponse.CreateResponse(userMessage, destinationsContentTiles);

    if (trips.Count() > 0)
    {
        response.AppLaunchArgument = destination;
    }

    await voiceServiceConnection.ReportSuccessAsync(response);
}

还可以使用启动参数以编程方式启动应用,以便在与应用服务类似的上下文中打开应用。 如果不提供启动参数,则会在主屏幕上启动应用。

在此处,我们将值为“Las Vegas”的 AppLaunchArgument 参数添加到 VoiceCommandServiceConnection 对象的 RequestAppLaunchAsync 调用中使用的 VoiceCommandResponse 对象

var userMessage = new VoiceCommandUserMessage();
userMessage.DisplayMessage = "Here are your trips.";
userMessage.SpokenMessage = 
  "You have one trip to Vegas coming up.";

response = VoiceCommandResponse.CreateResponse(userMessage);
response.AppLaunchArgument = "Las Vegas";
await  VoiceCommandServiceConnection.RequestAppLaunchAsync(response);

应用部件清单

若要启用应用的深层链接,必须在应用项目的 Package.appxmanifest 文件中声明 windows.personalAssistantLaunch 扩展。

在此处,我们将为 Adventure Works 应用声明 windows.personalAssistantLaunch 扩展

<Extensions>
  <uap:Extension Category="windows.appService" 
    EntryPoint="AdventureWorks.VoiceCommands.AdventureWorksVoiceCommandService">
    <uap:AppService Name="AdventureWorksVoiceCommandService"/>
  </uap:Extension>
  <uap:Extension Category="windows.personalAssistantLaunch"/> 
</Extensions>

Protocol 协定

应用使用 Protocol 协定通过统一资源标识符 (URI) 激活启动到前台。 应用必须重写其 OnActivated 事件并检查 Protocol 的 ActivationKind。 有关详细信息,请参阅处理 URI 激活

在此处,我们将解码 ProtocolActivatedEventArgs 提供的 URI 以访问启动参数。 对于此示例,Uri 设置为“windows.personalassistantlaunch:?LaunchContext=Las Vegas”

if (args.Kind == ActivationKind.Protocol)
  {
    var commandArgs = args as ProtocolActivatedEventArgs;
    Windows.Foundation.WwwFormUrlDecoder decoder = 
      new Windows.Foundation.WwwFormUrlDecoder(commandArgs.Uri.Query);
    var destination = decoder.GetFirstValueByName("LaunchContext");

    navigationCommand = new ViewModel.TripVoiceCommand(
      "protocolLaunch",
      "text",
      "destination",
      destination);

    navigationToPageType = typeof(View.TripDetails);

    rootFrame.Navigate(navigationToPageType, navigationCommand);

    // Ensure the current window is active.
    Window.Current.Activate();
  }