article

Eric@Amazon avatar image
Eric@Amazon posted

Creating an Android AVS app vs creating an Android companion app

As a recap of the difference between AVS app and Companion app - there are two approaches to authenticating a product to use AVS.

Companion App

You have a device (such as a smart speaker) that you want to add Alexa to. So, you build in support for AVS. Great! Now you need a way to authorize it and associate it with the user's account. This is the "companion app" approach. The companion app connects to your smart product and allows the user to login and authorize the speaker to use Alexa and connect to their Amazon account.

AVS App

You don't have a device you need to authorize - instead you want to speak to Alexa from within your Android application. This is the "AVS app" approach.

/////////////////////////////////////////////////////////
// Common code used for both AVS app and Companion App //
/////////////////////////////////////////////////////////

private static final String[] APP_SCOPES= { "alexa:all" };
private static final String SCOPE_DATA = "{\"alexa:all\":{\"productID\":\"testdevice1\", \"productInstanceAttributes\":{\"deviceSerialNumber\":\"123456\"}}}";
private AmazonAuthorizationManager mAuthManager = new AmazonAuthorizationManager((Context) this, Bundle.EMPTY); // <- in onCreate()

/////////////////////////////////////////////
//          Code for AVS app ONLY          //
/////////////////////////////////////////////

// in onStart() call getAccessToken() 
// If you're logged in, you'll get an access token. If not, you'll need to surface the login code in doLogin() 
private void getAccessToken() {
   mAuthManager.getToken(APP_SCOPES, new APIListener() { 
      @Override 
      public void onSuccess(Bundle response) { // Give the below access token to your AVS code
         String accessToken = response.getString(AuthzConstants.BUNDLE_KEY.TOKEN.val);
         boolean isLoggedIn = !TextUtils.isEmpty(mAccessToken);
      } 
      
      @Override 
      public void onError(AuthError ae) { 
         // Logged out 
      }
   });
} 

// if not logged in, show button that - when clicked - triggers doLogin()
private void doLogin() {
   Bundle options = new Bundle();
   options.putString(AuthzConstants.BUNDLE_KEY.SCOPE_DATA.val, SCOPE_DATA);
   mAuthManager.authorize(APP_SCOPES, options, new APIListener() {
      @Override
      public void onSuccess(Bundle response) {
         runOnUiThread(new Runnable() {
            public void run() {
               // Get the access token 
               getAccessToken();
            }
         });
      }

      @Override
      public void onError(AuthError ae) { 
         // Logged out 
      }
   });
} 

///////////////////////////////////////////// 
//       Code for Companion app ONLY       //
/////////////////////////////////////////////

// On the device:
// Create the code verifier/challenge & send the code challenge to the companion app
String codeVerifier = generateCodeVerifier();
String codeChallenge = generateCodeChallenge(codeVerifier, "S256");
sendCodeChallengeToDevice(codeChallenge): 

private String generateCodeVerifier() { 
   byte[] randomOctetSequence = generateRandomOctetSequence();
   String codeVerifier = base64UrlEncode(randomOctetSequence);
   return codeVerifier;
}

private String generateCodeChallenge(String codeVerifier, String codeChallengeMethod) throws NoSuchAlgorithmException { 
   String codeChallenge;
   if ("S256".equalsIgnoreCase(codeChallengeMethod)) {
      codeChallenge = base64UrlEncode(
         MessageDigest.getInstance("SHA-256").digest(
            codeVerifier.getBytes()));
   } else { 
      // Fall back to code_challenge_method = "plain" codeChallenge = codeVerifier;
   } 
   return codeChallenge;
} 

private String base64UrlEncode(byte[] arg) { 
   return Base64.encodeToString(arg, Base64.NO_PADDING | Base64.URL_SAFE | Base64.NO_WRAP);
} 

// Companion app:
private void doLogin(String codeChallenge) { Bundle options = new Bundle();
   options.putString(AuthzConstants.BUNDLE_KEY.SCOPE_DATA.val, SCOPE_DATA);
   options.putBoolean(AuthzConstants.BUNDLE_KEY.GET_AUTH_CODE.val, true);
   options.putString(AuthzConstants.BUNDLE_KEY.CODE_CHALLENGE.val, codeChallenge);
   options.putString(AuthzConstants.BUNDLE_KEY.CODE_CHALLENGE_METHOD.val, "S256");
   mAuthManager.authorize(APP_SCOPES, options, new APIListener() { 
      @Override 
      public void onSuccess(Bundle response) { 
         String authCode = response.getString(AuthzConstants.BUNDLE_KEY.AUTHORIZATION_CODE.val);
         // Securely send authCode to device 
      } 
      @Override 
      public void onError(AuthError ae) { 
         // Logged out 
      } 
   });
}
alexa voice service
10 |5000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Article

Contributors

rossbria contributed to this article ericatamazon contributed to this article