After you add a BroadcastReceiver in your manifest, Fire TV will send any intents with Alexa directives to the BroadcastReceiver class you specify, e.g., AlexaDirectiveReceiver. This BroadcastReceiver class is where you accept and handle the directives Alexa sends to your app. You'll need to develop your own handling logic in this class.
Sample App Notes
The sample app already reacts to directives, so you don't need to do any coding in this step. You can view how the sample app handles directives in the AlexaDirectiveReceiver class (inside java/com/example/vskfiretv/company/receiver). Expand the "How the Sample App Handles Directives" button section near the bottom of this topic for an explanation of the sample app's handling logic.
VSK directives are communicated to your app through the VSK Agent when a user makes a voice request through Alexa. More detail about the directives are described in the API Reference.
Once you receive the directive in your BroadcastReceiver, you need to fulfill the request of the directive in your app yourself. A commonly designed model-view-viewmodel (MVVM) application usually makes this fairly straightforward as most apps have Master View Model that already routes remote clicks and search queries around the application. Passing the pertinent string from the BroadcastReceiver to your View Model is usually all that is needed to execute a VSK directive in your app.
As an example, when the user says, "Find comedy movies," you might receive a SearchAndDisplayResults directive for MediaType: Movie and Genre: Comedies. If your app supports search, you already have a mechanism for the user to input text into an input field with the remote and search what they type. You can use the same search query mechanism with the VSK to execute against "comedy movies" as parsed from the directive.
Reacting to Directives
The directives Alexa sends depends on the capabilities you declare. Customize your BroadcastReceiver class to handle the directives within your app (as described in Step 7: Add a BroadcastReceiver). More specifically:
Add logic to parse the directive payload (which is present in intents received by these directives) and execute the requested behavior in your app.
Respond to the VSK Agent with the directive to indicate whether it was handled successfully or not. You include this status via the PendingIntent included in the broadcast intent. The status is a Boolean, with either a true (for success) or false (for failure) value. Sample response code is shown in the Add a BroadcastReceiver Java Class in Step 7.
Directives That Are Required for Certification
Alexa can send directives for a lot of different requests. Do you have to handle them all? If your app doesn't handle certain directives, will your app fail certification? In the list of utterances for each directive, if support for an utterance is required, the words "Required for certification" appear below this utterance.
Required utterances include searches for titles, actors, genres, franchises, as well as transport-control utterances. If an utterance is marked as required, but your app doesn't support the capability, your app won't fail certification and you can disregard that requirement. For optional directives such as ChangeChannel, the required directives are only requirements if you choose to handle that directive.
API Reference
The expected logic for implementing and handling each of the above directives is described in the API reference documentation. Alexa expects a specific response and action for each incoming directive. See the following for more details:
See the Utterances Reference for a detailed list of the various utterances that Fire TV devices support, including each phrase by locale as well. You can view the utterances for the RemoteVideoPlayer here:
For a detailed explanation about how the sample app handles directives, expand the following section.
How the Sample App Handles Directives
As you write code to handle the VSK directives, it might help to look more closely at how the AlexaDirectiveReceiver class in the sample app handles these directives.
The VSK Agent packages the directive in an intent, which is passed into the onReceive method:
publicvoidonReceive(finalContextcontext,finalIntentintent){Log.i(TAG,MessageFormat.format("Handling Intent from VSK Agent: {0}",intent));if(context==null||intent==null){return;}//...}
The code gets several properties from the directive and sets them to strings named directiveNameSpace, directiveName, directivePayload, and directivePlayloadVersion:
EXTRA_DIRECTIVE_NAMESPACE contains the VSK API interface name (e.g., RemoteVideoPlayer). The EXTRA_DIRECTIVE_NAME is the directive for the interface (e.g., SearchAndDisplayResults). The EXTRA_DIRECTIVE_PAYLOAD gets the payload properties for the directive. And EXTRA_DIRECTIVE_PAYLOAD_VERSION gets the interface version (e.g., 3). (You cannot print out the entire directive — just different properties that the VSK Agent exposes.)
A series of "if" conditions calls the appropriate function to handle the directive based on the type of directive received:
if(directiveName!=null){if("SearchAndPlay".equals(directiveName)){handleSearchAndPlay(directivePayload);}elseif("SearchAndDisplayResults".equals(directiveName)){handleSearchAndDisplayResults(directivePayload);}elseif("pause".equals(directiveName)){handlePause();}elseif("play".equals(directiveName)){handlePlay();}elseif("stop".equals(directiveName)){handleStop();}elseif("next".equals(directiveName)){handleNext();}elseif("previous".equals(directiveName)){handlePrevious();}elseif("fastForward".equals(directiveName)){handleFastForward();}elseif("rewind".equals(directiveName)){handleRewind();}elseif("startOver".equals(directiveName)){handleStartOver();}elseif("adjustSeekPosition".equals(directiveName)){handleAdjustSeekPosition(directivePayload);}elseif("ChangeChannel".equals(directiveName)){handleChangeChannel();}elseif("SendKeystroke".equals(directiveName)){handleSendKeystroke();}elseif("Test".equals(directiveName)){handleTestDirective();}else{Log.i(TAG,"Unknown Alexa Directive. Sending a failure response intent to the VSK Agent");sendPendingIntentResponse(context,intent,false);return;}//...}
If a SearchAndPlay directive is received, the handleSearchAndPlay function gets called, with the directivePayload passed in as a function parameter. If a SearchAndDisplayResults directive is received, the handleSearchAndDisplayResults function gets called, with the directivePayload passed in as a function parameter. And so on.
Note that the directivePayload names are all unique across the interfaces, so you don't need to combine the directiveNameSpace with the directivePayload.
Handling SearchAndPlay Directives
In the sample app, the handleSearchAndPlay function passes the directivePayload into another method called getMovieFromDirectivePayload to extract the movieName title, and then passes this movieName into a play intent sent to the PlaybackActivity to play the media:
privatevoidhandleSearchAndPlay(finalStringdirectivePayload){Log.i(TAG,"Handling SearchAndPlay directive...");finalMoviemovieToBePlayed=getMovieFromDirectivePayload(directivePayload);finalStringmovieName=movieToBePlayed.getTitle();Log.d(TAG,"Playing some random Movie "+movieName);finalIntentplayIntent=newIntent();finalStringpackageName=VSKReferenceApplication.getInstance().getApplicationContext().getPackageName();playIntent.setClassName(packageName,packageName+".PlaybackActivity");playIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// PlaybackActivity expects a movie to be currently selected, set this now in case there wasn't oneplayIntent.putExtra(DetailsActivity.MOVIE,movieToBePlayed);VSKReferenceApplication.getInstance().getApplicationContext().startActivity(playIntent);Log.i(TAG,"Handling SearchAndPlay directive finished");}
The getMovieFromDirectivePayload function would normally look through a data source for a matching movie name, but to keep the code simple, in this sample app, the function simply gets the first movie in a list compiled in MovieList:
privateMoviegetMovieFromDirectivePayload(finalStringdirectivePayload){// Process directive payload here and build an appropriate movie object to be played// For demonstration purposes, grabbing the first item in the sample movies list. Doesn't correspond to movie IDfinalMoviesomeMovie=MovieList.getList().get(0);returnsomeMovie;}
The MovieList is a class (inside java/com/example/vskfiretv/company) that has an array of about 5 different movies.
Handling SearchAndDisplayResults Directives
If a SearchAndDisplayResults is received, the handleSearchAndDisplayResults function is called, with the directivePayload from the directive passed in as a function. The handleSearchAndDisplayResults function uses a JsonParser to get both the user's transcribed search request (transcribed) and the entities object, which contains an array of entity objects to play, such as a Title, Franchise, Actor, Team, or MediaType.
The code then gets a random movie from the MovieList class, gathers all the metadata for that movie, and puts it into a searchedMovies object. It passes this to the the mainActivity to show the results the user searched for.
privatevoidhandleSearchAndDisplayResults(finalStringsearchPayload){Log.i(TAG,"Handling SearchAndDisplayResults directive...");finalJsonParserjsonParser=newJsonParser();finalJsonElementsearchPayloadJsonTree=jsonParser.parse(searchPayload);if(searchPayloadJsonTree.isJsonObject()){finalJsonObjectsearchPayloadJsonObject=searchPayloadJsonTree.getAsJsonObject();finalJsonObjectsearchTermJsonObject=searchPayloadJsonObject.getAsJsonObject("searchText");finalStringsearchText=searchTermJsonObject.get("transcribed").getAsString();finalJsonArraysearchEntitiesJsonArray=searchPayloadJsonObject.getAsJsonArray("entities");finalIterator<JsonElement>searchEntitiesIterator=searchEntitiesJsonArray.iterator();finalRandomrand=newRandom();finalList<Movie>movieList=MovieList.getList();intmoviesCount=movieList.size();finalList<Movie>searchedMovies=newArrayList<>();while(searchEntitiesIterator.hasNext()){finalJsonElemententityElement=searchEntitiesIterator.next();finalJsonObjectentity=entityElement.getAsJsonObject();finalStringentityValue=entity.get("value").getAsString();finalStringentityJsonString=entityElement.toString();finalMoviemovie=movieList.get(rand.nextInt(moviesCount));movie.setTitle(entityValue);movie.setDescription(entityJsonString);searchedMovies.add(MovieList.buildMovieInfo(movie.getMovieId(),movie.getTitle(),movie.getDescription(),movie.getStudio(),movie.getVideoUrl(),movie.getCardImageUrl(),movie.getBackgroundImageUrl()));}finalVSKReferenceApplicationmyReferenceApp=VSKReferenceApplication.getInstance();finalActivitycurrentActivity=myReferenceApp.getCurrentActivity();try{finalMainActivitymainActivity=(MainActivity)currentActivity;Log.i(TAG,MessageFormat.format("Showing search results for {0}",searchText));// Can only invoke this from the UI ThreadmainActivity.runOnUiThread(()->mainActivity.showResults(searchText,searchedMovies));}catch(finalExceptionex){Log.e(TAG,"Could not show search results on the home screen",ex);return;}}else{Log.i(TAG,"Invalid json for search payload");}Log.i(TAG,"Handling SearchAndDisplayResults directive finished");}
Important: The logic in the sample app is only for brief demo purposes and isn't handling logic that you would probably use in your real app. In these examples, the purpose is to demonstrate how to get information from the directives and pass this information into functions that handle the request and perform an action in the app to fulfill the request.
After your app finishes handling the request, you need to send a response to the VSK Agent indicating that the directive was handled successfully. In the sample app, you can see this logic inside the conditional statements after the directive handling in the AlexaDirectiveReceiver class. The intent and a status of true are passed into a sendPendingIntentResponse function:
// Send the PendingIntent back to the VSK agent if handling the directive is successfulLog.i(TAG,"Sending a success response intent to the VSK Agent");sendPendingIntentResponse(context,intent,true);}else{Log.i(TAG,"Received an empty directive from the VSK Agent");}
The status is a Boolean: it accepts either a true or false value. (No other statuses are available.)
The status gets added as an value in VSKIntentConstants.EXTRA_DIRECTIVE_STATUS, added as an extra in the PendingIntent, and then sent to the VSK Agent:
privatevoidsendPendingIntentResponse(finalContextcontext,finalIntentintent,finalbooleandirectiveExecutionStatus){finalPendingIntentpendingIntent=intent.getParcelableExtra(VSKIntentConstants.EXTRA_DIRECTIVE_RESPONSE_PENDING_INTENT);if(pendingIntent!=null){finalIntentresponseIntent=newIntent().putExtra(VSKIntentConstants.EXTRA_DIRECTIVE_STATUS,directiveExecutionStatus);try{pendingIntent.send(context,0,responseIntent);}catch(finalPendingIntent.CanceledExceptionex){Log.e(TAG,"Error sending pending intent to the VSK agent",ex);}}}