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#include <EGL/egl.h>
17#include <GLES2/gl2.h>
18#include <fstream>
19#include <iostream>
20
21#include "JNIHelper.h"
22
23namespace ndk_helper
24{
25
26#define CLASS_NAME "android/app/NativeActivity"
27
28//---------------------------------------------------------------------------
29//JNI Helper functions
30//---------------------------------------------------------------------------
31
32//---------------------------------------------------------------------------
33//Singleton
34//---------------------------------------------------------------------------
35JNIHelper* JNIHelper::GetInstance()
36{
37    static JNIHelper helper;
38    return &helper;
39}
40
41//---------------------------------------------------------------------------
42//Ctor
43//---------------------------------------------------------------------------
44JNIHelper::JNIHelper()
45{
46    pthread_mutex_init( &mutex_, NULL );
47}
48
49//---------------------------------------------------------------------------
50//Dtor
51//---------------------------------------------------------------------------
52JNIHelper::~JNIHelper()
53{
54    pthread_mutex_lock( &mutex_ );
55
56    JNIEnv *env;
57    activity_->vm->AttachCurrentThread( &env, NULL );
58
59    env->DeleteGlobalRef( jni_helper_java_ref_ );
60    env->DeleteGlobalRef( jni_helper_java_class_ );
61
62    activity_->vm->DetachCurrentThread();
63
64    pthread_mutex_destroy( &mutex_ );
65}
66
67//---------------------------------------------------------------------------
68//Init
69//---------------------------------------------------------------------------
70void JNIHelper::Init( ANativeActivity* activity,
71        const char* helper_class_name )
72{
73    JNIHelper& helper = *GetInstance();
74    pthread_mutex_lock( &helper.mutex_ );
75
76    helper.activity_ = activity;
77
78    JNIEnv *env;
79    helper.activity_->vm->AttachCurrentThread( &env, NULL );
80
81    //Retrieve app name
82    jclass android_content_Context = env->GetObjectClass( helper.activity_->clazz );
83    jmethodID midGetPackageName = env->GetMethodID( android_content_Context, "getPackageName",
84            "()Ljava/lang/String;" );
85
86    jstring packageName = (jstring) env->CallObjectMethod( helper.activity_->clazz,
87            midGetPackageName );
88    const char* appname = env->GetStringUTFChars( packageName, NULL );
89    helper.app_name_ = std::string( appname );
90
91    jclass cls = helper.RetrieveClass( env, helper_class_name );
92    helper.jni_helper_java_class_ = (jclass) env->NewGlobalRef( cls );
93
94    jmethodID constructor = env->GetMethodID( helper.jni_helper_java_class_, "<init>", "()V" );
95    helper.jni_helper_java_ref_ = env->NewObject( helper.jni_helper_java_class_, constructor );
96    helper.jni_helper_java_ref_ = env->NewGlobalRef( helper.jni_helper_java_ref_ );
97
98    env->ReleaseStringUTFChars( packageName, appname );
99    helper.activity_->vm->DetachCurrentThread();
100
101    pthread_mutex_unlock( &helper.mutex_ );
102}
103
104//---------------------------------------------------------------------------
105//readFile
106//---------------------------------------------------------------------------
107bool JNIHelper::ReadFile( const char* fileName,
108        std::vector<uint8_t>* buffer_ref )
109{
110    if( activity_ == NULL )
111    {
112        LOGI( "JNIHelper has not been initialized.Call init() to initialize the helper" );
113        return false;
114    }
115
116    //First, try reading from externalFileDir;
117    JNIEnv *env;
118    jmethodID mid;
119
120    pthread_mutex_lock( &mutex_ );
121    activity_->vm->AttachCurrentThread( &env, NULL );
122
123    jstring str_path = GetExternalFilesDirJString( env );
124    const char* path = env->GetStringUTFChars( str_path, NULL );
125    std::string s( path );
126
127    if( fileName[0] != '/' )
128    {
129        s.append( "/" );
130    }
131    s.append( fileName );
132    std::ifstream f( s.c_str(), std::ios::binary );
133
134    env->ReleaseStringUTFChars( str_path, path );
135    env->DeleteLocalRef( str_path );
136    activity_->vm->DetachCurrentThread();
137
138    if( f )
139    {
140        LOGI( "reading:%s", s.c_str() );
141        f.seekg( 0, std::ifstream::end );
142        int32_t fileSize = f.tellg();
143        f.seekg( 0, std::ifstream::beg );
144        buffer_ref->reserve( fileSize );
145        buffer_ref->assign( std::istreambuf_iterator<char>( f ), std::istreambuf_iterator<char>() );
146        f.close();
147        pthread_mutex_unlock( &mutex_ );
148        return true;
149    }
150    else
151    {
152        //Fallback to assetManager
153        AAssetManager* assetManager = activity_->assetManager;
154        AAsset* assetFile = AAssetManager_open( assetManager, fileName, AASSET_MODE_BUFFER );
155        if( !assetFile )
156        {
157            pthread_mutex_unlock( &mutex_ );
158            return false;
159        }
160        uint8_t* data = (uint8_t*) AAsset_getBuffer( assetFile );
161        int32_t size = AAsset_getLength( assetFile );
162        if( data == NULL )
163        {
164            AAsset_close( assetFile );
165
166            LOGI( "Failed to load:%s", fileName );
167            pthread_mutex_unlock( &mutex_ );
168            return false;
169        }
170
171        buffer_ref->reserve( size );
172        buffer_ref->assign( data, data + size );
173
174        AAsset_close( assetFile );
175        pthread_mutex_unlock( &mutex_ );
176        return true;
177    }
178}
179
180std::string JNIHelper::GetExternalFilesDir()
181{
182    if( activity_ == NULL )
183    {
184        LOGI( "JNIHelper has not been initialized. Call init() to initialize the helper" );
185        return std::string( "" );
186    }
187
188    pthread_mutex_lock( &mutex_ );
189
190    //First, try reading from externalFileDir;
191    JNIEnv *env;
192    jmethodID mid;
193
194    activity_->vm->AttachCurrentThread( &env, NULL );
195
196    jstring strPath = GetExternalFilesDirJString( env );
197    const char* path = env->GetStringUTFChars( strPath, NULL );
198    std::string s( path );
199
200    env->ReleaseStringUTFChars( strPath, path );
201    env->DeleteLocalRef( strPath );
202    activity_->vm->DetachCurrentThread();
203
204    pthread_mutex_unlock( &mutex_ );
205    return s;
206}
207
208uint32_t JNIHelper::LoadTexture( const char* file_name )
209{
210    if( activity_ == NULL )
211    {
212        LOGI( "JNIHelper has not been initialized. Call init() to initialize the helper" );
213        return 0;
214    }
215
216    JNIEnv *env;
217    jmethodID mid;
218
219    pthread_mutex_lock( &mutex_ );
220    activity_->vm->AttachCurrentThread( &env, NULL );
221
222    jstring name = env->NewStringUTF( file_name );
223
224    GLuint tex;
225    glGenTextures( 1, &tex );
226    glBindTexture( GL_TEXTURE_2D, tex );
227
228    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
229    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
230
231    mid = env->GetMethodID( jni_helper_java_class_, "loadTexture", "(Ljava/lang/String;)Z" );
232    jboolean ret = env->CallBooleanMethod( jni_helper_java_ref_, mid, name );
233    if( !ret )
234    {
235        glDeleteTextures( 1, &tex );
236        tex = -1;
237        LOGI( "Texture load failed %s", file_name );
238    }
239
240    //Generate mipmap
241    glGenerateMipmap( GL_TEXTURE_2D );
242
243    env->DeleteLocalRef( name );
244    activity_->vm->DetachCurrentThread();
245    pthread_mutex_unlock( &mutex_ );
246
247    return tex;
248
249}
250
251std::string JNIHelper::ConvertString( const char* str,
252        const char* encode )
253{
254    if( activity_ == NULL )
255    {
256        LOGI( "JNIHelper has not been initialized. Call init() to initialize the helper" );
257        return std::string( "" );
258    }
259
260    JNIEnv *env;
261
262    pthread_mutex_lock( &mutex_ );
263    activity_->vm->AttachCurrentThread( &env, NULL );
264
265    int32_t iLength = strlen( (const char*) str );
266
267    jbyteArray array = env->NewByteArray( iLength );
268    env->SetByteArrayRegion( array, 0, iLength, (const signed char*) str );
269
270    jstring strEncode = env->NewStringUTF( encode );
271
272    jclass cls = env->FindClass( "java/lang/String" );
273    jmethodID ctor = env->GetMethodID( cls, "<init>", "([BLjava/lang/String;)V" );
274    jstring object = (jstring) env->NewObject( cls, ctor, array, strEncode );
275
276    const char *cparam = env->GetStringUTFChars( object, NULL );
277
278    std::string s = std::string( cparam );
279
280    env->ReleaseStringUTFChars( object, cparam );
281    env->DeleteLocalRef( strEncode );
282    env->DeleteLocalRef( object );
283    activity_->vm->DetachCurrentThread();
284    pthread_mutex_unlock( &mutex_ );
285
286    return s;
287}
288
289//---------------------------------------------------------------------------
290//Audio helpers
291//---------------------------------------------------------------------------
292int32_t JNIHelper::GetNativeAudioBufferSize()
293{
294    if( activity_ == NULL )
295    {
296        LOGI( "JNIHelper has not been initialized. Call init() to initialize the helper" );
297        return 0;
298    }
299
300    JNIEnv *env;
301    jmethodID mid;
302
303    pthread_mutex_lock( &mutex_ );
304    activity_->vm->AttachCurrentThread( &env, NULL );
305
306    mid = env->GetMethodID( jni_helper_java_class_, "getNativeAudioBufferSize", "()I" );
307    int32_t i = env->CallIntMethod( jni_helper_java_ref_, mid );
308    activity_->vm->DetachCurrentThread();
309    pthread_mutex_unlock( &mutex_ );
310
311    return i;
312}
313
314int32_t JNIHelper::GetNativeAudioSampleRate()
315{
316    if( activity_ == NULL )
317    {
318        LOGI( "JNIHelper has not been initialized. Call init() to initialize the helper" );
319        return 0;
320    }
321
322    JNIEnv *env;
323    jmethodID mid;
324
325    pthread_mutex_lock( &mutex_ );
326    activity_->vm->AttachCurrentThread( &env, NULL );
327
328    mid = env->GetMethodID( jni_helper_java_class_, "getNativeAudioSampleRate", "()I" );
329    int32_t i = env->CallIntMethod( jni_helper_java_ref_, mid );
330    activity_->vm->DetachCurrentThread();
331    pthread_mutex_unlock( &mutex_ );
332
333    return i;
334}
335
336//---------------------------------------------------------------------------
337//Misc implementations
338//---------------------------------------------------------------------------
339jclass JNIHelper::RetrieveClass( JNIEnv *jni,
340        const char* class_name )
341{
342    jclass activity_class = jni->FindClass( CLASS_NAME );
343    jmethodID get_class_loader = jni->GetMethodID( activity_class, "getClassLoader",
344            "()Ljava/lang/ClassLoader;" );
345    jobject cls = jni->CallObjectMethod( activity_->clazz, get_class_loader );
346    jclass class_loader = jni->FindClass( "java/lang/ClassLoader" );
347    jmethodID find_class = jni->GetMethodID( class_loader, "loadClass",
348            "(Ljava/lang/String;)Ljava/lang/Class;" );
349
350    jstring str_class_name = jni->NewStringUTF( class_name );
351    jclass class_retrieved = (jclass) jni->CallObjectMethod( cls, find_class, str_class_name );
352    jni->DeleteLocalRef( str_class_name );
353    return class_retrieved;
354}
355
356jstring JNIHelper::GetExternalFilesDirJString( JNIEnv *env )
357{
358    if( activity_ == NULL )
359    {
360        LOGI( "JNIHelper has not been initialized. Call init() to initialize the helper" );
361        return NULL;
362    }
363
364    // Invoking getExternalFilesDir() java API
365    jclass cls_Env = env->FindClass( CLASS_NAME );
366    jmethodID mid = env->GetMethodID( cls_Env, "getExternalFilesDir",
367            "(Ljava/lang/String;)Ljava/io/File;" );
368    jobject obj_File = env->CallObjectMethod( activity_->clazz, mid, NULL );
369    jclass cls_File = env->FindClass( "java/io/File" );
370    jmethodID mid_getPath = env->GetMethodID( cls_File, "getPath", "()Ljava/lang/String;" );
371    jstring obj_Path = (jstring) env->CallObjectMethod( obj_File, mid_getPath );
372
373    return obj_Path;
374}
375
376} //namespace ndkHelper
377