From d76b67c340d1564abf8d14d976fdaf83bf2b3320 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Tue, 13 Jul 2010 17:48:30 -0700 Subject: [PATCH] IME events are now dispatched to native applications. And also: - APIs to show and hide the IME, and control its interaction with the app. - APIs to tell the app when its window resizes and needs to be redrawn. - API to tell the app the content rectangle of its window (to layout around the IME or status bar). There is still a problem with IME interaction -- we need a way for the app to deliver events to the IME before it handles them, so that for example the back key will close the IME instead of finishing the app. Change-Id: I37b75fc2ec533750ef36ca3aedd2f0cc0b5813cd --- api/current.xml | 66 +++- core/java/android/app/NativeActivity.java | 101 +++++- .../service/wallpaper/WallpaperService.java | 47 ++- core/java/android/view/SurfaceHolder.java | 17 + core/java/android/view/SurfaceView.java | 37 +- core/java/android/view/ViewRoot.java | 14 +- core/java/android/view/Window.java | 2 +- .../internal/view/RootViewSurfaceTaker.java | 2 +- core/jni/android_app_NativeActivity.cpp | 343 ++++++++++++++++-- .../android_app_NativeActivity.h | 60 +++ include/ui/Input.h | 4 +- include/ui/InputTransport.h | 26 -- libs/ui/InputTransport.cpp | 19 - native/android/input.cpp | 58 +-- native/android/native_activity.cpp | 8 + native/glue/threaded_app/threaded_app.c | 61 +++- native/include/android/native_activity.h | 42 +++ native/include/android_glue/threaded_app.h | 32 ++ .../internal/policy/impl/PhoneWindow.java | 6 +- 19 files changed, 769 insertions(+), 176 deletions(-) diff --git a/api/current.xml b/api/current.xml index d32a10809dc81..9e626373c84d2 100644 --- a/api/current.xml +++ b/api/current.xml @@ -26353,7 +26353,9 @@ > - + + + + + + + + + + + + + + + + + + + + + - + #include +#include #include #include @@ -33,6 +34,9 @@ #include "android_view_InputChannel.h" #include "android_view_KeyEvent.h" +//#define LOG_TRACE(...) +#define LOG_TRACE(...) LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__) + namespace android { @@ -42,6 +46,8 @@ static struct { jmethodID dispatchUnhandledKeyEvent; jmethodID setWindowFlags; jmethodID setWindowFormat; + jmethodID showIme; + jmethodID hideIme; } gNativeActivityClassInfo; // ------------------------------------------------------------------------ @@ -56,6 +62,8 @@ enum { CMD_DEF_KEY = 1, CMD_SET_WINDOW_FORMAT, CMD_SET_WINDOW_FLAGS, + CMD_SHOW_SOFT_INPUT, + CMD_HIDE_SOFT_INPUT, }; static void write_work(int fd, int32_t cmd, int32_t arg1=0, int32_t arg2=0) { @@ -64,6 +72,8 @@ static void write_work(int fd, int32_t cmd, int32_t arg1=0, int32_t arg2=0) { work.arg1 = arg1; work.arg2 = arg2; + LOG_TRACE("write_work: cmd=%d", cmd); + restart: int res = write(fd, &work, sizeof(work)); if (res < 0 && errno == EINTR) { @@ -88,43 +98,177 @@ static bool read_work(int fd, ActivityWork* outWork) { // ------------------------------------------------------------------------ -/* - * Specialized input queue that allows unhandled key events to be dispatched - * back to the native activity's Java framework code. - */ -struct MyInputQueue : AInputQueue { - explicit MyInputQueue(const android::sp& channel, int workWrite) - : AInputQueue(channel), mWorkWrite(workWrite) { +} // namespace android + +using namespace android; + +AInputQueue::AInputQueue(const sp& channel, int workWrite) : + mWorkWrite(workWrite), mConsumer(channel) { + int msgpipe[2]; + if (pipe(msgpipe)) { + LOGW("could not create pipe: %s", strerror(errno)); + mDispatchKeyRead = mDispatchKeyWrite = -1; + } else { + mDispatchKeyRead = msgpipe[0]; + mDispatchKeyWrite = msgpipe[1]; + int result = fcntl(mDispatchKeyRead, F_SETFL, O_NONBLOCK); + SLOGW_IF(result != 0, "Could not make AInputQueue read pipe " + "non-blocking: %s", strerror(errno)); + result = fcntl(mDispatchKeyWrite, F_SETFL, O_NONBLOCK); + SLOGW_IF(result != 0, "Could not make AInputQueue write pipe " + "non-blocking: %s", strerror(errno)); } +} + +AInputQueue::~AInputQueue() { + close(mDispatchKeyRead); + close(mDispatchKeyWrite); +} + +void AInputQueue::attachLooper(ALooper* looper, ALooper_callbackFunc* callback, void* data) { + mPollLoop = static_cast(looper); + mPollLoop->setLooperCallback(mConsumer.getChannel()->getReceivePipeFd(), + POLLIN, callback, data); + mPollLoop->setLooperCallback(mDispatchKeyRead, + POLLIN, callback, data); +} + +void AInputQueue::detachLooper() { + mPollLoop->removeCallback(mConsumer.getChannel()->getReceivePipeFd()); + mPollLoop->removeCallback(mDispatchKeyRead); +} + +int32_t AInputQueue::hasEvents() { + struct pollfd pfd[2]; + + pfd[0].fd = mConsumer.getChannel()->getReceivePipeFd(); + pfd[0].events = POLLIN; + pfd[0].revents = 0; + pfd[1].fd = mDispatchKeyRead; + pfd[0].events = POLLIN; + pfd[0].revents = 0; - virtual void doDefaultKey(android::KeyEvent* keyEvent) { + int nfd = poll(pfd, 2, 0); + if (nfd <= 0) return 0; + return (pfd[0].revents == POLLIN || pfd[1].revents == POLLIN) ? 1 : -1; +} + +int32_t AInputQueue::getEvent(AInputEvent** outEvent) { + *outEvent = NULL; + + char byteread; + ssize_t nRead = read(mDispatchKeyRead, &byteread, 1); + if (nRead == 1) { mLock.lock(); - LOGI("Default key: pending=%d write=%d\n", mPendingKeys.size(), mWorkWrite); - if (mPendingKeys.size() <= 0 && mWorkWrite >= 0) { - write_work(mWorkWrite, CMD_DEF_KEY); - } - mPendingKeys.add(keyEvent); - mLock.unlock(); - } - - KeyEvent* getNextEvent() { - KeyEvent* event = NULL; - - mLock.lock(); - if (mPendingKeys.size() > 0) { - event = mPendingKeys[0]; - mPendingKeys.removeAt(0); + if (mDispatchingKeys.size() > 0) { + KeyEvent* kevent = mDispatchingKeys[0]; + *outEvent = kevent; + mDispatchingKeys.removeAt(0); + mDeliveringKeys.add(kevent); } mLock.unlock(); - - return event; + if (*outEvent != NULL) { + return 0; + } } - int mWorkWrite; + int32_t res = mConsumer.receiveDispatchSignal(); + if (res != android::OK) { + LOGE("channel '%s' ~ Failed to receive dispatch signal. status=%d", + mConsumer.getChannel()->getName().string(), res); + return -1; + } + + InputEvent* myEvent = NULL; + res = mConsumer.consume(&mInputEventFactory, &myEvent); + if (res != android::OK) { + LOGW("channel '%s' ~ Failed to consume input event. status=%d", + mConsumer.getChannel()->getName().string(), res); + mConsumer.sendFinishedSignal(); + return -1; + } + + *outEvent = myEvent; + return 0; +} + +void AInputQueue::finishEvent(AInputEvent* event, bool handled) { + bool needFinished = true; + + if (!handled && ((InputEvent*)event)->getType() == INPUT_EVENT_TYPE_KEY + && ((KeyEvent*)event)->hasDefaultAction()) { + // The app didn't handle this, but it may have a default action + // associated with it. We need to hand this back to Java to be + // executed. + doDefaultKey((KeyEvent*)event); + needFinished = false; + } + + const size_t N = mDeliveringKeys.size(); + for (size_t i=0; i mPendingKeys; -}; + if (needFinished) { + int32_t res = mConsumer.sendFinishedSignal(); + if (res != android::OK) { + LOGW("Failed to send finished signal on channel '%s'. status=%d", + mConsumer.getChannel()->getName().string(), res); + } + } +} + +void AInputQueue::dispatchEvent(android::KeyEvent* event) { + mLock.lock(); + LOG_TRACE("dispatchEvent: dispatching=%d write=%d\n", mDispatchingKeys.size(), + mDispatchKeyWrite); + mDispatchingKeys.add(event); + mLock.unlock(); + +restart: + char dummy = 0; + int res = write(mDispatchKeyWrite, &dummy, sizeof(dummy)); + if (res < 0 && errno == EINTR) { + goto restart; + } + + if (res == sizeof(dummy)) return; + + if (res < 0) LOGW("Failed writing to dispatch fd: %s", strerror(errno)); + else LOGW("Truncated writing to dispatch fd: %d", res); +} + +KeyEvent* AInputQueue::consumeUnhandledEvent() { + KeyEvent* event = NULL; + + mLock.lock(); + if (mPendingKeys.size() > 0) { + event = mPendingKeys[0]; + mPendingKeys.removeAt(0); + } + mLock.unlock(); + + LOG_TRACE("consumeUnhandledEvent: KeyEvent=%p", event); + + return event; +} + +void AInputQueue::doDefaultKey(KeyEvent* keyEvent) { + mLock.lock(); + LOG_TRACE("Default key: pending=%d write=%d\n", mPendingKeys.size(), mWorkWrite); + if (mPendingKeys.size() <= 0 && mWorkWrite >= 0) { + write_work(mWorkWrite, CMD_DEF_KEY); + } + mPendingKeys.add(keyEvent); + mLock.unlock(); +} + +namespace android { // ------------------------------------------------------------------------ @@ -133,8 +277,8 @@ struct MyInputQueue : AInputQueue { */ struct NativeCode : public ANativeActivity { NativeCode(void* _dlhandle, ANativeActivity_createFunc* _createFunc) { - memset((ANativeActivity*)this, sizeof(ANativeActivity), 0); - memset(&callbacks, sizeof(callbacks), 0); + memset((ANativeActivity*)this, 0, sizeof(ANativeActivity)); + memset(&callbacks, 0, sizeof(callbacks)); dlhandle = _dlhandle; createActivityFunc = _createFunc; nativeWindow = NULL; @@ -188,7 +332,7 @@ struct NativeCode : public ANativeActivity { sp ic = android_view_InputChannel_getInputChannel(env, _channel); if (ic != NULL) { - nativeInputQueue = new MyInputQueue(ic, mainWorkWrite); + nativeInputQueue = new AInputQueue(ic, mainWorkWrite); if (nativeInputQueue->getConsumer().initialize() != android::OK) { delete nativeInputQueue; nativeInputQueue = NULL; @@ -210,8 +354,11 @@ struct NativeCode : public ANativeActivity { String8 externalDataPath; sp nativeWindow; + int32_t lastWindowWidth; + int32_t lastWindowHeight; + jobject inputChannel; - struct MyInputQueue* nativeInputQueue; + struct AInputQueue* nativeInputQueue; // These are used to wake up the main thread to process work. int mainWorkRead; @@ -231,6 +378,18 @@ void android_NativeActivity_setWindowFlags( write_work(code->mainWorkWrite, CMD_SET_WINDOW_FLAGS, values, mask); } +void android_NativeActivity_showSoftInput( + ANativeActivity* activity, int32_t flags) { + NativeCode* code = static_cast(activity); + write_work(code->mainWorkWrite, CMD_SHOW_SOFT_INPUT, flags); +} + +void android_NativeActivity_hideSoftInput( + ANativeActivity* activity, int32_t flags) { + NativeCode* code = static_cast(activity); + write_work(code->mainWorkWrite, CMD_HIDE_SOFT_INPUT, flags); +} + // ------------------------------------------------------------------------ /* @@ -246,10 +405,13 @@ static bool mainWorkCallback(int fd, int events, void* data) { if (!read_work(code->mainWorkRead, &work)) { return true; } + + LOG_TRACE("mainWorkCallback: cmd=%d", work.cmd); + switch (work.cmd) { case CMD_DEF_KEY: { KeyEvent* keyEvent; - while ((keyEvent=code->nativeInputQueue->getNextEvent()) != NULL) { + while ((keyEvent=code->nativeInputQueue->consumeUnhandledEvent()) != NULL) { jobject inputEventObj = android_view_KeyEvent_fromNative( code->env, keyEvent); code->env->CallVoidMethod(code->clazz, @@ -269,6 +431,14 @@ static bool mainWorkCallback(int fd, int events, void* data) { code->env->CallVoidMethod(code->clazz, gNativeActivityClassInfo.setWindowFlags, work.arg1, work.arg2); } break; + case CMD_SHOW_SOFT_INPUT: { + code->env->CallVoidMethod(code->clazz, + gNativeActivityClassInfo.showIme, work.arg1); + } break; + case CMD_HIDE_SOFT_INPUT: { + code->env->CallVoidMethod(code->clazz, + gNativeActivityClassInfo.hideIme, work.arg1); + } break; default: LOGW("Unknown work command: %d", work.cmd); break; @@ -283,6 +453,8 @@ static jint loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jobject messageQueue, jstring internalDataDir, jstring externalDataDir, int sdkVersion) { + LOG_TRACE("loadNativeCode_native"); + const char* pathStr = env->GetStringUTFChars(path, NULL); NativeCode* code = NULL; @@ -314,6 +486,12 @@ loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jobject messageQ } code->mainWorkRead = msgpipe[0]; code->mainWorkWrite = msgpipe[1]; + int result = fcntl(code->mainWorkRead, F_SETFL, O_NONBLOCK); + SLOGW_IF(result != 0, "Could not make main work read pipe " + "non-blocking: %s", strerror(errno)); + result = fcntl(code->mainWorkWrite, F_SETFL, O_NONBLOCK); + SLOGW_IF(result != 0, "Could not make main work write pipe " + "non-blocking: %s", strerror(errno)); code->pollLoop->setCallback(code->mainWorkRead, POLLIN, mainWorkCallback, code); code->ANativeActivity::callbacks = &code->callbacks; @@ -346,6 +524,7 @@ loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jobject messageQ static void unloadNativeCode_native(JNIEnv* env, jobject clazz, jint handle) { + LOG_TRACE("unloadNativeCode_native"); if (handle != 0) { NativeCode* code = (NativeCode*)handle; delete code; @@ -355,6 +534,7 @@ unloadNativeCode_native(JNIEnv* env, jobject clazz, jint handle) static void onStart_native(JNIEnv* env, jobject clazz, jint handle) { + LOG_TRACE("onStart_native"); if (handle != 0) { NativeCode* code = (NativeCode*)handle; if (code->callbacks.onStart != NULL) { @@ -366,6 +546,7 @@ onStart_native(JNIEnv* env, jobject clazz, jint handle) static void onResume_native(JNIEnv* env, jobject clazz, jint handle) { + LOG_TRACE("onResume_native"); if (handle != 0) { NativeCode* code = (NativeCode*)handle; if (code->callbacks.onResume != NULL) { @@ -377,6 +558,7 @@ onResume_native(JNIEnv* env, jobject clazz, jint handle) static void onSaveInstanceState_native(JNIEnv* env, jobject clazz, jint handle) { + LOG_TRACE("onSaveInstanceState_native"); if (handle != 0) { NativeCode* code = (NativeCode*)handle; if (code->callbacks.onSaveInstanceState != NULL) { @@ -389,6 +571,7 @@ onSaveInstanceState_native(JNIEnv* env, jobject clazz, jint handle) static void onPause_native(JNIEnv* env, jobject clazz, jint handle) { + LOG_TRACE("onPause_native"); if (handle != 0) { NativeCode* code = (NativeCode*)handle; if (code->callbacks.onPause != NULL) { @@ -400,6 +583,7 @@ onPause_native(JNIEnv* env, jobject clazz, jint handle) static void onStop_native(JNIEnv* env, jobject clazz, jint handle) { + LOG_TRACE("onStop_native"); if (handle != 0) { NativeCode* code = (NativeCode*)handle; if (code->callbacks.onStop != NULL) { @@ -411,6 +595,7 @@ onStop_native(JNIEnv* env, jobject clazz, jint handle) static void onLowMemory_native(JNIEnv* env, jobject clazz, jint handle) { + LOG_TRACE("onLowMemory_native"); if (handle != 0) { NativeCode* code = (NativeCode*)handle; if (code->callbacks.onLowMemory != NULL) { @@ -422,6 +607,7 @@ onLowMemory_native(JNIEnv* env, jobject clazz, jint handle) static void onWindowFocusChanged_native(JNIEnv* env, jobject clazz, jint handle, jboolean focused) { + LOG_TRACE("onWindowFocusChanged_native"); if (handle != 0) { NativeCode* code = (NativeCode*)handle; if (code->callbacks.onWindowFocusChanged != NULL) { @@ -433,6 +619,7 @@ onWindowFocusChanged_native(JNIEnv* env, jobject clazz, jint handle, jboolean fo static void onSurfaceCreated_native(JNIEnv* env, jobject clazz, jint handle, jobject surface) { + LOG_TRACE("onSurfaceCreated_native"); if (handle != 0) { NativeCode* code = (NativeCode*)handle; code->setSurface(surface); @@ -443,10 +630,17 @@ onSurfaceCreated_native(JNIEnv* env, jobject clazz, jint handle, jobject surface } } +static int32_t getWindowProp(ANativeWindow* window, int what) { + int value; + int res = window->query(window, what, &value); + return res < 0 ? res : value; +} + static void onSurfaceChanged_native(JNIEnv* env, jobject clazz, jint handle, jobject surface, jint format, jint width, jint height) { + LOG_TRACE("onSurfaceChanged_native"); if (handle != 0) { NativeCode* code = (NativeCode*)handle; sp oldNativeWindow = code->nativeWindow; @@ -456,10 +650,41 @@ onSurfaceChanged_native(JNIEnv* env, jobject clazz, jint handle, jobject surface code->callbacks.onNativeWindowDestroyed(code, oldNativeWindow.get()); } - if (code->nativeWindow != NULL && code->callbacks.onNativeWindowCreated != NULL) { - code->callbacks.onNativeWindowCreated(code, - code->nativeWindow.get()); + if (code->nativeWindow != NULL) { + if (code->callbacks.onNativeWindowCreated != NULL) { + code->callbacks.onNativeWindowCreated(code, + code->nativeWindow.get()); + } + code->lastWindowWidth = getWindowProp(code->nativeWindow.get(), + NATIVE_WINDOW_WIDTH); + code->lastWindowHeight = getWindowProp(code->nativeWindow.get(), + NATIVE_WINDOW_HEIGHT); } + } else { + // Maybe it resized? + int32_t newWidth = getWindowProp(code->nativeWindow.get(), + NATIVE_WINDOW_WIDTH); + int32_t newHeight = getWindowProp(code->nativeWindow.get(), + NATIVE_WINDOW_HEIGHT); + if (newWidth != code->lastWindowWidth + || newHeight != code->lastWindowHeight) { + if (code->callbacks.onNativeWindowResized != NULL) { + code->callbacks.onNativeWindowResized(code, + code->nativeWindow.get()); + } + } + } + } +} + +static void +onSurfaceRedrawNeeded_native(JNIEnv* env, jobject clazz, jint handle) +{ + LOG_TRACE("onSurfaceRedrawNeeded_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->nativeWindow != NULL && code->callbacks.onNativeWindowRedrawNeeded != NULL) { + code->callbacks.onNativeWindowRedrawNeeded(code, code->nativeWindow.get()); } } } @@ -467,6 +692,7 @@ onSurfaceChanged_native(JNIEnv* env, jobject clazz, jint handle, jobject surface static void onSurfaceDestroyed_native(JNIEnv* env, jobject clazz, jint handle, jobject surface) { + LOG_TRACE("onSurfaceDestroyed_native"); if (handle != 0) { NativeCode* code = (NativeCode*)handle; if (code->nativeWindow != NULL && code->callbacks.onNativeWindowDestroyed != NULL) { @@ -480,6 +706,7 @@ onSurfaceDestroyed_native(JNIEnv* env, jobject clazz, jint handle, jobject surfa static void onInputChannelCreated_native(JNIEnv* env, jobject clazz, jint handle, jobject channel) { + LOG_TRACE("onInputChannelCreated_native"); if (handle != 0) { NativeCode* code = (NativeCode*)handle; status_t err = code->setInputChannel(channel); @@ -498,6 +725,7 @@ onInputChannelCreated_native(JNIEnv* env, jobject clazz, jint handle, jobject ch static void onInputChannelDestroyed_native(JNIEnv* env, jobject clazz, jint handle, jobject channel) { + LOG_TRACE("onInputChannelDestroyed_native"); if (handle != 0) { NativeCode* code = (NativeCode*)handle; if (code->nativeInputQueue != NULL @@ -509,6 +737,38 @@ onInputChannelDestroyed_native(JNIEnv* env, jobject clazz, jint handle, jobject } } +static void +onContentRectChanged_native(JNIEnv* env, jobject clazz, jint handle, + jint x, jint y, jint w, jint h) +{ + LOG_TRACE("onContentRectChanged_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->callbacks.onContentRectChanged != NULL) { + ARect rect; + rect.left = x; + rect.top = y; + rect.right = x+w; + rect.bottom = y+h; + code->callbacks.onContentRectChanged(code, &rect); + } + } +} + +static void +dispatchKeyEvent_native(JNIEnv* env, jobject clazz, jint handle, jobject eventObj) +{ + LOG_TRACE("dispatchKeyEvent_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->nativeInputQueue != NULL) { + KeyEvent* event = new KeyEvent(); + android_view_KeyEvent_toNative(env, eventObj, INPUT_EVENT_NATURE_KEY, event); + code->nativeInputQueue->dispatchEvent(event); + } + } +} + static const JNINativeMethod g_methods[] = { { "loadNativeCode", "(Ljava/lang/String;Landroid/os/MessageQueue;Ljava/lang/String;Ljava/lang/String;I)I", (void*)loadNativeCode_native }, @@ -522,9 +782,12 @@ static const JNINativeMethod g_methods[] = { { "onWindowFocusChangedNative", "(IZ)V", (void*)onWindowFocusChanged_native }, { "onSurfaceCreatedNative", "(ILandroid/view/Surface;)V", (void*)onSurfaceCreated_native }, { "onSurfaceChangedNative", "(ILandroid/view/Surface;III)V", (void*)onSurfaceChanged_native }, + { "onSurfaceRedrawNeededNative", "(ILandroid/view/Surface;)V", (void*)onSurfaceRedrawNeeded_native }, { "onSurfaceDestroyedNative", "(I)V", (void*)onSurfaceDestroyed_native }, { "onInputChannelCreatedNative", "(ILandroid/view/InputChannel;)V", (void*)onInputChannelCreated_native }, { "onInputChannelDestroyedNative", "(ILandroid/view/InputChannel;)V", (void*)onInputChannelDestroyed_native }, + { "onContentRectChangedNative", "(IIIII)V", (void*)onContentRectChanged_native }, + { "dispatchKeyEventNative", "(ILandroid/view/KeyEvent;)V", (void*)dispatchKeyEvent_native }, }; static const char* const kNativeActivityPathName = "android/app/NativeActivity"; @@ -554,6 +817,12 @@ int register_android_app_NativeActivity(JNIEnv* env) GET_METHOD_ID(gNativeActivityClassInfo.setWindowFormat, gNativeActivityClassInfo.clazz, "setWindowFormat", "(I)V"); + GET_METHOD_ID(gNativeActivityClassInfo.showIme, + gNativeActivityClassInfo.clazz, + "showIme", "(I)V"); + GET_METHOD_ID(gNativeActivityClassInfo.hideIme, + gNativeActivityClassInfo.clazz, + "hideIme", "(I)V"); return AndroidRuntime::registerNativeMethods( env, kNativeActivityPathName, diff --git a/include/android_runtime/android_app_NativeActivity.h b/include/android_runtime/android_app_NativeActivity.h index f80832811b95b..d7a9a2c3be067 100644 --- a/include/android_runtime/android_app_NativeActivity.h +++ b/include/android_runtime/android_app_NativeActivity.h @@ -17,6 +17,8 @@ #ifndef _ANDROID_APP_NATIVEACTIVITY_H #define _ANDROID_APP_NATIVEACTIVITY_H +#include + #include #include "jni.h" @@ -29,7 +31,65 @@ extern void android_NativeActivity_setWindowFormat( extern void android_NativeActivity_setWindowFlags( ANativeActivity* activity, int32_t values, int32_t mask); +extern void android_NativeActivity_showSoftInput( + ANativeActivity* activity, int32_t flags); + +extern void android_NativeActivity_hideSoftInput( + ANativeActivity* activity, int32_t flags); } // namespace android + +/* + * NDK input queue API. + */ +struct AInputQueue { +public: + /* Creates a consumer associated with an input channel. */ + explicit AInputQueue(const android::sp& channel, int workWrite); + + /* Destroys the consumer and releases its input channel. */ + ~AInputQueue(); + + void attachLooper(ALooper* looper, ALooper_callbackFunc* callback, void* data); + + void detachLooper(); + + int32_t hasEvents(); + + int32_t getEvent(AInputEvent** outEvent); + + void finishEvent(AInputEvent* event, bool handled); + + + // ---------------------------------------------------------- + + inline android::InputConsumer& getConsumer() { return mConsumer; } + + void dispatchEvent(android::KeyEvent* event); + + android::KeyEvent* consumeUnhandledEvent(); + + int mWorkWrite; + +private: + void doDefaultKey(android::KeyEvent* keyEvent); + + android::InputConsumer mConsumer; + android::PreallocatedInputEventFactory mInputEventFactory; + android::sp mPollLoop; + + int mDispatchKeyRead; + int mDispatchKeyWrite; + + // This is only touched by the event reader thread. It is the current + // key events that came out of the mDispatchingKeys list and are now + //Êdelivered to the app. + android::Vector mDeliveringKeys; + + android::Mutex mLock; + android::Vector mPendingKeys; + android::Vector mDispatchingKeys; +}; + #endif // _ANDROID_APP_NATIVEACTIVITY_H diff --git a/include/ui/Input.h b/include/ui/Input.h index a2e0ba06a2c24..a7d23d48548db 100644 --- a/include/ui/Input.h +++ b/include/ui/Input.h @@ -43,7 +43,9 @@ enum { /* * Declare a concrete type for the NDK's input event forward declaration. */ -struct AInputEvent { }; +struct AInputEvent { + virtual ~AInputEvent() { } +}; namespace android { diff --git a/include/ui/InputTransport.h b/include/ui/InputTransport.h index 11714d536285d..226d1d5a4b20b 100644 --- a/include/ui/InputTransport.h +++ b/include/ui/InputTransport.h @@ -331,30 +331,4 @@ private: } // namespace android -/* - * NDK input queue API. - */ -struct AInputQueue { -public: - /* Creates a consumer associated with an input channel. */ - explicit AInputQueue(const android::sp& channel); - - /* Destroys the consumer and releases its input channel. */ - virtual ~AInputQueue(); - - inline android::InputConsumer& getConsumer() { return mConsumer; } - - android::status_t consume(android::InputEvent** event); - - void setPollLoop(const android::sp& pollLoop) { mPollLoop = pollLoop; } - const android::sp getPollLoop() const { return mPollLoop; } - - virtual void doDefaultKey(android::KeyEvent* keyEvent) = 0; - -private: - android::InputConsumer mConsumer; - android::PreallocatedInputEventFactory mInputEventFactory; - android::sp mPollLoop; -}; - #endif // _UI_INPUT_TRANSPORT_H diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp index 25def3c18ca18..fc83e316dee2d 100644 --- a/libs/ui/InputTransport.cpp +++ b/libs/ui/InputTransport.cpp @@ -690,22 +690,3 @@ void InputConsumer::populateMotionEvent(MotionEvent* motionEvent) const { } } // namespace android - -// --- AInputQueue --- - -using android::InputEvent; -using android::InputChannel; -using android::InputConsumer; -using android::sp; -using android::status_t; - -AInputQueue::AInputQueue(const sp& channel) : - mConsumer(channel) { -} - -AInputQueue::~AInputQueue() { -} - -status_t AInputQueue::consume(InputEvent** event) { - return mConsumer.consume(&mInputEventFactory, event); -} diff --git a/native/android/input.cpp b/native/android/input.cpp index 89d53e2790571..a4dde510b1f6f 100644 --- a/native/android/input.cpp +++ b/native/android/input.cpp @@ -22,6 +22,8 @@ #include #include +#include + #include using android::InputEvent; @@ -187,65 +189,21 @@ float AMotionEvent_getHistoricalSize(AInputEvent* motion_event, size_t pointer_i void AInputQueue_attachLooper(AInputQueue* queue, ALooper* looper, ALooper_callbackFunc* callback, void* data) { - queue->setPollLoop(static_cast(looper)); - ALooper_addFd(looper, queue->getConsumer().getChannel()->getReceivePipeFd(), - POLLIN, callback, data); + queue->attachLooper(looper, callback, data); } void AInputQueue_detachLooper(AInputQueue* queue) { - queue->getPollLoop()->removeCallback( - queue->getConsumer().getChannel()->getReceivePipeFd()); + queue->detachLooper(); } int AInputQueue_hasEvents(AInputQueue* queue) { - struct pollfd pfd; - - pfd.fd = queue->getConsumer().getChannel()->getReceivePipeFd(); - pfd.events = POLLIN; - pfd.revents = 0; - - int nfd = poll(&pfd, 1, 0); - if (nfd <= 0) return nfd; - return pfd.revents == POLLIN ? 1 : -1; + return queue->hasEvents(); } int32_t AInputQueue_getEvent(AInputQueue* queue, AInputEvent** outEvent) { - *outEvent = NULL; - - int32_t res = queue->getConsumer().receiveDispatchSignal(); - if (res != android::OK) { - LOGE("channel '%s' ~ Failed to receive dispatch signal. status=%d", - queue->getConsumer().getChannel()->getName().string(), res); - return -1; - } - - InputEvent* myEvent = NULL; - res = queue->consume(&myEvent); - if (res != android::OK) { - LOGW("channel '%s' ~ Failed to consume input event. status=%d", - queue->getConsumer().getChannel()->getName().string(), res); - queue->getConsumer().sendFinishedSignal(); - return -1; - } - - *outEvent = myEvent; - return 0; + return queue->getEvent(outEvent); } -void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, - int handled) { - if (!handled && ((InputEvent*)event)->getType() == INPUT_EVENT_TYPE_KEY - && ((KeyEvent*)event)->hasDefaultAction()) { - // The app didn't handle this, but it may have a default action - // associated with it. We need to hand this back to Java to be - // executed. - queue->doDefaultKey((KeyEvent*)event); - return; - } - - int32_t res = queue->getConsumer().sendFinishedSignal(); - if (res != android::OK) { - LOGW("Failed to send finished signal on channel '%s'. status=%d", - queue->getConsumer().getChannel()->getName().string(), res); - } +void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled) { + queue->finishEvent(event, handled != 0); } diff --git a/native/android/native_activity.cpp b/native/android/native_activity.cpp index 509cc3310aa25..0c6823aef1b7d 100644 --- a/native/android/native_activity.cpp +++ b/native/android/native_activity.cpp @@ -29,3 +29,11 @@ void ANativeActivity_setWindowFlags(ANativeActivity* activity, uint32_t addFlags, uint32_t removeFlags) { android_NativeActivity_setWindowFlags(activity, addFlags, addFlags|removeFlags); } + +void ANativeActivity_showSoftInput(ANativeActivity* activity, uint32_t flags) { + android_NativeActivity_showSoftInput(activity, flags); +} + +void ANativeActivity_hideSoftInput(ANativeActivity* activity, uint32_t flags) { + android_NativeActivity_hideSoftInput(activity, flags); +} diff --git a/native/glue/threaded_app/threaded_app.c b/native/glue/threaded_app/threaded_app.c index 2411e93b826e4..452c735236e5f 100644 --- a/native/glue/threaded_app/threaded_app.c +++ b/native/glue/threaded_app/threaded_app.c @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -75,6 +76,19 @@ int32_t android_app_exec_cmd(struct android_app* android_app, int8_t cmd) { pthread_cond_broadcast(&android_app->cond); pthread_mutex_unlock(&android_app->mutex); break; + + case APP_CMD_WINDOW_REDRAW_NEEDED: + LOGI("APP_CMD_WINDOW_REDRAW_NEEDED\n"); + pthread_mutex_lock(&android_app->mutex); + android_app->redrawNeeded = 0; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_CONTENT_RECT_CHANGED: + LOGI("APP_CMD_CONTENT_RECT_CHANGED\n"); + android_app->contentRect = android_app->pendingContentRect; + break; case APP_CMD_DESTROY: LOGI("APP_CMD_DESTROY\n"); @@ -133,6 +147,12 @@ static struct android_app* android_app_create(ANativeActivity* activity) { } android_app->msgread = msgpipe[0]; android_app->msgwrite = msgpipe[1]; + int result = fcntl(android_app->msgread, F_SETFL, O_NONBLOCK); + if (result != 0) LOGW("Could not make message read pipe " + "non-blocking: %s", strerror(errno)); + result = fcntl(android_app->msgwrite, F_SETFL, O_NONBLOCK); + if (result != 0) LOGW("Could not make message write pipe " + "non-blocking: %s", strerror(errno)); pthread_attr_t attr; pthread_attr_init(&attr); @@ -184,6 +204,23 @@ static void android_app_set_activity_state(struct android_app* android_app, int8 pthread_mutex_unlock(&android_app->mutex); } +static void android_app_wait_redraw(struct android_app* android_app) { + pthread_mutex_lock(&android_app->mutex); + android_app->redrawNeeded = 1; + android_app_write_cmd(android_app, APP_CMD_WINDOW_REDRAW_NEEDED); + while (android_app->redrawNeeded) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); +} + +static void android_app_set_content_rect(struct android_app* android_app, const ARect* rect) { + pthread_mutex_lock(&android_app->mutex); + android_app->pendingContentRect = *rect; + android_app_write_cmd(android_app, APP_CMD_CONTENT_RECT_CHANGED); + pthread_mutex_unlock(&android_app->mutex); +} + static void android_app_free(struct android_app* android_app) { pthread_mutex_lock(&android_app->mutex); android_app_write_cmd(android_app, APP_CMD_DESTROY); @@ -231,6 +268,8 @@ static void onStop(ANativeActivity* activity) { static void onLowMemory(ANativeActivity* activity) { LOGI("LowMemory: %p\n", activity); + android_app_write_cmd((struct android_app*)activity->instance, + APP_CMD_LOW_MEMORY); } static void onWindowFocusChanged(ANativeActivity* activity, int focused) { @@ -244,6 +283,23 @@ static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* wind android_app_set_window((struct android_app*)activity->instance, window); } +static void onNativeWindowResized(ANativeActivity* activity, ANativeWindow* window) { + LOGI("NativeWindowResized: %p -- %p\n", activity, window); + android_app_write_cmd((struct android_app*)activity->instance, + APP_CMD_WINDOW_RESIZED); +} + +static void onNativeWindowRedrawNeeded(ANativeActivity* activity, ANativeWindow* window) { + LOGI("NativeWindowRedrawNeeded: %p -- %p\n", activity, window); + android_app_wait_redraw((struct android_app*)activity->instance); +} + +static void onContentRectChanged(ANativeActivity* activity, const ARect* rect) { + LOGI("ContentRectChanged: %p -- (%d,%d)-(%d,%d)\n", activity, rect->left, + rect->top, rect->right, rect->bottom); + android_app_set_content_rect((struct android_app*)activity->instance, rect); +} + static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { LOGI("NativeWindowDestroyed: %p -- %p\n", activity, window); android_app_set_window((struct android_app*)activity->instance, NULL); @@ -268,12 +324,15 @@ void ANativeActivity_onCreate(ANativeActivity* activity, 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->onNativeWindowResized = onNativeWindowResized; + activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded; activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; activity->callbacks->onInputQueueCreated = onInputQueueCreated; activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; + activity->callbacks->onContentRectChanged = onContentRectChanged; + activity->callbacks->onLowMemory = onLowMemory; activity->instance = android_app_create(activity); } diff --git a/native/include/android/native_activity.h b/native/include/android/native_activity.h index d0ff05277330b..ea6f05fbced88 100644 --- a/native/include/android/native_activity.h +++ b/native/include/android/native_activity.h @@ -146,6 +146,21 @@ typedef struct ANativeActivityCallbacks { */ void (*onNativeWindowCreated)(ANativeActivity* activity, ANativeWindow* window); + /** + * The drawing window for this native activity has been resized. You should + * retrieve the new size from the window and ensure that your rendering in + * it now matches. + */ + void (*onNativeWindowResized)(ANativeActivity* activity, ANativeWindow* window); + + /** + * The drawing window for this native activity needs to be redrawn. To avoid + * transient artifacts during screen changes (such resizing after rotation), + * applications should not return from this function until they have finished + * drawing their window in its current state. + */ + void (*onNativeWindowRedrawNeeded)(ANativeActivity* activity, ANativeWindow* window); + /** * The drawing window for this native activity is going to be destroyed. * You MUST ensure that you do not touch the window object after returning @@ -169,6 +184,11 @@ typedef struct ANativeActivityCallbacks { */ void (*onInputQueueDestroyed)(ANativeActivity* activity, AInputQueue* queue); + /** + * The rectangle in the window in which content should be placed has changed. + */ + void (*onContentRectChanged)(ANativeActivity* activity, const ARect* rect); + /** * The system is running low on memory. Use this callback to release * resources you do not need, to help the system avoid killing more @@ -197,6 +217,28 @@ void ANativeActivity_setWindowFormat(ANativeActivity* activity, int32_t format); void ANativeActivity_setWindowFlags(ANativeActivity* activity, uint32_t addFlags, uint32_t removeFlags); +/** + * Flags for ANativeActivity_showSoftInput; see the Java InputMethodManager + * API for documentation. + */ +enum { + ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001, + ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002, +}; + +void ANativeActivity_showSoftInput(ANativeActivity* activity, uint32_t flags); + +/** + * Flags for ANativeActivity_hideSoftInput; see the Java InputMethodManager + * API for documentation. + */ +enum { + ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001, + ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002, +}; + +void ANativeActivity_hideSoftInput(ANativeActivity* activity, uint32_t flags); + #ifdef __cplusplus }; #endif diff --git a/native/include/android_glue/threaded_app.h b/native/include/android_glue/threaded_app.h index adfdbea06aebe..2b58e9c2d1f8c 100644 --- a/native/include/android_glue/threaded_app.h +++ b/native/include/android_glue/threaded_app.h @@ -48,6 +48,10 @@ struct android_app { // When non-NULL, this is the window surface that the app can draw in. ANativeWindow* window; + // Current content rectangle of the window; this is the area where the + // window's content should be placed to be seen by the user. + ARect contentRect; + // 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; @@ -69,8 +73,10 @@ struct android_app { int running; int destroyed; + int redrawNeeded; AInputQueue* pendingInputQueue; ANativeWindow* pendingWindow; + ARect pendingContentRect; }; enum { @@ -104,6 +110,26 @@ enum { */ APP_CMD_WINDOW_CHANGED, + /** + * Command from main thread: the current ANativeWindow has been resized. + * Please redraw with its new size. + */ + APP_CMD_WINDOW_RESIZED, + + /** + * Command from main thread: the system needs that the current ANativeWindow + * be redrawn. You should redraw the window before handing this to + * android_app_exec_cmd() in order to avoid transient drawing glitches. + */ + APP_CMD_WINDOW_REDRAW_NEEDED, + + /** + * Command from main thread: the content area of the window has changed, + * such as from the soft input window being shown or hidden. You can + * find the new content rect in android_app::contentRect. + */ + APP_CMD_CONTENT_RECT_CHANGED, + /** * Command from main thread: the app's activity window has gained * input focus. @@ -116,6 +142,12 @@ enum { */ APP_CMD_LOST_FOCUS, + /** + * Command from main thread: the system is running low on memory. + * Try to reduce your memory use. + */ + APP_CMD_LOW_MEMORY, + /** * Command from main thread: the app's activity has been started. */ diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 56425887a418f..b9232c8b0f351 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -104,7 +104,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // mDecor itself, or a child of mDecor where the contents go. private ViewGroup mContentParent; - SurfaceHolder.Callback mTakeSurfaceCallback; + SurfaceHolder.Callback2 mTakeSurfaceCallback; BaseSurfaceHolder mSurfaceHolder; InputQueue.Callback mTakeInputQueueCallback; @@ -248,7 +248,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } @Override - public void takeSurface(SurfaceHolder.Callback callback) { + public void takeSurface(SurfaceHolder.Callback2 callback) { mTakeSurfaceCallback = callback; } @@ -2038,7 +2038,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } - public android.view.SurfaceHolder.Callback willYouTakeTheSurface() { + public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() { return mFeatureId < 0 ? mTakeSurfaceCallback : null; }