從 Cortana 中背景應用程式到前景應用程式的深層連結

警告

自 Windows 10 May 2020 更新版 (版本 2004,代號「20H1」) 起,不再支援此功能。

Cortana 中提供背景應用程式的深層連結,以在特定狀態或內容中將應用程式啟動至前景。

注意

啟動前景應用程式時,Cortana 和背景應用程式服務都會終止。

預設情況下,深層連結會顯示在 Cortana 完成畫面上,如下所示 (「前往 AdventureWorks」),但您可以在其他各種畫面上顯示深層連結。

即將來程的 Cortana 背景應用程式完成螢幕快照

概觀

使用者可以透過 Cortana 存取您的應用程式,方法是:

  • 將它啟動為前景應用程式 (請參閱透過 Cortana 啟用具有語音命令的前景應用程式)。
  • 將特定功能公開為背景應用程式服務 (請參閱 使用語音命令在 Cortana 中啟用背景應用程式)。
  • 深層連結至特定頁面、內容和狀態或內容。

我們在此處討論深層連結。

當 Cortana 和 App Service 做為完整功能應用程式的閘道時,深層連結很有用 (不需要使用者透過 [開始] 功能表啟動應用程式),或提供無法透過 Cortana 存取應用程式內更豐富的詳細資料和功能。 深層連結是增加可用性並提升應用程式的另一種方式。

有三種方式可提供深層連結:

  • 各種 Cortana 畫面上的「前往<應用程式>」連結。
  • 嵌入在各種 Cortana 畫面上的內容圖塊中的連結。
  • 以程式設計方式從背景應用程式服務啟動前景應用程式。

Cortana 會在大部分畫面的內容卡下方顯示「前往<應用程式>」深層連結。

背景應用程式完成畫面上 Cortana [移至應用程式] 深層鏈接的螢幕快照。

您可以提供此連結的啟動引數,此連結會以與應用程式服務類似的內容開啟您的應用程式。 如果您沒有提供啟動引數,應用程式會啟動至主畫面。

在此範例中,從 AdventureWorks 範例 AdventureWorksVoiceCommandService.cs,我們會將指定的目的地 (destination) 字串傳遞至 SendCompletionMessageForDestination 方法,此方法會擷取所有相符的行程,並提供應用程式的深層連結。

首先,我們會建立 Cortana 所口說的 VoiceCommandUserMessage (userMessage),並在 Cortana 畫布上顯示。 接著會建立 VoiceCommandContentTile 清單物件,以顯示畫布上的結果卡片集合。

然後,這兩個物件會傳遞至 VoiceCommandResponse 物件的 CreateResponse 方法 (response)。 然後,我們將回應物件的 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 畫面上新增內容卡片的深層連結。

使用 AdventureWorks 即將進行交接的 Cortana 背景應用程式流程,適用於端對端 Cortana 背景應用程式流程的 Cortana 畫布螢幕快照AdventureWorks「即將到來的旅行」,具有切換螢幕

如同「前往<應用程式>」連結,您可以提供啟動引數,以使用應用程式服務類似的內容來開啟您的應用程式。 如果您沒有提供啟動引數,則內容圖塊不會連結至您的應用程式。

在此來自 AdventureWorks 範例的 AdventureWorksVoiceCommandService.cs 的範例中,我們將指定的目的地傳遞給 SendCompletionMessageForDestination 方法,該方法會擷取所有符合的行程並提供包含指向應用程式的深層連結的內容卡片。

首先,我們會建立 Cortana 所口說的 VoiceCommandUserMessage (userMessage),並在 Cortana 畫布上顯示。 接著會建立 VoiceCommandContentTile 清單物件,以顯示畫布上的結果卡片集合。

然後,這兩個物件會傳遞至 VoiceCommandResponse 物件的 CreateResponse 方法 (response)。 然後,我們將 AppLaunchArgument 屬性值設定為語音命令中目的地的值。

最後,我們呼叫 VoiceCommandServiceConnectionReportSuccessAsync 方法。 在此處,我們會將具有不同 AppLaunchArgument 參數值的兩個內容圖塊新增至 VoiceCommandContentTile 清單中,用於 VoiceCommandServiceConnection 物件的 ReportSuccessAsync 呼叫中。

/// <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 延伸模組。

此處,我們會宣告 windows.personalAssistantLaunchAdventure Works 應用程式的擴充功能。

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

通訊協議合約

您的應用程式會透過使用通訊協定合約的統一資源識別碼 (URI) 啟用,啟動至前景。 您的應用程式必須覆寫應用程式的 OnActivated 事件,並檢查通訊協定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();
  }