SDK for Android Getting Started
Getting started with an example app
CloudDriveListing Application
In this project, we will create an example Amazon Drive application that lists nodes. The code for this sample is available in the Apps-SDK.zip in the Apps-SDK/Android/AmazonCloudDrive/1.0.0/samples/SampleHelloWorldApp
directory.
Registration and being put on the Allow List
Create a Security Profile
The Amazon Drive API uses Login with Amazon for authentication. You can learn more about Login with Amazon or begin by registering your security profile.
- Navigate to the Login with Amazon section of the Amazon Developer Console.
- Click the button to create a new security profile.
- Enter security profile information including name, description, and privacy policy URL. For now, use any URL for the privacy policy - you can update it later.
Adding your Security Profile to the Allow List with Amazon Drive
To develop with Amazon Drive, you need to be invited to use the service to add your security profile to the allow list.
Creating a New Android Project
In this step, we create a new CloudDriveListing Android project.
- Launch Android Studio
- Select File -> New Project
- Enter
CloudDriveListing
for the Application name,example.com
for the Company Domain, and your desired Project location. - Check Phone and Tablet and set Minimum SDK to API 10 (or higher).
- Select Blank Activity and click Next.
- Enter
MainActivity
for the Activity Name,activity_main
for the Layout Name,MainActivity
for the Title, andmenu_main
for the Menu Resource Name. - Click Finish.
Adding Required Dependencies and Resources
In this step, we add all required dependencies and resources.
- Download and unzip the Amazon Mobile App SDK https://developer.amazon.com/apps-and-games/sdk-download
- Copy
Apps-SDK/Android/LoginWithAmazon/lib/login-with-amazon-sdk.jar
into your project'sapp/libs
folder. - Copy
Apps-SDK/Android/AmazonCloudDrive/1.0.0/lib/amazonclouddrive-1.0.0.jar
into your project'sapp/libs
folder. -
Open the app module's
build.gradle
file and add this to your dependencies:compile 'org.codehaus.jackson:jackson-core-asl:1.9.9' compile files('libs/amazonclouddrive-1.0.0.jar') compile files('libs/login-with-amazon-lib.jar')
- Download the and unzip LWA_for_Android.zip button pack for Android from https://developer.amazon.com/public/apis/engage/login-with-amazon/content/button.html
- Copy
hdpi/btnlwa_drkgry_login.png
andhdpi/btnlwa_drkgry_login_pressed.png
tores/drawable-hdpi
. Copy the other resolutions for this icon to their corresponding drawable folder.
Adding Required Permissions
The application requires the INTERNET and ACCESS_NETWORK_STATE permissions in order to use Amazon Drive.
Add the following permissions to your AndroidManifest.xml file's <manifest>
element:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Implementing a MainActivity and Declaring a Authorization Activity
In this step we will implement a MainActivity (displays a login button) and declare the Authorization Activity that is defined by Login with Amazon.
-
Create a new drawable selector file res/drawable/btnlwa_drkgry.xml with the following contents:
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="false" android:drawable="@drawable/btnlwa_drkgry_login" /> <item android:state_pressed="true" android:drawable="@drawable/btnlwa_drkgry_login_pressed" /> </selector>
-
Add the following strings to res/values/strings.xml:
<string name="menu_logout">Log out</string> <string name="login_with_amazon">Login with Amazon</string> <string name="logout">Logout</string> <string name="default_message">Welcome to Login with Amazon!\nIf this is your first time logging in, you will be asked to give permission for this application to access your profile data.</string> <string name="login_button_content_description">"Button for authorization through Login with Amazon"</string> <string name="return_to_app">Return To App</string>
-
Replace the contents of res/layout/activity_main.xml with the following code. If the editor is in Design mode, switch it to Text mode to enter the xml text.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/node_list" android:layout_width="match_parent" android:layout_height="match_parent" /> <ImageButton android:id="@+id/login_with_amazon_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@drawable/btnlwa_drkgry" android:background="@android:color/transparent" android:contentDescription="@string/login_button_content_description" /> </RelativeLayout>
-
AmazonAuthorizationManager
is used for logging the customer in and providing tokens that AmazonCloudDriveClient uses to make requests to the service. When the customer clicks on the login button, AmazonAuthorizationManager.authorize() is invoked with a listener that tells us the state of the log in attempt. Please note that, list APP_AUTHORIZATION_SCOPES have only two older scopes. In case you want to get access to any other scope, as under Scopes and Permissions , pass that as a string rather than using pre-defined list.mAuthManager.authorize(APP_AUTHORIZATION_SCOPES, Bundle.EMPTY, new LoginListener());
The LoginListener either moves the app to a Ready state or a RequiresLogin state.
/** * {@link AuthorizationListener} which is passed in to authorize calls made on the {@link AmazonAuthorizationManager} member. * Starts getToken workflow if the authorization was successful, or displays a toast if the user cancels authorization. */ private class LoginListener implements AuthorizationListener { /** * Authorization was completed successfully. * Display the profile of the user who just completed authorization * @param response bundle containing authorization response. Not used. */ @Override public void onSuccess(Bundle response) { moveStateToReady(); } /** * There was an error during the attempt to authorize the application. * Log the error, and reset the profile text view. * @param ae the error that occurred during authorize */ @Override public void onError(AuthError ae) { Log.e(TAG, "AuthError during authorization", ae); showToast("Error during authorization. Please try again."); moveStateToRequiresLogin(); } /** * Authorization was cancelled before it could be completed. * A toast is shown to the user, to confirm that the operation was cancelled, and the profile text view is reset. * @param cause bundle containing the cause of the cancellation. Not used. */ @Override public void onCancel(Bundle cause) { showToast("Authorization cancelled"); moveStateToRequiresLogin(); } }
If we can retrieve tokens, we know that the customer is already logged in so we can skip the login step.
onCreate
callsgetToken
as a first step to check if the customer is logged in.mAuthManager.getToken(APP_AUTHORIZATION_SCOPES, new GetTokenListener());
The GetTokenListener will move us to Ready state or RequiresLogin state.
/** * Listener for getting the token. Moves to the Ready state if successful, * otherwise, moves to RequiresLogin state. */ private class GetTokenListener implements AuthorizationListener { @Override public void onCancel(Bundle bundle) { moveStateToRequiresLogin(); } @Override public void onSuccess(Bundle bundle) { if (bundle.getString(AuthzConstants.BUNDLE_KEY.TOKEN.val) != null) { moveStateToReady(); } else { moveStateToRequiresLogin(); } } @Override public void onError(AuthError authError) { moveStateToRequiresLogin(); } }
Putting login pieces together, we have the following MainActivity:
package com.example.clouddrivelisting; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageButton; import android.widget.Toast; import com.amazon.clouddrive.auth.ApplicationScope; import com.amazon.identity.auth.device.AuthError; import com.amazon.identity.auth.device.authorization.api.AmazonAuthorizationManager; import com.amazon.identity.auth.device.authorization.api.AuthorizationListener; import com.amazon.identity.auth.device.authorization.api.AuthzConstants; public class MainActivity extends Activity { private static final String TAG = MainActivity.class.getName(); // Button that logs the user in private ImageButton mLoginButton; // LWA authorization manager private AmazonAuthorizationManager mAuthManager; // Authorization scopes used for getting information from // LWA and Amazon Drive private static final String[] APP_AUTHORIZATION_SCOPES = { ApplicationScope.CLOUDDRIVE_READ, ApplicationScope.CLOUDDRIVE_WRITE, "profile"}; /** * {@link AuthorizationListener} which is passed in to authorize calls made on the {@link AmazonAuthorizationManager} member. * Starts getToken workflow if the authorization was successful, or displays a toast if the user cancels authorization. */ private class LoginListener implements AuthorizationListener { /** * Authorization was completed successfully. * Display the profile of the user who just completed authorization * @param response bundle containing authorization response. Not used. */ @Override public void onSuccess(Bundle response) { moveStateToReady(); } /** * There was an error during the attempt to authorize the application. * Log the error, and reset the profile text view. * @param ae the error that occurred during authorize */ @Override public void onError(AuthError ae) { Log.e(TAG, "AuthError during authorization", ae); showToast("Error during authorization. Please try again."); moveStateToRequiresLogin(); } /** * Authorization was cancelled before it could be completed. * A toast is shown to the user, to confirm that the operation was cancelled, and the profile text view is reset. * @param cause bundle containing the cause of the cancellation. Not used. */ @Override public void onCancel(Bundle cause) { showToast("Authorization cancelled"); moveStateToRequiresLogin(); } } /** * Listener for getting the token. Moves to the Ready state if successful, * otherwise, moves to RequiresLogin state. */ private class GetTokenListener implements AuthorizationListener { @Override public void onCancel(Bundle bundle) { moveStateToRequiresLogin(); } @Override public void onSuccess(Bundle bundle) { if (bundle.getString(AuthzConstants.BUNDLE_KEY.TOKEN.val) != null) { moveStateToReady(); } else { moveStateToRequiresLogin(); } } @Override public void onError(AuthError authError) { moveStateToRequiresLogin(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLoginButton = (ImageButton) findViewById(R.id.login_with_amazon_button); mLoginButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { login(); } }); try { mAuthManager = new AmazonAuthorizationManager(this, Bundle.EMPTY); mAuthManager.getToken(APP_AUTHORIZATION_SCOPES, new GetTokenListener()); } catch (IllegalArgumentException e) { // // We cannot proceed if the API key is invalid. Finish the Activity. // showToast("Unable to Use Amazon Authorization Manager. APIKey is incorrect or does not exist."); Log.e(TAG, "Unable to Use Amazon Authorization Manager. APIKey is incorrect or does not exist.", e); finish(); } } /** * Moves to the Ready state. Hides the login button, creates AmazonCloudDrive client, starts fetch. */ private void moveStateToReady() { runOnUiThread(new Runnable() { @Override public void run() { mLoginButton.setVisibility(View.GONE); } }); } /** * Moves to RequiresLogin state. Shows the login button. */ private void moveStateToRequiresLogin() { runOnUiThread(new Runnable() { @Override public void run() { mLoginButton.setVisibility(View.VISIBLE); } }); } /** * Authorizes the application, moves to Ready or RequiresLogin when complete */ private void login() { mAuthManager.authorize(APP_AUTHORIZATION_SCOPES, Bundle.EMPTY, new LoginListener()); } /** * Shows a toast * @param toastMessage The message to show to the user */ private void showToast(final String toastMessage){ runOnUiThread(new Runnable() { @Override public void run() { Toast toast = Toast.makeText(getApplicationContext(), toastMessage, Toast.LENGTH_LONG); toast.setGravity(Gravity.CENTER, 0, 0); toast.show(); } }); } }
Once the customer is logged in, we can create an
AmazonAuthorizationConnectionFactory
instance:/** * Moves to the Ready state. Hides the login button, creates AmazonCloudDrive client, starts fetch. */ private void moveStateToReady() { runOnUiThread(new Runnable() { @Override public void run() { mLoginButton.setVisibility(View.GONE); } }); // // AmazonCloudDriveClient requires AccountConfiguration (which it uses to get // authentication tokens from LWA) and ClientConfiguration (which has the user agent). // mAmazonCloudDriveClient = new AmazonCloudDriveClient( new AccountConfiguration(new AmazonAuthorizationConnectionFactory(mAuthManager, APP_AUTHORIZATION_SCOPES)), new ClientConfiguration("ExampleAgent/1.0") ); refreshNodeListingFromCloudDrive(); }
To refresh the list of nodes from Amazon Drive, we use the
listNodesAsync
method. The asynchronous methods take a handler that is used to indicate success, error, and cancellation./** * Fetches the a node listing from Amazon Drive, updates ListView with results. */ private void refreshNodeListingFromCloudDrive() { ListNodesRequest listNodesRequest = new ListNodesRequest(); mAmazonCloudDriveClient.listNodesAsync(listNodesRequest, new AsyncHandler<ListNodesRequest, ListNodesResponse>() { @Override public void onError(ListNodesRequest listNodesRequest, final Exception e) { showToast("There was an error calling Amazon Drive " + e.getMessage()); } @Override public void onCanceled(ListNodesRequest listNodesRequest) { // This callback happens when the operation was canceled } @Override public void onSuccess(ListNodesRequest listNodesRequest, ListNodesResponse listNodesResponse) { List<Node> nodes = listNodesResponse.getData(); updateNodeListView(nodes); } }); }
Once we have the nodes, we can update the ListView with the information.
/** * Updates the mNodeList to show node kind and name. * @param nodes The nodes to show */ private void updateNodeListView(List<Node> nodes) { final List<String> nodeStrings = new ArrayList<String>(); for (Node node : nodes) { nodeStrings.add(node.getKind() + ":" + node.getName()); } runOnUiThread(new Runnable() { @Override public void run() { ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>( MainActivity.this, android.R.layout.simple_list_item_1, nodeStrings); mNodeList.setAdapter(arrayAdapter); } }); }
Putting all of the pieces together, we have the following MainActivity:
package com.example.clouddrivelisting; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.ImageButton; import android.widget.ListView; import android.widget.Toast; import com.amazon.clouddrive.AmazonCloudDrive; import com.amazon.clouddrive.AmazonCloudDriveClient; import com.amazon.clouddrive.auth.AmazonAuthorizationConnectionFactory; import com.amazon.clouddrive.auth.ApplicationScope; import com.amazon.clouddrive.configuration.AccountConfiguration; import com.amazon.clouddrive.configuration.ClientConfiguration; import com.amazon.clouddrive.handlers.AsyncHandler; import com.amazon.clouddrive.model.ListNodesRequest; import com.amazon.clouddrive.model.ListNodesResponse; import com.amazon.clouddrive.model.Node; import com.amazon.identity.auth.device.AuthError; import com.amazon.identity.auth.device.authorization.api.AmazonAuthorizationManager; import com.amazon.identity.auth.device.authorization.api.AuthorizationListener; import com.amazon.identity.auth.device.authorization.api.AuthzConstants; import java.util.ArrayList; import java.util.List; public class MainActivity extends Activity { private static final String TAG = MainActivity.class.getName(); // Button that logs the user in private ImageButton mLoginButton; // ListView that displays Amazon Drive Node information private ListView mNodeList; // LWA authorization managger private AmazonAuthorizationManager mAuthManager; // Amazon Drive client used for getting the nodes private AmazonCloudDrive mAmazonCloudDriveClient; // Authorization scopes used for getting information from // LWA and Amazon Drive private static final String[] APP_AUTHORIZATION_SCOPES = { ApplicationScope.CLOUDDRIVE_READ, ApplicationScope.CLOUDDRIVE_WRITE, "profile"}; /** * {@link AuthorizationListener} which is passed in to authorize calls made on the {@link AmazonAuthorizationManager} member. * Starts getToken workflow if the authorization was successful, or displays a toast if the user cancels authorization. */ private class LoginListener implements AuthorizationListener { /** * Authorization was completed successfully. * Display the profile of the user who just completed authorization * @param response bundle containing authorization response. Not used. */ @Override public void onSuccess(Bundle response) { moveStateToReady(); } /** * There was an error during the attempt to authorize the application. * Log the error, and reset the profile text view. * @param ae the error that occurred during authorize */ @Override public void onError(AuthError ae) { Log.e(TAG, "AuthError during authorization", ae); showToast("Error during authorization. Please try again."); moveStateToRequiresLogin(); } /** * Authorization was cancelled before it could be completed. * A toast is shown to the user, to confirm that the operation was cancelled, and the profile text view is reset. * @param cause bundle containing the cause of the cancellation. Not used. */ @Override public void onCancel(Bundle cause) { showToast("Authorization cancelled"); moveStateToRequiresLogin(); } } /** * Listener for getting the token. Moves to the Ready state if successful, * otherwise, moves to RequiresLogin state. */ private class GetTokenListener implements AuthorizationListener { @Override public void onCancel(Bundle bundle) { moveStateToRequiresLogin(); } @Override public void onSuccess(Bundle bundle) { if (bundle.getString(AuthzConstants.BUNDLE_KEY.TOKEN.val) != null) { moveStateToReady(); } else { moveStateToRequiresLogin(); } } @Override public void onError(AuthError authError) { moveStateToRequiresLogin(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLoginButton = (ImageButton) findViewById(R.id.login_with_amazon_button); mLoginButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { login(); } }); mNodeList = (ListView) findViewById(R.id.node_list); try { mAuthManager = new AmazonAuthorizationManager(this, Bundle.EMPTY); mAuthManager.getToken(APP_AUTHORIZATION_SCOPES, new GetTokenListener()); } catch (IllegalArgumentException e) { // // We cannot proceed if the API key is invalid. Finish the Activity. // showToast("Unable to Use Amazon Authorization Manager. APIKey is incorrect or does not exist."); Log.e(TAG, "Unable to Use Amazon Authorization Manager. APIKey is incorrect or does not exist.", e); finish(); } } /** * Moves to the Ready state. Hides the login button, creates AmazonCloudDrive client, starts fetch. */ private void moveStateToReady() { runOnUiThread(new Runnable() { @Override public void run() { mLoginButton.setVisibility(View.GONE); } }); // // AmazonCloudDriveClient requires AccountConfiguration (which it uses to get // authentication tokens from LWA) and ClientConfiguration (which has the user agent). // mAmazonCloudDriveClient = new AmazonCloudDriveClient( new AccountConfiguration(new AmazonAuthorizationConnectionFactory(mAuthManager, APP_AUTHORIZATION_SCOPES)), new ClientConfiguration("ExampleAgent/1.0") ); refreshNodeListingFromCloudDrive(); } /** * Moves to RequiresLogin state. Shows the login button. */ private void moveStateToRequiresLogin() { runOnUiThread(new Runnable() { @Override public void run() { mLoginButton.setVisibility(View.VISIBLE); } }); } /** * Authorizes the application, moves to Ready or RequiresLogin when complete */ private void login() { mAuthManager.authorize(APP_AUTHORIZATION_SCOPES, Bundle.EMPTY, new LoginListener()); } /** * Updates the mNodeList to show node kind and name. * @param nodes The nodes to show */ private void updateNodeListView(List<Node> nodes) { final List<String> nodeStrings = new ArrayList<String>(); for (Node node : nodes) { nodeStrings.add(node.getKind() + ":" + node.getName()); } runOnUiThread(new Runnable() { @Override public void run() { ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>( MainActivity.this, android.R.layout.simple_list_item_1, nodeStrings); mNodeList.setAdapter(arrayAdapter); } }); } /** * Fetches the a node listing from Amazon Drive, updates ListView with results. */ private void refreshNodeListingFromCloudDrive() { ListNodesRequest listNodesRequest = new ListNodesRequest(); mAmazonCloudDriveClient.listNodesAsync(listNodesRequest, new AsyncHandler<ListNodesRequest, ListNodesResponse>() { @Override public void onError(ListNodesRequest listNodesRequest, final Exception e) { showToast("There was an error calling Amazon Drive " + e.getMessage()); } @Override public void onCanceled(ListNodesRequest listNodesRequest) { // This callback happens when the operation was canceled } @Override public void onSuccess(ListNodesRequest listNodesRequest, ListNodesResponse listNodesResponse) { List<Node> nodes = listNodesResponse.getData(); updateNodeListView(nodes); } }); } /** * Shows a toast * @param toastMessage The message to show to the user */ private void showToast(final String toastMessage){ runOnUiThread(new Runnable() { @Override public void run() { Toast toast = Toast.makeText(getApplicationContext(), toastMessage, Toast.LENGTH_LONG); toast.setGravity(Gravity.CENTER, 0, 0); toast.show(); } }); } }
Add the
AuthorizationActivity
declaration to the AndroidManifest.xml. The host in the intent-filter must match the name of your application's package.<activity android:name="com.amazon.identity.auth.device.authorization.AuthorizationActivity" android:theme="@android:style/Theme.NoDisplay" android:allowTaskReparenting="true" android:launchMode="singleTask"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:host="com.example.clouddrivelisting" android:scheme="amzn" /> </intent-filter> </activity>
Generating an api_key.txt File
In this step, you will generate an api_key.txt that will be used by Login with Amazon to identify your application.
Create an API Key
An API key allows Amazon to verify your app's identity. You need to create a different API key for every application and keystore signature.
- Select your security profile from the Security Profiles section of the Amazon Developer Console, or else use the settings icon next to your profile in the Login with Amazon section to navigate to the profile's Android/Kindle Settings.
- Click Add an API Key. You must create a different API key for each package and keystore signature combination.
- Enter
CloudDriveListing
for the API Key Name. - Enter
com.example.clouddrivelisting
for the Package. - Enter your keystore's signature for the Signature. See https://developer.amazon.com/public/apis/engage/login-with-amazon/docs/register_android.html for details on how to get signature for your keystore.
- Click the Add button to generate an API key. We will use the generated API key in the following step.
Add API key to your project
- Create an assets directory for your application in
app/src/main/assets
of your project directory. - Create a new
api_key.txt
file. Copy the contents of the API key generated above into this file.
Running Your Application
Run the application. After logging in and accepting the application's requested permissions, you should see a list of nodes from Amazon Drive for that account.
Support
If you have any questions, see the Developer Forum.