lib/mnit_android: update to use android::native_app_glue
[nit.git] / lib / mnit_android / android_app.nit
1 # This file is part of NIT (http://www.nitlanguage.org).
2 #
3 # Copyright 2012-2014 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Impements the services of `mnit:app` using the API from the Android ndk
18 module android_app
19
20 import mnit
21 import android
22 import android_sensor
23
24 in "C header" `{
25 #include <jni.h>
26 #include <errno.h>
27 #include <android/log.h>
28 #include <android_native_app_glue.h>
29 #include <android/sensor.h>
30
31 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "mnit", __VA_ARGS__))
32 #ifdef DEBUG
33 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "mnit", __VA_ARGS__))
34 #else
35 #define LOGI(...) (void)0
36 #endif
37 `}
38
39 in "C" `{
40 #include <EGL/egl.h>
41 #include <GLES/gl.h>
42 #define GL_GLEXT_PROTOTYPES 1
43 #include <GLES/glext.h>
44 #include <errno.h>
45
46 extern EGLDisplay mnit_display;
47 extern EGLSurface mnit_surface;
48 extern EGLContext mnit_context;
49 extern EGLConfig mnit_config;
50 extern int32_t mnit_width;
51 extern int32_t mnit_height;
52 extern float mnit_zoom;
53
54 //int mnit_orientation_changed;
55 float mnit_zoom;
56
57 /* Handle inputs from the Android platform and sort them before
58 sending them in the Nit App */
59 static int32_t mnit_handle_input(struct android_app* app, AInputEvent* event) {
60 App nit_app = app->userData;
61 LOGI("handle input %i", (int)pthread_self());
62 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
63 LOGI("key");
64 return App_extern_input_key(nit_app, event);
65 }
66 else if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
67 LOGI("motion");
68 return App_extern_input_motion(nit_app, event);
69 }
70
71 return 0;
72 }
73
74 void mnit_frame(App nit_app)
75 {
76 if (mnit_display == EGL_NO_DISPLAY) {
77 LOGI("no frame");
78 return;
79 }
80
81 /*if (mnit_orientation_changed)
82 {
83 mnit_orientation_changed = 0;
84
85 if (mnit_surface != EGL_NO_SURFACE) {
86 eglDestroySurface(mnit_display, mnit_surface);
87 }
88 EGLSurface surface = eglCreateWindowSurface(mnit_display, mnit_config, mnit_java_app->window, NULL);
89
90 if (eglMakeCurrent(mnit_display, surface, surface, mnit_context) == EGL_FALSE) {
91 LOGW("Unable to eglMakeCurrent");
92 }
93
94 eglQuerySurface(mnit_display, surface, EGL_WIDTH, &mnit_width);
95 eglQuerySurface(mnit_display, surface, EGL_HEIGHT, &mnit_height);
96
97 mnit_surface = surface;
98
99 glViewport(0, 0, mnit_width, mnit_height);
100 glMatrixMode(GL_PROJECTION);
101 glLoadIdentity();
102 glOrthof(0.0f, mnit_width, mnit_height, 0.0f, 0.0f, 1.0f);
103 glMatrixMode(GL_MODELVIEW);
104 }
105 */
106
107 LOGI("frame");
108
109 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
110 glClear(GL_COLOR_BUFFER_BIT); // | GL_DEPTH_BUFFER_BIT);
111
112 App_full_frame(nit_app);
113
114 LOGI("frame b");
115 }
116 `}
117
118
119 extern class InnerAndroidMotionEvent in "C" `{AInputEvent *`}
120 super Pointer
121 private fun pointers_count: Int is extern `{
122 return AMotionEvent_getPointerCount(recv);
123 `}
124 private fun just_went_down: Bool is extern `{
125 return (AMotionEvent_getAction(recv) & AMOTION_EVENT_ACTION_MASK) == AMOTION_EVENT_ACTION_DOWN;
126 `}
127 private fun edge: Int is extern `{
128 return AMotionEvent_getEdgeFlags(recv);
129 `}
130 private fun index_down_pointer: Int is extern `{
131 int a = AMotionEvent_getAction(recv);
132 if ((a & AMOTION_EVENT_ACTION_MASK) == AMOTION_EVENT_ACTION_POINTER_DOWN)
133 return (a & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
134 else return -1;
135 `}
136
137 private fun action: AMotionEventAction `{ return AMotionEvent_getAction(recv); `}
138 end
139
140 extern class AMotionEventAction `{ int32_t `}
141 protected fun action: Int `{ return recv & AMOTION_EVENT_ACTION_MASK; `}
142 fun is_down: Bool do return action == 0
143 fun is_up: Bool do return action == 1
144 fun is_move: Bool do return action == 2
145 fun is_cancel: Bool do return action == 3
146 fun is_outside: Bool do return action == 4
147 fun is_pointer_down: Bool do return action == 5
148 fun is_pointer_up: Bool do return action == 6
149 end
150
151 interface AndroidInputEvent
152 super InputEvent
153 end
154
155 class AndroidMotionEvent
156 super AndroidInputEvent
157 super MotionEvent
158
159 private init(ie: InnerAndroidMotionEvent) do inner_event = ie
160 private var inner_event: InnerAndroidMotionEvent
161
162 private var pointers_cache: nullable Array[AndroidPointerEvent] = null
163 fun pointers: Array[AndroidPointerEvent]
164 do
165 if pointers_cache != null then
166 return pointers_cache.as(not null)
167 else
168 var pointers = new Array[AndroidPointerEvent]
169 var pointers_count = inner_event.pointers_count
170 for i in [0 .. pointers_count [do
171 var pointer_event = new AndroidPointerEvent(self, i)
172 pointers.add(pointer_event)
173 end
174 pointers_cache = pointers
175 return pointers
176 end
177 end
178
179 redef fun just_went_down: Bool do return inner_event.just_went_down
180 fun edge: Int do return inner_event.edge
181
182 redef fun down_pointer: nullable AndroidPointerEvent
183 do
184 var i = inner_event.index_down_pointer
185 if i > 0 then
186 return pointers[i]
187 else
188 return null
189 end
190 end
191 end
192
193 class AndroidPointerEvent
194 super PointerEvent
195 super AndroidInputEvent
196
197 protected var motion_event: AndroidMotionEvent
198 protected var pointer_id: Int
199
200 redef fun x: Float do return extern_x(motion_event.inner_event, pointer_id)
201 private fun extern_x(motion_event: InnerAndroidMotionEvent, pointer_id: Int): Float is extern `{
202 return ((int) AMotionEvent_getX(motion_event, pointer_id) * mnit_zoom);
203 `}
204
205 redef fun y: Float do return extern_y(motion_event.inner_event, pointer_id)
206 private fun extern_y(motion_event: InnerAndroidMotionEvent, pointer_id: Int): Float is extern `{
207 return ((int) AMotionEvent_getY(motion_event, pointer_id) * mnit_zoom) + 32;
208 `}
209
210 fun pressure: Float do return extern_pressure(motion_event.inner_event, pointer_id)
211 private fun extern_pressure(motion_event: InnerAndroidMotionEvent, pointer_id: Int): Float is extern `{
212 return AMotionEvent_getPressure(motion_event, pointer_id);
213 `}
214
215 redef fun pressed
216 do
217 var action = motion_event.inner_event.action
218 return action.is_down or action.is_move
219 end
220
221 redef fun depressed do return not pressed
222 end
223
224 extern class AndroidKeyEvent in "C" `{AInputEvent *`}
225 super KeyEvent
226 super AndroidInputEvent
227
228 fun action: Int is extern `{
229 return AKeyEvent_getAction(recv);
230 `}
231 redef fun is_down: Bool do return action == 0
232 redef fun is_up: Bool do return action == 1
233
234 fun key_code: Int is extern `{
235 return AKeyEvent_getKeyCode(recv);
236 `}
237
238 fun key_char: Char is extern `{
239 int code = AKeyEvent_getKeyCode(recv);
240 if (code >= AKEYCODE_0 && code <= AKEYCODE_9)
241 return '0'+code-AKEYCODE_0;
242 if (code >= AKEYCODE_A && code <= AKEYCODE_Z)
243 return 'a'+code-AKEYCODE_A;
244 return 0;
245 `}
246
247 fun is_back_key: Bool do return key_code == 2
248 fun is_menu_key: Bool do return key_code == 82
249 fun is_search_key: Bool do return key_code == 84
250 end
251
252 redef class App
253 redef type IE: AndroidInputEvent
254 redef type D: Opengles1Display
255
256 var accelerometer = new AndroidSensor
257 var magnetic_field = new AndroidSensor
258 var gyroscope = new AndroidSensor
259 var light = new AndroidSensor
260 var proximity = new AndroidSensor
261 var sensormanager: ASensorManager
262 var eventqueue: ASensorEventQueue
263 var sensors_support_enabled writable = false
264
265 redef fun init_window
266 do
267 set_as_input_handler native_app_glue
268 display = new Opengles1Display
269
270 super
271 end
272
273 private fun set_as_input_handler(app_glue: NativeAppGlue) `{
274 app_glue->onInputEvent = mnit_handle_input;
275 `}
276
277 # these are used as a callback from native to type incoming events
278 private fun extern_input_key(event: AndroidKeyEvent): Bool
279 do
280 return input(event)
281 end
282 private fun extern_input_motion(event: InnerAndroidMotionEvent): Bool
283 do
284 var ie = new AndroidMotionEvent(event)
285 var handled = input(ie)
286
287 if not handled then
288 for pe in ie.pointers do
289 input(pe)
290 end
291 end
292
293 return handled
294 end
295
296 private fun extern_input_sensor_accelerometer(event: ASensorAccelerometer) do input(event)
297 private fun extern_input_sensor_magnetic_field(event: ASensorMagneticField) do input(event)
298 private fun extern_input_sensor_gyroscope(event: ASensorGyroscope) do input(event)
299 private fun extern_input_sensor_light(event: ASensorLight) do input(event)
300 private fun extern_input_sensor_proximity(event: ASensorProximity) do input(event)
301
302 # Sensors support
303 # The user decides which sensors he wants to use by setting them enabled
304 private fun enable_sensors
305 do
306 if sensors_support_enabled then enable_sensors_management else return
307 if accelerometer.enabled then enable_accelerometer
308 if magnetic_field.enabled then enable_magnetic_field
309 if gyroscope.enabled then enable_gyroscope
310 if light.enabled then enable_light
311 if proximity.enabled then enable_proximity
312 end
313
314 private fun enable_sensors_management
315 do
316 sensormanager = new ASensorManager.get_instance
317 #eventqueue = sensormanager.create_event_queue(new NdkAndroidApp)
318 eventqueue = initialize_event_queue(sensormanager, native_app_glue.looper)
319 end
320
321 # HACK: need a nit method to get mnit_java_app, then we can use the appropriate sensormanager.create_event_queue method to initialize the event queue
322 private fun initialize_event_queue(sensormanager: ASensorManager, looper: ALooper): ASensorEventQueue `{
323 return ASensorManager_createEventQueue(sensormanager, looper, LOOPER_ID_USER, NULL, NULL);
324 `}
325
326 private fun enable_accelerometer
327 do
328 accelerometer.asensor = sensormanager.get_default_sensor(new ASensorType.accelerometer)
329 if accelerometer.asensor.address_is_null then
330 print "Accelerometer sensor unavailable"
331 else
332 if eventqueue.enable_sensor(accelerometer.asensor) < 0 then print "Accelerometer enabling failed"
333 eventqueue.set_event_rate(accelerometer.asensor, accelerometer.event_rate)
334 end
335 end
336
337 private fun enable_magnetic_field
338 do
339 magnetic_field.asensor = sensormanager.get_default_sensor(new ASensorType.magnetic_field)
340 if magnetic_field.asensor.address_is_null then
341 print "Magnetic Field unavailable"
342 else
343 if eventqueue.enable_sensor(magnetic_field.asensor) < 0 then print "Magnetic Field enabling failed"
344 eventqueue.set_event_rate(magnetic_field.asensor, magnetic_field.event_rate)
345 end
346 end
347
348 private fun enable_gyroscope
349 do
350 gyroscope.asensor = sensormanager.get_default_sensor(new ASensorType.gyroscope)
351 if gyroscope.asensor.address_is_null then
352 print "Gyroscope sensor unavailable"
353 else
354 if eventqueue.enable_sensor(gyroscope.asensor) < 0 then print "Gyroscope enabling failed"
355 eventqueue.set_event_rate(gyroscope.asensor, gyroscope.event_rate)
356 end
357 end
358
359 private fun enable_light
360 do
361 light.asensor = sensormanager.get_default_sensor(new ASensorType.light)
362 if light.asensor.address_is_null then
363 print "Light sensor unavailable"
364 else
365 if eventqueue.enable_sensor(light.asensor) < 0 then print "Light enabling failed"
366 eventqueue.set_event_rate(light.asensor, light.event_rate)
367 end
368 end
369
370 private fun enable_proximity
371 do
372 proximity.asensor = sensormanager.get_default_sensor(new ASensorType.proximity)
373 if proximity.asensor.address_is_null then
374 print "Proximity sensor unavailable"
375 else
376 if eventqueue.enable_sensor(proximity.asensor) < 0 then print "Proximity enabling failed"
377 eventqueue.set_event_rate(light.asensor, light.event_rate)
378 end
379 end
380
381 redef fun run is extern import full_frame, generate_input, enable_sensors, native_app_glue `{
382 struct android_app* app_glue = App_native_app_glue(recv);
383 LOGI("nitni loop");
384
385 //Enbales sensors if needed
386 App_enable_sensors(recv);
387
388 while (1) {
389 App_generate_input(recv);
390
391 if (app_glue->destroyRequested != 0) return;
392
393 mnit_frame(recv);
394 }
395 /* App_exit(); // this is unreachable anyway*/
396 `}
397
398 redef fun generate_input import save_state, pause, resume, gained_focus, lost_focus, init_window, term_window, extern_input_key, extern_input_motion, extern_input_sensor_accelerometer, extern_input_sensor_magnetic_field, extern_input_sensor_gyroscope, extern_input_sensor_light, extern_input_sensor_proximity, eventqueue, native_app_glue `{
399 int ident;
400 int events;
401 static int block = 0;
402 struct android_poll_source* source;
403 struct android_app *app_glue = App_native_app_glue(recv);
404
405 while ((ident=ALooper_pollAll(0, NULL, &events,
406 (void**)&source)) >= 0) { /* first 0 is for non-blocking */
407
408 // Process this event.
409 if (source != NULL)
410 source->process(app_glue, source);
411
412 //If a sensor has data, process it
413 if(ident == LOOPER_ID_USER) {
414 //maybe add a boolean to the app to know if we want to use Sensor API or ASensorEvent directly ...
415 ASensorEvent* events = malloc(sizeof(ASensorEvent)*10);
416 int nbevents;
417 ASensorEventQueue* queue = App_eventqueue(recv);
418 while((nbevents = ASensorEventQueue_getEvents(queue, events, 10)) > 0) {
419 int i;
420 for(i = 0; i < nbevents; i++){
421 ASensorEvent event = events[i];
422 switch (event.type) {
423 case ASENSOR_TYPE_ACCELEROMETER:
424 App_extern_input_sensor_accelerometer(recv, &event);
425 break;
426 case ASENSOR_TYPE_MAGNETIC_FIELD:
427 App_extern_input_sensor_magnetic_field(recv, &event);
428 break;
429 case ASENSOR_TYPE_GYROSCOPE:
430 App_extern_input_sensor_gyroscope(recv, &event);
431 break;
432 case ASENSOR_TYPE_LIGHT:
433 App_extern_input_sensor_light(recv, &event);
434 break;
435 case ASENSOR_TYPE_PROXIMITY:
436 App_extern_input_sensor_proximity(recv, &event);
437 break;
438 }
439 }
440 }
441 }
442
443 // Check if we are exiting.
444 if (app_glue->destroyRequested != 0) return;
445 }
446 `}
447 end
448
449 extern class JavaClassLoader in "Java" `{java.lang.ClassLoader`}
450 super JavaObject
451 end
452
453 redef class Sys
454 # Get the running JVM
455 redef fun create_default_jvm
456 do
457 var jvm = app.native_app_glue.ndk_native_activity.vm
458 var jni_env = jvm.attach_current_thread
459 if jni_env.address_is_null then jni_env = jvm.env
460
461 self.jvm = jvm
462 self.jni_env = jni_env
463 end
464
465 #protected fun ndk_jvm: JavaVM do`{ return mnit_java_app->activity->vm; `}
466
467 private var class_loader: nullable JavaObject = null
468 private var class_loader_method: nullable JMethodID = null
469 redef fun load_jclass(name)
470 do
471 var class_loader = self.class_loader
472 if class_loader == null then
473 find_class_loader(app.native_app_glue.ndk_native_activity.java_native_activity)
474 class_loader = self.class_loader
475 assert class_loader != null
476 end
477
478 var class_loader_method = self.class_loader_method
479 assert class_loader_method != null
480
481 return load_jclass_intern(class_loader, class_loader_method, name)
482 end
483
484 private fun find_class_loader(native_activity: NativeActivity) import jni_env, class_loader=, JavaObject.as nullable, class_loader_method=, JMethodID.as nullable `{
485 JNIEnv *env = Sys_jni_env(recv);
486
487 // Retrieve main activity
488 jclass class_activity = (*env)->GetObjectClass(env, native_activity);
489 if (class_activity == NULL) {
490 __android_log_print(ANDROID_LOG_ERROR, "Nit", "retreiving activity class");
491 (*env)->ExceptionDescribe(env);
492 exit(1);
493 }
494
495 jmethodID class_activity_getClassLoader = (*env)->GetMethodID(env, class_activity, "getClassLoader", "()Ljava/lang/ClassLoader;");
496 if (class_activity_getClassLoader == NULL) {
497 __android_log_print(ANDROID_LOG_ERROR, "Nit", "retreiving 'getClassLoader' method");
498 (*env)->ExceptionDescribe(env);
499 exit(1);
500 }
501
502 // Call activity.getClassLoader
503 jobject instance_class_loader = (*env)->CallObjectMethod(env, native_activity, class_activity_getClassLoader);
504 if (instance_class_loader == NULL) {
505 __android_log_print(ANDROID_LOG_ERROR, "Nit", "retreiving class loader instance");
506 (*env)->ExceptionDescribe(env);
507 exit(1);
508 }
509
510 jclass class_class_loader = (*env)->GetObjectClass(env, instance_class_loader);
511 if (class_class_loader == NULL) {
512 __android_log_print(ANDROID_LOG_ERROR, "Nit", "retreiving class of class loader");
513 (*env)->ExceptionDescribe(env);
514 exit(1);
515 }
516
517 // Get the method ClassLoader.findClass
518 jmethodID class_class_loader_findClass = (*env)->GetMethodID(env, class_class_loader, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
519 if (class_class_loader_findClass == NULL) {
520 __android_log_print(ANDROID_LOG_ERROR, "Nit", "retreiving 'findClass' method");
521 (*env)->ExceptionDescribe(env);
522 exit(1);
523 }
524
525 // Return the values to Nit
526 Sys_class_loader__assign(recv, JavaObject_as_nullable((*env)->NewGlobalRef(env, instance_class_loader)));
527 Sys_class_loader_method__assign(recv, JMethodID_as_nullable(class_class_loader_findClass));
528
529 // Clean up
530 (*env)->DeleteLocalRef(env, class_activity);
531 (*env)->DeleteLocalRef(env, instance_class_loader);
532 (*env)->DeleteLocalRef(env, class_class_loader);
533 `}
534
535 private fun load_jclass_intern(instance_class_loader: JavaObject, class_loader_findClass: JMethodID, name: NativeString): JClass import jni_env `{
536 JNIEnv *env = Sys_jni_env(recv);
537 jobject class_name = (*env)->NewStringUTF(env, name);
538
539 jclass java_class = (*env)->CallObjectMethod(env, instance_class_loader, class_loader_findClass, class_name);
540 if (java_class == NULL) {
541 __android_log_print(ANDROID_LOG_ERROR, "Nit", "loading targetted class");
542 (*env)->ExceptionDescribe(env);
543 exit(1);
544 }
545
546 (*env)->DeleteLocalRef(env, class_name);
547
548 return java_class;
549 `}
550 end