Developer Console

Step 4: Integrate Amazon Device Messaging (ADM) (VSK Fire TV)

Amazon Device Messaging (ADM) is a push notification technology that allows your app to interact with your Lambda. Your Lambda will send instructions to your app using ADM.

Note that video skills use ADM differently from the most common use cases for ADM. Video skills use ADM only to carry messages from Lambda to your app (in a one-way fashion). For this reason, only the relevant sections from ADM's documentation are embedded here. If you read ADM's documentation directly, you will find many other uses and details that might not be relevant to a video skill on Fire TV.

Sample App Instructions

If you're using the sample Fire TV app, ADM is already integrated into the code, and the ADM JAR has been added (if you switch to Project's view in Android Studio and go to app > libs, you'll see the amazon-device-messaging-1.1.0.jar). So you can skip to the next step: Step 5: Sign Your App and Configure a Security Profile. However, if you want to get a better sense of the code for integrating ADM, see the the AndroidManifest.xml, MainActivity.java, and VSKFireTVMessageHandler.java files.

Update Your App Manifest

Updating your AndroidManifest.xml file is the first step in receiving messages sent via ADM. Make the following changes to your existing file:

  1. Open your AndroidManifest.xml file and add the Amazon namespace:

    xmlns:amazon="http://schemas.amazon.com/apk/res/android"
    
  2. To declare the permissions necessary to support ADM, after the manifest element, add the permission and uses-permission elements.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:amazon="http://schemas.amazon.com/apk/res/android"
    package="[YOUR PACKAGE NAME]"
    android:versionCode="1"
    android:versionName="1.0">
    <!-- This permission ensures that no other application can intercept your
    ADM messages. -->
    <permission
        android:name="[YOUR PACKAGE NAME].permission.RECEIVE_ADM_MESSAGE"
        android:protectionLevel="signature" />
    <uses-permission android:name="[YOUR PACKAGE NAME].permission.RECEIVE_ADM_MESSAGE" />
    <!-- This permission allows your app access to receive push notifications
    from ADM. -->
    <uses-permission android:name="com.amazon.device.messaging.permission.RECEIVE" />
    <uses-permission android:name="amazon.speech.permission.SEND_DATA_TO_ALEXA" />
    <!-- ADM uses WAKE_LOCK to keep the processor from sleeping when a message is received. -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    ...
    </manifest>
    
  3. Explicitly enable ADM and declare whether your app requires ADM (android:required="true") or can work without ADM being present (android:required="false"). If you specify android:required="false", your app must degrade gracefully if ADM is unavailable. In the application node of the manifest, add the amazon:enable-feature element .

    ...
    <application
          android:icon="@drawable/ic_launcher"
          android:label="@string/app_name"
          android:theme="@style/AppTheme">
    
          <!-- You must explicitly enable ADM and declare whether your app cannot work without
             ADM (android:required="true") or can work without ADM (android:required="false").
             If you specify android:required="false", your app must degrade gracefully if ADM
             is unavailable. -->
             <amazon:enable-feature
    	  android:name="com.amazon.device.messaging"
                 android:required="true"/>
    ...
    
  4. Declare a broadcast receiver to handle the REGISTRATION and RECEIVE intents that ADM sends. ADM requires that the receiver be defined in the AndroidManifest.xml file, rather than programmatically. Immediately after amazon:enable-feature add the following elements.

     <!-- You must replace the names in the service and receiver tags
         with names that are appropriate to your package. -->
    
     <service
         android:name="[YOUR SERVICE NAME]"
         android:exported="false" />
    
     <receiver
         android:name="[YOUR RECEIVER NAME]"
    
         <!-- This permission ensures that only ADM can send your app registration broadcasts. -->
         android:permission="com.amazon.device.messaging.permission.SEND" >
    
         <!-- To interact with ADM, your app must listen for the following intents. -->
         <intent-filter>
     <action android:name="com.amazon.device.messaging.intent.REGISTRATION" />
     <action android:name="com.amazon.device.messaging.intent.RECEIVE" />
    
     <!-- Replace the name in the category tag with your app's package name. -->
     <category android:name="[YOUR PACKAGE NAME]" />
         </intent-filter>
     </receiver>
    
  5. After updating your AndroidManifest.xml file, you can confirm that the changes are correct for ADM by calling ADMManifest.checkManifestAuthoredProperly().

    If you receive a java.lang.NoClassDefFoundError: com.amazon.device.messaging.ADM error, check your manifest to verify that you have added the amazon:enable-feature element in the location specified in step 3.

The following code is a complete example of an AndroidManifest.xml file enabled for ADM.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:amazon="http://schemas.amazon.com/apk/res/android"
   package="[YOUR PACKAGE NAME]"
   android:versionCode="1"
   android:versionName="1.0" >

   <uses-sdk
      android:minSdkVersion="15"
      android:targetSdkVersion="15" />

   <permission
      android:name="[YOUR PACKAGE NAME].permission.RECEIVE_ADM_MESSAGE"
      android:protectionLevel="signature" />
   <uses-permission android:name="[YOUR PACKAGE NAME].permission.RECEIVE_ADM_MESSAGE" />

   <uses-permission android:name="com.amazon.device.messaging.permission.RECEIVE" />

   <uses-permission android:name="amazon.speech.permission.SEND_DATA_TO_ALEXA" />

   <uses-permission android:name="android.permission.WAKE_LOCK" />

   <application
      android:icon="@drawable/ic_launcher"
      android:label="@string/app_name"
      android:theme="@style/AppTheme" >

      <amazon:enable-feature
         android:name="com.amazon.device.messaging"
         android:required="true"/>

      <service
         android:name="[YOUR SERVICE NAME]"
         android:exported="false" />

      <receiver
         android:name="[YOUR RECEIVER NAME]"
         android:permission="com.amazon.device.messaging.permission.SEND" >

         <intent-filter>
         <action android:name="com.amazon.device.messaging.intent.REGISTRATION" />
         <action android:name="com.amazon.device.messaging.intent.RECEIVE" />

         <category android:name="[YOUR PACKAGE NAME]" />
         </intent-filter>

      </receiver>
   </application>
</manifest>

Implement Handling for Registration and Messages

The broadcast receiver declared in your manifest both listens for intents and invokes your app when intents arrive. Your app communicates with the broadcast receiver via the following callback methods defined in the com.amazon.device.messaging.ADMMessageHandlerBase class:

  • onRegistered. Called when the registration ID for the app instance is ready. Your app must transmit this registration ID to your server. Doing so enables your server to send messages to the app instance.

  • onUnregistered. Called if your app instance becomes unregistered from ADM.

  • onRegistrationError. Called if your app's ADM registration request fails for any reason, such as no Amazon user being signed in to the device.

  • onMessage. Called when the ADM Client delivers a message to your app instance.

Your app must override these callbacks when you implement subclasses of com.amazon.device.messaging.ADMMessageHandlerBase and com.amazon.device.messaging.ADMMessageReceiver, as shown in the following code sample:

public class MyADMMessageHandler extends ADMMessageHandlerBase
{
    public static class Receiver extends ADMMessageReceiver
    {
        public Receiver()
        {
            super(MyADMMessageHandler.class);
        }

    // Nothing else is required here; your broadcast receiver automatically
    // forwards intents to your service for processing.
    }

    @Override
    protected void onRegistered(final String newRegistrationId)
    {
        // You start the registration process by calling startRegister() in your Main
        // Activity. When the registration ID is ready, ADM calls onRegistered() on
        // your app. Transmit the passed-in registration ID to your server, so your
        // server can send messages to this app instance. onRegistered() is also
        // called if your registration ID is rotated or changed for any reason; your
        // app should pass the new registration ID to your server if this occurs.
        // Your server needs to be able to handle a registration ID up to 1536 characters
        // in length.

  	// Provide the acquired ADM registration ID to the Alexa Client library.
        AlexaClientManager.getSharedInstance().setDownChannelReady(true, newRegistrationId);
    }

    @Override
    protected void onUnregistered(final String registrationId)
    {
        // If your app is unregistered on this device, inform your server that
        // this app instance is no longer a valid target for messages.
    }

    @Override
    protected void onRegistrationError(final String errorId)
    {
        // You should consider a registration error fatal. In response, your app may
        // degrade gracefully, or you may wish to notify the user that this part of
        // your app's functionality is not available.
    }

    @Override
    protected void onMessage(final Intent intent)
    {
        // Extract the message content from the set of extras attached to
        // the com.amazon.device.messaging.intent.RECEIVE intent.

        // Create strings to access the message and timeStamp fields from the JSON data.
        final String msgKey = getString(R.string.json_data_msg_key);
        final String timeKey = getString(R.string.json_data_time_key);

        // Obtain the intent action that will be triggered in onMessage() callback.
        final String intentAction = getString(R.string.intent_msg_action);

        // Obtain the extras that were included in the intent.
        final Bundle extras = intent.getExtras();

        // Extract the message and time from the extras in the intent.
        // ADM makes no guarantees about delivery or the order of messages.
        // Due to varying network conditions, messages may be delivered more than once.
        // Your app must be able to handle instances of duplicate messages.
        final String msg = extras.getString(msgKey);
        final String time = extras.getString(timeKey);
    }
}

Gracefully Degrade If ADM Is Unavailable

In your manifest file, you declare whether your app can (android:required="false") or cannot (android:required="true") work without ADM. If you specify android:required="false", your app must degrade gracefully if ADM is unavailable.

Designing your app to accommodate the absence of ADM allows you to build a single APK that can be installed and run on devices that may or may not include ADM.

To modify your app to gracefully degrade

  1. Use code similar to the following to check for ADM.

    ADMAvailable = false ;
    try
    {
        Class.forName( "com.amazon.device.messaging.ADM" );
        ADMAvailable = true ;
    }
    catch (ClassNotFoundException e)
    {
        // Handle the exception.
    }
    
  2. Add the following code to any of your code that requires the ADM library runtime.

    if (ADMAvailable)
    {
        // Your code that requires ADM goes here.
    }
    
  3. In your AndroidManifest.xml file, verify that the application element specifies:

    amazon:enable-feature android:name="com.amazon.device.messaging" android:required="false" /
    

Alternatives to ADM

Instead of using ADM to send instructions from your Lambda function to your app, you can also send actions to your app using your own protocol and cloud service. With this option, you own the delivery of commands to your app. The protocol and characteristics of the communication are entirely up to you and can be managed from your Lambda code or from a service call into your infrastructure.

This approach requires an identifier unique to this instance of the application to be provided to the Alexa Client upon initialization. This identifier will be used to target this application instance when a directive is sent to your Lambda code. (If you do not already have an identifier for the app instance or do not wish to pass it to Alexa, consider using ANDROID_ID for this purpose.)

If you choose this approach, you will need to work out much of the implementation code on your own. These steps will involve the following:

  • Add Alexa Client Library to your project
  • Pass an application instance identifier to client library at initialization
  • Ensure the application instance identifier is known to your cloud
  • Implement the Lambda function and app communication mechanism as you see fit
  • Complete application integration requirements for Alexa Client Library

Next Steps

Continue on to Step 5: Sign Your App and Configure a Security Profile.

(If you run into any issues that prevent you from continuing, see Troubleshooting for Cloudside Integrations.)


Last updated: Oct 13, 2021