From 11a89ea0bd01839bcc066c2fed1f1764b441b875 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Wed, 7 Jul 2010 14:33:00 -0700 Subject: [PATCH] Update native_activity sample to use new glue code. The native_activity sample is now built with the new threaded_app glue code, removing all of the boiler-plate code from it. This adds the glue code (header and .a) to the NDK. Change-Id: I2a7be473811f22f948dcda3da8034dd0bd62049d --- .../arch-arm/usr/include/android/input.h | 5 +- .../arch-arm/usr/include/android/looper.h | 134 ++++++- .../usr/include/android_glue/threaded_app.h | 168 +++++++++ .../android-9/arch-arm/usr/lib/libandroid.so | Bin 9524 -> 9524 bytes .../arch-arm/usr/lib/libthreaded_app.a | Bin 0 -> 50768 bytes ndk/samples/native-activity/jni/Android.mk | 2 +- ndk/samples/native-activity/jni/main.c | 337 +++--------------- 7 files changed, 357 insertions(+), 289 deletions(-) create mode 100644 ndk/platforms/android-9/arch-arm/usr/include/android_glue/threaded_app.h create mode 100644 ndk/platforms/android-9/arch-arm/usr/lib/libthreaded_app.a diff --git a/ndk/platforms/android-9/arch-arm/usr/include/android/input.h b/ndk/platforms/android-9/arch-arm/usr/include/android/input.h index 75be85abb..014b6a314 100644 --- a/ndk/platforms/android-9/arch-arm/usr/include/android/input.h +++ b/ndk/platforms/android-9/arch-arm/usr/include/android/input.h @@ -534,10 +534,11 @@ struct AInputQueue; typedef struct AInputQueue AInputQueue; /* - * Add this input queue to a looper for processing. + * Add this input queue to a looper for processing. See + * ALooper_addFd() for information on the callback and data params. */ void AInputQueue_attachLooper(AInputQueue* queue, ALooper* looper, - ALooper_callbackFunc callback, void* data); + ALooper_callbackFunc* callback, void* data); /* * Remove the input queue from the looper it is currently attached to. diff --git a/ndk/platforms/android-9/arch-arm/usr/include/android/looper.h b/ndk/platforms/android-9/arch-arm/usr/include/android/looper.h index 90a89838a..291721624 100644 --- a/ndk/platforms/android-9/arch-arm/usr/include/android/looper.h +++ b/ndk/platforms/android-9/arch-arm/usr/include/android/looper.h @@ -24,25 +24,151 @@ extern "C" { #endif +/** + * ALooper + * + * A looper is the state tracking an event loop for a thread. + * Loopers do not define event structures or other such things; rather + * they are a lower-level facility to attach one or more discrete objects + * listening for an event. An "event" here is simply data available on + * a file descriptor: each attached object has an associated file descriptor, + * and waiting for "events" means (internally) polling on all of these file + * descriptors until one or more of them have data available. + * + * A thread can have only one ALooper associated with it. + */ struct ALooper; typedef struct ALooper ALooper; +/** + * For callback-based event loops, this is the prototype of the function + * that is called. It is given the file descriptor it is associated with, + * a bitmask of the poll events that were triggered (typically POLLIN), and + * the data pointer that was originally supplied. + * + * Implementations should return 1 to continue receiving callbacks, or 0 + * to have this file descriptor and callback unregistered from the looper. + */ typedef int ALooper_callbackFunc(int fd, int events, void* data); +/** + * Return the ALooper associated with the calling thread, or NULL if + * there is not one. + */ ALooper* ALooper_forThread(); -ALooper* ALooper_prepare(); +enum { + /** + * Option for ALooper_prepare: this ALooper will accept calls to + * ALooper_addFd() that do not have a callback (that is provide NULL + * for the callback). In this case the caller of ALooper_pollOnce() + * or ALooper_pollAll() MUST check the return from these functions to + * discover when data is available on such fds and process it. + */ + ALOOPER_PREPARE_ALLOW_NON_CALLBACKS = 1<<0 +}; -int32_t ALooper_pollOnce(int timeoutMillis); +/** + * Prepare an ALooper associated with the calling thread, and return it. + * If the thread already has an ALooper, it is returned. Otherwise, a new + * one is created, associated with the thread, and returned. + * + * The opts may be ALOOPER_PREPARE_ALLOW_NON_CALLBACKS or 0. + */ +ALooper* ALooper_prepare(int32_t opts); +enum { + /** + * Result from ALooper_pollOnce() and ALooper_pollAll(): one or + * more callbacks were executed. + */ + ALOOPER_POLL_CALLBACK = -1, + + /** + * Result from ALooper_pollOnce() and ALooper_pollAll(): the + * timeout expired. + */ + ALOOPER_POLL_TIMEOUT = -2, + + /** + * Result from ALooper_pollOnce() and ALooper_pollAll(): an error + * occurred. + */ + ALOOPER_POLL_ERROR = -3, +}; + +/** + * Wait for events to be available, with optional timeout in milliseconds. + * Invokes callbacks for all file descriptors on which an event occurred. + * + * If the timeout is zero, returns immediately without blocking. + * If the timeout is negative, waits indefinitely until an event appears. + * + * Returns ALOOPER_POLL_CALLBACK if a callback was invoked. + * + * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given + * timeout expired. + * + * Returns ALOPER_POLL_ERROR if an error occurred. + * + * Returns a value >= 0 containing a file descriptor if it has data + * and it has no callback function (requiring the caller here to handle it). + * In this (and only this) case outEvents and outData will contain the poll + * events and data associated with the fd. + * + * This method does not return until it has finished invoking the appropriate callbacks + * for all file descriptors that were signalled. + */ +int32_t ALooper_pollOnce(int timeoutMillis, int* outEvents, void** outData); + +/** + * Like ALooper_pollOnce(), but performs all pending callbacks until all + * data has been consumed or a file descriptor is available with no callback. + * This function will never return ALOOPER_POLL_CALLBACK. + */ +int32_t ALooper_pollAll(int timeoutMillis, int* outEvents, void** outData); + +/** + * Acquire a reference on the given ALooper object. This prevents the object + * from being deleted until the reference is removed. This is only needed + * to safely hand an ALooper from one thread to another. + */ void ALooper_acquire(ALooper* looper); +/** + * Remove a reference that was previously acquired with ALooper_acquire(). + */ void ALooper_release(ALooper* looper); -void ALooper_setCallback(ALooper* looper, int fd, int events, +/** + * Add a new file descriptor to be polled by the looper. If the same file + * descriptor was previously added, it is replaced. + * + * "fd" is the file descriptor to be added. + * "events" are the poll events to wake up on. Typically this is POLLIN. + * "callback" is the function to call when there is an event on the file + * descriptor. + * "id" is an identifier to associated with this file descriptor, or 0. + * "data" is a private data pointer to supply to the callback. + * + * There are two main uses of this function: + * + * (1) If "callback" is non-NULL, then + * this function will be called when there is data on the file descriptor. It + * should execute any events it has pending, appropriately reading from the + * file descriptor. + * + * (2) If "callback" is NULL, the fd will be returned by ALooper_pollOnce + * when it has data available, requiring the caller to take care of processing + * it. + */ +void ALooper_addFd(ALooper* looper, int fd, int events, ALooper_callbackFunc* callback, void* data); -int32_t ALooper_removeCallback(ALooper* looper, int fd); +/** + * Remove a previously added file descriptor from the looper. + */ +int32_t ALooper_removeFd(ALooper* looper, int fd); #ifdef __cplusplus }; diff --git a/ndk/platforms/android-9/arch-arm/usr/include/android_glue/threaded_app.h b/ndk/platforms/android-9/arch-arm/usr/include/android_glue/threaded_app.h new file mode 100644 index 000000000..80de3bf2b --- /dev/null +++ b/ndk/platforms/android-9/arch-arm/usr/include/android_glue/threaded_app.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include + +/** + * This is the interface for the standard glue code of a threaded + * application. In this model, the application's code is running + * in its own thread separate from the main thread of the process. + * It is not required that this thread be associated with the Java + * VM, although it will need to be in order to make JNI calls any + * Java objects. + */ +struct android_app { + // The application can place a pointer to its own state object + // here if it likes. + void* userData; + + // The ANativeActivity object instance that this app is running in. + ANativeActivity* activity; + + // The ALooper associated with the app's thread. + ALooper* looper; + + // When non-NULL, this is the input queue from which the app will + // receive user input events. + AInputQueue* inputQueue; + + // When non-NULL, this is the window surface that the app can draw in. + ANativeWindow* window; + + // Current state of the app's activity. May be either APP_CMD_START, + // APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP; see below. + int activityState; + + // This is non-zero when the application's NativeActivity is being + // destroyed and waiting for the app thread to complete. + int destroyRequested; + + // ------------------------------------------------- + // Below are "private" implementation of the glue code. + + pthread_mutex_t mutex; + pthread_cond_t cond; + + int msgread; + int msgwrite; + + pthread_t thread; + + int running; + int destroyed; + AInputQueue* pendingInputQueue; + ANativeWindow* pendingWindow; +}; + +enum { + /** + * Looper data ID of commands coming from the app's main thread. + * These can be retrieved and processed with android_app_read_cmd() + * and android_app_exec_cmd(). + */ + LOOPER_ID_MAIN = 1, + + /** + * Looper data ID of events coming from the AInputQueue of the + * application's window. These can be read via the inputQueue + * object of android_app. + */ + LOOPER_ID_EVENT = 2 +}; + +enum { + /** + * Command from main thread: the AInputQueue has changed. Upon processing + * this command, android_app->inputQueue will be updated to the new queue + * (or NULL). + */ + APP_CMD_INPUT_CHANGED, + + /** + * Command from main thread: the ANativeWindow has changed. Upon processing + * this command, android_app->window will be updated to the new window surface + * (or NULL). + */ + APP_CMD_WINDOW_CHANGED, + + /** + * Command from main thread: the app's activity window has gained + * input focus. + */ + APP_CMD_GAINED_FOCUS, + + /** + * Command from main thread: the app's activity window has lost + * input focus. + */ + APP_CMD_LOST_FOCUS, + + /** + * Command from main thread: the app's activity has been started. + */ + APP_CMD_START, + + /** + * Command from main thread: the app's activity has been resumed. + */ + APP_CMD_RESUME, + + /** + * Command from main thread: the app's activity has been paused. + */ + APP_CMD_PAUSE, + + /** + * Command from main thread: the app's activity has been stopped. + */ + APP_CMD_STOP, + + /** + * Command from main thread: the app's activity is being destroyed, + * and waiting for the app thread to clean up and exit before proceeding. + */ + APP_CMD_DESTROY, +}; + +/** + * Call if android_app->destroyRequested is non-zero. Upon return, the + * android_app structure is no longer valid and must not be touched. + */ +void android_app_destroy(struct android_app* android_app); + +/** + * Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next + * app command message. + */ +int8_t android_app_read_cmd(struct android_app* android_app); + +/** + * Call with the command returned by android_app_read_cmd() to do the + * default processing of the given command. + */ +void android_app_exec_cmd(struct android_app* android_app, int8_t cmd); + +/** + * This is the function that application code must implement, representing + * the main entry to the app. + */ +extern void android_main(struct android_app* app); diff --git a/ndk/platforms/android-9/arch-arm/usr/lib/libandroid.so b/ndk/platforms/android-9/arch-arm/usr/lib/libandroid.so index d185a8fb16fa3a48b212b52f681450f292d3de71..002b9294696903b1ddcf5465d444a3c282843e1d 100644 GIT binary patch delta 3401 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 diff --git a/ndk/platforms/android-9/arch-arm/usr/lib/libthreaded_app.a b/ndk/platforms/android-9/arch-arm/usr/lib/libthreaded_app.a new file mode 100644 index 0000000000000000000000000000000000000000..9b6e7dd8cbdc17120138b776867f284246965c07 GIT binary patch literal 50768 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%0A>5yItah ztb7iC`*sepkr1soC%^rT6zdQ9LS(+jV$%3;7}LvG?0$y-8y5SC6ypkFAM}eo3$f=g zuh>Brd)F^^kj3&aMXdN=7zKU<$+B3=Aw z6Ed&yy?sV+5BtSFW3jiS7?(i%Bfr>}$b8x__7RI61H;DKM=Ul9B0_?BWWO)PFjTAs z$UNv5d!NOwWwBkDV)px-ypKw;9Z1;^NinwVZDban?zQb9w(T2anSOhS{dOF>!isZ6 zx8IRseIcKL%$Y1E9qL{ zJf4B*sODf$8(YWyzu&f*M!Vo zh=Xaz^R%~`yi@GGG#D3RF_I4=cN~dgFiwN77~&p|Xx^OQ;S)Z8NA3!rIE=&`EK4S! z!x+$0AYv+>*8}DH+sxqsp2Z7A1}Z)*a}&h?S2vM(nRT=0PtD1}bOIZK+SdRqy2AQ>qxhAu!ZE16&6=`%6^6{lkq|uqprUQ)7(A0$BAdP`k z-`Lzv4w5EvRp^o0ro|~))%wM5lNszw&a`$L%MIT`TS;e;zki3GGa+jbnv zc`xBh!O7a*#@qqKw-hDg%c345h)G(A6iGiQtR_(+T2&u=Dw6R_h+`v$Q1Q2gv)bA z@JI>Ib6RRdN=17~Wkw7r98p+=;)Um=XouSI!kb16m;hyAe*2}8nlN<4fLSB*CyXd) zAB6AZC;R49yLbCp1&i`c^rPiYS#*3f-f7#?(q*NGzWDc{PCK}aVsy%}Wy{7D8Q^53 zaIAQz9o9~q2`rp7eBS%)pSm-!D`bmGQzHC`(fyl}F}lzG}v=4Ru+B&wPC9@R^Ix9DH~odp@OocrePCYoM}mS?2&N=MY4{f_gjm9^Ere9IPVC2e^}GwNlb zw>y@Z9{5R3Tzi~jA=yiRs=H^w?W4ZCQ1pPXXNaCd9a!1 z`?K@cS0j2m??Ri_;lst^`}j2A(}+)ne!^|e+Gh7Io}c$gmd!fYeY1DoiT%E;-B^#} zQQ!KZc=5v>cX;Qbbn8*@3e2i#T0Akau&HI~neh{wJ6Kbz7bVIYTidD|YZ6o2@FP4X zyQZnVp*G&w)E2LipXP}-*EdtuYROOa)HlvEyt3;0hW3_3d~r*C8#3d8U+{_JCV?e7 zSmnHEJhwg3o+vdx>XV?NIX+;3R3WK0KLeDaG(QzIQ!X;omJSlqls6^InrhlxN7q$1 z&I`D%MeEy|noUC{SGTun+ovR2+ZSpYA!%td4E1h=Ks&u(D$2J?CQq&!J+ZW^eA49U z)2c>~E15KQOlb$Jq^(Wv66VF_>N$QfBN1vl$sruL^1QTTZl1p`VyMYp)A)|r zojcl{qrB$!)=cW+_yl!*n5oA`;HZvWSS~BlIn(K!5$zn!DD5~L`9T}oA(sX02W=FK zjUKd-8R?vX?`TCk+xY5NCh~jY;~m_>8P%~9d67tG+v$v3l%SqIRqxqJ(Q_`9netJu ze6SwWkLoD($cS`~I-Miw>+yfnPq02k-%_6-kM$`v_0EoT&T=|uMmuL@bdEB9J@E04 zzDNZ7;Qxj$*dLSOFWetNpKL?0KEblZqLaQ(+m|1vu^lWwB&qku3bHJ&i4p5ELGqFI zF6*h5Ykb?9%sig5Y-=iWy(3sUj?DjEY0fL#I@dEsWEQJKWOpImg~(}zG<()RSSPJo zZ%Iz~RY+)Q!R?U4RqG^0CKIT(<;oukbEK(S6dGHuwLOy5(yMJTa*+=9r|3~bmSlC1 zBos?>8ve?`tD`tRwp`0g7XPpYp6#jsV&HbYxu+@y>djT-xlbwa}kY3dX7`2Pn@BK5xuS`p%k(ePh%6PdR4Uv!fp z`qK)QbbNl52lS`;Ix!(hu~LukJ@qIP>m%1 z%F&L$)LPc>F-|u_d&D0Pex}jlGQ{$rg zmbUilhEYq~5)%_`bxpM;7BxvZrAOzsBocUJ(KfuQ&1$V*mHiX=$lm zYR#_=2xAj%WAFltKK!VvX=q>_(-K^tHW(&?qpUU2R^{EpVJzovp2&skAAF@o`r-irN) zTIMFEqJyj{iG~Co2C_fJgdc7+-qdAuLv<^5AyS4zO54@M)V3Bloj$D$xBV^Xu9l{S z;a-~8Ufoh##epIA0zoOUlJ&Bh8uiwngQubi0f!-rqeF@X8dEZY zG(6R_s;Zi+=OwCI>+0uX^w%z!k!WdUcfiWjc0AClNfXGF5TII9>zZ0p9Hz!SK%ZeP zY@NpyRP1}P$FQB(Rwq#~J&*cp$2VYLVnd|Os+zx0W^#F{@O(xIEwS^=cx=Tc%7Q9B zDW)gxH2jZ#`uM1qyOT1%_3 zb=K&u8tBa6?vsv^gc%k|rvVoTDO(imLJX&dIcQod&TPriQ&uvqq#`AhI+^@=;B|9q zj|8VXJ6bxzUnkh3vMNZwnz)9}r!;4(Q{GzA+FC!au^NjdXOGmz9}oV-WC1gJHLBjY zs*3gT=H~1LaSj(dh9O zPF@ ztXxUbjN~*QU6;USd{x!F#`c=3Mb%Xecs9<4FEu~;3oLEvmDY?EGlm58$7w6P59H}W z$4Uw2AvbbELo7HbORVa)ww4ef;3xecoMFvvsGipf^Rhb}Dju{i<0sHj7((3hO<6s> z_1TOu?UHm?7XHYstf}GhQ_IT<@4O>RJC>uS#Tc>H+{VdhR%v1`-a#=ZMhUKW;y3Z; zNy4mq#&v46k8t{$K54?F%9)e=k)>PC!D}63eZCBwmPBi!1!wZp*x4N3;!3CD{CaO@ zrMeDI18P!NXBgt_@y86=zF^{J#Lxz%gQyOwUVnycZSr*1w;3e4k_Ss1=h)+@{^ z{ErY8N0Bvuj)`k%E(R9^e+HQOXvPSem^>pV=`&!_8?A^w&sivs{xQWS;?=LVrj{}6 zV1Fu9R8E~%RaQBA`c!Ljduv@FwoQ%Z*^!(oWh@29jbAa}yoQq1Ic$td1#nF?cGC3t z=(xPThE8Zl5agBulR7!Jb=+XxH)jpDHn=?bmr|)-juaFLZm|3ejvBw#sl|dLtyXTP zlIL11@F`a{Gsa9ARXKHxAIYiHN~TPsZ4;*8pho+2J!LS{%%+9yZHXnEgXN8jnik+0 zrZ)%uk{HN=E2`2lqo$8d$(dSSF=o;WO_KZKA{V^UMp>l=88@)tJ zV|7C*ZVD$?$YXo1aP!E%!9&-P$CtJI2gl*K(vLS4I(& zi$Fku5$N}{dVy85R!gPKYM^U%@&=B+WzYuhbCPR|UX$`<>`!LTDp|&;js;dVS=3yQ z1p7dY1y?lGS>kW3P?7~FNC}KD419ULR?joiI`)=UJmv1#SgfkT0OZC=z)BTvg<7y_ z(q#4}QWpaDla7}L-rLEal+lbzCfm)z!76)~vWFML=+hH6JeZRy0^|js|-BXT87sp%;liB4voci_?S{XYo+!Z z+N0g3)6i?6KM9jvB|f~KLU-z_WNIo`rGZ`ukkJWz0|S?`-nzrC$Mw=&F1`{@Ho>3j zXnK9YK+w z8sw)2W7Eg;m5d52mO2u(T{1rWA(6W2;_adNXMJq+V$Y%tGbGu!sZBPkPOw`8D>a9p z8O|J!!HMWEvFX;Zpf!X?Uj#~hqlY8h_)f8#^po07xiIA@Na+e030y(U`ieE$_m=Y7 zk>y`0a+>;$lEuf&H*;Q2F&-SE$<@5P)q6p#axU8tynFH&pky}&*1GUe42{#KMsqp} zzMUmO;y^c@V8(pPS%jXFqdUbUujWmcnI=l5XiSF{X*Ed@J}=qP4n>d!0e&~8!bDj^8e@Q>uB@M?h#|)ubAqSO-UW;ddfJ$a|sMRLpLE* za%fYMwnMEw>|^X>BgZ)>M6H3XOBc3P&jGfz7_9SBxT|Qh2J)>sYv3I03kKo@a1?2{Q4@mShF)+9m4lJ26aUe3Q=L|etM~g;ZU%TW8dM$PM zHP7Le({R#yI08>R1;>Y8oP-^vqonb3B$~JhUCAdmZo2FRnO@%L?1ZCvp7?AyY51YR1asnIdSm#VY92XZ=TyDNOa3=6oLxYubwfSym5QB-2^*8yAkbcjKYY+{14F6Fp6H|C=J( zZh1TrcBxM-(znf&@LS_Of;ilUc{GvUNI<6*(f!H8&3KA5?eupwgC{p!$YKlpa66ix z)=WSr)d>brUeySjJDiZlZhDfoSBHI?iu#%~CX-M6lT8k~sWp$653`zH9@ht9pBH! zPG&F#6Vmp4rt|$r0Fs4@z&45{&8#Y?Wk{0 z<7c_?UYdE=nauFJRpwnW%feSLgvTFJ;1TB029J~R;oE`#iO0)YuMCo6Z=&ZcpgSj! zChu}^_{yAjcs9;N27j!?J95|KXCFJ`XP}Y6Z(;LoH`Z$sK3*9lt(j-xyGzMe;e6eA z=z{h(JZ#=Mr#*bt+qbt2Y1-pEemoA2O#QxxXPPe_2JKz&i1}Uv?J?i0Uo+CQ=Y^t6 zF@(V3)%^zKA>I5v)se^cc|JV9o_(cRZSdO(0`qh6@%-*VI@pe$#g-LEo_BcmHX+0F zLEXwgJNPQ6XYVDXY0qoN*~Jh72X#C?#RqA>9ptecw8g{o`w`MXzjp-Mfo}Hw@&{IE z+v|_gV)I1=WXa*#>jqNL-X_?)8Vtt8#m8&MX==}F$A`sG1`hh-!QXbmKffL1u^qI< z!}B`>q@dp+k0$$rZ!LR%?>F}Rb{r@+R5c&txvDV5}{125Q)Qut+` zF@7iF>QjrZ*RP3-~XSP6Tll>*M+M{H_T3-4tjCe~8BO`&;$vjjLTpXb1nF$zWWb3bX^irQ_RsLG5|%`0xnr z*l&1#JIG@@Xp4u}jt@hA_XgTAM0qSX5(U`TUONuJUb=DB1-xK8J`A*DSis&;wdb{? z|7a)!hc~Wf8=l_|@>n0*;sLcB7li!g{wz7q&rlxAJ)(ZSb_{_%uWxC{vv*aW=UhR49SaXDSj5R#J9pte-w8g_~$7t|^ej5VqplPoiw}jep2b5e- zy~DG&Fl6u2Ks&~O?AcqT_Plnye1vxJcd`&aza8YU9kj*6^Sc$iU_0&zv}2s|SS}9( zD%c-;U@zVL_(RCvra(J*P3pD7QF~rHa!a8M9Q4IwnBn>DAdmH-EgoJw27?!D$IF3s zOjI7rtq8TF3+(l!Aw4{MiIBa$fp%1aOCIelQhQ!I&W63<;kDyN!}Hrg9?R1f56|y) zA-`V-+A&3WEcdMX1;|kfd+FM-F=Q|IvE)3ThLZG4drzo6uN@7r7d*Un{NC{Vc96&N zw8ew=c>Ffxw?F)P*X=W*_x$oF&w}Hs4ffKtiz+Uk1+QFZX zMA~l$d29!5@nHRU91C7>TwM@o#|6-n$8u9c?brZ&>DqC6$X-LB9Tx@cm8(6k9WTRP z@bKD^Fg(8<=i@y4$oe{ki9;z$Mu>0u@E1x9pluV*N(MEXh*Z*`Tap2vcwj^Z%0GO?*)N& zGza|dP`_R~*1_I~G^B^ujx`~B4X}rQ(vB9O*N#Wjp4X05Wu7I2-niOpcz!#`LzYEb zJUqW|g#7k-JlP-Z%44~H=(J#etbsjVM|y{6&jv5pze8Xz4ifugF+N^9;%d)p#|yBR z3%z%E_R0;*t~`^A z9}*vjl;cTBzn6vlHo$K)4AAdo_;`MA3fr4tS--^G@(%jsaec_%-Y2jQp#x}-za`+= zTOYD_>r~5HN&|X$_U;VX>;H@y2efxNKAyd8A$y&0V%zN*1A6whhU^W2yhu;<0^N{~H!zgK%jYGB=T>@^+hSu^#c%|C|xKGc-_{U@?L zzkdtyc7Yc>Jl?;+b5JEuN8SKW0D3&`Wr61({{XK;2VeAgi@Oe)4dvcv3*h!RSjZ-`mPTW7*!b z@rgiB3q$ZR2Yyr&%I|oh`Hgd5a#F6mX#YfC$Ap8?o$Bvo>m>Cz9Qufi{$vryIwdJc zdG4$w4+o<=)n0$8FZW|u zy0M>(q>+b(><_cbQsObpw`}-~Qu1)DGm-*Q4t8Nu<8fA~zGum=xIg8W3fFgpuVKQG zU%5|aIhduX_KQRIi!J^)H*fnz!uCh`8YUcTbVwc(vOh-bb1s@=j5QDTzxKq#`g5$W zVZxC+m1LHK-GtQokGJ^0?BJhVLr47G22;k7Uj_`8Pp%})b#PaoYOhl28~2M;TB~6E zSUQj+9FKE7g`kuA3sA5>Eo6UM$o@3zCfM$CMEf&B_GgCVSt0wgLiT4_YhkaDdt#- zQ2Dtbxh`a{PU_p!XVqETV4QQ*9O3?)?SSqwf)rxxPOeke`O{x`6**BE38y^SRsI0r_`Gn_t#C%Hlwyk`M!|+WJul(nN8e|&(XNP z`yizMG9+gO?h)Aj6GHN-A$d?p9v6})h2)x$yfh?#7c%QxEcIPz-4xP4p!yF*-(u|w z>EBd6uMCdH`2Q%R5C5+MeeiQMfp2;AskH`Hj4tAzL218~1;t?h9rWtv=BgUJ7ld#A zApf+Ke0|HdV)PK$@m|jIzH05ig66#kr|-_CeOWN|#YgWo#ME4MfG;Ek-#blmli%@6 zGJ|hz1>V4|7(FzlA%T}=D@G4vLr*_U@4YKpF?u)&XGr{t4x@$NSwxWW{uKTWmcOUT z-ytc*%zJd83>BsLA9T8;#nmA3HCTFVuoxSRZ{t*XZNq=fz#m({n^VZ++w@iKjjU`{ z4Z5zmrN~-{uK?gBt%Zq&Xs7?JS@S*AdVE>h&yrtHGCBOhcU4uQrKPb6Ukq(Q0zaDM zeT7xNaGO$6a$vnyenSE8ZDF{0Y#lwGX#Ao=3z7`}YSZHCKurv(y-{dKd^=pfDTYQS z@KyB$e&<5IIaXC&TU%C}YJE;iQ*~`kHGZDM3)@2cPb8L%iS%(BWYsh@;g@T?dP{J; zw+OvVQ%^5fI?}VxiQ_3sl4BEj{Fw>;j$4rIzv&lbBe3b&{;PUSm_1d9LB%H(UsQZu@jXTEzp)0+#p4xE zRXjs+tm1UV8pVqhmn&YUDE~PT>^`9K6N)b?zM=S6#eXVhWSIJNQ7lj#tXQHrS+Pp7 zQSmZGK4WLQex%6%Mv3y{iZ3X>uJ{+l&lUNj&*bwzoFevC_^RSN ziXST;QjB3OVm*2(7AlTZtW>O0Y*JjVxLWaU#m$O46<<|+NAY9DLy9qMB8z{;LdB7a zm5No0O^VAES1aDFxLI+h;;V}9D1NMXNHNwy{VNtKj#R8vtWs=JT&}oU@ovSf)^o_0Gao}k!Y@l3^WiV4L9ipvyND1KjYmEui` zYZdQO+@N^B;#S2Siq9y%sQ9YlYl?3v{!#I-ivLhNp!gLL=c+@B4mKSkKf!$l5$*1# za!-{{CU(YuH>p^t`r)b{rSe#n&sBM*%5#X=Y@Dk&U-hl3zf9%tseGN{j}*5mK2D5V z)(*v|iKzEWs((%8KdAhk%70V&bCthRITJTZ5scvuiXDlt*PVzsl4M#RsV0*XXCLG;1Ji4oVyvluu6?jIf*kAQSRXiG*%9p5orOMZ;d=n9mGHy}4UG?{<{y~)=R{1HFf1&b= z#0&BNwJQEb^?y+PyDER6@@Fc4sj_uC%GcoC1;uP4;?vH|6`Rms=S$qE%HYcA5;A< z)xV_jZk1mruE76NtoV-V-&g&oDj!t&5b;Xva}PI|MMRuB5fP_jRnAwrFY#LJ11J`$ z{tVS)vrfv7SNUAxjg~cCahB@msJ=nvOBBDS`PUJ#xpAZ74^@Ac>Nl$Vl*%tE4&?ng z*3VKR*2VLQJch=}uOBH~v~L|m>XqMf)rv^s1y@0~cf zRz;O9hnYC|o|_z7l04Ca$LC&7SEKHS_R0YsZnvxsTg`h$9UeCCQgnF4eE*=sqn3r! zsnubdd8XRoXXgJ(@9>!UP9y8VcUC)WH~-&chsVwTLD}I6^E9W!lji?s>+qCi-9+9F z^Zp3yy$-U2N^Kw>k3Ts2v9df+uTB8kUqn3CJojh0m5`kR^SwEzk9l{?InlCiVE### zwT9^89aiGW>aVZ*%Ta%)n0H#7ewMX~yi?78y5RK3|D#QL0RG_U7Z(tv#A&DbKMFhS zGT((_`%uK{@U+GW?Hf+{7p7dlB7Uk` zZdE_LB9IG_e!CHeX@4QU(ogWuqaVhp9Prx(3C2%y|5be|lKd3=lS1{GW_bNDkK_o~$K#(Lst@K{nBN;2jE}T)wuv*_&7E{#Udp7jW^`?BkYYj5KnDWveQh(SP zLK$|(X+DQkhnLN}-0U}on*Gg?aNgdeytSJDW6j@4L_D^u9#c^InN4?YG5@Ct>$RKm zt(LV%^=}YiY_H1uh__kRyDGm=yd8gV^t;g@+adk(Yh71RPqv5li-_wCuiu>p{qSXP_LONeo^Zj!%-G74{3J%)sw_uf#M?cw?Fpvqqp zi{aM>qI^#x)`en~ONnqkTjg3J=Jg7dWq%*@cB9Ihh!-NBD(@j;-W^o=YhoSR8v(Mt zM0946%0q}~T!qSf*3I*3oyrZw6*zCGyo!kN$nUz+-bNzE)w3$UKt%t%tMdCqjX&cY zwIBm?6+0=$75gZjs#vJV`NV!1r8r)3k|NKi)X!0@S6rxgsp1O7)x;i_b+h7+6z^92 zsp12Q82ZA;sZ8*BMNAoy-z3J(dtb!&RQ^EmOT}*#qwJSBt|Jt?D;}rVPjR3kzbnsi z#&_+A<%;rL2=aw0S1a=U74jMsTNM{8eqZq##oLG&{C6tet9YN{-~sjpT{D7GmsQCzMl z&(FZ;I~`nyZc)5T@yCh}EB;JzhvL(U{H`<0zpVI{;ya3eQT&_Ymqe^{-za8a;h=n! z;xUTy91i(?RPL)-sCb&|WhQVybT*c!Qdnulz*iZ2cBF+mV z6vrr*E6!59P_b6APH{02=aEYkuTZ>7@m587t_a?bRsM-0zq8Bpjyz`sKCSX|imxfY zskl$^UB%CcI8S|{cu0}owPn6Mj|6sC`2SK!C6!{%b=AWWCKyirTaK({|qZP+1PEwq%I7@N1Vy)r=#U{l?ipvy#K*V*y zb&5Y!yiM^w#RnB1Rot$KW?B)XJ?umo*A+OW%XL7U2%#?#0#1c;J{(TO`Kp+R^Ts%0 zvA!!?W?AP^#(FoKi1i9nMAn05BG!W?M9jMtL@2K&Vtn5~#5i6{#Q0iAT%q3qLBDUI zjQ-wEMErLV(S9Dm>!NJRoh>U@u@kX}Uatc2PukN{F-|Np_ff>Yl+nKaipY|7B1>>M zu~@&`0K`8zPn9Z;BclEICpejiI80MKkBIg00>#-x#HCiTj)?d)C^i$3iO>ixAtGLK yJqBDs8OoK4R}&GxRf;zdSLk&%a4luT^A5#zM8tK2;zlCkyGd~i5pjN0@&5qt{E6lO literal 0 HcmV?d00001 diff --git a/ndk/samples/native-activity/jni/Android.mk b/ndk/samples/native-activity/jni/Android.mk index fa1ab4177..8ce70aae0 100644 --- a/ndk/samples/native-activity/jni/Android.mk +++ b/ndk/samples/native-activity/jni/Android.mk @@ -18,6 +18,6 @@ include $(CLEAR_VARS) LOCAL_MODULE := native-activity LOCAL_SRC_FILES := main.c glutils.c -LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv1_CM +LOCAL_LDLIBS := -lthreaded_app -llog -landroid -lEGL -lGLESv1_CM include $(BUILD_SHARED_LIBRARY) diff --git a/ndk/samples/native-activity/jni/main.c b/ndk/samples/native-activity/jni/main.c index 3d2faeef9..6960ff6ab 100644 --- a/ndk/samples/native-activity/jni/main.c +++ b/ndk/samples/native-activity/jni/main.c @@ -18,38 +18,14 @@ #include #include -#include -#include -#include -#include -#include + +#include #include "glutils.h" -// -------------------------------------------------------------------- -// Rendering and input engine thread -// -------------------------------------------------------------------- - struct engine { - pthread_mutex_t mutex; - pthread_cond_t cond; + struct android_app* app; - int msgread; - int msgwrite; - - ANativeActivity* activity; - pthread_t thread; - - int running; - int destroyed; - ALooper* looper; - AInputQueue* inputQueue; - ANativeWindow* window; - AInputQueue* pendingInputQueue; - ANativeWindow* pendingWindow; - - // private to engine thread. - int destroyRequested; int animating; EGLDisplay display; EGLSurface surface; @@ -61,14 +37,6 @@ struct engine { int32_t y; }; -enum { - ENGINE_CMD_INPUT_CHANGED, - ENGINE_CMD_WINDOW_CHANGED, - ENGINE_CMD_GAINED_FOCUS, - ENGINE_CMD_LOST_FOCUS, - ENGINE_CMD_DESTROY, -}; - static int engine_init_display(struct engine* engine) { // initialize opengl and egl const EGLint attribs[] = { @@ -84,8 +52,8 @@ static int engine_init_display(struct engine* engine) { EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(display, 0, 0); - selectConfigForNativeWindow(display, attribs, engine->window, &config); - surface = eglCreateWindowSurface(display, config, engine->window, NULL); + selectConfigForNativeWindow(display, attribs, engine->app->window, &config); + surface = eglCreateWindowSurface(display, config, engine->app->window, NULL); context = eglCreateContext(display, config, NULL, NULL); eglQuerySurface(display, surface, EGL_WIDTH, &w); eglQuerySurface(display, surface, EGL_HEIGHT, &h); @@ -161,19 +129,17 @@ static int engine_term_display(struct engine* engine) { engine->surface = EGL_NO_SURFACE; } -static int engine_process_event(int fd, int events, void* param) { - struct engine* engine = (struct engine*)param; - +static int engine_do_ui_event(struct engine* engine) { AInputEvent* event = NULL; - if (AInputQueue_getEvent(engine->inputQueue, &event) >= 0) { + if (AInputQueue_getEvent(engine->app->inputQueue, &event) >= 0) { LOGI("New input event: type=%d\n", AInputEvent_getType(event)); if (AInputEvent_getType(event) == INPUT_EVENT_TYPE_MOTION) { engine->animating = 1; engine->x = AMotionEvent_getX(event, 0); engine->y = AMotionEvent_getY(event, 0); - AInputQueue_finishEvent(engine->inputQueue, event, 1); + AInputQueue_finishEvent(engine->app->inputQueue, event, 1); } else { - AInputQueue_finishEvent(engine->inputQueue, event, 0); + AInputQueue_finishEvent(engine->app->inputQueue, event, 0); } } else { LOGI("Failure reading next input event: %s\n", strerror(errno)); @@ -182,262 +148,69 @@ static int engine_process_event(int fd, int events, void* param) { return 1; } -static int engine_process_cmd(int fd, int events, void* param) { - struct engine* engine = (struct engine*)param; - - int8_t cmd; - if (read(engine->msgread, &cmd, sizeof(cmd)) == sizeof(cmd)) { - switch (cmd) { - case ENGINE_CMD_INPUT_CHANGED: - LOGI("Engine: ENGINE_CMD_INPUT_CHANGED\n"); - pthread_mutex_lock(&engine->mutex); - if (engine->inputQueue != NULL) { - AInputQueue_detachLooper(engine->inputQueue); - } - engine->inputQueue = engine->pendingInputQueue; - if (engine->inputQueue != NULL) { - LOGI("Attaching input queue to looper"); - AInputQueue_attachLooper(engine->inputQueue, - engine->looper, engine_process_event, engine); - } - pthread_cond_broadcast(&engine->cond); - pthread_mutex_unlock(&engine->mutex); - break; - - case ENGINE_CMD_WINDOW_CHANGED: - LOGI("Engine: ENGINE_CMD_WINDOW_CHANGED\n"); - engine_term_display(engine); - pthread_mutex_lock(&engine->mutex); - engine->window = engine->pendingWindow; - pthread_cond_broadcast(&engine->cond); - pthread_mutex_unlock(&engine->mutex); - if (engine->window != NULL) { - engine_init_display(engine); - engine_draw_frame(engine); - } - break; - - case ENGINE_CMD_LOST_FOCUS: - engine->animating = 0; +static void engine_do_main_cmd(struct engine* engine) { + int8_t cmd = android_app_read_cmd(engine->app); + switch (cmd) { + case APP_CMD_WINDOW_CHANGED: + engine_term_display(engine); + android_app_exec_cmd(engine->app, cmd); + if (engine->app->window != NULL) { + engine_init_display(engine); engine_draw_frame(engine); - break; - - case ENGINE_CMD_DESTROY: - LOGI("Engine: ENGINE_CMD_DESTROY\n"); - engine->destroyRequested = 1; - break; - } - } else { - LOGW("No data on command pipe!"); + } + break; + case APP_CMD_LOST_FOCUS: + android_app_exec_cmd(engine->app, cmd); + engine->animating = 0; + engine_draw_frame(engine); + break; + default: + android_app_exec_cmd(engine->app, cmd); + break; } - - return 1; } -static void* engine_entry(void* param) { - struct engine* engine = (struct engine*)param; +void android_main(struct android_app* state) { + struct engine engine; - ALooper* looper = ALooper_prepare(); - ALooper_setCallback(looper, engine->msgread, POLLIN, engine_process_cmd, engine); - engine->looper = looper; - - pthread_mutex_lock(&engine->mutex); - engine->running = 1; - pthread_cond_broadcast(&engine->cond); - pthread_mutex_unlock(&engine->mutex); + memset(&engine, 0, sizeof(engine)); + state->userData = &engine; + engine.app = state; // loop waiting for stuff to do. while (1) { // Read all pending events. - while (ALooper_pollOnce(engine->animating ? 0 : -1)) { - ; + int fd; + int events; + void* data; + while ((fd=ALooper_pollAll(engine.animating ? 0 : -1, &events, &data)) >= 0) { + LOGI("Poll returned: %d", (int)data); + switch ((int)data) { + case LOOPER_ID_MAIN: + engine_do_main_cmd(&engine); + break; + case LOOPER_ID_EVENT: + engine_do_ui_event(&engine); + break; + } } - if (engine->destroyRequested) { + if (state->destroyRequested) { LOGI("Engine thread destroy requested!"); - pthread_mutex_lock(&engine->mutex); - engine_term_display(engine); - if (engine->inputQueue != NULL) { - AInputQueue_detachLooper(engine->inputQueue); - } - engine->destroyed = 1; - pthread_cond_broadcast(&engine->cond); - pthread_mutex_unlock(&engine->mutex); - // Can't touch engine object after this. - return NULL; // EXIT THREAD + engine_term_display(&engine); + android_app_destroy(state); + // Can't touch android_app object after this. + return; } - if (engine->animating) { + if (engine.animating) { // Done with events; draw next animation frame. - engine->angle += .01f; - if (engine->angle > 1) { - engine->angle = 0; + engine.angle += .01f; + if (engine.angle > 1) { + engine.angle = 0; } - engine_draw_frame(engine); + engine_draw_frame(&engine); } } } - -static struct engine* engine_create(ANativeActivity* activity) { - struct engine* engine = (struct engine*)malloc(sizeof(struct engine)); - memset(engine, 0, sizeof(struct engine)); - engine->activity = activity; - - pthread_mutex_init(&engine->mutex, NULL); - pthread_cond_init(&engine->cond, NULL); - - int msgpipe[2]; - if (pipe(msgpipe)) { - LOGI("could not create pipe: %s", strerror(errno)); - } - engine->msgread = msgpipe[0]; - engine->msgwrite = msgpipe[1]; - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&engine->thread, &attr, engine_entry, engine); - - // Wait for thread to start. - pthread_mutex_lock(&engine->mutex); - while (!engine->running) { - pthread_cond_wait(&engine->cond, &engine->mutex); - } - pthread_mutex_unlock(&engine->mutex); - - return engine; -} - -static void engine_write_cmd(struct engine* engine, int8_t cmd) { - if (write(engine->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) { - LOGI("Failure writing engine cmd: %s\n", strerror(errno)); - } -} - -static void engine_set_input(struct engine* engine, AInputQueue* inputQueue) { - pthread_mutex_lock(&engine->mutex); - engine->pendingInputQueue = inputQueue; - engine_write_cmd(engine, ENGINE_CMD_INPUT_CHANGED); - while (engine->inputQueue != engine->pendingInputQueue) { - pthread_cond_wait(&engine->cond, &engine->mutex); - } - pthread_mutex_unlock(&engine->mutex); -} - -static void engine_set_window(struct engine* engine, ANativeWindow* window) { - pthread_mutex_lock(&engine->mutex); - engine->pendingWindow = window; - engine_write_cmd(engine, ENGINE_CMD_WINDOW_CHANGED); - while (engine->window != engine->pendingWindow) { - pthread_cond_wait(&engine->cond, &engine->mutex); - } - pthread_mutex_unlock(&engine->mutex); -} - -static void engine_destroy(struct engine* engine) { - pthread_mutex_lock(&engine->mutex); - engine_write_cmd(engine, ENGINE_CMD_DESTROY); - while (!engine->destroyed) { - pthread_cond_wait(&engine->cond, &engine->mutex); - } - pthread_mutex_unlock(&engine->mutex); - - close(engine->msgread); - close(engine->msgwrite); - pthread_cond_destroy(&engine->cond); - pthread_mutex_destroy(&engine->mutex); - free(engine); -} - -// -------------------------------------------------------------------- -// Native activity interaction (called from main thread) -// -------------------------------------------------------------------- - -static void onDestroy(ANativeActivity* activity) -{ - LOGI("Destroy: %p\n", activity); - engine_destroy((struct engine*)activity->instance); -} - -static void onStart(ANativeActivity* activity) -{ - LOGI("Start: %p\n", activity); -} - -static void onResume(ANativeActivity* activity) -{ - LOGI("Resume: %p\n", activity); -} - -static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) -{ - LOGI("SaveInstanceState: %p\n", activity); - return NULL; -} - -static void onPause(ANativeActivity* activity) -{ - LOGI("Pause: %p\n", activity); -} - -static void onStop(ANativeActivity* activity) -{ - LOGI("Stop: %p\n", activity); -} - -static void onLowMemory(ANativeActivity* activity) -{ - LOGI("LowMemory: %p\n", activity); -} - -static void onWindowFocusChanged(ANativeActivity* activity, int focused) -{ - LOGI("WindowFocusChanged: %p -- %d\n", activity, focused); - engine_write_cmd((struct engine*)activity->instance, - focused ? ENGINE_CMD_GAINED_FOCUS : ENGINE_CMD_LOST_FOCUS); -} - -static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) -{ - LOGI("NativeWindowCreated: %p -- %p\n", activity, window); - engine_set_window((struct engine*)activity->instance, window); -} - -static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) -{ - LOGI("NativeWindowDestroyed: %p -- %p\n", activity, window); - engine_set_window((struct engine*)activity->instance, NULL); -} - -static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) -{ - LOGI("InputQueueCreated: %p -- %p\n", activity, queue); - engine_set_input((struct engine*)activity->instance, queue); -} - -static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) -{ - LOGI("InputQueueDestroyed: %p -- %p\n", activity, queue); - engine_set_input((struct engine*)activity->instance, NULL); -} - -void ANativeActivity_onCreate(ANativeActivity* activity, - void* savedState, size_t savedStateSize) -{ - LOGI("Creating: %p\n", activity); - activity->callbacks->onDestroy = onDestroy; - activity->callbacks->onStart = onStart; - activity->callbacks->onResume = onResume; - activity->callbacks->onSaveInstanceState = onSaveInstanceState; - activity->callbacks->onPause = onPause; - activity->callbacks->onStop = onStop; - activity->callbacks->onLowMemory = onLowMemory; - activity->callbacks->onWindowFocusChanged = onWindowFocusChanged; - activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; - activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; - activity->callbacks->onInputQueueCreated = onInputQueueCreated; - activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; - - activity->instance = engine_create(activity); -}