Appstore Blogs

Appstore Blogs

Want the latest?

appstore topics

Recent Posts

Archive

Showing posts tagged with Fire OS

June 17, 2014

Russell Beattie

The Cordova Push Notifications Plugin is a project that lets hybrid web app developers create apps that respond to custom push notifications from services such as the Apple Push Notification Service (APNS), Google Cloud Messaging (GCM) and most recently Amazon Device Messaging (ADM). This enables hybrid web apps to be notified when an online service has information for it - such as a personal message, sports score or appointment - even if the app is not currently running.

If you're unfamiliar with Amazon Device Messaging, it's a simple, efficient and free messaging service from Amazon that lets developers send push notifications from the cloud to Kindle Fire devices, helping app creators more deeply engage with their customers by keeping them up to date and involved.

You can find the plugin, and complete documentation on the Cordova PushPlugin Github repository, but here are the basics on getting started using the plugin for Amazon Fire OS.

Overview

Macintosh HD:Users:beattier:Desktop:cordova_adm:adm-cordova.png

Amazon Device Messaging works similarly to the other push notification services and has three main parts involved in the process:

  • Your server sends notifications (messages, updates, scores, etc.) to the ADM Servers to be routed to the client apps.
  • ADM Servers queue and route notifications to client apps.
  • Your  app receives the notification on a device.

The Cordova Push Notifications Plugin allows your hybrid web app to use JavaScript to register with the ADM Server and receive notification events when sent from your server.

Please note that ADM only works with Fire OS devices and the Cordova Amazon Fire OS platform target. For more information about setting up a Fire OS Cordova project, please see our earlier blog post,  Building Higher Performance Cordova-based Fire OS Apps By Implementing Amazon WebView Support.

ADM Credentials

In order for your server to send notifications to your app, both the server and app, need to be authenticated/registered with the ADM Server:

  1. Create a sample Cordova app for the Amazon Fire OS platform so you have the app name, package name and the debug build ready.
  2. Create a developer account on the Amazon Developer Portal if you don't already have one
  3. Fill out the New App Submission form and turn Device Messaging switch to ON.
  4. Create Security Profile to obtain ADM credentials for your app.
    Note: For development, you can use the MD5 signature of the debug apk created by Cordova build process found in <project_path>/platforms/amazon-fireos/ant-build.

You will need to make note of the OAuth Credentials (Client ID and Client Secret) to be used on your notifications server and the API Key to be used in your client app. These can be found on the Security Profile Management pages in the areas marked in green below:

Macintosh HD:Users:beattier:Desktop:cordova_adm:credentials.png

Below is a graphic which shows a general overview of the authentication process and how your client app and your server to authenticate and register with the ADM Server:

Macintosh HD:Users:beattier:Desktop:cordova_adm:authentication2.png

Install Push Notifications Plugin

Once you have created an initial Cordova app for the Amazon Fire OS platform, use the standard plugin command from your project directory to install the Push Notifications Plugin.

cordova plugin add https://github.com/phonegap-build/PushPlugin.git

This will download the plugin and install it in your app's project directory. There will be a new folder called <com.phonegap.plugins.PushPlugin> inside your project's plugin directory.  This folder contains: the source files for the plugin, an Example directory that has a sample web app you can use to receive ADM notifications on the client, and a Node.js script you can use to send notifications, simulating what a server would normally do.

Before you can start testing notifications, you will need to follow the steps below to download and install the ADM support libraries and add the proper credentials to your project.

Install Amazon Device Messaging Library

The push notifications plugin needs the support files found in the ADM API. Follow these steps to download the SDK and install the library files in your Cordova project:

  1. Download the Amazon Mobile App SDK and unzip.
  2. Create a folder called ext_libs in <project_path>/platforms/amazon-fireos folder.
  3. Copy amazon-device-messaging-x.x.x.jar into the <project_path>/platforms/amazon-fireos/ext_libs
  4. Create a new text file called ant.properties in the <project_path>/platforms/amazon-fireos folder, and add a java.compiler.classpath entry pointing at the ADM library. For example:
    java.compiler.classpath=./ext_libs/amazon-device-messaging-1.0.1.jar
    
  5. Create a new text file called api_key.txt in the <project_path>/platforms/amazon-fireos/assets folder, and copy the API Key you obtained in the Credentials section above. Note: Be careful not to add any extra spaces to the text file.

Create Sample App

Here's a basic web app that can be used to test receiving notifications on the client. Create new index.html and index.js files in your project's www directory and copy the markup/code below.

The index.html page is simply a container for the messages to be displayed on the device:

index.html:

<!DOCTYPE html>
<html>
<head>
	<title>ADM Test</title>
</head>
<body>
	<h1>ADM Test</h1>
	<pre id="container" style="word-wrap:break-word"></pre>
	<script src="cordova.js"></script>
	<script src="index.js"></script>
</body>
</html>

The index.js script uses the PushPlugin to display notifications received on the client.

index.js:

var container = document.getElementById("container");
var pushNotification;

document.addEventListener("deviceready", handleDeviceReady, true);

function handleDeviceReady(){

    pushNotification = window.plugins.pushNotification;
    pushNotification.register( handleSuccess, handleError, {"ecb":"handleNotification"});

}

function handleNotification(e) {

    log("**** EVENT RECEIVED: " + e.event );

    if(e.event == "registered"){
        if(e.regid.length > 0){
            log("REGISTRATION ID:  <br>" + e.regid);
            log("<a href=\"mailto:?subject=ADM%20Registration%20ID&body=" + encodeURIComponent(e.regid) + "\">Email Registration ID</a>");
        }
    } else if(e.event == "message"){
        if ( e.foreground ) {
            log("FOREGROUND NOTIFICATION");
        } else {
            if ( e.coldstart )  {
                log("COLDSTART NOTIFICATION");
            } else {
                log("BACKGROUND NOTIFICATION");
            }
        }
        log("MESSAGE: " + e.payload.message );
        log("TIME: " + e.payload.timeStamp );
        log("URL: " + e.payload.url );
    } else if(e.event == "error"){
        log("ERROR: " + e.msg );
    }
}

function handleSuccess(result) {
    log("Plugin Success: " + result );
}

function handleError(error) {
    log("Plugin Error: " + error );
}

function log(msg){
    container.innerHTML += msg + "<br>";
    console.log(msg);
}

Notes about the JavaScript:

  • The handleDeviceReady method will use the pushNotification object to register the client with the ADM Server. This requires that the api_key.txt file contains the correct information.
  • The Registration ID that is returned to the client is used by your server to send notifications to the correct device/app combination. The ID is very long, so there's a link to email the ID to yourself to be used in testing.
  • The notification event has a flag to tell your app whether it was in the foreground, background or not started when the notification arrived.
  • The ADM notification event contains a payload object consisting of arbitrary fields populated by your server. In this example, the server will send a message, timestamp and URL, but each app can be different.
  • If there are errors with authentication or other issues such as connectivity problems, the app should log to the screen any error messages sent to your app.

Once you have your client app ready, use Cordova's command line to install the app on your test device (usually using the "cordova run" command). If you have set up everything correctly, the app will start, authenticate with the ADM Server and display the Registration ID unique to your device/app to be used by your notifications server.

Test ADM Notifications

Normally, you will have a dedicated server which sends app notifications via the ADM Server, using the Registration ID to uniquely identify each instance of your app running on a specific device. Your app will be responsible for getting the Registration ID from each registered device to your server.

For testing purposes, we are instead going to use the pushADM.js Node script found in the <project_path>/plugins/com.phonegap.plugins.PushPlugin/Example/server folder. This script acts in place of your server, authenticating with Amazon and then pushing notifications to the ADM Servers to be delivered to your app. (Node is a prerequisite for Cordova, so you should already have it installed on your system.)

Before we can use the script, we need to add in your OAuth Credentials and Registration ID. Here is a snippet from the top part of the test script that you will be editing:

pushADM.js:

// Client ID and Client Secret received from ADM portal
var CLIENT_ID = ""; 
var CLIENT_SECRET = "";

// Registration ID, received on device after it registers with ADM server
var REGISTRATION_IDS = [""];

// Message payload to be sent to client
var payload = {
        data: {
            message: "PushPlugin works!!",
            url: "http://www.amazon.com",
            timeStamp: new Date().toISOString()
        },
        consolidationKey: "my app",
        expiresAfter: 3600
};
...

Edit the pushADM.js script :

  1. Copy the OAuth credentials - Client ID and Client Secret - you obtained in the Credentials section above into the CLIENT_ID and CLIENT_SECRET variables.
  2. Copy one or more Registration IDs from a registered app into the REGISTRATION_IDS array. Note: You can fill this var with IDs from multiple devices for testing. Using the sample app above, you can email the Registration ID to yourself and then copy/paste it into the test script.
  3. Optionally edit the payload object with custom values to be sent to your app.

Run the pushADM.js script from a command line using Node:

node pushADM.js

The result on the command line should be a series of log messages which shows the script authenticating with the ADM Server and then sending a message. The app running on your Kindle Fire should then instantly show the message if it is in the foreground, or add a notification alert to the top bar if it is in the background or not running. You can run this script as many times as you need to test your app.

Here is what the of the sample web app above looks like after receiving a notification:

Macintosh HD:Users:beattier:Desktop:cordova_adm:Screenshot_2014-05-28-14-17-15.png

Summary

The steps above should give you a good start towards implementing ADM push notifications into your app. Once you have your app working, it will be very easy to customize the notification payload as you need to create compelling new apps and services for your customers.

If you have trouble, please make sure to check out the links below for information and support:

Good luck!

-Russ

 

 

May 12, 2014

Russell Beattie

For the past several years, HTML5 web app developers have been using the Apache Cordova project to create an incredible array of apps targeting native platforms. Many, such as the Wikipedia Foundation's mobile app, have had tens of millions of downloads across platforms. Cordova is the fundamental tool which enables developers to use open web technologies - HTML5, CSS3 and JavaScript - to create both fundamental and innovative mobile apps. This is why Amazon supports the Cordova project and enables developers to target the Amazon Fire OS platform. We want to enable web developers to take advantage of the advanced capabilities of the Chromium-based Amazon WebView integrated into Kindle Fire tablets running Fire OS.

We've covered the basics of setting up your Amazon Fire OS development environment for Cordova, so for this post, I'm going to create a new web app from scratch, covering all the varied little details you'll need to know to get your app from idea to Appstore. To do this, I created a simple drawing app that shows off both mobile and platform specific capabilities, and used Cordova to wrap it up into a native app ready for publication.

For the sake of simplicity, I developed an app targeting just Amazon devices. Though much of the power of Cordova is the fact that it allows developers to create cross platform apps, it can also be used as a way of quickly creating apps targeted at a single specific platform. This lets me use some more advanced HTML5 capabilities available on the Fire OS platform - in this case the accelerated 2D drawing canvas - and it also lets me cut some corners and use my rusty Java skills to add a few lines of native code to enable some needed functionality. In the future, I'll take this bit of native code and turn it into a plugin that can be used to target a wider range of devices.

I called the app Simple Draw, and it's available on the Amazon Appstore right now, downloadable for free. If you have a Kindle Fire device, go grab the app and try it out. It's a very simple drawing app targeted at young children - you can draw with your finger in various neon colors, clear the screen, or save the drawing as an image, which you can view in your Photos and share with others.

Here's everything you'd need to do to create this app yourself.

Setup

First, we'll start with the basics - setting up a new Cordova project and adding in support for Fire OS: 

$ cordova create simpledraw com.example.html5.simpledraw SimpleDraw
$ cd simpledraw
$ cordova platform add amazon-fireos

This will create our new Cordova project directory, pre-fill it with the skeleton folders, and then add in the appropriate Fire OS support files (amazon-fireos). (The first time you run through this process, you'll be prompted to download the AWV SDK found here) Make sure you change the package name to be unique to you or your company, rather than com.example.html5 in the above example.

The resulting directory should look like the following - I expanded a couple important folders to show where we're going to be tinkering.

Web Assets

Now that we've got somewhere to put our app's files, we can go in and clear out the placeholder files and folders that are in the project’s www folder. This is where your app's web files go. The Simple Draw web app above only needs an index.html and a main.js JavaScript file, but other apps could have various text files, images, sound files, etc.

Let's take a quick look at both files.

HTML

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Draw</title>
        <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
        <style>
            html, body { height: 100%; margin: 0; padding: 0; background: #fff; color: #fff}
        </style>
    </head>
    <body>
        <script src="main.js"></script>
    </body>
</html>

You'll notice there's not much there. It's really just a placeholder for a full-screen app that uses the HTML5 Canvas, so it doesn't need to be very complicated. It's important to set the viewport meta tag correctly, so my app knows that it's meant to be used on a handheld device, and to then call the JavaScript file at the end to make sure the DOM is loaded by the time I want to start manipulating it.

JavaScript

Next is the JavaScript, which is a bit longer, but is relatively straight forward and doesn't rely on any external libraries. The script creates a new full-screen canvas element, then listens for touch events that are used to draw on and to select from the menu bar at the bottom. There is a colorful gradient to choose your color and a couple icons at the bottom left which are used to clear the screen or save the image to your device's Pictures directory. Everything except that last bit can be done using pure JavaScript.

var canvas = document.createElement('canvas');
canvas.style.position = 'absolute';
var context = canvas.getContext("2d");
context.fillStyle = "#000";
context.lineCap = 'round'

document.body.appendChild(canvas);

var pictureCanvas = document.createElement("canvas");
var pictureContext = pictureCanvas.getContext("2d");

var width;
var height;

var menuSize = 40;
var lineColor = '#fff';
var lineWidth = 6;
var isDown = false;
var points = [];

// listen for events
window.addEventListener("resize", reset, false);

canvas.addEventListener("touchstart", pointerDown, false);
canvas.addEventListener("touchmove", pointerMove, false);
canvas.addEventListener("touchend", pointerUp, false);

document.body.addEventListener("touchcancel", pointerUp, false);

//set up and begin
reset();
requestAnimationFrame(animate);

// functions
 function reset(e){
    width  =  window.innerWidth;
    height = window.innerHeight;
    canvas.width = width;
    canvas.height = height;
    pictureCanvas.width = width;
    pictureCanvas.height = height - menuSize;
    context.fillRect(0,0, width, height);
    drawMenuBar();
    lineColor = "#fff";
    points = [];
 }

function drawMenuBar(){
    // color gradient
    var grad = context.createLinearGradient(menuSize * 2, height - menuSize, width, height);
    grad.addColorStop(0, 'black');
    grad.addColorStop(1 / 8, 'red');
    grad.addColorStop(2 / 8, 'orange');
    grad.addColorStop(3 / 8, 'yellow');
    grad.addColorStop(4 / 8, 'green')
    grad.addColorStop(5 / 8, 'aqua');
    grad.addColorStop(6 / 8, 'blue');
    grad.addColorStop(7 / 8, 'purple');
    grad.addColorStop(1, 'white');
    context.fillStyle = grad;
    context.fillRect(menuSize * 2, height - menuSize, width, height);

    // icons
    var refreshIcon = new Image();
    refreshIcon.onload = function(){
        context.drawImage(refreshIcon, -2 , height - menuSize);
    }

 

    refreshIcon.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAACg0lEQVRYCe2XMa8pQRTHx/MSjRBRENEpCBEJEoUQvgO1UqGlFirxaURoVSqh0IioFKIRiYRCKObt2XdnMruse8bam1vsSTZzdpzzPz9nszOzDkIIVa5fa39+LdkXmA1o9gnZHbQ7aLYDZvP/mhWA/FQqReLxOAmFQqrcfr8nq9WKLJfLT8irCzUs1lKXx+Oh3W6XbrdbamTwW6fToRArqy/Ey4FBYrVapYfDwYjrYR5iIUcoKuPLAbbb7QcA7ATkvgGJB2w0Gg8s8BhbrRZNJpPU7XarF/gw9+zxg4YkJA4wk8nQ+/2uAez3+9TlchkWhN8gRjTQAC0JSBzgZDIR69Bms4kuArGigdZHAXO5nKhPB4OBTAE1FnJEA00k5Pcd7PV6ojaNRqNYcR4Xi8U0GqD5McDZbMbFF4sFVvghDnKZgSYGEHWaCYfDitZ/m8/nzJUexVxR85UQCtDv93ON4/HIfVlHzBU1X+mgAN8RflZUhBI1n8WyORTgbrdj8SSbzXJf1hFzRc1XOihAZd3iGul0mihvMb/HOspbTCCXmajJ5ozGb98m2XXQ6XTSWq2m0bV0HVT+GdXvJJFIRAMAMeyCNe50OvFt0PKdBAqLe/F4POYwDIqNlUqFLXUU/B/biwEATiLX65UadS+RSNDz+cwBL5cL95lj2WmGdahUKj3tntfrpZvNhnGoo/70Y/l5kEHqR4fDQUejkQZOvPnRE7UeDu7r9brIo/HX67WpbxLHV0FlMGfBYJAUi0VSKBRIuVxWv/KUzpLb7UYCgQBR3uq3CnwMUF/d5/ORfD6vQg+HQzKdTvUhqHvLAFHVEUGorQ6hY1mIDWi2tXYH7Q6a7YDZ/H90B3qb+wyF3wAAAABJRU5ErkJggg==";

    var downloadIcon = new Image();
    downloadIcon.onload = function(){
        context.drawImage(downloadIcon, menuSize, height - menuSize);
    }
    downloadIcon.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAABCklEQVRYCe2X0QmDUAxFtegA4hCKfy4huIvgCG4hiLMIziHuoP4LqQFfwVLa2puWR0ngka/7OJ4EVNdxHNqOtXWxlmwHU0B0QmpQDaIG0LzIDlZV5RDR4aBgJi8CaC77RldA1KoaVIOoATSvO6gGUQNoXndQDaIG0Lz1O8gPyD/up06SJLQsy/aN+l61bXvq/juec3AmnOc5rev6krDve/J9//eADFqW5VPAcRwpDEMEjrOfGTS5pmkeQs7zTHEco3A4oOd51HXdAZJHn2WZBBwOyCaDIKBhGG6QRVFIwckAMmQURTRNE9V1LQknB8iQaZoSj9zsp0R390u2ZmdZ/yZRQHRx1ODfG7wCaGXbMjKT0dAAAAAASUVORK5CYII=";

    //icons border
    context.strokeStyle = "#fff";
    context.lineWidth = 2;
    context.beginPath();
    context.moveTo(menuSize, height-menuSize);
    context.lineTo(menuSize, height);
    context.moveTo(menuSize * 2, height-menuSize);
    context.lineTo(menuSize * 2, height);
    context.stroke();
}

function saveImage(){
        if(confirm('Save Image?')){
            pictureContext.drawImage(canvas, 0, 0);
            var dataUrl = pictureCanvas.toDataURL("image/png");
            if(typeof SimpleDraw !== 'undefined'){
                SimpleDraw.savePNG(dataUrl);
            }
        }
}

function eraseImage(){
    if(confirm('Erase Image?')){
        reset();
    }
}

function drawLine(){
    if(points.length > 1){
        context.strokeStyle = lineColor;
        context.lineWidth = lineWidth;
        context.lineCap = 'round'
        context.lineJoin = 'round';
        context.shadowBlur = lineWidth/2;
        context.shadowColor = lineColor
        context.beginPath();
        context.moveTo(points[0].x, points[0].y);
        for (var i = 1; i < points.length; i++) {
            context.lineTo(points[i].x, points[i].y);
        }
        context.stroke();
    }
}

// events
function animate(time) {
    drawLine();
    requestAnimationFrame(animate);
}

function pointerUp() {
    isDown = false;
    points = [];
}

function pointerDown(e) {
    e.preventDefault();

    var point = {};
    point.x = e.targetTouches[0].pageX;
    point.y = e.targetTouches[0].pageY;

    if(point.y > height - menuSize){
        if(point.x < menuSize){
            eraseImage();
            return;
        }
        if(point.x > menuSize && point.x < menuSize * 2){
            saveImage();
            return;
        }
        if(point.x > menuSize * 2){
            var data = context.getImageData(point.x, point.y, 1, 1).data;
            lineColor = 'rgb(' + data[0] + ',' + data[1] + ',' + data[2] + ')';
        }
    }  else {
        isDown = true;
        points.push(point);
    }

}

function pointerMove(e) {
    e.preventDefault();

    var point = {};
    point.x = e.targetTouches[0].pageX;
    point.y = e.targetTouches[0].pageY;

    if(isDown && point.y < height - menuSize){
        points.push(point);
    }
}

A few notes about the Javascript:

  • For the menu, I used an image program to create some basic icons, which I converted into data:// URLs. This makes the script a bit more contained. Images still take time to load even from a data URL, so I added onload listeners to draw the icons to the canvas.
  • Rather than draw to the canvas every time the event fired, I call the drawLine() function using requestAnimationFrame. This makes sure the screen is ready to be redrawn, and makes the app a bit more performant.
  • Since modern desktop browsers enable touch-event emulation, I didn't bother adding in mouse events as I'm targeting only mobile devices.
  • The saveImage() function has a reference to a SimpleDraw object. This doesn't exist in the web engine until we add it using the Java wrapper (below).

So we're all set with the main functionality of the app, and can quickly test the script by building/running the application on the device.

Please note: Cordova apps for Fire OS don't currently support emulation because of the integrated nature of Amazon WebView.

Plug your Kindle Fire into your computer via a USB cable and perform:

$ cordova run

On first run, this will compile the application using the Android Developer Toolkit, then push the app's debug version to your Kindle Fire so you can test.

Hey, it works! Well, almost - it doesn't actually save the images, so we'll have to wire that up next.

Native

When I first started testing out the functionality of the app, I assumed I'd be able to use the Cordova FileSystem API plugin to save the resulting image to the local file system. So I added the plugin via the command line (see the Cordova docs on how to do this), and used it to save the canvas image data as files. But I couldn't see them from the Photo gallery! That's not useful for this particular application - so I uninstalled that plugin, and decided to add that bit of native functionality myself.

The problem is that Fire OS (and other Android systems) keeps a separate database of images that needs to be updated in order for the images to appear in the photo browser. So in addition to saving the file, I needed to update the system to recognize the fact that the file is an image and to update the database. To do this, I needed to add a few lines of Java code to add in a custom JavaScript Interface in Cordova's native app wrapper code, found deep within the platforms folder here:

/simpledraw/platforms/amazon-fireos/src/com/example/html5/simpledraw/SimpleDraw.java

If you've done any sort of Java development, you'll recognize what that long path is - it's the standard Java directory system based on the name of the package. I had to do some digging on the web to find the right APIs to wire together, but in the end it was only a few lines of Java. I would have preferred to stay within JavaScript, but having the option to dive into native code if I needed to was very useful.

Here's the resulting SimpleDraw.java CordovaActivity class:

package com.example.html5.simpledraw;

import org.apache.cordova.*;

import java.text.SimpleDateFormat;
import java.util.Date;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Base64;
import android.view.Gravity;
import android.webkit.JavascriptInterface;
import android.widget.Toast;

public class SimpleDraw extends CordovaActivity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        super.init();

        //super.getFactory().enableDeveloperToolsUnix(this.getPackageName() + ".devtools");

        this.appView.addJavascriptInterface(new JSObject(), "SimpleDraw");

        super.loadUrl(Config.getStartUrl());

    }

    public class JSObject {

        @JavascriptInterface
        public void savePNG(final String dataUrl) {

            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
                    String fileName = "drawing-" + df.format(new Date()) + ".png";

                    byte bytes[] = Base64.decode(dataUrl.substring(dataUrl.indexOf(",") + 1), Base64.DEFAULT);

                    Bitmap bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

                    MediaStore.Images.Media.insertImage(getContentResolver(), bmp, fileName, fileName);

                    Toast toast = Toast.makeText(getApplicationContext(), "Image saved!", Toast.LENGTH_SHORT);
                    toast.setGravity(Gravity.CENTER, 0, 0);
                    toast.show();


                }
            });

        }
    }

}

Notes about the native code:

  • The native functionality is exposed to the web engine using the addJavascriptInterface() method, which takes a parameter defining the name of the JavaScript object you want to use - in this case I named it the same as the project, but it could be anything.
  • The commented out line in the onCreate() method can be used to enable access to Chromium's remote Developer Tools. You can then connect to your device remotely using this command, then browsing to localhost:9222 in your browser: 
adb forward tcp:9222 localabstract:com.example.html5.simpledraw.devtools
  • From JavaScript, the canvas is converted into an image/png data URL using the toDataURL() method. This is what’s passed to the Java class via the SimpleDraw.savePNG() method. It's then converted into an array of bytes to be saved as an image file.
  • Rather than using the basic File API, I used the built-in MediaStore class to save the converted PNG image to the file system, which automatically updates the device's internal data table so it appears instantly in the Photo gallery.

AndroidManifest.xml

In order for the app to be able to save files to the device's local storage, it needs to have the right permissions. This is done by editing the AndroidManifest.xml file found in the /platforms/amazon-fireos directory.

In the example below, you can see that the WRITE_EXTERNAL_STORAGE permission has been added to the list of user-permissions, allowing the app to save images to the file system. Additionally, the android:screenOrientation flag has been set in the application element to lock the screen to landscape mode only.

The resulting file looks like this: 

<?xml version='1.0' encoding='utf-8'?>
<manifest android:hardwareAccelerated="true" android:versionCode="1" android:versionName="0.0.1" android:windowSoftInputMode="adjustPan" package="com.lab126.html5.simpledraw" xmlns:android="http://schemas.android.com/apk/res/android">
    <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application android:hardwareAccelerated="true" android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale" android:label="@string/app_name" android:name="SimpleDraw" android:screenOrientation="landscape" android:theme="@android:style/Theme.Black.NoTitleBar">

... more xml configuration here ...

</manifest>

Now when you test out the app and click the download arrow, the image will be saved and you'll get a native Toast message pop up confirming the process. 

If you check in the Photos app, you'll be able to see your image. Hooray! So the app now works, but we're still a few steps away from publishing it to the Amazon Appstore.

Config.xml

The Cordova config.xml settings file can be found in your main project folder. Enter in your contact details, the name and description of your app, and then add in Fullscreen and BackgroundColor preference tags (see below) which will make your app fill the entire screen, and prevent any sort of color flash as it loads.

Here's what the config.xml file looks like: 

<?xml version='1.0' encoding='utf-8'?>
<widget id="com.example.html5.simpledraw" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>SimpleDraw</name>
    <description>
        A sample drawing app using Cordova for Amazon Fire OS
    </description>
    <author email="beattier@amazon.com" href="http://www.amazon.com">
        Russell Beattie, HTML5 Platform Technical Evangelist
    </author>
    <content src="index.html" />
    <access origin="*" />
    <preference name="Fullscreen" value="true" />
    <preference name="BackgroundColor" value="0xff000000"/>
</widget>

Icons

Next we'll need to replace the different sized app icons that our project used before we are ready to publish. These images are found in the /platforms/amazon-fireos/res directory, and will need to be created and copied over manually. I used a drawing program to whip up a quick app icon and then exported it as a giant 512px square image (you'll use this later when submitting your app to the Appstore). I then shrunk the images as needed and saved them as icon.png files in the following folders:

/res/drawable = 96px

/res/drawable-hdpi = 72px

/res/drawable-ldpi = 36px

/res/drawable-mdpi = 48px

/res/drawable-xhdpi = 96px

Now when you build and run your project, the generic Cordova icon will be replaced by your app's custom icon.

Signing

We're almost there! One last step before we're ready to publish the app is to create a signed -release version of the app's .apk file. Up until now, the Cordova build process has created -debug versions of the app for testing, but now we'll set up the files needed to sign the apk when the --release flag is passed to the build command.

First, you'll need to either create or locate your Java keystore. If you haven't created one yet, definitely check out the Android documentation which summarizes the keystore process quite well. However the following command will get you started:

 

$ keytool -genkey -v -keystore my-release-key.keystore -alias myapps -keyalg RSA -keysize 2048 -validity 10000

This will prompt you for all the information you need to enter and create a new keystore file for you to use to self-sign your app. Put the keystore somewhere safe and accessible from your Cordova project, and don't forget the password!

Next, you need to create a new text file called ant.properties in the base of the amazon-fireos directory: /platforms/amazon-fireos/ant.properties. In it, add two lines pointing at the appropriate directory: 

key.store=/Users/example/keystore/my-release-key.keystore
key.alias=myapps

Now you can create a release version of your app to submit to the Amazon Appstore by just using the Cordova command line tool. Call the build command and add a --release flag like this:

$ cordova build --release

This time it will go through the normal build cycle, and then stop to ask you for your password to your keystore and alias. Once it is complete, there will be a brand new signed apk ending in -release.apk saved in the platform's ant-build directory. For example /platforms/amazon-fireos/ant-build/SimpleDraw-release.apk .

Submitting your app

Now that you have your apk ready to go, you can submit it to the Amazon Appstore! You'll need to gather some screenshots, that big icon image from earlier, and fill out some details describing your app on the site, but within a short time the submission process will be complete.

To start the process, head to the Amazon Developer Portal and click on the "Submit Android App" button (you'll need an Amazon developer account of course – that’s free).  That will guide you through the process - for more information about the type of information you need to answer, definitely check out our helpful submission documentation as well.

Good luck!

Hopefully this will save you some time when developing your own Cordova app for Amazon Fire OS. Though it may seem like a lot of steps the first time through, it's really only a few extra files that need to be created or changed, and the end result is a fun new app created using familiar, yet powerful, web technologies. Good luck with your app - and definitely contact us if you have any questions or concerns!

 

Want the latest?

appstore topics

Recent Posts

Archive