Developer Console

Get started with Amazon Stylus SDK

Prerequisites

Install Android Studio

We recommend you download the latest version of Android Studio, but it isn't required to implement the Predictive Touch API. If you're using another IDE, make sure you have the Android SDK (30 or above) installed and meet all other system requirements necessary for Android development. This documentation will assume you're using Android Studio.

Download the Amazon Stylus SDK for Fire Tablets

This download allows you to implement Predictive Touch in your Stylus enabled Apps. Extract the SDK contents to a location of your choice.

Add the SDK to Android Studio

  1. Download the SDK, save it to a preferred folder, and extract the zip file.
  2. Open your existing project or make a new project in Android Studio.
  3. Change the folder structure from Android to Project.

    What you should see when changing folder structure in Android Studio
  4. If not already created, inside of the app (old Android Studio) or Application (newer Android Studio), create a libs folder.

    What you should see when changing folder structure in Android Studio
  5. Copy the StylusSDKSupportLibrary-1.0.aar extracted in step 1 to the libs folder you just created.
  6. In your app's build.gradle file, reference the AAR file in the dependencies object: Add the implementation files (libs/StylusSDKSupportLibrary-1.0.aar) in the build.gradle file's dependency section.
dependencies {
    implementation files('libs/StylusSDKSupportLibrary-1.0.aar')

Now, you can use the Amazon Stylus SDK APIs in your project.

API specifications

You will be working with these classes and APIs for predictive touch:

  • com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch
  • com.amazon.stylussupportsdk.predictedtouches.AmazonPredictedEvents

AmazonPredictiveTouch This class is to carry out all the tasks like registering/deregistering for Predictive Touches. It also helps in getting the predicted points, so that apps can ask for them. Always call isFeatureRuntimeAvailable(Context) to know if the Predictive Touches are supported at runtime on device. If not supported, apps can refrain from calling other API's as those will be non-useful. The App then needs to initialize AmazonPredictiveTouch using init(Context) in order to use predictions.

AmazonPredictiveTouch This class contains the predicted coordinates. An instance of this is supplied to the app it is using AmazonPredictiveTouch#getLatestPredictions(long).


    /**
     * To initialize the Predictive Touch engine.
     * An app/process should call it only once, unless they deregistered explicitly earlier.
     * Calling it multiple times will not be useful and may result in non-delivery of
     * predictions to the app due to duplicate registrations from same process.
     * @param context the context of the application.
     */
    public static void init(Context ctx);
    
    /**
     * Always call this method to know if the Predictive Touches are supported at runtime
     * on device. If not supported, apps can refrain from calling other
     * API's as those will be non-useful
     * @param context the context of the application.
     * @return true if feature is supported in this device, else false
     */
    public static boolean isFeatureRuntimeAvailable(Context ctx);
    
    /**
     * To manually register for getting Predictive Touches in case app deregistered earlier
     * and want to re-register
     * @return true if feature supported and either we are already registered or register
     *   is successful, else false
     */
    public static boolean registerPredictiveTouches();
    
    /**
     * To manually deregister with Predictive Touches in case app doesn't want predictions
     * @return true if feature supported and either we are not registered or deregister
     *  is successful, else false
     */
    public static boolean deregisterPredictiveTouches();
    
    /**
     * To let app check if it has registered for predictions or not
     * @return true if registered else false
     */
    public static boolean isPredictiveTouchesRegistered();
    
    /**
     * To let the app set the view for which it needs predictions
     * @param view the view in which app will use predictions, should be non null
     */
    public static void setCurrentView(View v);
    
    /**
     * To get the latest prediction stored in PredictiveTouchManager it received from algorithm
     * If the feature is not supported or we don't have any predictions, this will return null
     * @param t latest event timestamp the app has, to get predictions after t
     * @return an {@link AmazonPredictedEvents} object containing predictions or null if
     *   there isn't any predictions received.
     */
    public static AmazonPredictedEvents getLatestPredictions(long t);
}



    /**
     * To get the number of predictions in this {@link AmazonPredictedEvents} instance
     * @return Number of predictions
     */
    public int getNumberOfPredictions();
    
    /**
     * To get the x coordinate of predicted point at passed index. Apps should check
     * if -1.0f is returned,then don't use it to draw
     * @param index the index of predicted point
     * @return x coordinate of the predicted point at passed index if index is valid, else -1.0f
     */
    public float getXAt(int index);
     
    /**
     * To get the y coordinate of predicted point at passed index. Apps should check
     * if -1.0f is returned, then don't use it to draw
     * @param index the index of predicted point
     * @return y coordinate of the predicted point at passed index if index is valid, else -1.0f
     */
    public float getYAt(int index);
     
    /**
     * To get the timestamp of predicted point at passed index. Apps should check
     * if -1L is returned, then don't use it as it indicates invalid.
     * @param index the index of predicted point
     * @return timestamp of the predicted point at passed index if index is valid, else -1L
     */
    public long getEventTimeAt(int index);
}


Implementing the Amazon Stylus SDK

Overview

  1. Check for feature availability using AmazonPredictiveTouch.isFeatureRuntimeAvailable().
  2. Initialize the AmazonPredictiveTouch using AmazonPredictiveTouch.init().
  3. Check if PredictiveTouches is registered using public static boolean isPredictiveTouchesRegistered().
  4. Register for predictions using AmazonPredictiveTouch.registerPredictiveTouches().
  5. Set the view for which you will use predictions using AmazonPredictiveTouch.setCurrentView().
  6. Identify if it's a MOVE event in onTouchEvent and note its timestamp and coordinates.
  7. Get predictions using AmazonPredictiveTouch.getLatestPredictions() and draw using AmazonPredictedEvents.
  8. Deregister predictions using AmazonPredictiveTouch.deregisterPredictiveTouches().

See the details for each of these below.

Step 1: Check for Amazon Predictive Touch availability

Use the AmazonPredictiveTouch.isFeatureRuntimeAvailable(android.content.Context context) method to know whether the feature is available on the device.

If the feature is not available or supported, this method will return false. Otherwise, it will be true.

import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch;
 
private boolean isPredictiveTouchesSupported() {
        /* sample implementation */
        return AmazonPredictiveTouch.isFeatureRuntimeAvailable(this) == true;
}
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ..........
    ..........
    if (isPredictiveTouchesSupported()) {
        // PredictiveTouches should be available. Code
        // to init and register for predictions goes here
    }
    else {
        // PredictiveTouches not available. Write it in a log message
        Log.w(TAG, "PredictiveTouches is not supported");
    }
}


import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch

private fun isPredictiveTouchesSupported(): Boolean {
    /* sample implementation */
    return AmazonPredictiveTouch.isFeatureRuntimeAvailable(this)
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    if (isPredictiveTouchesSupported()) {
        // PredictiveTouches should be available. Code
        // to init and register for predictions goes here
    } else {
        // PredictiveTouches not available. Write it in a log message
        Log.w(TAG, "PredictiveTouches is not supported")
    }
}


Step 2: Initialize the AmazonPredictiveTouch

Use the AmazonPredictiveTouch.init(android.content.Context context) method to initialize.

This should be done using the onCreate() method of an Activity or during View creation (if needed only for that view).

Initialization will also register with the PredictionService by default, so you can skip calling AmazonPredictiveTouch.registerPredictiveTouches() after this step.

Now, predictive touch APIs can be used in all app processes.

import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch;
 
public class MainActivity extends Activity {
      
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ..........
        ..........
        if (isPredictiveTouchesSupported()) {
            // PredictiveTouches should be available. Code
            // to init and register for predictions goes here
            AmazonPredictiveTouch.init(MainActivity.this);
        }
        else {
            // PredictiveTouches not available. Write it in a log message
            Log.w(TAG, "PredictiveTouches is not supported");
        }
    }
}


import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch

class MainActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ..........
        ..........
        if (isPredictiveTouchesSupported()) {
            // PredictiveTouches should be available. Code
            // to init and register for predictions goes here
            AmazonPredictiveTouch.init(this@MainActivity)
        } else {
            // PredictiveTouches not available. Write it in a log message
            Log.w(TAG, "PredictiveTouches is not supported")
        }
    }
}


Step 3: Check if PredictiveTouches is registered

Use the utility method isPredictiveTouchesRegistered() to check if PredictiveTouches is registered. This returns a boolean value.

For the App to receive predictive points, you must initialize or register the listener with either init() or registerPredictiveTouches(). You will only need to use registerPredictiveTouches() in the cases where you have called deregisterPredictiveTouches() on certain views where you do not want the prediction and then come back to the view where the prediction is needed.

Step 4: Register for predictions

This is required when you used AmazonPredictiveTouch.deregisterPredictiveTouches() earlier to save compute/memory resources as you didn't need predictions, but now you again need predictions to be enabled.

In order to start receiving predictions again, use the AmazonPredictiveTouch.registerPredictiveTouches() method. This is an idempotent API, so calling it multiple times is not needed and will not result in different behavior. It will return true if registration is successful, otherwise it will return false.

import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch;
 
public class MainActivity extends Activity {
  
    @Override
    protected void onResume() {
        super.onResume();
        // Assuming that at resume, user will be writing/drawing, etc. and we deregistered earlier,
        // so need to register for predictions.
        AmazonPredictiveTouch.registerPredictiveTouches();
        .............
        .............
    }
}


import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch

class MainActivity : Activity() {
    override fun onResume() {
        super.onResume()
        // Assuming that at resume, user will be writing/drawing, etc. and we deregistered earlier,
        // so need to register for predictions.
        AmazonPredictiveTouch.registerPredictiveTouches()
    }
}


Step 5: Set the view for which you will use predictions

Each view has its own geometrical transform. For instance, its position on the screen, possible rotation, or scaling effects. The touch points need to be transformed accordingly.

In order to provide accurate predicted events to the current view where you will use predictions, set your current view. This can be done by switching to a new view that uses predictions. Use the method setCurrentView(android.view.View view).

Call this method before drawing in onTouchEvent() for MotionEvent.ACTION_DOWN for each view, which gets called every time a view is touched.

import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch;

public class TouchDisplayView extends View {

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // existing code 
        final int action = event.getAction();
        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: {
                AmazonPredictiveTouch.setCurrentView(this);
                // existing code  
            }
        }
    }
}


import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch

class TouchDisplayView : View() {
    override fun onTouchEvent(event: MotionEvent): Boolean {
        // existing code 
        val action = event.action
        when (action and MotionEvent.ACTION_MASK) {
            MotionEvent.ACTION_DOWN -> {
                AmazonPredictiveTouch.setCurrentView(this)
            }
        }
    }
}


Step 6: Identify if it's a MOVE event using TouchEvent and note its timestamp and coordinates

Only use predictions for MOVE events. You can identify if it’s a MOVE event using the onTouchEvent() method. Make note of its timestamp and coordinates. To predict points for the latest MOVE event, use the timestamp. Also, we want to draw predicted points after the latest touch points X, Y coordinates, so we need the coordinates for it.

This can be done using onTouchEvent() with variables such as mLatestX, mLatestY, and mCurrentMoveTime. See the sample code below.

import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch;
  
public class TouchDisplayView extends View {
  
    private long mCurrentMoveTime = -1;
    private float mLatestX, mLatestY;
    private Path mCurrentStroke = new Path(); // Path to store stroke data
  
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        ............
  
        // Set the timestamp -1
        mCurrentMoveTime = -1;
  
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            .............
            case MotionEvent.ACTION_MOVE: {
  
                // Get the timestamp, X, Y values of current move event
                mCurrentMoveTime = event.getEventTime();
                mLatestX = event.getX();
                mLatestY = event.getY();
  
                for (i in 0 until event.historySize) {
                    // add history points to current stroke
                    mCurrentStroke.lineTo(event.getHistoricalX(0, i), event.getHistoricalY(0, i))
                }
                // We are using lineTo for the sake of example, please use custom draw logic as required
                mCurrentStroke.lineTo(mLatestX,mLatestY); // add point to current stroke
                this.invalidate();
                break;
            }
            .............
        }
    }
}


import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch

class TouchDisplayView : View() {
    private var mCurrentMoveTime: Long = -1
    private var mLatestX = 0f
    private var mLatestY = 0f
    private val mCurrentStroke: Path = Path() // Path to store stroke data
    override fun onTouchEvent(event: MotionEvent): Boolean {


        // Set the timestamp -1
        mCurrentMoveTime = -1
        when (event.action and MotionEvent.ACTION_MASK) {
            MotionEvent.ACTION_MOVE -> {


                // Get the timestamp, X, Y values of current move event
                mCurrentMoveTime = event.eventTime
                mLatestX = event.x
                mLatestY = event.y
                
                for (i in 0 until event.historySize) {
                    // add history points to current stroke
                    mCurrentStroke.lineTo(event.getHistoricalX(0, i), event.getHistoricalY(0, i))
                }
                
                // We are using lineTo for the sake of example, please use custom draw logic as required
                mCurrentStroke.lineTo(mLatestX, mLatestY) // add point to current stroke
                this.invalidate()
            }
        }
    }
}


Step 7: Get the predictions and draw

Now that we know it's a MOVE event and have its timestamp and its coordinates, we need to get the predictions corresponding to this timestamp.

  1. Use getLatestPredictions(long t) to get the predictions.
  2. This will give the AmazonPredictedEvents object. You can use this object to find the following:
    1. The number of predictions using the getNumberOfPredictions() method. This returns an integer.
    2. Event time using getEventTimeAt(int index). This returns a timestamp of type long.
    3. X / Y coordinates using the getXAt(int index) and getYAt(int index) methods. These return float values.
  3. Add the predictions to the current path or separate path for predictions to draw.

  4. Draw the path containing predictions.
import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch;
import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictedEvents;
  
public class TouchDisplayView extends View {
      
    @Override
    protected void onDraw(Canvas canvas) {
  
        // Get the latest predictions corresponding to mCurrentMoveTime
        AmazonPredictedEvents latestPred = AmazonPredictiveTouch.getLatestPredictions(mCurrentMoveTime);
  
        Path predicted = new Path();
  
        // Add the predictions to Path if latest predictions are available
        if (mCurrentMoveTime != -1 /*valid move*/ && latestPred != null
                && latestPred.getNumberOfPredictions() != 0) {
            Log.i(TAG,
                    "DRAWSTART EVENTTIME: " + mCurrentMoveTime + " currentTime "
                    + SystemClock.uptimeMillis() + " latestPredTime "
                    + latestPred.getEventTimeAt(latestPred.getNumberOfPredictions() - 1));
  
            predicted.moveTo(mLatestX, mLatestY);
            for (int i = 0; i < latestPred.getNumberOfPredictions(); i++) {
                // We are using lineTo for the sake of example, please use custom draw logic as required
                if (latestPred.getXAt(i) > -1.0f && latestPred.getYAt(i) > -1.0f)
                    predicted.lineTo(latestPred.getXAt(i), latestPred.getYAt(i));
            }
        }
  
        // Clear the canvas first, so that last predicted points get erased and we don't have unwanted artifacts
        canvas.drawColor(Color.WHITE);
 
        // Draw the actual points
        canvas.drawPath(mCurrentStroke, mPaint);
        // Draw the path containing predictions with another Paint object, to confirm
        if (!predicted.isEmpty())
            canvas.drawPath(predicted, mPaintPred);
  
        if (mCurrentMoveTime != -1) {
            Log.i(TAG, "DRAWEND EVENTTIME: " + mCurrentMoveTime + " currentTime " + SystemClock.uptimeMillis());
        }
        super.onDraw(canvas);
    }
}


import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch
import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictedEvents

class TouchDisplayView() : View() {
    override fun onDraw(canvas: Canvas) {

        // Get the latest predictions corresponding to mCurrentMoveTime
        val latestPred: AmazonPredictedEvents =
            AmazonPredictiveTouch.getLatestPredictions(mCurrentMoveTime)
        val predicted = Path()

        // Add the predictions to Path if latest predictions are available
        if (mCurrentMoveTime !== -1 /*valid move*/ && latestPred != null && latestPred.getNumberOfPredictions() !== 0) {
            Log.i(
                TAG,
                "DRAWSTART EVENTTIME: " + mCurrentMoveTime.toString() + " currentTime "
                        + SystemClock.uptimeMillis().toString() + " latestPredTime "
                        + latestPred.getEventTimeAt(latestPred.getNumberOfPredictions() - 1)
            )
            predicted.moveTo(mLatestX, mLatestY)
            for (i in 0 until latestPred.getNumberOfPredictions()) {
                // We are using lineTo for the sake of example, please use custom draw logic as required
                if (latestPred.getXAt(i) > -1.0f && latestPred.getYAt(i) > -1.0f) predicted.lineTo(
                    latestPred.getXAt(i),
                    latestPred.getYAt(i)
                )
            }
        }

        // Clear the canvas first, so that last predicted points get erased and we don't have unwanted artifacts
        canvas.drawColor(Color.WHITE)

        // Draw the actual points
        canvas.drawPath(mCurrentStroke, mPaint)
        // Draw the path containing predictions with another Paint object, to confirm
        if (!predicted.isEmpty()) canvas.drawPath(predicted, mPaintPred)
        if (mCurrentMoveTime !== -1) {
            Log.i(
                TAG,
                "DRAWEND EVENTTIME: " + mCurrentMoveTime.toString() + " currentTime " + SystemClock.uptimeMillis()
            )
        }
        super.onDraw(canvas)
    }
}


Step 8: Deregister predictions

Call the deregisterPredictiveTouches() method to deregister PredictiveTouches. You may consider doing this when the app moves to an Activity or View that does not need predictions, or your app goes into the background, to reduce CPU/Memory usage.

It is important that you link deregisterPredictiveTouches() of PredictiveTouches with the lifecycle of the Activity/View where it was initialized.

For example, for an activity, use init() in the onCreate() method and use deregisterPredictiveTouches() in the onDestroy() method. In scenarios where the process is not terminated but the Activity or View is destroyed, you will not be able re-initialize PredictiveTouches in the onCreate() method if it was not deregistered earlier. Display rotation is an example where the process is not killed (the process is still registered with the service), but the activity is re-created (which tries to create a new instance).

On a successful deregister, the method will return true, otherwise it will return false.


import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch;
  
public class MainActivity extends Activity {
 
    // OPTIONAL: can do this when you want to stop for predictions while your app in background to save the predictive touches memory footprint.
    @Override
    protected void onPause() {
        AmazonPredictiveTouch.deregisterPredictiveTouches();
        super.onPause();
    }
     
    // This is a must for components who can die while the process is still alive (like activity, view, etc)
    @Override
    protected void onDestroy() {
        AmazonPredictiveTouch.deregisterPredictiveTouches();
        super.onDestroy();
    }
}


import com.amazon.stylussupportsdk.predictedtouches.AmazonPredictiveTouch

class MainActivity : Activity() {
    // OPTIONAL: can do this when you want to stop for predictions while your app in background to save the predictive touches memory footprint.
    override fun onPause() {
        AmazonPredictiveTouch.deregisterPredictiveTouches()
        super.onPause()
    }

    // This is a must for components who can die while the process is still alive (like activity, view, etc)
    override fun onDestroy() {
        AmazonPredictiveTouch.deregisterPredictiveTouches()
        super.onDestroy()
    }
}


Troubleshooting

If you are having trouble getting or drawing predictions, check the following:

Is the feature available on the device?
Solution: Update Fire OS to latest version and call AmazonPredictiveTouch.isFeatureRuntimeAvailable() to check if your device supports this feature. Refer to Step 1 for details.
Do I need to maintain two APKs if my app will also be available on devices that don’t support Predictive Touch?
Solution: No, there is no need to maintain two different APKs. Use AmazonPredictiveTouch.isFeatureRuntimeAvailable() to determine if the device supports Predictive Touch before initializing AmazonPredictiveTouch.
Can I draw the predictions with a separate paint object (like a different color) and see that the predictions are being drawn?
Solution: Use the code sample in Step 7.
Registering predictions (Step 4) or setting the view for predictions (Step 5) fails:
Currently, for a process we only support one channel for receiving predictions, and so init() should only be called once in a process.
It’s not uncommon to have multiple activities or a rotation case where the same activity is recreated and init() is called again.
Solutions:
  • Use deregisterPredictiveTouches() when exiting an activity/view where init() was called.
  • Use the isPredictiveTouchesRegistered() API to check if init() needs to be called. If it returns true, there is no need to call init() again.

Last updated: Nov 10, 2023