initial commit
[shapestoy] / src / android / main.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <time.h>
4 #include <unistd.h>
5 #include <sys/time.h>
6 #include <EGL/egl.h>
7 #include <GLES2/gl2.h>
8 #include <android/window.h>
9 #include "android_native_app_glue.h"
10 #include "demo.h"
11 #include "logger.h"
12
13 static void handle_command(struct android_app *app, int32_t cmd);
14 static int handle_input(struct android_app *app, AInputEvent *ev);
15 static int handle_touch_input(struct android_app *app, AInputEvent *ev);
16 static int init_gl(void);
17 static void destroy_gl(void);
18 static unsigned long get_time_msec(void);
19 static void hide_navbar(struct android_app *state);
20 static const char *cmdname(uint32_t cmd);
21
22 struct android_app *app;
23
24 static EGLDisplay dpy;
25 static EGLSurface surf;
26 static EGLContext ctx;
27 static int init_done, paused, win_valid;
28
29 static int width, height;
30
31 static long init_time;
32
33
34 void android_main(struct android_app *app_ptr)
35 {
36         app = app_ptr;
37
38         app->onAppCmd = handle_command;
39         app->onInputEvent = handle_input;
40
41         hide_navbar(app);
42
43         start_logger();
44
45         printf("Running %d bit version\n", (int)sizeof(void*) << 3);
46
47         load_config("demo.cfg");
48
49         for(;;) {
50                 int num_events;
51                 struct android_poll_source *pollsrc;
52
53                 while(ALooper_pollAll(0, 0, &num_events, (void**)&pollsrc) >= 0) {
54                         if(pollsrc) {
55                                 pollsrc->process(app, pollsrc);
56                         }
57                 }
58
59                 if(app->destroyRequested) {
60                         return;
61                 }
62
63                 sys_time = (long)get_time_msec();
64                 if(!init_done) {
65                         if(win_valid && sys_time - init_time >= 700) {
66                                 if(demo_init() == -1) {
67                                         exit(1);
68                                 }
69                                 demo_reshape(width, height);
70                                 init_done = 1;
71                         }
72
73                 } else {
74                         if(!paused) {
75                                 demo_display();
76                                 eglSwapBuffers(dpy, surf);
77                         }
78                 }
79         }
80 }
81
82 void swap_buffers(void)
83 {
84         eglSwapBuffers(dpy, surf);
85 }
86
87 static void handle_command(struct android_app *app, int32_t cmd)
88 {
89         int xsz, ysz;
90
91         printf("DBG android command: %s\n", cmdname(cmd));
92
93         switch(cmd) {
94         case APP_CMD_PAUSE:
95                 paused = 1;     /* TODO: handle timers */
96                 break;
97         case APP_CMD_RESUME:
98                 paused = 0;
99                 break;
100
101         case APP_CMD_INIT_WINDOW:
102                 ANativeActivity_setWindowFlags(app->activity, AWINDOW_FLAG_KEEP_SCREEN_ON, 0);
103                 if(init_gl() == -1) {
104                         exit(1);
105                 }
106                 init_time = (long)get_time_msec();
107                 win_valid = 1;
108                 break;
109
110         case APP_CMD_TERM_WINDOW:
111                 if(init_done) {
112                         demo_cleanup();
113                 }
114                 init_done = 0;
115                 win_valid = 0;
116                 destroy_gl();
117                 break;
118
119         case APP_CMD_WINDOW_RESIZED:
120         case APP_CMD_CONFIG_CHANGED:
121                 xsz = ANativeWindow_getWidth(app->window);
122                 ysz = ANativeWindow_getHeight(app->window);
123                 if(xsz != width || ysz != height) {
124                         printf("reshape(%d, %d)\n", xsz, ysz);
125                         demo_reshape(xsz, ysz);
126                         width = xsz;
127                         height = ysz;
128                 }
129                 break;
130
131         /*
132         case APP_CMD_SAVE_STATE:
133         case APP_CMD_GAINED_FOCUS:
134         case APP_CMD_LOST_FOCUS:
135         */
136         default:
137                 break;
138         }
139 }
140
141 static int handle_input(struct android_app *app, AInputEvent *ev)
142 {
143         int evtype = AInputEvent_getType(ev);
144
145         switch(evtype) {
146         case AINPUT_EVENT_TYPE_MOTION:
147                 return handle_touch_input(app, ev);
148
149         default:
150                 break;
151         }
152         return 0;
153 }
154
155 static int handle_touch_input(struct android_app *app, AInputEvent *ev)
156 {
157         int i, pcount, x, y, idx;
158         unsigned int action;
159         static int prev_pos[2];
160
161         action = AMotionEvent_getAction(ev);
162
163         idx = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
164                 AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
165
166         x = AMotionEvent_getX(ev, idx);
167         y = AMotionEvent_getY(ev, idx);
168
169         switch(action & AMOTION_EVENT_ACTION_MASK) {
170         case AMOTION_EVENT_ACTION_DOWN:
171         case AMOTION_EVENT_ACTION_POINTER_DOWN:
172                 demo_mouse(0, 1, x, y);
173
174                 prev_pos[0] = x;
175                 prev_pos[1] = y;
176                 break;
177
178         case AMOTION_EVENT_ACTION_UP:
179         case AMOTION_EVENT_ACTION_POINTER_UP:
180                 demo_mouse(0, 0, x, y);
181
182                 prev_pos[0] = x;
183                 prev_pos[1] = y;
184                 break;
185
186         case AMOTION_EVENT_ACTION_MOVE:
187                 pcount = AMotionEvent_getPointerCount(ev);
188                 for(i=0; i<pcount; i++) {
189                         int id = AMotionEvent_getPointerId(ev, i);
190                         if(id == 0) {
191                                 demo_motion(x, y);
192                                 prev_pos[0] = x;
193                                 prev_pos[1] = y;
194                                 break;
195                         }
196                 }
197                 break;
198
199         default:
200                 break;
201         }
202
203         return 1;
204 }
205
206 static int init_gl(void)
207 {
208         static const int eglattr[] = {
209                 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
210                 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
211                 EGL_RED_SIZE, 5,
212                 EGL_GREEN_SIZE, 5,
213                 EGL_BLUE_SIZE, 5,
214                 EGL_DEPTH_SIZE, 16,
215                 EGL_NONE
216         };
217         static const int ctxattr[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
218
219         EGLConfig eglcfg;
220         int count, vis;
221
222         dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
223         if(!dpy || !eglInitialize(dpy, 0, 0)) {
224                 fprintf(stderr, "failed to initialize EGL\n");
225                 destroy_gl();
226                 return -1;
227         }
228
229         if(!eglChooseConfig(dpy, eglattr, &eglcfg, 1, &count)) {
230                 fprintf(stderr, "no matching EGL config found\n");
231                 destroy_gl();
232                 return -1;
233         }
234
235         /* configure the native window visual according to the chosen EGL config */
236         eglGetConfigAttrib(dpy, &eglcfg, EGL_NATIVE_VISUAL_ID, &vis);
237         ANativeWindow_setBuffersGeometry(app->window, 0, 0, vis);
238
239         if(!(surf = eglCreateWindowSurface(dpy, eglcfg, app->window, 0))) {
240                 fprintf(stderr, "failed to create window\n");
241                 destroy_gl();
242                 return -1;
243         }
244
245         if(!(ctx = eglCreateContext(dpy, eglcfg, EGL_NO_CONTEXT, ctxattr))) {
246                 fprintf(stderr, "failed to create OpenGL ES context\n");
247                 destroy_gl();
248                 return -1;
249         }
250         eglMakeCurrent(dpy, surf, surf, ctx);
251
252         eglQuerySurface(dpy, surf, EGL_WIDTH, &width);
253         eglQuerySurface(dpy, surf, EGL_HEIGHT, &height);
254         return 0;
255 }
256
257 static void destroy_gl(void)
258 {
259         if(!dpy) return;
260
261         eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
262
263         if(ctx) {
264                 eglDestroyContext(dpy, ctx);
265                 ctx = 0;
266         }
267         if(surf) {
268                 eglDestroySurface(dpy, surf);
269                 surf = 0;
270         }
271
272         eglTerminate(dpy);
273         dpy = 0;
274 }
275
276 static unsigned long get_time_msec(void)
277 {
278         struct timespec ts;
279         static struct timespec ts0;
280
281         clock_gettime(CLOCK_MONOTONIC, &ts);
282         if(ts0.tv_sec == 0 && ts0.tv_nsec == 0) {
283                 ts0 = ts;
284                 return 0;
285         }
286         return (ts.tv_sec - ts0.tv_sec) * 1000 + (ts.tv_nsec - ts0.tv_nsec) / 1000000;
287 }
288
289 static void hide_navbar(struct android_app *state)
290 {
291         JNIEnv *env;
292         jclass cactivity, cwin, cview;
293         jobject win, view;
294         jmethodID get_window, get_decor_view, set_system_ui_visibility;
295         jfieldID field_flag_fs, field_flag_hidenav, field_flag_immersive;
296         int flag_fs, flag_hidenav, flag_immersive;
297
298         (*state->activity->vm)->AttachCurrentThread(state->activity->vm, &env, 0);
299
300         cactivity = (*env)->FindClass(env, "android/app/NativeActivity");
301         get_window = (*env)->GetMethodID(env, cactivity, "getWindow", "()Landroid/view/Window;");
302
303         cwin = (*env)->FindClass(env, "android/view/Window");
304         get_decor_view = (*env)->GetMethodID(env, cwin, "getDecorView", "()Landroid/view/View;");
305
306         cview = (*env)->FindClass(env, "android/view/View");
307         set_system_ui_visibility = (*env)->GetMethodID(env, cview, "setSystemUiVisibility", "(I)V");
308
309         win = (*env)->CallObjectMethod(env, state->activity->clazz, get_window);
310         view = (*env)->CallObjectMethod(env, win, get_decor_view);
311
312         field_flag_fs = (*env)->GetStaticFieldID(env, cview, "SYSTEM_UI_FLAG_FULLSCREEN", "I");
313         field_flag_hidenav = (*env)->GetStaticFieldID(env, cview, "SYSTEM_UI_FLAG_HIDE_NAVIGATION", "I");
314         field_flag_immersive = (*env)->GetStaticFieldID(env, cview, "SYSTEM_UI_FLAG_IMMERSIVE_STICKY", "I");
315
316         flag_fs = (*env)->GetStaticIntField(env, cview, field_flag_fs);
317         flag_hidenav = (*env)->GetStaticIntField(env, cview, field_flag_hidenav);
318         flag_immersive = (*env)->GetStaticIntField(env, cview, field_flag_immersive);
319
320         (*env)->CallVoidMethod(env, view, set_system_ui_visibility, flag_fs | flag_hidenav | flag_immersive);
321
322         (*state->activity->vm)->DetachCurrentThread(state->activity->vm);
323 }
324
325 static const char *cmdname(uint32_t cmd)
326 {
327         static const char *names[] = {
328                 "APP_CMD_INPUT_CHANGED",
329                 "APP_CMD_INIT_WINDOW",
330                 "APP_CMD_TERM_WINDOW",
331                 "APP_CMD_WINDOW_RESIZED",
332                 "APP_CMD_WINDOW_REDRAW_NEEDED",
333                 "APP_CMD_CONTENT_RECT_CHANGED",
334                 "APP_CMD_GAINED_FOCUS",
335                 "APP_CMD_LOST_FOCUS",
336                 "APP_CMD_CONFIG_CHANGED",
337                 "APP_CMD_LOW_MEMORY",
338                 "APP_CMD_START",
339                 "APP_CMD_RESUME",
340                 "APP_CMD_SAVE_STATE",
341                 "APP_CMD_PAUSE",
342                 "APP_CMD_STOP",
343                 "APP_CMD_DESTROY"
344         };
345         if(cmd >= sizeof names / sizeof *names) {
346                 return "unknown";
347         }
348         return names[cmd];
349 }