1page.title=Sample: Teapot
2@jd:body
3
4<div id="qv-wrapper">
5    <div id="qv">
6      <h2>On this page</h2>
7
8      <ol>
9        <li><a href="#am">AndroidManifest.xml</a></li>
10        <li><a href="#ap">Application.mk</a></li>
11        <li><a href="#ji">Java-side Implementation</a></li>
12        <li><a href="#ni">Native-side Implementation</a></li>
13          </ol>
14        </li>
15      </ol>
16    </div>
17  </div>
18
19<p>The Teapot sample is located under in the {@code samples/Teapot/} directory, under the NDK
20installation's root directory. This sample uses the OpenGL library to render the iconic
21<a href="http://math.hws.edu/bridgeman/courses/324/s06/doc/opengl.html#basic">Utah
22teapot</a>. In particular, it showcases the {@code ndk_helper} helper class,
23a collection of native helper functions required for implementing games and
24similar applications as native applications. This class provides:</p>
25
26<ul>
27<li>An abstraction layer, {@code GLContext}, that handles certain NDK-specific behaviors.</li>
28<li>Helper functions that are useful but not present in the NDK, such as tap detection.</li>
29<li>Wrappers for JNI calls for platform features such as texture loading.</li>
30</ul>
31
32<h2 id="am">AndroidManifest.xml</h2>
33<p>The activity declaration here is not {@link android.app.NativeActivity} itself, but
34a subclass of it: {@code TeapotNativeActivity}.</p>
35
36<pre class="no-pretty-print">
37    &lt;activity android:name="com.sample.teapot.TeapotNativeActivity"
38            android:label="@string/app_name"
39            android:configChanges="orientation|keyboardHidden"&gt;
40</pre>
41
42<p>Ultimately, the name of the shared-object file that the build system builds is
43{@code libTeapotNativeActivity.so}. The build system adds the {@code lib} prefix and the {@code .so}
44extension; neither is part of the value that the manifest originally assigns to
45{@code android:value}.</p>
46
47<pre class="no-pretty-print">
48        &lt;meta-data android:name="android.app.lib_name"
49                android:value="TeapotNativeActivity" /&gt;
50</pre>
51
52<h2 id="ap">Application.mk</h2>
53<p>An app that uses the {@link android.app.NativeActivity} framework class must not specify an
54Android API level lower than 9, which introduced that class. For more information about the
55{@link android.app.NativeActivity} class, see
56<a href="{@docRoot}ndk/guides/concepts.html#naa">Native Activities and Applications</a>.
57</p>
58
59<pre class="no-pretty-print">
60APP_PLATFORM := android-9
61</pre>
62
63<p>The next line tells the build system to build for all supported architectures.</p>
64<pre class="no-pretty-print">
65APP_ABI := all
66</pre>
67
68<p>Next, the file tells the build system which
69<a href="{@docRoot}ndk/guides/cpp-support.html">C++ runtime support library</a> to use. </p>
70
71<pre class="no-pretty-print">
72APP_STL := stlport_static
73</pre>
74
75<h2 id="ji">Java-side Implementation</h2>
76<p>The {@code TeapotNativeActivity.java} file is located in
77{@code samples/Teapot/src/com/sample/teapot}, under the NDK installation root directory. It handles
78activity lifecycle events, and also enables the app to display text on the screen. The following
79block of code is most important from the perspective of the native-side implementation: The native
80code calls it to display a popup window for displaying text.</p>
81
82<pre class="no-pretty-print">
83
84void setImmersiveSticky() {
85    View decorView = getWindow().getDecorView();
86    decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN
87            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
88            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
89            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
90            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
91            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
92}
93</pre>
94
95<h2 id="ni">Native-side Implementation</h2>
96
97<p>This section explores the part of the Teapot app implemented in C++.</p>
98
99<h3>TeapotRenderer.h</h3>
100
101<p>These function calls perform the actual rendering of the teapot. It uses
102{@code ndk_helper} for matrix calculation and to reposition the camera
103based on where the user taps.</p>
104
105<pre class="no-pretty-print">
106ndk_helper::Mat4 mat_projection_;
107ndk_helper::Mat4 mat_view_;
108ndk_helper::Mat4 mat_model_;
109
110
111ndk_helper::TapCamera* camera_;
112</pre>
113
114<h3>TeapotNativeActivity.cpp</h3>
115
116<p>The following lines include {@code ndk_helper} in the native source file, and define the
117helper-class name.</p>
118
119<pre class="no-pretty-print">
120
121#include "NDKHelper.h"
122
123//-------------------------------------------------------------------------
124//Preprocessor
125//-------------------------------------------------------------------------
126#define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper
127function
128</pre>
129
130<p>The first use of the {@code ndk_helper} class is to handle the
131EGL-related lifecycle, associating EGL context states (created/lost) with
132Android lifecycle events. The {@code ndk_helper} class enables the application to preserve context
133information so that the system can restore a destroyed activity. This ability is useful, for
134example, when the target machine is rotated (causing an activity to be
135destroyed, then immediately restored in the new orientation), or when the lock
136screen appears.</p>
137
138<pre class="no-pretty-print">
139ndk_helper::GLContext* gl_context_; // handles EGL-related lifecycle.
140</pre>
141
142<p>Next, {@code ndk_helper} provides touch control.</p>
143
144<pre class="no-pretty-print">
145ndk_helper::DoubletapDetector doubletap_detector_;
146ndk_helper::PinchDetector pinch_detector_;
147ndk_helper::DragDetector drag_detector_;
148ndk_helper::PerfMonitor monitor_;
149</pre>
150
151<p>It also provides camera control (openGL view frustum).</p>
152
153<pre class="no-pretty-print">
154ndk_helper::TapCamera tap_camera_;
155</pre>
156
157<p>The app then prepares to use the device's sensors, using the native APIs provided in the NDK.</p>
158
159<pre class="no-pretty-print">
160ASensorManager* sensor_manager_;
161const ASensor* accelerometer_sensor_;
162ASensorEventQueue* sensor_event_queue_;
163</pre>
164
165<p>The app calls the following functions in response to various Android
166lifecycle events and EGL context state changes, using various functionalities
167provided by {@code ndk_helper} via the {@code Engine} class.</p>
168
169<pre class="no-pretty-print">
170
171void LoadResources();
172void UnloadResources();
173void DrawFrame();
174void TermDisplay();
175void TrimMemory();
176bool IsReady();
177</pre>
178
179<p>Then, the following function calls back to the Java side to update the UI display.</p>
180
181<pre class="no-pretty-print">
182void Engine::ShowUI()
183{
184    JNIEnv *jni;
185    app_-&gt;activity-&gt;vm-&gt;AttachCurrentThread( &amp;jni, NULL );
186
187
188    //Default class retrieval
189    jclass clazz = jni-&gt;GetObjectClass( app_-&gt;activity-&gt;clazz );
190    jmethodID methodID = jni-&gt;GetMethodID( clazz, "showUI", "()V" );
191    jni-&gt;CallVoidMethod( app_-&gt;activity-&gt;clazz, methodID );
192
193
194    app_-&gt;activity-&gt;vm-&gt;DetachCurrentThread();
195    return;
196}
197</pre>
198
199<p>Next, this function calls back to the Java side to draw a text box
200superimposed on the screen rendered on the native side, and showing frame
201count.</p>
202
203<pre class="no-pretty-print">
204void Engine::UpdateFPS( float fFPS )
205{
206    JNIEnv *jni;
207    app_-&gt;activity-&gt;vm-&gt;AttachCurrentThread( &amp;jni, NULL );
208
209
210    //Default class retrieval
211    jclass clazz = jni-&gt;GetObjectClass( app_-&gt;activity-&gt;clazz );
212    jmethodID methodID = jni-&gt;GetMethodID( clazz, "updateFPS", "(F)V" );
213    jni-&gt;CallVoidMethod( app_-&gt;activity-&gt;clazz, methodID, fFPS );
214
215
216    app_-&gt;activity-&gt;vm-&gt;DetachCurrentThread();
217    return;
218}
219</pre>
220
221<p>The application gets the system clock and supplies it to the renderer
222for time-based animation based on real-time clock. This information is used, for example, in
223calculating momentum, where speed declines as a function of time.</p>
224
225<pre class="no-pretty-print">
226renderer_.Update( monitor_.GetCurrentTime() );
227</pre>
228
229<p>The application now checks whether the context information that {@code GLcontext} holds is still
230valid. If not, {@code ndk-helper} swaps the buffer, reinstantiating the GL context.</p>
231
232<pre class="no-pretty-print">
233if( EGL_SUCCESS != gl_context_-&gt;Swap() )  // swaps
234buffer.
235</pre>
236
237<p>The program passes touch-motion events to the gesture detector defined
238in the {@code ndk_helper} class. The gesture detector tracks multitouch
239gestures, such as pinch-and-drag, and sends a notification when triggered by
240any of these events.</p>
241
242<pre class="no-pretty-print">
243    if( AInputEvent_getType( event ) == AINPUT_EVENT_TYPE_MOTION )
244    {
245        ndk_helper::GESTURE_STATE doubleTapState =
246            eng->doubletap_detector_.Detect( event );
247        ndk_helper::GESTURE_STATE dragState = eng->drag_detector_.Detect( event );
248        ndk_helper::GESTURE_STATE pinchState = eng->pinch_detector_.Detect( event );
249
250        //Double tap detector has a priority over other detectors
251        if( doubleTapState == ndk_helper::GESTURE_STATE_ACTION )
252        {
253            //Detect double tap
254            eng->tap_camera_.Reset( true );
255        }
256        else
257        {
258            //Handle drag state
259            if( dragState & ndk_helper::GESTURE_STATE_START )
260            {
261                //Otherwise, start dragging
262                ndk_helper::Vec2 v;
263                eng->drag_detector_.GetPointer( v );
264                eng->TransformPosition( v );
265                eng->tap_camera_.BeginDrag( v );
266            }
267           // ...else other possible drag states...
268
269            //Handle pinch state
270            if( pinchState & ndk_helper::GESTURE_STATE_START )
271            {
272                //Start new pinch
273                ndk_helper::Vec2 v1;
274                ndk_helper::Vec2 v2;
275                eng->pinch_detector_.GetPointers( v1, v2 );
276                eng->TransformPosition( v1 );
277                eng->TransformPosition( v2 );
278                eng->tap_camera_.BeginPinch( v1, v2 );
279            }
280            // ...else other possible pinch states...
281        }
282        return 1;
283    }
284</pre>
285
286<p>The {@code ndk_helper} class also provides access to a vector-math library
287({@code vecmath.h}), using it here to transform touch coordinates.</p>
288
289<pre class="no-pretty-print">
290void Engine::TransformPosition( ndk_helper::Vec2& vec )
291{
292    vec = ndk_helper::Vec2( 2.0f, 2.0f ) * vec
293            / ndk_helper::Vec2( gl_context_->GetScreenWidth(),
294            gl_context_->GetScreenHeight() ) - ndk_helper::Vec2( 1.f, 1.f );
295}
296</pre>
297</ul>
298
299<p>The {@code HandleCmd()} method handles commands posted from the
300android_native_app_glue library. For more information about what the messages
301mean, refer to the comments in the {@code android_native_app_glue.h} and
302{@code .c} source files.</p>
303
304<pre class="no-pretty-print">
305void Engine::HandleCmd( struct android_app* app,
306        int32_t cmd )
307{
308    Engine* eng = (Engine*) app->userData;
309    switch( cmd )
310    {
311    case APP_CMD_SAVE_STATE:
312        break;
313    case APP_CMD_INIT_WINDOW:
314        // The window is being shown, get it ready.
315        if( app->window != NULL )
316        {
317            eng->InitDisplay();
318            eng->DrawFrame();
319        }
320        break;
321    case APP_CMD_TERM_WINDOW:
322        // The window is being hidden or closed, clean it up.
323        eng->TermDisplay();
324        eng->has_focus_ = false;
325        break;
326    case APP_CMD_STOP:
327        break;
328    case APP_CMD_GAINED_FOCUS:
329        eng->ResumeSensors();
330        //Start animation
331        eng->has_focus_ = true;
332        break;
333    case APP_CMD_LOST_FOCUS:
334        eng->SuspendSensors();
335        // Also stop animating.
336        eng->has_focus_ = false;
337        eng->DrawFrame();
338        break;
339    case APP_CMD_LOW_MEMORY:
340        //Free up GL resources
341        eng->TrimMemory();
342        break;
343    }
344}
345</pre>
346
347<p>The {@code ndk_helper} class posts {@code APP_CMD_INIT_WINDOW} when {@code android_app_glue}
348receives an {@code onNativeWindowCreated()} callback from the system.
349Applications can normally perform window initializations, such as EGL
350initialization. They do this outside of the activity lifecycle, since the
351activity is not yet ready.</p>
352
353<pre class="no-pretty-print">
354    //Init helper functions
355    ndk_helper::JNIHelper::Init( state->activity, HELPER_CLASS_NAME );
356
357    state->userData = &g_engine;
358    state->onAppCmd = Engine::HandleCmd;
359    state->onInputEvent = Engine::HandleInput;
360</pre>
361