Merge: Nitgs optims
[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 int mnit_animating = 0;
57
58 /* This is confusing; the type come from android_native_app_glue.h
59 and so identifies the java part of the app */
60 struct android_app *mnit_java_app;
61
62 /* This is the pure Nit App */
63 App nit_app;
64
65 /* The main of the Nit application, compiled somewhere else */
66 extern int main(int, char**);
67
68 /* Wraps App_full_frame() and check for orientation. */
69 void mnit_frame();
70
71 void mnit_term_display()
72 {
73 // At this point we have nothing to do
74 }
75
76 /* Handle inputs from the Android platform and sort them before
77 sending them in the Nit App */
78 static int32_t mnit_handle_input(struct android_app* app, AInputEvent* event) {
79 LOGI("handle input %i", (int)pthread_self());
80 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
81 LOGI("key");
82 return App_extern_input_key(nit_app, event);
83 }
84 else if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
85 LOGI("motion");
86 return App_extern_input_motion(nit_app, event);
87 }
88
89 return 0;
90 }
91
92 static void mnit_handle_cmd(struct android_app* app, int32_t cmd) {
93
94 mnit_java_app = app;
95 AConfiguration_setOrientation(mnit_java_app->config, ACONFIGURATION_ORIENTATION_LAND);
96 LOGI("cmd %i", (int)pthread_self());
97
98 switch (cmd) {
99 case APP_CMD_SAVE_STATE:
100 LOGI ("save state");
101 mnit_java_app->savedStateSize = 1;
102 mnit_java_app->savedState = malloc(1);
103 App_save(nit_app);
104 break;
105
106 case APP_CMD_INIT_WINDOW:
107 LOGI ("init window");
108 if (mnit_java_app->window != NULL) {
109 LOGI("init window in");
110 App_init_window(nit_app);
111 mnit_frame();
112 mnit_animating = 1;
113 }
114 break;
115
116 case APP_CMD_TERM_WINDOW:
117 LOGI ("term window");
118 mnit_term_display();
119 App_term_window(nit_app);
120 break;
121
122 case APP_CMD_GAINED_FOCUS:
123 LOGI ("gain foc");
124 mnit_animating = 1;
125 App_gained_focus(nit_app);
126 LOGI ("gain foc 1");
127 break;
128
129 case APP_CMD_LOST_FOCUS:
130 LOGI ("lost foc");
131 mnit_animating = 0;
132 App_lost_focus(nit_app);
133 mnit_frame();
134 break;
135
136 case APP_CMD_PAUSE:
137 LOGI ("app pause");
138 App_pause(nit_app);
139 break;
140
141 /*
142 case APP_CMD_STOP:
143 LOGI ("app stop");
144 App_stop(nit_app);
145 break;
146
147 case APP_CMD_DESTROY:
148 LOGI ("app destrop");
149 App_destroy(nit_app);
150 break;
151
152 case APP_CMD_START:
153 LOGI ("app start");
154 App_start(nit_app);
155 break;
156 */
157
158 case APP_CMD_RESUME:
159 LOGI ("app resume");
160 App_resume(nit_app);
161 break;
162
163 case APP_CMD_LOW_MEMORY:
164 LOGI ("app low mem");
165 break;
166
167 case APP_CMD_CONFIG_CHANGED:
168 LOGI ("app cmd conf ch");
169 break;
170
171 case APP_CMD_INPUT_CHANGED:
172 LOGI ("app cmd in ch");
173 break;
174
175 case APP_CMD_WINDOW_RESIZED:
176 mnit_orientation_changed = 1;
177 LOGI ("app win res");
178 break;
179
180 case APP_CMD_WINDOW_REDRAW_NEEDED:
181 LOGI ("app win redraw needed");
182 break;
183
184 case APP_CMD_CONTENT_RECT_CHANGED:
185 LOGI ("app content rect ch");
186 break;
187 }
188 }
189
190 void android_main(struct android_app* app)
191 {
192 mnit_java_app = app;
193
194 app_dummy();
195
196 main(0, NULL);
197 }
198
199 void mnit_frame()
200 {
201 if (mnit_display == EGL_NO_DISPLAY) {
202 LOGI("no frame");
203 return;
204 }
205
206 if (mnit_orientation_changed)
207 {
208 mnit_orientation_changed = 0;
209
210 if (mnit_surface != EGL_NO_SURFACE) {
211 eglDestroySurface(mnit_display, mnit_surface);
212 }
213 EGLSurface surface = eglCreateWindowSurface(mnit_display, mnit_config, mnit_java_app->window, NULL);
214
215 if (eglMakeCurrent(mnit_display, surface, surface, mnit_context) == EGL_FALSE) {
216 LOGW("Unable to eglMakeCurrent");
217 }
218
219 eglQuerySurface(mnit_display, surface, EGL_WIDTH, &mnit_width);
220 eglQuerySurface(mnit_display, surface, EGL_HEIGHT, &mnit_height);
221
222 mnit_surface = surface;
223
224 glViewport(0, 0, mnit_width, mnit_height);
225 glMatrixMode(GL_PROJECTION);
226 glLoadIdentity();
227 glOrthof(0.0f, mnit_width, mnit_height, 0.0f, 0.0f, 1.0f);
228 glMatrixMode(GL_MODELVIEW);
229 }
230
231 LOGI("frame");
232
233 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
234 glClear(GL_COLOR_BUFFER_BIT); // | GL_DEPTH_BUFFER_BIT);
235
236 App_full_frame(nit_app);
237
238 LOGI("frame b");
239 }
240 `}
241
242
243 extern InnerAndroidMotionEvent in "C" `{AInputEvent *`}
244 super Pointer
245 private fun pointers_count: Int is extern `{
246 return AMotionEvent_getPointerCount(recv);
247 `}
248 private fun just_went_down: Bool is extern `{
249 return (AMotionEvent_getAction(recv) & AMOTION_EVENT_ACTION_MASK) == AMOTION_EVENT_ACTION_DOWN;
250 `}
251 private fun edge: Int is extern `{
252 return AMotionEvent_getEdgeFlags(recv);
253 `}
254 private fun index_down_pointer: Int is extern `{
255 int a = AMotionEvent_getAction(recv);
256 if ((a & AMOTION_EVENT_ACTION_MASK) == AMOTION_EVENT_ACTION_POINTER_DOWN)
257 return (a & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
258 else return -1;
259 `}
260
261 private fun action: AMotionEventAction `{ return AMotionEvent_getAction(recv); `}
262 end
263
264 extern class AMotionEventAction `{ int32_t `}
265 protected fun action: Int `{ return recv & AMOTION_EVENT_ACTION_MASK; `}
266 fun is_down: Bool do return action == 0
267 fun is_up: Bool do return action == 1
268 fun is_move: Bool do return action == 2
269 fun is_cancel: Bool do return action == 3
270 fun is_outside: Bool do return action == 4
271 fun is_pointer_down: Bool do return action == 5
272 fun is_pointer_up: Bool do return action == 6
273 end
274
275 interface AndroidInputEvent
276 super InputEvent
277 end
278
279 class AndroidMotionEvent
280 super AndroidInputEvent
281 super MotionEvent
282
283 private init(ie: InnerAndroidMotionEvent) do inner_event = ie
284 private var inner_event: InnerAndroidMotionEvent
285
286 private var pointers_cache: nullable Array[AndroidPointerEvent] = null
287 fun pointers: Array[AndroidPointerEvent]
288 do
289 if pointers_cache != null then
290 return pointers_cache.as(not null)
291 else
292 var pointers = new Array[AndroidPointerEvent]
293 var pointers_count = inner_event.pointers_count
294 for i in [0 .. pointers_count [do
295 var pointer_event = new AndroidPointerEvent(self, i)
296 pointers.add(pointer_event)
297 end
298 pointers_cache = pointers
299 return pointers
300 end
301 end
302
303 redef fun just_went_down: Bool do return inner_event.just_went_down
304 fun edge: Int do return inner_event.edge
305
306 redef fun down_pointer: nullable AndroidPointerEvent
307 do
308 var i = inner_event.index_down_pointer
309 if i > 0 then
310 return pointers[i]
311 else
312 return null
313 end
314 end
315 end
316
317 class AndroidPointerEvent
318 super PointerEvent
319 super AndroidInputEvent
320
321 protected var motion_event: AndroidMotionEvent
322 protected var pointer_id: Int
323
324 redef fun x: Float do return extern_x(motion_event.inner_event, pointer_id)
325 private fun extern_x(motion_event: InnerAndroidMotionEvent, pointer_id: Int): Float is extern `{
326 return ((int) AMotionEvent_getX(motion_event, pointer_id) * mnit_zoom);
327 `}
328
329 redef fun y: Float do return extern_y(motion_event.inner_event, pointer_id)
330 private fun extern_y(motion_event: InnerAndroidMotionEvent, pointer_id: Int): Float is extern `{
331 return ((int) AMotionEvent_getY(motion_event, pointer_id) * mnit_zoom) + 32;
332 `}
333
334 fun pressure: Float do return extern_pressure(motion_event.inner_event, pointer_id)
335 private fun extern_pressure(motion_event: InnerAndroidMotionEvent, pointer_id: Int): Float is extern `{
336 return AMotionEvent_getPressure(motion_event, pointer_id);
337 `}
338
339 redef fun pressed
340 do
341 var action = motion_event.inner_event.action
342 return action.is_down or action.is_move
343 end
344
345 redef fun depressed do return not pressed
346 end
347
348 extern AndroidKeyEvent in "C" `{AInputEvent *`}
349 super KeyEvent
350 super AndroidInputEvent
351
352 fun action: Int is extern `{
353 return AKeyEvent_getAction(recv);
354 `}
355 redef fun is_down: Bool do return action == 0
356 redef fun is_up: Bool do return action == 1
357
358 fun key_code: Int is extern `{
359 return AKeyEvent_getKeyCode(recv);
360 `}
361
362 fun key_char: Char is extern `{
363 int code = AKeyEvent_getKeyCode(recv);
364 if (code >= AKEYCODE_0 && code <= AKEYCODE_9)
365 return '0'+code-AKEYCODE_0;
366 if (code >= AKEYCODE_A && code <= AKEYCODE_Z)
367 return 'a'+code-AKEYCODE_A;
368 return 0;
369 `}
370
371 fun is_back_key: Bool do return key_code == 2
372 fun is_menu_key: Bool do return key_code == 82
373 fun is_search_key: Bool do return key_code == 84
374 end
375
376 redef class Object
377 # Uses Android logs for every print
378 redef fun print(text: Object) is extern import Object.to_s, String.to_cstring `{
379 __android_log_print(ANDROID_LOG_INFO, "mnit print", "%s", String_to_cstring(Object_to_s(text)));
380 `}
381 end
382
383 redef class App
384 redef type IE: AndroidInputEvent
385 redef type D: Opengles1Display
386
387 var accelerometer = new AndroidSensor
388 var magnetic_field = new AndroidSensor
389 var gyroscope = new AndroidSensor
390 var light = new AndroidSensor
391 var proximity = new AndroidSensor
392 var sensormanager: ASensorManager
393 var eventqueue: ASensorEventQueue
394 var sensors_support_enabled writable = false
395
396 redef fun log_warning(msg) is extern import String.to_cstring `{
397 LOGW("%s", String_to_cstring(msg));
398 `}
399 redef fun log_info(msg) is extern import String.to_cstring `{
400 LOGI("%s", String_to_cstring(msg));
401 `}
402
403 redef fun init_window
404 do
405 super
406
407 display = new Opengles1Display
408 end
409
410 # these are used as a callback from native to type incoming events
411 private fun extern_input_key(event: AndroidKeyEvent): Bool
412 do
413 return input(event)
414 end
415 private fun extern_input_motion(event: InnerAndroidMotionEvent): Bool
416 do
417 var ie = new AndroidMotionEvent(event)
418 var handled = input(ie)
419
420 if not handled then
421 for pe in ie.pointers do
422 input(pe)
423 end
424 end
425
426 return handled
427 end
428
429 private fun extern_input_sensor_accelerometer(event: ASensorAccelerometer) do input(event)
430 private fun extern_input_sensor_magnetic_field(event: ASensorMagneticField) do input(event)
431 private fun extern_input_sensor_gyroscope(event: ASensorGyroscope) do input(event)
432 private fun extern_input_sensor_light(event: ASensorLight) do input(event)
433 private fun extern_input_sensor_proximity(event: ASensorProximity) do input(event)
434
435 # Sensors support
436 # The user decides which sensors he wants to use by setting them enabled
437 private fun enable_sensors
438 do
439 if sensors_support_enabled then enable_sensors_management else return
440 if accelerometer.enabled then enable_accelerometer
441 if magnetic_field.enabled then enable_magnetic_field
442 if gyroscope.enabled then enable_gyroscope
443 if light.enabled then enable_light
444 if proximity.enabled then enable_proximity
445 end
446
447 private fun enable_sensors_management
448 do
449 sensormanager = new ASensorManager.get_instance
450 #eventqueue = sensormanager.create_event_queue(new NdkAndroidApp)
451 eventqueue = initialize_event_queue(sensormanager)
452 end
453
454 # 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
455 private fun initialize_event_queue(sensormanager: ASensorManager): ASensorEventQueue `{
456 return ASensorManager_createEventQueue(sensormanager, mnit_java_app->looper, LOOPER_ID_USER, NULL, NULL);
457 `}
458
459 private fun enable_accelerometer
460 do
461 accelerometer.asensor = sensormanager.get_default_sensor(new ASensorType.accelerometer)
462 if accelerometer.asensor.address_is_null then
463 print "Accelerometer sensor unavailable"
464 else
465 if eventqueue.enable_sensor(accelerometer.asensor) < 0 then print "Accelerometer enabling failed"
466 eventqueue.set_event_rate(accelerometer.asensor, accelerometer.event_rate)
467 end
468 end
469
470 private fun enable_magnetic_field
471 do
472 magnetic_field.asensor = sensormanager.get_default_sensor(new ASensorType.magnetic_field)
473 if magnetic_field.asensor.address_is_null then
474 print "Magnetic Field unavailable"
475 else
476 if eventqueue.enable_sensor(magnetic_field.asensor) < 0 then print "Magnetic Field enabling failed"
477 eventqueue.set_event_rate(magnetic_field.asensor, magnetic_field.event_rate)
478 end
479 end
480
481 private fun enable_gyroscope
482 do
483 gyroscope.asensor = sensormanager.get_default_sensor(new ASensorType.gyroscope)
484 if gyroscope.asensor.address_is_null then
485 print "Gyroscope sensor unavailable"
486 else
487 if eventqueue.enable_sensor(gyroscope.asensor) < 0 then print "Gyroscope enabling failed"
488 eventqueue.set_event_rate(gyroscope.asensor, gyroscope.event_rate)
489 end
490 end
491
492 private fun enable_light
493 do
494 light.asensor = sensormanager.get_default_sensor(new ASensorType.light)
495 if light.asensor.address_is_null then
496 print "Light sensor unavailable"
497 else
498 if eventqueue.enable_sensor(light.asensor) < 0 then print "Light enabling failed"
499 eventqueue.set_event_rate(light.asensor, light.event_rate)
500 end
501 end
502
503 private fun enable_proximity
504 do
505 proximity.asensor = sensormanager.get_default_sensor(new ASensorType.proximity)
506 if proximity.asensor.address_is_null then
507 print "Proximity sensor unavailable"
508 else
509 if eventqueue.enable_sensor(proximity.asensor) < 0 then print "Proximity enabling failed"
510 eventqueue.set_event_rate(light.asensor, light.event_rate)
511 end
512 end
513
514 redef fun main_loop is extern import full_frame, generate_input, enable_sensors `{
515 LOGI("nitni loop");
516
517 nit_app = recv;
518
519 mnit_java_app->userData = &nit_app;
520 mnit_java_app->onAppCmd = mnit_handle_cmd;
521 mnit_java_app->onInputEvent = mnit_handle_input;
522
523 //Enbales sensors if needed
524 App_enable_sensors(nit_app);
525
526 while (1) {
527 App_generate_input(recv);
528
529 if (mnit_java_app->destroyRequested != 0) return;
530
531 if (mnit_animating == 1) {
532 mnit_frame();
533 LOGI("frame at loop end 1");
534 }
535 }
536 /* App_exit(); // this is unreachable anyway*/
537 `}
538
539 redef fun generate_input import save, 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 `{
540 int ident;
541 int events;
542 static int block = 0;
543 struct android_poll_source* source;
544
545 while ((ident=ALooper_pollAll(0, NULL, &events,
546 (void**)&source)) >= 0) { /* first 0 is for non-blocking */
547
548 // Process this event.
549 if (source != NULL)
550 source->process(mnit_java_app, source);
551
552 //If a sensor has data, process it
553 if(ident == LOOPER_ID_USER) {
554 //maybe add a boolean to the app to know if we want to use Sensor API or ASensorEvent directly ...
555 ASensorEvent* events = malloc(sizeof(ASensorEvent)*10);
556 int nbevents;
557 ASensorEventQueue* queue = App_eventqueue(nit_app);
558 while((nbevents = ASensorEventQueue_getEvents(queue, events, 10)) > 0) {
559 int i;
560 for(i = 0; i < nbevents; i++){
561 ASensorEvent event = events[i];
562 switch (event.type) {
563 case ASENSOR_TYPE_ACCELEROMETER:
564 App_extern_input_sensor_accelerometer(nit_app, &event);
565 break;
566 case ASENSOR_TYPE_MAGNETIC_FIELD:
567 App_extern_input_sensor_magnetic_field(nit_app, &event);
568 break;
569 case ASENSOR_TYPE_GYROSCOPE:
570 App_extern_input_sensor_gyroscope(nit_app, &event);
571 break;
572 case ASENSOR_TYPE_LIGHT:
573 App_extern_input_sensor_light(nit_app, &event);
574 break;
575 case ASENSOR_TYPE_PROXIMITY:
576 App_extern_input_sensor_proximity(nit_app, &event);
577 break;
578 }
579 }
580 }
581 }
582
583 // Check if we are exiting.
584 if (mnit_java_app->destroyRequested != 0) {
585 mnit_term_display();
586 return;
587 }
588 }
589 `}
590 end