Android Native Application API

Exclusive offer: get 50% off this eBook here
Android Native Development Kit Cookbook

Android Native Development Kit Cookbook — Save 50%

A step-by-step tutorial with more than 60 concise recipes on Android NDK development skills book and ebook.

£18.99    £9.50
by Feipeng Liu | May 2013 | Cookbooks

Thanks to the Android native application APIs, it is possible to write an Android application with pure native code since Android API level 9 (Android 2.3, Gingerbread). That is, not a single line of Java code is needed. The Android native APIs are defined in several header files under the <NDK root>/platforms/android-<API level>/arch-arm/usr/ include/android/ folder.

In this article by Feipeng Liu author of Android Native Development Kit Cookbook, we will cover the following recipes:

  • Creating a native activity with the native_activity.h interface

  • Creating a native activity with the Android native app glue

  • Managing native windows at Android NDK

  • Detecting and handling input events at Android NDK

  • Accessing sensors at Android NDK

  • Managing assets at Android NDK

(For more resources related to this topic, see here.)

Based on the features provided by the functions defined in these header files, the APIs can be grouped as follows:

  • Activity lifecycle management:

    • native_activity.h

    • looper.h

  • Windows management:

    • rect.h

    • window.h

    • native_window.h

    • native_window_jni.h

  • Input (including key and motion events) and sensor events:

    • input.h

    • keycodes.h

    • sensor.h

  • Assets, configuration, and storage management:

    • configuration.h

    • asset_manager.h

    • asset_manager_jni.h

    • storage_manager.h

    • obb.h

In addition, Android NDK also provides a static library named native app glue to help create and manage native activities. The source code of this library can be found under the sources/android/native_app_glue/ directory.

In this article, we will first introduce the creation of a native activity with the simple callback model provided by native_acitivity.h, and the more complicated but flexible two-threaded model enabled by the native app glue library. We will then discuss window management at Android NDK, where we will draw something on the screen from the native code. Input events handling and sensor accessing are introduced next. Lastly, we will introduce asset management, which manages the files under the assets folder of our project. Note that the APIs covered in this article can be used to get rid of the Java code completely, but we don't have to do so. The Managing assets at Android NDK recipe provides an example of using the asset management API in a mixed-code Android project.

Before we start, it is important to keep in mind that although no Java code is needed in a native activity, the Android application still runs on Dalvik VM, and a lot of Android platform features are accessed through JNI. The Android native application API just hides the Java world for us.

Creating a native activity with the native_activity.h interface

The Android native application API allows us to create a native activity, which makes writing Android apps in pure native code possible. This recipe introduces how to write a simple Android application with pure C/C++ code.

Getting ready

Readers are expected to have basic understanding of how to invoke JNI functions.

How to do it…

The following steps to create a simple Android NDK application without a single line of Java code:

  1. Create an Android application named NativeActivityOne. Set the package name as cookbook.chapter5.nativeactivityone.

  2. Right-click on the NativeActivityOne project, select Android Tools | Add Native Support.
  3. Change the AndroidManifest.xml file as follows:

    <manifest xmlns:android="http://schemas.android.com/apk/res/
    android"
    package="cookbook.chapter5.nativeactivityone"
    android:versionCode="1"
    android:versionName="1.0">
    <uses-sdk android:minSdkVersion="9"/>
    <application android:label="@string/app_name"
    android:icon="@drawable/ic_launcher"
    android:hasCode="true">
    <activity android:name="android.app.NativeActivity"
    android:label="@string/app_name"
    android:configChanges="orientation|keyboardHidden">
    <meta-data android:name="android.app.lib_name"
    android:value="NativeActivityOne" />
    <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.
    LAUNCHER" />
    </intent-filter>
    </activity>
    </application>
    </manifest>

    We should ensure that the following are set correctly in the preceding file:

    • The activity name must be set to android.app.NativeActivity.

    • The value of the android.app.lib_name metadata must be set to the native module name without the lib prefix and .so suffix.

    • android:hasCode needs to be set to true, which indicates that the application contains code. Note that the documentation in <NDK root>/docs/NATIVE-ACTIVITY.HTML gives an example of the AndroidManifest.xml file with android:hasCode set to false, which will not allow the application to start.

  4. Add two files named NativeActivityOne.cpp and mylog.h under the jni folder. The ANativeActivity_onCreate method should be implemented in NativeActivityOne.cpp. The following is an example of the implementation:

    void ANativeActivity_onCreate(ANativeActivity* activity,
    void* savedState, size_t savedStateSize) {
    printInfo(activity);
    activity->callbacks->onStart = onStart;
    activity->callbacks->onResume = onResume;
    activity->callbacks->onSaveInstanceState = onSaveInstanceState;
    activity->callbacks->onPause = onPause;
    activity->callbacks->onStop = onStop;
    activity->callbacks->onDestroy = onDestroy;
    activity->callbacks->onWindowFocusChanged =
    onWindowFocusChanged;
    activity->callbacks->onNativeWindowCreated =
    onNativeWindowCreated;
    activity->callbacks->onNativeWindowResized =
    onNativeWindowResized;
    activity->callbacks->onNativeWindowRedrawNeeded =
    onNativeWindowRedrawNeeded;
    activity->callbacks->onNativeWindowDestroyed =
    onNativeWindowDestroyed;
    activity->callbacks->onInputQueueCreated = onInputQueueCreated;
    activity->callbacks->onInputQueueDestroyed =
    onInputQueueDestroyed;
    activity->callbacks->onContentRectChanged =
    onContentRectChanged;
    activity->callbacks->onConfigurationChanged =
    onConfigurationChanged;
    activity->callbacks->onLowMemory = onLowMemory;
    activity->instance = NULL;
    }

  5. Add the Android.mk file under the jni folder:

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE := NativeActivityOne
    LOCAL_SRC_FILES := NativeActivityOne.cpp
    LOCAL_LDLIBS := -landroid -llog
    include $(BUILD_SHARED_LIBRARY)

  6. Build the Android application and run it on an emulator or a device. Start a terminal and display the logcat output using the following:

    $ adb logcat -v time NativeActivityOne:I *:S

    Alternatively, you can use the logcat view at Eclipse to see the logcat output. When the application starts, you should be able to see the following logcat output:

    As shown in the screenshot, a few Android activity lifecycle callback functions are executed. We can manipulate the phone to cause other callbacks being executed. For example, long pressing the home button and then pressing the back button will cause the onWindowFocusChanged callback to be executed.

How it works…

In our example, we created a simple, "pure" native application to output logs when the Android framework calls into the callback functions defined by us. The "pure" native application is not really pure native. Although we did not write a single line of Java code, the Android framework still runs some Java code on Dalvik VM.

Android framework provides an android.app.NativeActivity.java class to help us create a "native" activity. In a typical Java activity, we extend android.app.Activity and overwrite the activity lifecycle methods. NativeActivity is also a subclass of android. app.Activity and does similar things. At the start of a native activity, NativeActivity. java will call ANativeActivity_onCreate, which is declared in native_activity.h and implemented by us. In the ANativeActivity_onCreate method, we can register our callback methods to handle activity lifecycle events and user inputs. At runtime, NativeActivity will invoke these native callback methods when the corresponding events occurred.

In a word, NativeActivity is a wrapper that hides the managed Android Java world for our native code, and exposes the native interfaces defined in native_activity.h.

The ANativeActivity data structure: Every callback method in the native code accepts an instance of the ANativeActivity structure. Android NDK defines the ANativeActivity data structure in native_acitivity.h as follows:

typedef struct ANativeActivity {
struct ANativeActivityCallbacks* callbacks;
JavaVM* vm;
JNIEnv* env;
jobject clazz;
const char* internalDataPath;
const char* externalDataPath;
int32_t sdkVersion;
void* instance;
AAssetManager* assetManager;
} ANativeActivity;

The various attributes of the preceding code are explained as follows:

  • callbacks: It is a data structure that defines all the callbacks that the Android framework will invoke with the main UI thread.

  • vm: It is the application process' global Java VM handle. It is used in some JNI functions.

  • env: It is a JNIEnv interface pointer. JNIEnv is used through local storage data , so this field is only accessible through the main UI thread.

  • clazz: It is a reference to the android.app.NativeActivity object created by the Android framework. It can be used to access fields and methods in the android. app.NativeActivity Java class. In our code, we accessed the toString method of android.app.NativeActivity.

  • internalDataPath: It is the internal data directory path for the application.

  • externalDataPath: It is the external data directory path for the application.

    internalDataPath and externalDataPath are NULL at Android 2.3.x. This is a known bug and has been fixed since Android 3.0. If we are targeting devices lower than Android 3.0, then we need to find other ways to get the internal and external data directories.

  • sdkVersion: It is the Android platform's SDK version code. Note that this refers to the version of the device/emulator that runs the app, not the SDK version used in our development.

  • instance: It is not used by the framework. We can use it to store user-defined data and pass it around.

  • assetManager: It is the a pointer to the app's instance of the asset manager. We will need it to access assets data. We will discuss it in more detail in the Managing assets at Android NDK recipe of this article

There's more…

The native_activity.h interface provides a simple single thread callback mechanism, which allows us to write an activity without Java code. However, this single thread approach infers that we must quickly return from our native callback methods. Otherwise, the application will become unresponsive to user actions (for example, when we touch the screen or press the Menu button, the app does not respond because the GUI thread is busy executing the callback function).

A way to solve this issue is to use multiple threads. For example, many games take a few seconds to load. We will need to offload the loading to a background thread, so that the UI can display the loading progress and be responsive to user inputs. Android NDK comes with a static library named android_native_app_glue to help us in handling such cases. The details of this library are covered in the Creating a native activity with the Android native app glue recipe.

A similar problem exists at Java activity. For example, if we write a Java activity that searches the entire device for pictures at onCreate, the application will become unresponsive. We can use AsyncTask to search and load pictures in the background, and let the main UI thread display a progress bar and respond to user inputs.

Creating a native activity with the Android native app glue

The previous recipe described how the interface defined in native_activity.h allows us to create native activity. However, all the callbacks defined are invoked with the main UI thread, which means we cannot do heavy processing in the callbacks.

Android SDK provides AsyncTask, Handler, Runnable, Thread, and so on, to help us handle things in the background and communicate with the main UI thread. Android NDK provides a static library named android_native_app_glue to help us execute callback functions and handle user inputs in a separate thread. This recipe will discuss the android_native_app_glue library in detail.

Getting ready

The android_native_app_glue library is built on top of the native_activity.h interface. Therefore, readers are recommended to read the Creating a native activity with the native_activity.h interface recipe before going through this one.

How to do it…

The following steps create a simple Android NDK application based on the android_native_app_glue library:

  1. Create an Android application named NativeActivityTwo. Set the package name as cookbook.chapter5.nativeactivitytwo.

  2. Right-click on the NativeActivityTwo project, select Android Tools | Add Native Support.

  3. Change the AndroidManifest.xml file as follows:

    <manifest xmlns:android="http://schemas.android.com/apk/res/
    android"
    package="cookbook.chapter5.nativeactivitytwo"
    android:versionCode="1"
    android:versionName="1.0">
    <uses-sdk android:minSdkVersion="9"/>
    <application android:label="@string/app_name"
    android:icon="@drawable/ic_launcher"
    android:hasCode="true">
    <activity android:name="android.app.NativeActivity"
    android:label="@string/app_name"
    android:configChanges="orientation|keyboardHidden">
    <meta-data android:name="android.app.lib_name"
    android:value="NativeActivityTwo" />
    <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.
    LAUNCHER" />
    </intent-filter>
    </activity>
    </application>
    </manifest>

  4. Add two files named NativeActivityTwo.cpp and mylog.h under the jni folder. NativeActivityTwo.cpp is shown as follows:

    #include <jni.h>
    #include <android_native_app_glue.h>
    #include "mylog.h"
    void handle_activity_lifecycle_events(struct android_app* app,
    int32_t cmd) {
    LOGI(2, "%d: dummy data %d", cmd, *((int*)(app->userData)));
    }
    void android_main(struct android_app* app) {
    app_dummy(); // Make sure glue isn't stripped.
    int dummyData = 111;
    app->userData = &dummyData;
    app->onAppCmd = handle_activity_lifecycle_events;
    while (1) {
    int ident, events;
    struct android_poll_source* source;
    if ((ident=ALooper_pollAll(-1, NULL, &events, (void**)&source)) >=
    0) {
    source->process(app, source);
    }
    }
    }

  5. Add the Android.mk file under the jni folder:

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE := NativeActivityTwo
    LOCAL_SRC_FILES := NativeActivityTwo.cpp
    LOCAL_LDLIBS := -llog -landroid
    LOCAL_STATIC_LIBRARIES := android_native_app_glue
    include $(BUILD_SHARED_LIBRARY)
    $(call import-module,android/native_app_glue)

  6. Build the Android application and run it on an emulator or device. Start a terminal and display the logcat output by using the following command:

    adb logcat -v time NativeActivityTwo:I *:S

    When the application starts, you should be able to see the following logcat output and the device screen will shows a black screen:

    On pressing the back button, the following output will be shown:

How it works…

This recipe demonstrates how the android_native_app_glue library is used to create a native activity.

The following steps should be followed to use the android_native_app_glue library:

  • Implement a function named android_main. This function should implement an event loop, which will poll for events continuously. This method will run in the background thread created by the library.

  • Two event queues are attached to the background thread by default, including the activity lifecycle event queue and the input event queue. When polling events using the looper created by the library, you can identify where the event is coming from, by checking the returned identifier (either LOOPER_ID_MAIN or LOOPER_ID_INPUT). It is also possible to attach additional event queues to the background thread.

  • When an event is returned, the data pointer will point to an android_poll_source data structure. We can call the process function of this structure. The process is a function pointer, which points to android_app->onAppCmd for activity lifecycle events, and android_app->onInputEvent for input events. We can provide our own processing functions and direct the corresponding function pointers to these functions.

In our example, we implement a simple function named handle_activity_lifecycle_ events and point the android_app->onAppCmd function pointer to it. This function simply prints the cmd value and the user data passed along with the android_app data structure. cmd is defined in android_native_app_glue.h as an enum. For example, when the app starts, the cmd values are 10, 11, 0, 1, and 6, which correspond to APP_CMD_START, APP_CMD_RESUME, APP_CMD_INPUT_CHANGED, APP_CMD_INIT_WINDOW, and APP_CMD_ GAINED_FOCUS respectively.

android_native_app_glue Library Internals: The source code of the android_native_ app_glue library can be found under the sources/android/native_app_glue folder of Android NDK. It only consists of two files, namely android_native_app_glue.c and android_native_app_glue.h. Let's first describe the flow of the code and then discuss some important aspects in detail.

Since the source code for native_app_glue is provided, we can modify it if necessary, although in most cases it won't be necessary.

android_native_app_glue is built on top of the native_activity.h interface. As shown in the following code (extracted from sources/android/native_app_glue/ android_native_app_glue.c). It implements the ANativeActivity_onCreate function, where it registers the callback functions and calls the android_app_create function. Note that the returned android_app instance is pointed by the instance field of the native activity, which can be passed to various callback functions:

void ANativeActivity_onCreate(ANativeActivity* activity,
void* savedState, size_t savedStateSize) {
LOGV("Creating: %p\n", activity);
activity->callbacks->onDestroy = onDestroy;
activity->callbacks->onStart = onStart;
activity->callbacks->onResume = onResume;
… …
activity->callbacks->onNativeWindowCreated =
onNativeWindowCreated;
activity->callbacks->onNativeWindowDestroyed =
onNativeWindowDestroyed;
activity->callbacks->onInputQueueCreated = onInputQueueCreated;
activity->callbacks->onInputQueueDestroyed =
onInputQueueDestroyed;
activity->instance = android_app_create(activity, savedState,
savedStateSize);
}

The android_app_create function (shown in the following code snippet) initializes an instance of the android_app data structure, which is defined in android_native_app_ glue.h. This function creates a unidirectional pipe for inter-thread communication. After that, it spawns a new thread (let's call it background thread thereafter) to run the android_ app_entry function with the initialized android_app data as the input argument. The main thread will wait for the background thread to start and then return:

static struct android_app* android_app_create(ANativeActivity*
activity, void* savedState, size_t savedStateSize) {
struct android_app* android_app = (struct android_app*)
malloc(sizeof(struct android_app));
memset(android_app, 0, sizeof(struct android_app));
android_app->activity = activity;
pthread_mutex_init(&android_app->mutex, NULL);
pthread_cond_init(&android_app->cond, NULL);
……
int msgpipe[2];
if (pipe(msgpipe)) {
LOGE("could not create pipe: %s", strerror(errno));
return NULL;
}
android_app->msgread = msgpipe[0];
android_app->msgwrite = msgpipe[1];
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&android_app->thread, &attr, android_app_entry,
android_app);
// Wait for thread to start.
pthread_mutex_lock(&android_app->mutex);
while (!android_app->running) {
pthread_cond_wait(&android_app->cond, &android_app->mutex);
}
pthread_mutex_unlock(&android_app->mutex);
return android_app;
}

The background thread starts with the android_app_entry function (as shown in the following code snippet), where a looper is created. Two event queues will be attached to the looper. The activity lifecycle events queue is attached to the android_app_entry function. When the activity's input queue is created, the input queue is attached (to the android_ app_pre_exec_cmd function of android_native_app_glue.c). After attaching the activity lifecycle event queue, the background thread signals the main thread it is already running. It then calls a function named android_main with the android_app data. android_main is the function we need to implement, as shown in our sample code. It must run in a loop until the activity exits:

static void* android_app_entry(void* param) {
struct android_app* android_app = (struct android_app*)param;
… …
//Attach life cycle event queue with identifier LOOPER_ID_MAIN
android_app->cmdPollSource.id = LOOPER_ID_MAIN;
android_app->cmdPollSource.app = android_app;
android_app->cmdPollSource.process = process_cmd;
android_app->inputPollSource.id = LOOPER_ID_INPUT;
android_app->inputPollSource.app = android_app;
android_app->inputPollSource.process = process_input;
ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_
CALLBACKS);
ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN,
ALOOPER_EVENT_INPUT, NULL, &android_app->cmdPollSource);
android_app->looper = looper;
pthread_mutex_lock(&android_app->mutex);
android_app->running = 1;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
android_main(android_app);
android_app_destroy(android_app);
return NULL;
}

The following diagram indicates how the main and background thread work together to create the multi-threaded native activity:

We use the activity lifecycle event queue as an example. The main thread invokes the callback functions, which simply writes to the write end of the pipe, while true loop implemented in the android_main function will poll for events. Once an event is detected, the function calls the event handler, which reads the exact command from the read end of the pipe and handles it. The android_native_app_glue library implements all the main thread stuff and part of the background thread stuff for us. We only need to supply the polling loop and the event handler as illustrated in our sample code.

Pipe: The main thread creates a unidirectional pipe in the android_app_create function by calling the pipe method. This method accepts an array of two integers. After the function is returned, the first integer will be set as the file descriptor referring to the read end of the pipe, while the second integer will be set as the file descriptor referring to the write end of the pipe.

A pipe is usually used for Inter-process Communication (IPC), but here it is used for communication between the main UI thread and the background thread created at android_ app_entry. When an activity lifecycle event occurs, the main thread will execute the corresponding callback function registered at ANativeActivity_onCreate. The callback function simply writes a command to the write end of the pipe and then waits for a signal from the background thread. The background thread is supposed to poll for events continuously and once it detects a lifecycle event, it will read the exact event from the read end of the pipe, signal the main thread to unblock and handle the events. Because the signal is sent right after receiving the command and before actual processing of the events, the main thread can return from the callback function quickly without worrying about the possible long processing of the events.

Different operating systems have different implementations for the pipe. The pipe implemented by Android system is "half-duplex", where communication is unidirectional. That is, one file descriptor can only write, and the other file descriptor can only read. Pipes in some operating system is "full-duplex", where the two file descriptors can both read and write.

Looper is an event tracking facility, which allows us to attach one or more event queues for an event loop of a thread. Each event queue has an associated file descriptor. An event is data available on a file descriptor. In order to use a looper, we need to include the android/ looper.h header file.

The library attaches two event queues for the event loop to be created by us in the background thread, including the activity lifecycle event queue and the input event queue. The following steps should be performed in order to use a looper:

  1. Create or obtain a looper associated with the current thread: This is done by the ALooper_prepare function:

    ALooper* ALooper_prepare(int opts);

    This function prepares a looper associated with the calling thread and returns it. If the looper doesn't exist, it creates one, associates it with the thread, and returns it

  2. Attach an event queue: This is done by ALooper_addFd. The function has the following prototype:

    int ALooper_addFd(ALooper* looper, int fd, int ident, int events,
    ALooper_callbackFunc callback, void* data);

    The function can be used in two ways. Firstly, if callback is set to NULL, the ident set will be returned by ALooper_pollOnce and ALooper_pollAll. Secondly, if callback is non-NULL, then the callback function will be executed and ident is ignored. The android_native_app_glue library uses the first approach to attach a new event queue to the looper. The input argument fd indicates the file descriptor associated with the event queue. ident is the identifier for the events from the event queue, which can be used to classify the event. The identifier must be bigger than zero when callback is set to NULL. callback is set to NULL in the library source code, and data points to the private data that will be returned along with the identifier at polling.

    In the library, this function is called to attach the activity lifecycle event queue to the background thread. The input event queue is attached using the input queue specific function AInputQueue_attachLooper, which we will discuss in the Detecting and handling input events at NDK recipe.

  3. Poll for events: This can be done by either one of the following two functions:

    int ALooper_pollOnce(int timeoutMillis, int* outFd, int*
    outEvents, void** outData);
    int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents,
    void** outData);

    These two methods are equivalent when callback is set to NULL in ALooper_addFd. They have the same input arguments. timeoutMillis specifies the timeout for polling. If it is set to zero, then the functions return immediately; if it is set to negative, they will wait indefinitely until an event occurs. The functions return the identifier (greater than zero) when an event occurs from any input queues attached to the looper. In this case, outFd, outEvents, and outData will be set to the file descriptor, poll events, and data associated with the event. Otherwise, they will be set to NULL.

  4. Detach event queues: This is done by the following function:

    int ALooper_removeFd(ALooper* looper, int fd);

    It accepts the looper and file descriptor associated with the event queue, and detaches the queue from the looper.

Android Native Development Kit Cookbook A step-by-step tutorial with more than 60 concise recipes on Android NDK development skills book and ebook.
Published: March 2013
eBook Price: £18.99
Book Price: £30.99
See more
Select your format and quantity:

Managing native windows at Android NDK

The previous recipes in this article provided simple examples with the logcat output only. This recipe will discuss how to manage the native window at Android NDK.

Getting ready

Readers are recommended to read the following recipes before going through this one:

  • Creating a native activity with the native_activity.h interface

  • Creating a native activity with the Android native app glue

How to do it…

The following steps create the sample application:

  1. Create an Android application named NativeWindowManagement. Set the package name as cookbook.chapter5.nativewindowmanagement.

  2. Right-click on the NativeWindowManagement project, select Android Tools | Add Native Support.

  3. Update AndroidManifest.xml. Please refer to previous recipe or the downloaded code for details. Note that the metadata android.app.lib_name must have its value as NativeWindowManagement.

  4. Add two files named NativeWindowManagement.cpp and mylog.h under the jni folder. NativeWindowManagement.cpp is modified based on previous recipe. The following code snippet shows the updated part:

    void drawSomething(struct android_app* app) {
    ANativeWindow_Buffer lWindowBuffer;
    ANativeWindow* lWindow = app->window;
    ANativeWindow_setBuffersGeometry(lWindow, 0, 0, WINDOW_FORMAT_
    RGBA_8888);
    if (ANativeWindow_lock(lWindow, &lWindowBuffer, NULL) < 0) {
    return;
    }
    memset(lWindowBuffer.bits, 0, lWindowBuffer.
    stride*lWindowBuffer.height*sizeof(uint32_t));
    int sqh = 150, sqw = 100;
    int wst = lWindowBuffer.stride/2 - sqw/2;
    int wed = wst + sqw;
    int hst = lWindowBuffer.height/2 - sqh/2;
    int hed = hst + sqh;
    for (int i = hst; i < hed; ++i) {
    for (int j = wst; j < wed; ++j) {
    ((char*)(lWindowBuffer.bits))[(i*lWindowBuffer.stride +
    j)*sizeof(uint32_t)] = (char)255; //R
    ((char*)(lWindowBuffer.bits))[(i*lWindowBuffer.stride +
    j)*sizeof(uint32_t) + 1] = (char)0; //G
    ((char*)(lWindowBuffer.bits))[(i*lWindowBuffer.stride +
    j)*sizeof(uint32_t) + 2] = (char)0; //B
    ((char*)(lWindowBuffer.bits))[(i*lWindowBuffer.stride +
    j)*sizeof(uint32_t) + 3] = (char)255; //A
    }
    }
    ANativeWindow_unlockAndPost(lWindow);
    }
    void handle_activity_lifecycle_events(struct android_app* app,
    int32_t cmd) {
    LOGI(2, "%d: dummy data %d", cmd, *((int*)(app->userData)));
    switch (cmd) {
    case APP_CMD_INIT_WINDOW:
    drawSomething(app);
    break;
    }
    }

  5. Add the Android.mk file under the jni folder, which is similar to the one used in the previous recipe. You just need to replace the module name as NativeWindowManagement and the source file as NativeWindowManagement.cpp.

  6. Build the Android application and run it on an emulator or device. Start a terminal and display the logcat output by using the following command:

    $ adb logcat -v time NativeWindowManagement:I *:S

    When the application starts, we will see the following logcat:

    The device screen will display a red rectangle at the center of the screen, as follows:

How it works…

The NDK interface for native window management is defined in the window.h , rect.h,native_window_jni.h, and native_window.h header files. The first two simply define some constants and data structures. native_window_jni.h defines a single function named ANativeWindow_fromSurface, which helps us to obtain a native window from a Java surface object.

Perform the following steps to draw a square on the phone screen:

  1. Set the window buffer format and size:This is done by the ANativeWindow_setBuffersGeometry function:

    int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window,
    int32_t width, int32_t height, int32_t format);

    This function updates the native window buffer associated with the native window referred by the window input argument. The window size and format are changed according to the rest of the input arguments. Three formats are defined in native_window.h, including WINDOW_FORMAT_RGBA_8888, WINDOW_FORMAT_ RGBX_8888, and WINDOW_FORMAT_RGB_565. If the size or the format is set to 0,then the native window's base value will be used.

  2. Lock the window's next drawing surface: This is done by the ANativeWindow_lock function:

    int32_t ANativeWindow_lock(ANativeWindow* window, ANativeWindow_
    Buffer* outBuffer, ARect* inOutDirtyBounds);

    After this call is returned, the input argument outBuffer will refer to the window buffer for drawing.

  3. Clear the buffer: This is optional. Sometimes we may just want to overwrite a part of the window buffer. In our example, we called memset to set all the data to 0.

  4. Draw something to the buffer: In our example, we first calculate the start and end width and height of the rectangle, and then set the red and alpha bytes of the rectangle area as 255. This will show us a red rectangle.

  5. Unlock the window's drawing surface and post the new buffer to display: This is done with the ANativeWindow_unlockAndPost function:

    int32_t ANativeWindow_unlockAndPost(ANativeWindow* window);

Detecting and handling input events at Android NDK

Input events are essential for user interaction in Android apps. This recipe discusses how to detect and handle input events in Android NDK.

Getting ready

We will further develop the example in last recipe. Please read the Managing native windows at Android NDK recipe before going through this one.

How to do it…

The following steps create a sample application, which detects and handles input events at the native code:

  1. Create an Android application named NativeInputs. Set the package name as cookbook.chapter5.nativeinputs.

  2. Right-click on the NativeInputs project, select Android Tools | Add Native Support.

  3. Update AndroidManifest.xml. Please refer to previous recipe or the downloaded code for details. Note that the metadata android.app.lib_name must have a value as NativeInputs.

  4. Add two files named NativeInputs.cpp and mylog.h under the jni folder. NativeInputs.cpp is modified based on the previous recipe. Let us see a part of its code here:

    • handle_input_events: This is the event handler method for input events. Note that when a motion event with move action (AINPUT_EVENT_ TYPE_MOTION) is detected, we update app->userData and set app- >redrawNeeded to 1:

      int mPreviousX = -1;
      int32_t handle_input_events(struct android_app* app,
      AInputEvent* event) {
      int etype = AInputEvent_getType(event);
      switch (etype) {
      case AINPUT_EVENT_TYPE_KEY:
      … ...
      break;
      case AINPUT_EVENT_TYPE_MOTION:
      int32_t action, posX, pointer_index;
      action = AMotionEvent_getAction(event);
      pointer_index = (action&AMOTION_EVENT_ACTION_POINTER_
      INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
      posX = AMotionEvent_getX(event, pointer_index);
      if (action == AMOTION_EVENT_ACTION_MOVE) {
      int xMove = posX - mPreviousX;
      USERDATA* userData = (USERDATA*)app->userData;
      userData->xMove = xMove;
      app->redrawNeeded = 1;
      }
      mPreviousX = posX;
      break;
      }
      }

    • android_main: We update the while true loop. When app- >redrawNeeded is set, we redraw the rectangle:

      void android_main(struct android_app* app) {
      … ...
      while (1) {
      int ident, events;
      struct android_poll_source* source;
      if ((ident=ALooper_pollOnce(app->redrawNeeded?0:-1,
      NULL, &events, (void**)&source)) >= 0) {
      if (NULL!=source) {
      source->process(app, source);
      }
      if (app->redrawNeeded) {
      drawSomething(app);
      }
      }
      }
      }

  5. Add the Android.mk file under the jni folder, which is similar to previous recipe. We just need to replace the module name as NativeInputs and the source file as NativeInputs.cpp.

  6. Build the Android application and run it on an emulator or device. We can move a figure across the screen to see the rectangle moving horizontally:

How it works…

This recipe discusses input events handling with the android_native_app_glue library at Android NDK.

Input event queue in android_native_app_glue: android_native_app_glue attaches the input event queue for us by default.

  1. When the input queue is created for an activity, the onInputQueueCreated callback is called on the main thread, which writes APP_CMD_INPUT_CHANGED to the write end of the pipe we described in previous recipe. The background thread will receive the command and call AInputQueue_attachLooper the function to attach the input queue to the background thread looper.

  2. When an input event occurs, it will be handled by process_input (the function pointer source->process in the while true loop we called points to process_ input if the event is an input event). Inside process_input, AInputQueue_ getEvent is firstly called to retrieve the event. Then, AInputQueue_ preDispatchEvent is called to send the key for pre-dispatching. This could possibly result in it being consumed by the current Input Method Editor (IME) before the app. Followed by this is the android_app->onInputEvent, which is a function pointer-pointing to an event handler provided by us. If no event handler is provided by us, it's set to NULL. After that, AInputQueue_finishEvent is called to indicate that event handling is over.

  3. Lastly, when the input queue is destroyed, the onInputQueueDestroyed callback is called on the main thread, which also writes APP_CMD_INPUT_CHANGED. The background thread will read the command and call a function named AInputQueue_detachLooper to detach the input queue from the thread looper.

Event handler: In the handle_input_events function, we first called AInputEvent_ getType to get the input event type. The android/input.h header file defines two input event types, namely AINPUT_EVENT_TYPE_KEY and AINPUT_EVENT_TYPE_MOTION. The first event type indicates that the input event is a key event, while the second one indicates that it is a motion event.

We called AKeyEvent_getAction, AKeyEvent_getFlags, and AKeyEvent_ getKeyCode to get the action, flags, and key code of a key event and printed a string to describe it. On the other hand, we called AMotionEvent_getAction and AMotionEvent_getX to get the action and the x position of a motion event. Note that the AMotionEvent_getX function requires the second input argument as the pointer index. The pointer index is obtained by using the following code:

pointer_index = (action&AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;

There are a lot more input event functions, which can be found at andoid/input.h.

Android Native Development Kit Cookbook A step-by-step tutorial with more than 60 concise recipes on Android NDK development skills book and ebook.
Published: March 2013
eBook Price: £18.99
Book Price: £30.99
See more
Select your format and quantity:

Accessing sensors at Android NDK

Many Android devices have built-in sensors to detect and measure motion, orientation, and other environmental conditions. It is possible to access sensors in Android NDK. This recipe will discuss how to do it in detail.

Getting ready

The example provided in this recipe is based on the sample code in the previous two recipes. Readers are recommended to read them first:

  • Managing native windows at Android NDK

  • Detecting and handling input events at Android NDK

How to do it…

The following steps develop the sample Android application, which demonstrates how to access sensors from Android NDK:

  1. Create an Android application named nativesensors. Set the package name as cookbook.chapter5.nativesensors.

  2. Right-click on the nativesensors project, select Android Tools | Add Native Support.

  3. Update AndroidManifest.xml. Please refer to previous recipe or the downloaded code for details. Note that the metadata android.app.lib_name must have a value as nativesensors.

  4. Add two files named nativesensors.cpp and mylog.h under the jni folder. Let's show a part of the code in nativesensors.cpp.

    • handle_activity_lifecycle_events: This function handles activity lifecycle events. We enable the sensor when the activity is in focus and disable it when the activity loses its focus. This saves the battery life by avoiding reading sensors when our activity is not in focus:

      void handle_activity_lifecycle_events(struct android_app*
      app, int32_t cmd) {
      USERDATA* userData;
      switch (cmd) {
      …...
      case APP_CMD_SAVE_STATE:
      // save current state
      userData = (USERDATA*)(app->userData);
      app->savedState = malloc(sizeof(SAVED_USERDATA));
      *((SAVED_USERDATA*)app->savedState) = userData-
      >drawingData;
      app->savedStateSize = sizeof(SAVED_USERDATA);
      break;
      case APP_CMD_GAINED_FOCUS:
      userData = (USERDATA*)(app->userData);
      if (NULL != userData->accelerometerSensor) {
      ASensorEventQueue_enableSensor(userData-
      >sensorEventQueue,
      userData->accelerometerSensor);
      ASensorEventQueue_setEventRate(userData-
      >sensorEventQueue,
      userData->accelerometerSensor, (1000L/60)*1000);
      }
      break;
      case APP_CMD_LOST_FOCUS:
      USERDATA userData = *(USERDATA*) app->userData;
      if (NULL!=userData.accelerometerSensor) {
      ASensorEventQueue_disableSensor(userData.sensorEventQueue,
      userData.accelerometerSensor);
      }
      break;
      }
      }

    • android_main: We continuously poll for events and handle the sensor events identified by the LOOPER_ID_USER identifier:

      void android_main(struct android_app* app) {
      … ...
      while (0==app->destroyRequested) {
      int ident, events;
      struct android_poll_source* source;
      if ((ident=ALooper_pollOnce(-1, NULL, &events,
      (void**)&source)) >= 0) {
      if (LOOPER_ID_USER == ident) {
      ASensorEvent event;
      while (ASensorEventQueue_getEvents(userData.
      sensorEventQueue,
      &event, 1) > 0) {
      int64_t currentTime = get_time();
      … ...
      if ((currentTime - lastTime) > TIME_THRESHOLD) {
      long diff = currentTime - lastTime;
      float speedX = (event.acceleration.x - lastX)/
      diff*10000;
      float speedY = (event.acceleration.y - lastY)/
      diff*10000;
      float speedZ = (event.acceleration.z - lastZ)/
      diff*10000;
      float speed = fabs(speedX + speedY + speedZ);
      …...
      }
      }
      }
      }
      }
      ASensorManager_destroyEventQueue(userData.sensorManager, userData.
      sensorEventQueue);
      }

  5. Add the Android.mk file under the jni folder, which is similar to the one used in the previous recipe. We just need to replace the module name as nativesensors and the source file as nativesensors.cpp.

  6. Build the Android application and run it on an emulator or device. We can shake the device to see the rectangle moving horizontally:

How it works…

In our example, we used the accelerometer sensor to detect phone shaking. Then, based on the phone shaking speed, we move the red rectangle to one side of the phone screen. Once the rectangle reaches an edge of the phone screen, it starts to move to the other edge.

The example code provides a simple algorithm to determine whether a shake has happened or not. More complex and accurate algorithms exist and can be implemented. We can also adjust the SHAKE_TIMEOUT and SHAKE_COUNT_THRESHOLD constants to fine tune the algorithm.

The important part of the example is how to access sensors. Let's summarize the steps:

  1. Get a reference to the sensor manager: This is done by using the following function:

    ASensorManager* ASensorManager_getInstance();

  2. Get the default sensor of a given type: We can also get a list of all available sensors. This is done by using the following two functions respectively:

    ASensor const* ASensorManager_getDefaultSensor(ASensorManager*
    manager, int type);
    int ASensorManager_getSensorList(ASensorManager* manager,
    ASensorList* list);

    The available types are defined in android/sensor.h. In our example, we print all sensor names and types but only use ASENSOR_TYPE_ACCELEROMETER.

  3. Create a new sensor queue and attach it to the looper of the thread: This is done by using the ASensorManager_createEventQueue function as follows:

    ASensorEventQueue* ASensorManager_createEventQueue(ASensorMana
    ger* manager, ALooper* looper, int ident, ALooper_callbackFunc
    callback, void* data);

    The usage of this function is similar to the usagw of the ALooper_addFd function in the Creating a native activity with the Android native app glue recipe and AInputQueue_attachLooper in the Detecting and handling input events at Android NDK recipe. In our example, we set the ident as LOOPER_ID_USER. Note that we may also define a new looper ID by changing the code of android_native_ app_glue.h and setting it here.

  4. Enable and configure the sensor:

    int ASensorEventQueue_enableSensor(ASensorEventQueue* queue,
    ASensor const* sensor);
    int ASensorEventQueue_setEventRate(ASensorEventQueue* queue,
    ASensor const* sensor, int32_t usec);

    The first function enables the sensor referred by the sensor input argument. The second function sets the delivery rate of the events, in microseconds, for the sensor referred by the sensor input argument. In our example, we called these two functions when the activity gained focus.

  5. Poll for events and get the available events from the queue: The polling is done by calling ALooper_pollOnce, as shown in the previous recipe. If the event identifier returned is LOOPER_ID_USER, we know that it is a sensor event and we can use the following function to get it:

    ssize_t ASensorEventQueue_getEvents(ASensorEventQueue* queue,
    ASensorEvent* events, size_t count);

    count indicates the maximum number of available events we want to get. In our example, we set it to 1. It is also possible to define an array of ASensorEvent and get multiple events at one time.

  6. Handle sensor events: The sensor event is represented by the ASensorEvent data structure, which can be found at android/sensor.h (the exact path to the file is <Android NDK root dir>/platforms/android-<version>/arch-arm/ usr/include/android/sensor.h). In our example, we accessed the acceleration readings at the x, y, and z axes, and used the readings to determine if a phone shake has happened.

  7. Disable the sensor: After you are done accessing the sensors, you can disable it with the following function:

    int ASensorEventQueue_disableSensor(ASensorEventQueue* queue,
    ASensor const* sensor);

  8. Destroy the sensor event queue and free all resources associated with it:

    int ASensorManager_destroyEventQueue(ASensorManager* manager,
    ASensorEventQueue* queue);

Managing assets at Android NDK

Assets provide a way for Android apps to include various types of files, including text, image, audio, video, and so on. This recipe discusses how to load asset files from Android NDK.

How to do it…

The following steps describe how the sample application is developed:

  1. Create an Android application named NativeAssets. Set the package name as cookbook.chapter5.nativeassets.

  2. Right-click on the NativeAssets project, select Android Tools | Add Native Support.

  3. Add three Java files, namely MyActivity.java, MySurfaceView.java, and MyRenderer.java under the cookbook.chapter5.nativeassets package.

    The last file is slightly changed, where the naLoadTexture native method signature is updated as follows:

    private static native void naLoadTexture(AssetManager
    pAssetManager);

    In the onSurfaceCreated method, we called the native method by passing a Java AssetManager instance:

    naLoadTexture(mContext.getAssets());

  4. Create two folders under the jni folder, namely dice and libpng-1.5.12. In the libpng-1.5.12 folder, we place the source files of libpng, which can be downloaded from http://sourceforge.net/projects/libpng/files/.

    In the dice folder, we add the Cube.cpp , Cube.h, mylog.h, and DiceG1.cpp files.The DiceG1.cpp file is updated by adding procedures to read .png assets files from the assets folder. Let's show a part of the updated code:

    • readPng: It is the callback function used at png_set_read_fn. It reads the data from the asset file:

      void readPng(png_structp pPngPtr, png_bytep pBuf, png_size_t
      pCount) {
      AAsset* assetF = (AAsset*)png_get_io_ptr(pPngPtr);
      AAsset_read(assetF, pBuf, pCount);
      }

    • naLoadTexture: It reads all the .png files under the assets top-level directory and loads the data to OpenGL for texture mapping:

      void naLoadTexture(JNIEnv* env, jclass clazz, jobject
      pAssetManager) {
      AAssetManager* assetManager = AAssetManager_fromJava(env,
      pAssetManager);
      AAssetDir* texDir = AAssetManager_openDir(assetManager,
      "");
      const char* texFn;
      int pId = 0;
      while (NULL != (texFn = AAssetDir_
      getNextFileName(texDir))) {
      AAsset* assetF = AAssetManager_open(assetManager, texFn,
      AASSET_MODE_UNKNOWN);
      //read the png header
      png_byte header[8];
      png_byte *imageData;
      …...
      if (8 != AAsset_read(assetF, header, 8)) {
      goto FEND;
      }
      …...
      //init png reading by setting a read callback
      png_set_read_fn(pngPtr, assetF, readPng);
      …...
      // Loads image data into OpenGL.
      glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0,
      format, type, imageData);
      FEND:
      AAsset_close(assetF);
      pId++;
      }
      AAssetDir_close(texDir);
      }

  5. Add an Android.mk file under jni, jni/dice, and jni/libpng-1.5.12 respectively. The Android.mk file under the jni top-level folder is as follows. This simply instructs the Android build system to include the Android.mk files under each sub-directory under the jni folder:

    LOCAL_PATH := $(call my-dir)
    include $(call all-subdir-makefiles)

    The Android.mk file under the jni/libpng-1.5.12 folder is as follows. This compiles libpng as a local static library:

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_CFLAGS :=
    LOCAL_MODULE := libpng
    LOCAL_SRC_FILES :=\
    png.c \
    pngerror.c \
    pngget.c \
    pngmem.c \
    pngpread.c \
    pngread.c \
    pngrio.c \
    pngrtran.c \
    pngrutil.c \
    pngset.c \
    pngtrans.c \
    pngwio.c \
    pngwrite.c \
    pngwtran.c \
    pngwutil.c
    LOCAL_LDLIBS := -lz
    include $(BUILD_STATIC_LIBRARY)

    The Android.mk file under the jni/dice folder is as follows:

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE := DiceG1NativeAssets
    LOCAL_C_INCLUDES := $(LOCAL_PATH)/../libpng-1.5.12/
    LOCAL_STATIC_LIBRARIES := libpng
    LOCAL_SRC_FILES := Cube.cpp DiceG1.cpp
    LOCAL_LDLIBS := -lGLESv1_CM -llog -landroid -lz
    include $(BUILD_SHARED_LIBRARY)

  6. Build the Android NDK application and run it on an Android device. The app will display a cube textured as a dice;

How it works…

In the example, we load the .png files from the assets folder and used them as OpenGL textures. You can use the following steps to read assets:

  1. Get a native AAssetManager object from the Java AssetManager object: This is done by the AAssetManager_fromJava function, which is defined in asset_manager_jni.h.

  2. Open an asset directory: This is done by AAssetManager_openDir.

    AAssetDir* AAssetManager_openDir(AAssetManager* mgr, const char*
    dirName);

    To open the top-level directory "assets", we set dirName to "". For the subdirectories, we will need to supply the directory name.

  3. Get an asset file name:

    const char* AAssetDir_getNextFileName(AAssetDir* assetDir);

    Iterate over the files under the asset directory referred by the input argument assetDir. If all files have been returned or there are no files, NULL is returned.

  4. Open an asset file: This is done by using AAssetManager_open:

    AAsset* AAssetManager_open(AAssetManager* mgr, const char*
    filename, int mode);

Summary

In this article, we learned about the Android Native Application API.

Resources for Article :


Further resources on this subject:


About the Author :


Feipeng Liu

Feipeng Liu is a technology enthusiast with a focus on multimedia systems and applications. He started mobile applications development in 2008 on Windows Mobile. In 2010, he started developing apps for Android with NDK. His Android apps have been used by many users. One of his apps, Video Converter Android, reached 1 million downloads within 10 months. Feipeng received his B.ENG in Electrical and Electronic Engineering, Nanyang Technological University and Master of Computing degree in the Department of Computer Science, National University of Singapore.

Books From Packt


Android Database Programming
Android Database Programming

Android Application Testing Guide
Android Application Testing Guide

Android NDK Beginner’s Guide
Android NDK Beginner’s Guide

Android User Interface Development: Beginner's Guide
Android User Interface Development: Beginner's Guide

Android 3.0 Animations: Beginner’s Guide
Android 3.0 Animations: Beginner’s Guide

Android 3.0 Application Development Cookbook
Android 3.0 Application Development Cookbook

Android 4: New Features for Application Development
Android 4: New Features for Application Development

AndEngine for Android Game Development Cookbook
AndEngine for Android Game Development Cookbook


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software