android: intro `App::paused` and use it in mnit
[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 redef fun full_frame do if not paused then super
278
279 # these are used as a callback from native to type incoming events
280 private fun extern_input_key(event: AndroidKeyEvent): Bool
281 do
282 return input(event)
283 end
284 private fun extern_input_motion(event: InnerAndroidMotionEvent): Bool
285 do
286 var ie = new AndroidMotionEvent(event)
287 var handled = input(ie)
288
289 if not handled then
290 for pe in ie.pointers do
291 input(pe)
292 end
293 end
294
295 return handled
296 end
297
298 private fun extern_input_sensor_accelerometer(event: ASensorAccelerometer) do input(event)
299 private fun extern_input_sensor_magnetic_field(event: ASensorMagneticField) do input(event)
300 private fun extern_input_sensor_gyroscope(event: ASensorGyroscope) do input(event)
301 private fun extern_input_sensor_light(event: ASensorLight) do input(event)
302 private fun extern_input_sensor_proximity(event: ASensorProximity) do input(event)
303
304 # Sensors support
305 # The user decides which sensors he wants to use by setting them enabled
306 private fun enable_sensors
307 do
308 if sensors_support_enabled then enable_sensors_management else return
309 if accelerometer.enabled then enable_accelerometer
310 if magnetic_field.enabled then enable_magnetic_field
311 if gyroscope.enabled then enable_gyroscope
312 if light.enabled then enable_light
313 if proximity.enabled then enable_proximity
314 end
315
316 private fun enable_sensors_management
317 do
318 sensormanager = new ASensorManager.get_instance
319 #eventqueue = sensormanager.create_event_queue(new NdkAndroidApp)
320 eventqueue = initialize_event_queue(sensormanager, native_app_glue.looper)
321 end
322
323 # 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
324 private fun initialize_event_queue(sensormanager: ASensorManager, looper: ALooper): ASensorEventQueue `{
325 return ASensorManager_createEventQueue(sensormanager, looper, LOOPER_ID_USER, NULL, NULL);
326 `}
327
328 private fun enable_accelerometer
329 do
330 accelerometer.asensor = sensormanager.get_default_sensor(new ASensorType.accelerometer)
331 if accelerometer.asensor.address_is_null then
332 print "Accelerometer sensor unavailable"
333 else
334 if eventqueue.enable_sensor(accelerometer.asensor) < 0 then print "Accelerometer enabling failed"
335 eventqueue.set_event_rate(accelerometer.asensor, accelerometer.event_rate)
336 end
337 end
338
339 private fun enable_magnetic_field
340 do
341 magnetic_field.asensor = sensormanager.get_default_sensor(new ASensorType.magnetic_field)
342 if magnetic_field.asensor.address_is_null then
343 print "Magnetic Field unavailable"
344 else
345 if eventqueue.enable_sensor(magnetic_field.asensor) < 0 then print "Magnetic Field enabling failed"
346 eventqueue.set_event_rate(magnetic_field.asensor, magnetic_field.event_rate)
347 end
348 end
349
350 private fun enable_gyroscope
351 do
352 gyroscope.asensor = sensormanager.get_default_sensor(new ASensorType.gyroscope)
353 if gyroscope.asensor.address_is_null then
354 print "Gyroscope sensor unavailable"
355 else
356 if eventqueue.enable_sensor(gyroscope.asensor) < 0 then print "Gyroscope enabling failed"
357 eventqueue.set_event_rate(gyroscope.asensor, gyroscope.event_rate)
358 end
359 end
360
361 private fun enable_light
362 do
363 light.asensor = sensormanager.get_default_sensor(new ASensorType.light)
364 if light.asensor.address_is_null then
365 print "Light sensor unavailable"
366 else
367 if eventqueue.enable_sensor(light.asensor) < 0 then print "Light enabling failed"
368 eventqueue.set_event_rate(light.asensor, light.event_rate)
369 end
370 end
371
372 private fun enable_proximity
373 do
374 proximity.asensor = sensormanager.get_default_sensor(new ASensorType.proximity)
375 if proximity.asensor.address_is_null then
376 print "Proximity sensor unavailable"
377 else
378 if eventqueue.enable_sensor(proximity.asensor) < 0 then print "Proximity enabling failed"
379 eventqueue.set_event_rate(light.asensor, light.event_rate)
380 end
381 end
382
383 redef fun run is extern import full_frame, generate_input, enable_sensors, native_app_glue `{
384 struct android_app* app_glue = App_native_app_glue(recv);
385 LOGI("nitni loop");
386
387 //Enbales sensors if needed
388 App_enable_sensors(recv);
389
390 while (1) {
391 App_generate_input(recv);
392
393 if (app_glue->destroyRequested != 0) return;
394
395 mnit_frame(recv);
396 }
397 /* App_exit(); // this is unreachable anyway*/
398 `}
399
400 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 `{
401 int ident;
402 int events;
403 static int block = 0;
404 struct android_poll_source* source;
405 struct android_app *app_glue = App_native_app_glue(recv);
406
407 while ((ident=ALooper_pollAll(0, NULL, &events,
408 (void**)&source)) >= 0) { /* first 0 is for non-blocking */
409
410 // Process this event.
411 if (source != NULL)
412 source->process(app_glue, source);
413
414 //If a sensor has data, process it
415 if(ident == LOOPER_ID_USER) {
416 //maybe add a boolean to the app to know if we want to use Sensor API or ASensorEvent directly ...
417 ASensorEvent* events = malloc(sizeof(ASensorEvent)*10);
418 int nbevents;
419 ASensorEventQueue* queue = App_eventqueue(recv);
420 while((nbevents = ASensorEventQueue_getEvents(queue, events, 10)) > 0) {
421 int i;
422 for(i = 0; i < nbevents; i++){
423 ASensorEvent event = events[i];
424 switch (event.type) {
425 case ASENSOR_TYPE_ACCELEROMETER:
426 App_extern_input_sensor_accelerometer(recv, &event);
427 break;
428 case ASENSOR_TYPE_MAGNETIC_FIELD:
429 App_extern_input_sensor_magnetic_field(recv, &event);
430 break;
431 case ASENSOR_TYPE_GYROSCOPE:
432 App_extern_input_sensor_gyroscope(recv, &event);
433 break;
434 case ASENSOR_TYPE_LIGHT:
435 App_extern_input_sensor_light(recv, &event);
436 break;
437 case ASENSOR_TYPE_PROXIMITY:
438 App_extern_input_sensor_proximity(recv, &event);
439 break;
440 }
441 }
442 }
443 }
444
445 // Check if we are exiting.
446 if (app_glue->destroyRequested != 0) return;
447 }
448 `}
449 end
450
451 extern class JavaClassLoader in "Java" `{java.lang.ClassLoader`}
452 super JavaObject
453 end
454
455 redef class Sys
456 # Get the running JVM
457 redef fun create_default_jvm
458 do
459 var jvm = app.native_app_glue.ndk_native_activity.vm
460 var jni_env = jvm.attach_current_thread
461 if jni_env.address_is_null then jni_env = jvm.env
462
463 self.jvm = jvm
464 self.jni_env = jni_env
465 end
466
467 #protected fun ndk_jvm: JavaVM do`{ return mnit_java_app->activity->vm; `}
468
469 private var class_loader: nullable JavaObject = null
470 private var class_loader_method: nullable JMethodID = null
471 redef fun load_jclass(name)
472 do
473 var class_loader = self.class_loader
474 if class_loader == null then
475 find_class_loader(app.native_app_glue.ndk_native_activity.java_native_activity)
476 class_loader = self.class_loader
477 assert class_loader != null
478 end
479
480 var class_loader_method = self.class_loader_method
481 assert class_loader_method != null
482
483 return load_jclass_intern(class_loader, class_loader_method, name)
484 end
485
486 private fun find_class_loader(native_activity: NativeActivity) import jni_env, class_loader=, JavaObject.as nullable, class_loader_method=, JMethodID.as nullable `{
487 JNIEnv *env = Sys_jni_env(recv);
488
489 // Retrieve main activity
490 jclass class_activity = (*env)->GetObjectClass(env, native_activity);
491 if (class_activity == NULL) {
492 __android_log_print(ANDROID_LOG_ERROR, "Nit", "retreiving activity class");
493 (*env)->ExceptionDescribe(env);
494 exit(1);
495 }
496
497 jmethodID class_activity_getClassLoader = (*env)->GetMethodID(env, class_activity, "getClassLoader", "()Ljava/lang/ClassLoader;");
498 if (class_activity_getClassLoader == NULL) {
499 __android_log_print(ANDROID_LOG_ERROR, "Nit", "retreiving 'getClassLoader' method");
500 (*env)->ExceptionDescribe(env);
501 exit(1);
502 }
503
504 // Call activity.getClassLoader
505 jobject instance_class_loader = (*env)->CallObjectMethod(env, native_activity, class_activity_getClassLoader);
506 if (instance_class_loader == NULL) {
507 __android_log_print(ANDROID_LOG_ERROR, "Nit", "retreiving class loader instance");
508 (*env)->ExceptionDescribe(env);
509 exit(1);
510 }
511
512 jclass class_class_loader = (*env)->GetObjectClass(env, instance_class_loader);
513 if (class_class_loader == NULL) {
514 __android_log_print(ANDROID_LOG_ERROR, "Nit", "retreiving class of class loader");
515 (*env)->ExceptionDescribe(env);
516 exit(1);
517 }
518
519 // Get the method ClassLoader.findClass
520 jmethodID class_class_loader_findClass = (*env)->GetMethodID(env, class_class_loader, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
521 if (class_class_loader_findClass == NULL) {
522 __android_log_print(ANDROID_LOG_ERROR, "Nit", "retreiving 'findClass' method");
523 (*env)->ExceptionDescribe(env);
524 exit(1);
525 }
526
527 // Return the values to Nit
528 Sys_class_loader__assign(recv, JavaObject_as_nullable((*env)->NewGlobalRef(env, instance_class_loader)));
529 Sys_class_loader_method__assign(recv, JMethodID_as_nullable(class_class_loader_findClass));
530
531 // Clean up
532 (*env)->DeleteLocalRef(env, class_activity);
533 (*env)->DeleteLocalRef(env, instance_class_loader);
534 (*env)->DeleteLocalRef(env, class_class_loader);
535 `}
536
537 private fun load_jclass_intern(instance_class_loader: JavaObject, class_loader_findClass: JMethodID, name: NativeString): JClass import jni_env `{
538 JNIEnv *env = Sys_jni_env(recv);
539 jobject class_name = (*env)->NewStringUTF(env, name);
540
541 jclass java_class = (*env)->CallObjectMethod(env, instance_class_loader, class_loader_findClass, class_name);
542 if (java_class == NULL) {
543 __android_log_print(ANDROID_LOG_ERROR, "Nit", "loading targetted class");
544 (*env)->ExceptionDescribe(env);
545 exit(1);
546 }
547
548 (*env)->DeleteLocalRef(env, class_name);
549
550 return java_class;
551 `}
552 end