1/*
2 * Copyright 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//--------------------------------------------------------------------------------
18// Include files
19//--------------------------------------------------------------------------------
20#include <jni.h>
21#include <errno.h>
22
23#include <vector>
24#include <EGL/egl.h>
25#include <GLES/gl.h>
26
27#include <android/sensor.h>
28#include <android/log.h>
29#include <android_native_app_glue.h>
30#include <android/native_window_jni.h>
31#include <cpu-features.h>
32
33#include "MoreTeapotsRenderer.h"
34
35//-------------------------------------------------------------------------
36//Preprocessor
37//-------------------------------------------------------------------------
38#define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper function
39//-------------------------------------------------------------------------
40//Constants
41//-------------------------------------------------------------------------
42const int32_t NUM_TEAPOTS_X = 8;
43const int32_t NUM_TEAPOTS_Y = 8;
44const int32_t NUM_TEAPOTS_Z = 8;
45
46//-------------------------------------------------------------------------
47//Shared state for our app.
48//-------------------------------------------------------------------------
49struct android_app;
50class Engine
51{
52    MoreTeapotsRenderer renderer_;
53
54    ndk_helper::GLContext* gl_context_;
55
56    bool initialized_resources_;
57    bool has_focus_;
58
59    ndk_helper::DoubletapDetector doubletap_detector_;
60    ndk_helper::PinchDetector pinch_detector_;
61    ndk_helper::DragDetector drag_detector_;
62    ndk_helper::PerfMonitor monitor_;
63
64    ndk_helper::TapCamera tap_camera_;
65
66    android_app* app_;
67
68    ASensorManager* sensor_manager_;
69    const ASensor* accelerometer_sensor_;
70    ASensorEventQueue* sensor_event_queue_;
71
72    void UpdateFPS( float fps );
73    void ShowUI();
74    void TransformPosition( ndk_helper::Vec2& vec );
75
76public:
77    static void HandleCmd( struct android_app* app,
78            int32_t cmd );
79    static int32_t HandleInput( android_app* app,
80            AInputEvent* event );
81
82    Engine();
83    ~Engine();
84    void SetState( android_app* state );
85    int InitDisplay();
86    void LoadResources();
87    void UnloadResources();
88    void DrawFrame();
89    void TermDisplay();
90    void TrimMemory();
91    bool IsReady();
92
93    void UpdatePosition( AInputEvent* event,
94            int32_t index,
95            float& x,
96            float& y );
97
98    void InitSensors();
99    void ProcessSensors( int32_t id );
100    void SuspendSensors();
101    void ResumeSensors();
102};
103
104//-------------------------------------------------------------------------
105//Ctor
106//-------------------------------------------------------------------------
107Engine::Engine() :
108                initialized_resources_( false ),
109                has_focus_( false ),
110                app_( NULL ),
111                sensor_manager_( NULL ),
112                accelerometer_sensor_( NULL ),
113                sensor_event_queue_( NULL )
114{
115    gl_context_ = ndk_helper::GLContext::GetInstance();
116}
117
118//-------------------------------------------------------------------------
119//Dtor
120//-------------------------------------------------------------------------
121Engine::~Engine()
122{
123}
124
125/**
126 * Load resources
127 */
128void Engine::LoadResources()
129{
130    renderer_.Init( NUM_TEAPOTS_X, NUM_TEAPOTS_Y, NUM_TEAPOTS_Z );
131    renderer_.Bind( &tap_camera_ );
132}
133
134/**
135 * Unload resources
136 */
137void Engine::UnloadResources()
138{
139    renderer_.Unload();
140}
141
142/**
143 * Initialize an EGL context for the current display.
144 */
145int Engine::InitDisplay()
146{
147    if( !initialized_resources_ )
148    {
149        gl_context_->Init( app_->window );
150        LoadResources();
151        initialized_resources_ = true;
152    }
153    else
154    {
155        // initialize OpenGL ES and EGL
156        if( EGL_SUCCESS != gl_context_->Resume( app_->window ) )
157        {
158            UnloadResources();
159            LoadResources();
160        }
161    }
162
163    ShowUI();
164
165    // Initialize GL state.
166    glEnable( GL_CULL_FACE );
167    glEnable( GL_DEPTH_TEST );
168    glDepthFunc( GL_LEQUAL );
169
170    //Note that screen size might have been changed
171    glViewport( 0, 0, gl_context_->GetScreenWidth(), gl_context_->GetScreenHeight() );
172    renderer_.UpdateViewport();
173
174    tap_camera_.SetFlip( 1.f, -1.f, -1.f );
175    tap_camera_.SetPinchTransformFactor( 10.f, 10.f, 8.f );
176
177    return 0;
178}
179
180/**
181 * Just the current frame in the display.
182 */
183void Engine::DrawFrame()
184{
185    float fps;
186    if( monitor_.Update( fps ) )
187    {
188        UpdateFPS( fps );
189    }
190    double dTime = monitor_.GetCurrentTime();
191    renderer_.Update( dTime );
192
193    // Just fill the screen with a color.
194    glClearColor( 0.5f, 0.5f, 0.5f, 1.f );
195    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
196    renderer_.Render();
197
198    // Swap
199    if( EGL_SUCCESS != gl_context_->Swap() )
200    {
201        UnloadResources();
202        LoadResources();
203    }
204}
205
206/**
207 * Tear down the EGL context currently associated with the display.
208 */
209void Engine::TermDisplay()
210{
211    gl_context_->Suspend();
212
213}
214
215void Engine::TrimMemory()
216{
217    LOGI( "Trimming memory" );
218    gl_context_->Invalidate();
219}
220/**
221 * Process the next input event.
222 */
223int32_t Engine::HandleInput( android_app* app,
224        AInputEvent* event )
225{
226    Engine* eng = (Engine*) app->userData;
227    if( AInputEvent_getType( event ) == AINPUT_EVENT_TYPE_MOTION )
228    {
229        ndk_helper::GESTURE_STATE doubleTapState = eng->doubletap_detector_.Detect( event );
230        ndk_helper::GESTURE_STATE dragState = eng->drag_detector_.Detect( event );
231        ndk_helper::GESTURE_STATE pinchState = eng->pinch_detector_.Detect( event );
232
233        //Double tap detector has a priority over other detectors
234        if( doubleTapState == ndk_helper::GESTURE_STATE_ACTION )
235        {
236            //Detect double tap
237            eng->tap_camera_.Reset( true );
238        }
239        else
240        {
241            //Handle drag state
242            if( dragState & ndk_helper::GESTURE_STATE_START )
243            {
244                //Otherwise, start dragging
245                ndk_helper::Vec2 v;
246                eng->drag_detector_.GetPointer( v );
247                eng->TransformPosition( v );
248                eng->tap_camera_.BeginDrag( v );
249            }
250            else if( dragState & ndk_helper::GESTURE_STATE_MOVE )
251            {
252                ndk_helper::Vec2 v;
253                eng->drag_detector_.GetPointer( v );
254                eng->TransformPosition( v );
255                eng->tap_camera_.Drag( v );
256            }
257            else if( dragState & ndk_helper::GESTURE_STATE_END )
258            {
259                eng->tap_camera_.EndDrag();
260            }
261
262            //Handle pinch state
263            if( pinchState & ndk_helper::GESTURE_STATE_START )
264            {
265                //Start new pinch
266                ndk_helper::Vec2 v1;
267                ndk_helper::Vec2 v2;
268                eng->pinch_detector_.GetPointers( v1, v2 );
269                eng->TransformPosition( v1 );
270                eng->TransformPosition( v2 );
271                eng->tap_camera_.BeginPinch( v1, v2 );
272            }
273            else if( pinchState & ndk_helper::GESTURE_STATE_MOVE )
274            {
275                //Multi touch
276                //Start new pinch
277                ndk_helper::Vec2 v1;
278                ndk_helper::Vec2 v2;
279                eng->pinch_detector_.GetPointers( v1, v2 );
280                eng->TransformPosition( v1 );
281                eng->TransformPosition( v2 );
282                eng->tap_camera_.Pinch( v1, v2 );
283            }
284        }
285        return 1;
286    }
287    return 0;
288}
289
290/**
291 * Process the next main command.
292 */
293void Engine::HandleCmd( struct android_app* app,
294        int32_t cmd )
295{
296    Engine* eng = (Engine*) app->userData;
297    switch( cmd )
298    {
299    case APP_CMD_SAVE_STATE:
300        break;
301    case APP_CMD_INIT_WINDOW:
302        // The window is being shown, get it ready.
303        if( app->window != NULL )
304        {
305            eng->InitDisplay();
306            eng->DrawFrame();
307        }
308        break;
309    case APP_CMD_TERM_WINDOW:
310        // The window is being hidden or closed, clean it up.
311        eng->TermDisplay();
312        eng->has_focus_ = false;
313        break;
314    case APP_CMD_STOP:
315        break;
316    case APP_CMD_GAINED_FOCUS:
317        eng->ResumeSensors();
318        //Start animation
319        eng->has_focus_ = true;
320        break;
321    case APP_CMD_LOST_FOCUS:
322        eng->SuspendSensors();
323        // Also stop animating.
324        eng->has_focus_ = false;
325        eng->DrawFrame();
326        break;
327    case APP_CMD_LOW_MEMORY:
328        //Free up GL resources
329        eng->TrimMemory();
330        break;
331    }
332}
333
334//-------------------------------------------------------------------------
335//Sensor handlers
336//-------------------------------------------------------------------------
337void Engine::InitSensors()
338{
339    sensor_manager_ = ASensorManager_getInstance();
340    accelerometer_sensor_ = ASensorManager_getDefaultSensor( sensor_manager_,
341            ASENSOR_TYPE_ACCELEROMETER );
342    sensor_event_queue_ = ASensorManager_createEventQueue( sensor_manager_, app_->looper,
343            LOOPER_ID_USER, NULL, NULL );
344}
345
346void Engine::ProcessSensors( int32_t id )
347{
348    // If a sensor has data, process it now.
349    if( id == LOOPER_ID_USER )
350    {
351        if( accelerometer_sensor_ != NULL )
352        {
353            ASensorEvent event;
354            while( ASensorEventQueue_getEvents( sensor_event_queue_, &event, 1 ) > 0 )
355            {
356            }
357        }
358    }
359}
360
361void Engine::ResumeSensors()
362{
363    // When our app gains focus, we start monitoring the accelerometer.
364    if( accelerometer_sensor_ != NULL )
365    {
366        ASensorEventQueue_enableSensor( sensor_event_queue_, accelerometer_sensor_ );
367        // We'd like to get 60 events per second (in us).
368        ASensorEventQueue_setEventRate( sensor_event_queue_, accelerometer_sensor_,
369                (1000L / 60) * 1000 );
370    }
371}
372
373void Engine::SuspendSensors()
374{
375    // When our app loses focus, we stop monitoring the accelerometer.
376    // This is to avoid consuming battery while not being used.
377    if( accelerometer_sensor_ != NULL )
378    {
379        ASensorEventQueue_disableSensor( sensor_event_queue_, accelerometer_sensor_ );
380    }
381}
382
383//-------------------------------------------------------------------------
384//Misc
385//-------------------------------------------------------------------------
386void Engine::SetState( android_app* state )
387{
388    app_ = state;
389    doubletap_detector_.SetConfiguration( app_->config );
390    drag_detector_.SetConfiguration( app_->config );
391    pinch_detector_.SetConfiguration( app_->config );
392}
393
394bool Engine::IsReady()
395{
396    if( has_focus_ )
397        return true;
398
399    return false;
400}
401
402void Engine::TransformPosition( ndk_helper::Vec2& vec )
403{
404    vec = ndk_helper::Vec2( 2.0f, 2.0f ) * vec
405            / ndk_helper::Vec2( gl_context_->GetScreenWidth(), gl_context_->GetScreenHeight() )
406            - ndk_helper::Vec2( 1.f, 1.f );
407}
408
409void Engine::ShowUI()
410{
411    JNIEnv *jni;
412    app_->activity->vm->AttachCurrentThread( &jni, NULL );
413
414    //Default class retrieval
415    jclass clazz = jni->GetObjectClass( app_->activity->clazz );
416    jmethodID methodID = jni->GetMethodID( clazz, "showUI", "()V" );
417    jni->CallVoidMethod( app_->activity->clazz, methodID );
418
419    app_->activity->vm->DetachCurrentThread();
420    return;
421}
422
423void Engine::UpdateFPS( float fps )
424{
425    JNIEnv *jni;
426    app_->activity->vm->AttachCurrentThread( &jni, NULL );
427
428    //Default class retrieval
429    jclass clazz = jni->GetObjectClass( app_->activity->clazz );
430    jmethodID methodID = jni->GetMethodID( clazz, "updateFPS", "(F)V" );
431    jni->CallVoidMethod( app_->activity->clazz, methodID, fps );
432
433    app_->activity->vm->DetachCurrentThread();
434    return;
435}
436
437Engine g_engine;
438
439/**
440 * This is the main entry point of a native application that is using
441 * android_native_app_glue.  It runs in its own thread, with its own
442 * event loop for receiving input events and doing other things.
443 */
444void android_main( android_app* state )
445{
446    app_dummy();
447
448    g_engine.SetState( state );
449
450    //Init helper functions
451    ndk_helper::JNIHelper::GetInstance()->Init( state->activity, HELPER_CLASS_NAME );
452
453    state->userData = &g_engine;
454    state->onAppCmd = Engine::HandleCmd;
455    state->onInputEvent = Engine::HandleInput;
456
457#ifdef USE_NDK_PROFILER
458    monstartup("libMoreTeapotsNativeActivity.so");
459#endif
460
461    // Prepare to monitor accelerometer
462    g_engine.InitSensors();
463
464    // loop waiting for stuff to do.
465    while( 1 )
466    {
467        // Read all pending events.
468        int id;
469        int events;
470        android_poll_source* source;
471
472        // If not animating, we will block forever waiting for events.
473        // If animating, we loop until all events are read, then continue
474        // to draw the next frame of animation.
475        while( (id = ALooper_pollAll( g_engine.IsReady() ? 0 : -1, NULL, &events, (void**) &source ))
476                >= 0 )
477        {
478            // Process this event.
479            if( source != NULL )
480                source->process( state, source );
481
482            g_engine.ProcessSensors( id );
483
484            // Check if we are exiting.
485            if( state->destroyRequested != 0 )
486            {
487                g_engine.TermDisplay();
488                return;
489            }
490        }
491
492        if( g_engine.IsReady() )
493        {
494            // Drawing is throttled to the screen update rate, so there
495            // is no need to do timing here.
496            g_engine.DrawFrame();
497        }
498    }
499}
500
501