Lab archive: lab-9.zip Lab solutions: lab-9-solved.zip
Create a new Android Application project. Do not let it create a new activity, you will be adding a native activity. After the project is created add native support to it.
The native activity has a main function and requires the <android/native_activity.h> header.
void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize)
Add this function to your file and print a log entry from it. You will have to add -landroid to LOCAL_LDLIBS in Android.mk, in addition to -llog and make sure you are building a shared library.
Edit AndroidManifest.xml. Here you will have to declare your activity.
<activity android:name="android.app.NativeActivity" android:configChanges="orientation|keyboardHidden" android:label="@string/app_name" > <meta-data android:name="android.app.lib_name" android:value="library_name" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
Change library_name to match the name of your shared library, without the lib prefix and the .so postfix. Add this activity inside the application. Add inside the application tag android:hasCode=“false”. Make sure minSDKVersion is at least 10.
Build and launch the application. It should start, print the log, and after a few seconds it should tell you application is not responding. This is because there are no callbacks defined for the activity.
If you want to create a native application you have to register all the callbacks (onStart, onStop, onDestroy, onCreateInputQueue, etc.) and treat the events properly. This tends to add a lot of code, and potentially duplicate code. Since SDK 10 there is a library called app glue, which does all this, and it exposes events in two callbacks instead. It also adds multithreading, separating input from life-cycle events.
To add native app glue, first of all, remove the ANativeActivity_onCreate function and add the android_native_app_glue.h header. Secondly, add *LOCAL_STATIC_LIBRARIES := android_native_app_glue* to the application module and import the module by adding the following line to the end of your Android.mk file.
$(call import-module, android/native_app_glue)
The activity still needs an entry point:
void android_main(struct android_app* app) { app_dummy(); // Make sure glue isn't stripped. app->userData = NULL; app->onAppCmd = handle_activity_lifecycle_events; app->onInputEvent = handle_activity_input_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); } } }
Notice that the app defines two callbacks, one for life-cycle events and the other for input events, and then goes into a loop waiting for events and dispatching them to the corresponding function. You can look over the app glue code in LD_LIBRARY_PATH </note>
Import the project from the lab archive.
Look over the code. Like with an Android Java application, you have to first create an EGL context in which to draw and then call the drawing functions. Unlike the Java version, there is no renderer class, so you can call the draw functions at any time. In this case they are drawn in a loop, after processing events. It is usually advisable to limit the frame rate to 30 or 60, since otherwise the application might drain a lot of battery. The rest of the code is identical to the one used in the previous session.
To access a touch event you have to add code to the input handler function. Use AInputEvent_getType(event) to get the type of the event. What you want in this case is events of the type AINPUT_EVENT_TYPE_MOTION. To get the type of motion event, use AMotionEvent_getAction(event), and look for move events, AMOTION_EVENT_ACTION_MOVE. Memorize the position of this event:
AMotionEvent_getX(event, 0); AMotionEvent_getY(event, 0);
The last parameter is the pointer index. On multi-touch displays you can get more than one pointer, and for some gestures this is important.
Use the memorized positions to move the camera. Look for ry and rz in the renderFrame() function.
To use the sensors you must first get an instance of the Sensor manager, then ask for the type of sensor you want to use and finally define a way to identify the events, and potentially add a callback. For example, for an accelerometer:
sensorManager = ASensorManager_getInstance(); accelerometerSensor = ASensorManager_getDefaultSensor(sensorManager, ASENSOR_TYPE_ACCELEROMETER); sensorEventQueue = ASensorManager_createEventQueue(sensorManager, looper, LOOPER_ID_USER, NULL, NULL); ASensorEventQueue_enableSensor(sensorEventQueue, accelerometerSensor); ASensorEventQueue_setEventRate(sensorEventQueue, accelerometerSensor, 500);
The android_app structure contains a looper field, which you can use. LOOPER_ID_USER is the first available id when using native app glue, the ones smaller than LOOPER_ID_USER are used internally by the library.
To get an accelerometer event, after processing the event, in case the source is not NULL, check the value returned by ALooper_pollAll (or ALooper_pollOne). This should match the identifier you set in ASensorManager_createEventQueue. If it does match, use:
ASensorEventQueue_getEvents(sensorEventQueue, event, num_events)
Event should be a pointer towards an ASensorEvent structure if num_events is 1 or an ASensorEvent array, if num_events is more than 1. ASensorEventQueue_getEvents returns the number of events it read. The data read from the accelerometer will be in the accelerometer structure inside the event. Use this data to rotate the camera on the rx direction.