Home | History | Annotate | Download | only in samples
      1 page.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
     20 installation'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
     22 teapot</a>. In particular, it showcases the {@code ndk_helper} helper class,
     23 a collection of native helper functions required for implementing games and 
     24 similar 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
     34 a 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}
     44 extension; 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
     54 Android 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">
     60 APP_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">
     65 APP_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">
     72 APP_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
     78 activity lifecycle events, and also enables the app to display text on the screen. The following
     79 block of code is most important from the perspective of the native-side implementation: The native
     80 code calls it to display a popup window for displaying text.</p>
     81 
     82 <pre class="no-pretty-print">
     83 
     84 void 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
    103 based on where the user taps.</p>
    104 
    105 <pre class="no-pretty-print">
    106 ndk_helper::Mat4 mat_projection_;
    107 ndk_helper::Mat4 mat_view_;
    108 ndk_helper::Mat4 mat_model_;
    109 
    110 
    111 ndk_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
    117 helper-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
    127 function
    128 </pre>
    129 
    130 <p>The first use of the {@code ndk_helper} class is to handle the
    131 EGL-related lifecycle, associating EGL context states (created/lost) with
    132 Android lifecycle events. The {@code ndk_helper} class enables the application to preserve context
    133 information so that the system can restore a destroyed activity. This ability is useful, for
    134 example, when the target machine is rotated (causing an activity to be
    135 destroyed, then immediately restored in the new orientation), or when the lock
    136 screen appears.</p>
    137 
    138 <pre class="no-pretty-print">
    139 ndk_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">
    145 ndk_helper::DoubletapDetector doubletap_detector_;
    146 ndk_helper::PinchDetector pinch_detector_;
    147 ndk_helper::DragDetector drag_detector_;
    148 ndk_helper::PerfMonitor monitor_;
    149 </pre>
    150 
    151 <p>It also provides camera control (openGL view frustum).</p>
    152 
    153 <pre class="no-pretty-print">
    154 ndk_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">
    160 ASensorManager* sensor_manager_;
    161 const ASensor* accelerometer_sensor_;
    162 ASensorEventQueue* sensor_event_queue_;
    163 </pre>
    164 
    165 <p>The app calls the following functions in response to various Android
    166 lifecycle events and EGL context state changes, using various functionalities
    167 provided by {@code ndk_helper} via the {@code Engine} class.</p>
    168 
    169 <pre class="no-pretty-print">
    170 
    171 void LoadResources();
    172 void UnloadResources();
    173 void DrawFrame();
    174 void TermDisplay();
    175 void TrimMemory();
    176 bool 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">
    182 void 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
    200 superimposed on the screen rendered on the native side, and showing frame
    201 count.</p>
    202 
    203 <pre class="no-pretty-print">
    204 void 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
    222 for time-based animation based on real-time clock. This information is used, for example, in
    223 calculating momentum, where speed declines as a function of time.</p>
    224 
    225 <pre class="no-pretty-print">
    226 renderer_.Update( monitor_.GetCurrentTime() );
    227 </pre>
    228 
    229 <p>The application now checks whether the context information that {@code GLcontext} holds is still
    230 valid. If not, {@code ndk-helper} swaps the buffer, reinstantiating the GL context.</p>
    231 
    232 <pre class="no-pretty-print">
    233 if( EGL_SUCCESS != gl_context_-&gt;Swap() )  // swaps
    234 buffer.
    235 </pre>
    236 
    237 <p>The program passes touch-motion events to the gesture detector defined
    238 in the {@code ndk_helper} class. The gesture detector tracks multitouch
    239 gestures, such as pinch-and-drag, and sends a notification when triggered by
    240 any 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">
    290 void 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
    300 android_native_app_glue library. For more information about what the messages
    301 mean, 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">
    305 void 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}
    348 receives an {@code onNativeWindowCreated()} callback from the system.
    349 Applications can normally perform window initializations, such as EGL
    350 initialization. They do this outside of the activity lifecycle, since the
    351 activity 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