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
This commit is contained in:
Dianne Hackborn
2010-07-13 17:48:30 -07:00
parent fd03582995
commit d76b67c340
19 changed files with 769 additions and 176 deletions

View File

@ -26353,7 +26353,9 @@
>
<implements name="android.view.InputQueue.Callback">
</implements>
<implements name="android.view.SurfaceHolder.Callback">
<implements name="android.view.SurfaceHolder.Callback2">
</implements>
<implements name="android.view.ViewTreeObserver.OnGlobalLayoutListener">
</implements>
<constructor name="NativeActivity"
type="android.app.NativeActivity"
@ -26363,6 +26365,17 @@
visibility="public"
>
</constructor>
<method name="onGlobalLayout"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="onInputQueueCreated"
return="void"
abstract="false"
@ -26434,6 +26447,19 @@
<parameter name="holder" type="android.view.SurfaceHolder">
</parameter>
</method>
<method name="surfaceRedrawNeeded"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="holder" type="android.view.SurfaceHolder">
</parameter>
</method>
<field name="META_DATA_LIB_NAME"
type="java.lang.String"
transient="false"
@ -142938,6 +142964,19 @@
<parameter name="holder" type="android.view.SurfaceHolder">
</parameter>
</method>
<method name="onSurfaceRedrawNeeded"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="holder" type="android.view.SurfaceHolder">
</parameter>
</method>
<method name="onTouchEvent"
return="void"
abstract="false"
@ -179068,6 +179107,29 @@
</parameter>
</method>
</interface>
<interface name="SurfaceHolder.Callback2"
abstract="true"
static="true"
final="false"
deprecated="not deprecated"
visibility="public"
>
<implements name="android.view.SurfaceHolder.Callback">
</implements>
<method name="surfaceRedrawNeeded"
return="void"
abstract="true"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="holder" type="android.view.SurfaceHolder">
</parameter>
</method>
</interface>
<class name="SurfaceView"
extends="android.view.View"
abstract="false"
@ -187657,7 +187719,7 @@
deprecated="not deprecated"
visibility="public"
>
<parameter name="callback" type="android.view.SurfaceHolder.Callback">
<parameter name="callback" type="android.view.SurfaceHolder.Callback2">
</parameter>
</method>
<method name="togglePanel"

View File

@ -2,6 +2,7 @@ package android.app;
import dalvik.system.PathClassLoader;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@ -11,12 +12,16 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.AttributeSet;
import android.view.InputChannel;
import android.view.InputQueue;
import android.view.KeyEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.WindowManager;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.inputmethod.InputMethodManager;
import java.io.File;
@ -24,15 +29,26 @@ import java.io.File;
* Convenience for implementing an activity that will be implemented
* purely in native code. That is, a game (or game-like thing).
*/
public class NativeActivity extends Activity implements SurfaceHolder.Callback,
InputQueue.Callback {
public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
InputQueue.Callback, OnGlobalLayoutListener {
public static final String META_DATA_LIB_NAME = "android.app.lib_name";
private NativeContentView mNativeContentView;
private InputMethodManager mIMM;
private int mNativeHandle;
private InputQueue mCurInputQueue;
private SurfaceHolder mCurSurfaceHolder;
final int[] mLocation = new int[2];
int mLastContentX;
int mLastContentY;
int mLastContentWidth;
int mLastContentHeight;
private boolean mDispatchingUnhandledKey;
private boolean mDestroyed;
private native int loadNativeCode(String path, MessageQueue queue,
@ -49,18 +65,44 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback,
private native void onSurfaceCreatedNative(int handle, Surface surface);
private native void onSurfaceChangedNative(int handle, Surface surface,
int format, int width, int height);
private native void onSurfaceRedrawNeededNative(int handle, Surface surface);
private native void onSurfaceDestroyedNative(int handle);
private native void onInputChannelCreatedNative(int handle, InputChannel channel);
private native void onInputChannelDestroyedNative(int handle, InputChannel channel);
private native void onContentRectChangedNative(int handle, int x, int y, int w, int h);
private native void dispatchKeyEventNative(int handle, KeyEvent event);
static class NativeContentView extends View {
NativeActivity mActivity;
public NativeContentView(Context context) {
super(context);
}
public NativeContentView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
String libname = "main";
ActivityInfo ai;
mIMM = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
getWindow().takeSurface(this);
getWindow().takeInputQueue(this);
getWindow().setFormat(PixelFormat.RGB_565);
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
mNativeContentView = new NativeContentView(this);
mNativeContentView.mActivity = this;
setContentView(mNativeContentView);
mNativeContentView.requestFocus();
mNativeContentView.getViewTreeObserver().addOnGlobalLayoutListener(this);
try {
ai = getPackageManager().getActivityInfo(
@ -165,6 +207,18 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback,
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (mDispatchingUnhandledKey) {
return super.dispatchKeyEvent(event);
} else {
// Key events from the IME do not go through the input channel;
// we need to intercept them here to hand to the application.
dispatchKeyEventNative(mNativeHandle, event);
return true;
}
}
public void surfaceCreated(SurfaceHolder holder) {
if (!mDestroyed) {
mCurSurfaceHolder = holder;
@ -179,6 +233,13 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback,
}
}
public void surfaceRedrawNeeded(SurfaceHolder holder) {
if (!mDestroyed) {
mCurSurfaceHolder = holder;
onSurfaceRedrawNeededNative(mNativeHandle, holder.getSurface());
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
mCurSurfaceHolder = null;
if (!mDestroyed) {
@ -200,10 +261,32 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback,
}
}
public void onGlobalLayout() {
mNativeContentView.getLocationInWindow(mLocation);
int w = mNativeContentView.getWidth();
int h = mNativeContentView.getHeight();
if (mLocation[0] != mLastContentX || mLocation[1] != mLastContentY
|| w != mLastContentWidth || h != mLastContentHeight) {
mLastContentX = mLocation[0];
mLastContentY = mLocation[1];
mLastContentWidth = w;
mLastContentHeight = h;
if (!mDestroyed) {
onContentRectChangedNative(mNativeHandle, mLastContentX,
mLastContentY, mLastContentWidth, mLastContentHeight);
}
}
}
void dispatchUnhandledKeyEvent(KeyEvent event) {
View decor = getWindow().getDecorView();
if (decor != null) {
decor.dispatchKeyEvent(event);
try {
mDispatchingUnhandledKey = true;
View decor = getWindow().getDecorView();
if (decor != null) {
decor.dispatchKeyEvent(event);
}
} finally {
mDispatchingUnhandledKey = false;
}
}
@ -214,4 +297,12 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback,
void setWindowFormat(int format) {
getWindow().setFormat(format);
}
void showIme(int mode) {
mIMM.showSoftInput(mNativeContentView, mode);
}
void hideIme(int mode) {
mIMM.hideSoftInputFromWindow(mNativeContentView.getWindowToken(), mode);
}
}

View File

@ -336,7 +336,7 @@ public abstract class WallpaperService extends Service {
? (mWindowFlags&~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
: (mWindowFlags|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
if (mCreated) {
updateSurface(false, false);
updateSurface(false, false, false);
}
}
@ -420,6 +420,13 @@ public abstract class WallpaperService extends Service {
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
/**
* Convenience for {@link SurfaceHolder.Callback#surfaceRedrawNeeded
* SurfaceHolder.Callback.surfaceRedrawNeeded()}.
*/
public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
}
/**
* Convenience for {@link SurfaceHolder.Callback#surfaceCreated
* SurfaceHolder.Callback.surfaceCreated()}.
@ -450,7 +457,7 @@ public abstract class WallpaperService extends Service {
}
}
void updateSurface(boolean forceRelayout, boolean forceReport) {
void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
if (mDestroyed) {
Log.w(TAG, "Ignoring updateSurface: destroyed");
}
@ -467,7 +474,7 @@ public abstract class WallpaperService extends Service {
final boolean typeChanged = mType != mSurfaceHolder.getRequestedType();
final boolean flagsChanged = mCurWindowFlags != mWindowFlags;
if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged
|| typeChanged || flagsChanged) {
|| typeChanged || flagsChanged || redrawNeeded) {
if (DEBUG) Log.v(TAG, "Changes: creating=" + creating
+ " format=" + formatChanged + " size=" + sizeChanged);
@ -555,6 +562,10 @@ public abstract class WallpaperService extends Service {
}
}
}
redrawNeeded |= creating
|| (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0;
if (forceReport || creating || surfaceCreating
|| formatChanged || sizeChanged) {
if (DEBUG) {
@ -578,10 +589,24 @@ public abstract class WallpaperService extends Service {
}
}
}
if (redrawNeeded) {
onSurfaceRedrawNeeded(mSurfaceHolder);
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
if (c instanceof SurfaceHolder.Callback2) {
((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
mSurfaceHolder);
}
}
}
}
} finally {
mIsCreating = false;
mSurfaceCreated = true;
if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
if (redrawNeeded) {
mSession.finishDrawing(mWindow);
}
}
@ -618,7 +643,7 @@ public abstract class WallpaperService extends Service {
onCreate(mSurfaceHolder);
mInitializing = false;
updateSurface(false, false);
updateSurface(false, false, false);
}
void doDesiredSizeChanged(int desiredWidth, int desiredHeight) {
@ -647,7 +672,7 @@ public abstract class WallpaperService extends Service {
// If becoming visible, in preview mode the surface
// may have been destroyed so now we need to make
// sure it is re-created.
updateSurface(false, false);
updateSurface(false, false, false);
}
onVisibilityChanged(visible);
}
@ -852,7 +877,7 @@ public abstract class WallpaperService extends Service {
return;
}
case MSG_UPDATE_SURFACE:
mEngine.updateSurface(true, false);
mEngine.updateSurface(true, false, false);
break;
case MSG_VISIBILITY_CHANGED:
if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine
@ -868,14 +893,8 @@ public abstract class WallpaperService extends Service {
} break;
case MSG_WINDOW_RESIZED: {
final boolean reportDraw = message.arg1 != 0;
mEngine.updateSurface(true, false);
mEngine.updateSurface(true, false, reportDraw);
mEngine.doOffsetsChanged();
if (reportDraw) {
try {
mEngine.mSession.finishDrawing(mEngine.mWindow);
} catch (RemoteException e) {
}
}
} break;
case MSG_TOUCH_EVENT: {
MotionEvent ev = (MotionEvent)message.obj;

View File

@ -118,6 +118,23 @@ public interface SurfaceHolder {
public void surfaceDestroyed(SurfaceHolder holder);
}
/**
* Additional callbacks that can be received for {@link Callback}.
*/
public interface Callback2 extends Callback {
/**
* Called when the application needs to redraw the content of its
* surface, after it is resized or for some other reason. By not
* returning here until the redraw is complete, you can ensure that
* the user will not see your surface in a bad state (at its new
* size before it has been correctly drawn that way). This will
* typically be preceeded by a call to {@link #surfaceChanged}.
*
* @param holder The SurfaceHolder whose surface has changed.
*/
public void surfaceRedrawNeeded(SurfaceHolder holder);
}
/**
* Add a Callback interface for this holder. There can several Callback
* interfaces associated to a holder.

View File

@ -123,7 +123,7 @@ public class SurfaceView extends View {
handleGetNewSurface();
} break;
case UPDATE_WINDOW_MSG: {
updateWindow(false);
updateWindow(false, false);
} break;
}
}
@ -132,7 +132,7 @@ public class SurfaceView extends View {
final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
= new ViewTreeObserver.OnScrollChangedListener() {
public void onScrollChanged() {
updateWindow(false);
updateWindow(false, false);
}
};
@ -210,7 +210,7 @@ public class SurfaceView extends View {
super.onWindowVisibilityChanged(visibility);
mWindowVisibility = visibility == VISIBLE;
mRequestedVisible = mWindowVisibility && mViewVisibility;
updateWindow(false);
updateWindow(false, false);
}
@Override
@ -218,7 +218,7 @@ public class SurfaceView extends View {
super.setVisibility(visibility);
mViewVisibility = visibility == VISIBLE;
mRequestedVisible = mWindowVisibility && mViewVisibility;
updateWindow(false);
updateWindow(false, false);
}
/**
@ -232,7 +232,7 @@ public class SurfaceView extends View {
*/
protected void showSurface() {
if (mSession != null) {
updateWindow(true);
updateWindow(true, false);
}
}
@ -265,7 +265,7 @@ public class SurfaceView extends View {
protected void onDetachedFromWindow() {
getViewTreeObserver().removeOnScrollChangedListener(mScrollChangedListener);
mRequestedVisible = false;
updateWindow(false);
updateWindow(false, false);
mHaveFrame = false;
if (mWindow != null) {
try {
@ -290,7 +290,7 @@ public class SurfaceView extends View {
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updateWindow(false);
updateWindow(false, false);
}
@Override
@ -343,7 +343,7 @@ public class SurfaceView extends View {
}
// reposition ourselves where the surface is
mHaveFrame = true;
updateWindow(false);
updateWindow(false, false);
super.dispatchDraw(canvas);
}
@ -397,7 +397,7 @@ public class SurfaceView extends View {
mWindowType = type;
}
private void updateWindow(boolean force) {
private void updateWindow(boolean force, boolean redrawNeeded) {
if (!mHaveFrame) {
return;
}
@ -425,7 +425,7 @@ public class SurfaceView extends View {
final boolean typeChanged = mType != mRequestedType;
if (force || creating || formatChanged || sizeChanged || visibleChanged
|| typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]
|| mUpdateWindowNeeded || mReportDrawNeeded) {
|| mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
if (localLOGV) Log.i(TAG, "Changes: creating=" + creating
+ " format=" + formatChanged + " size=" + sizeChanged
@ -524,6 +524,8 @@ public class SurfaceView extends View {
}
try {
redrawNeeded |= creating | reportDrawNeeded;
if (visible) {
mDestroyReportNeeded = true;
@ -541,8 +543,13 @@ public class SurfaceView extends View {
}
if (creating || formatChanged || sizeChanged
|| visibleChanged || realSizeChanged) {
}
if (redrawNeeded) {
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
if (c instanceof SurfaceHolder.Callback2) {
((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
mSurfaceHolder);
}
}
}
} else {
@ -550,7 +557,7 @@ public class SurfaceView extends View {
}
} finally {
mIsCreating = false;
if (creating || reportDrawNeeded) {
if (redrawNeeded) {
mSession.finishDrawing(mWindow);
}
}
@ -580,7 +587,7 @@ public class SurfaceView extends View {
void handleGetNewSurface() {
mNewSurfaceNeeded = true;
updateWindow(false);
updateWindow(false, false);
}
/**
@ -696,7 +703,7 @@ public class SurfaceView extends View {
mRequestedFormat = format;
if (mWindow != null) {
updateWindow(false);
updateWindow(false, false);
}
}
@ -713,7 +720,7 @@ public class SurfaceView extends View {
case SURFACE_TYPE_PUSH_BUFFERS:
mRequestedType = type;
if (mWindow != null) {
updateWindow(false);
updateWindow(false, false);
}
break;
}

View File

@ -137,7 +137,7 @@ public final class ViewRoot extends Handler implements ViewParent,
int mViewVisibility;
boolean mAppVisible = true;
SurfaceHolder.Callback mSurfaceHolderCallback;
SurfaceHolder.Callback2 mSurfaceHolderCallback;
BaseSurfaceHolder mSurfaceHolder;
boolean mIsCreating;
boolean mDrawingAllowed;
@ -1263,6 +1263,18 @@ public final class ViewRoot extends Handler implements ViewParent,
Log.v("ViewRoot", "FINISHED DRAWING: " + mWindowAttributes.getTitle());
}
mReportNextDraw = false;
if (mSurfaceHolder != null && mSurface.isValid()) {
mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
if (c instanceof SurfaceHolder.Callback2) {
((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
mSurfaceHolder);
}
}
}
}
try {
sWindowSession.finishDrawing(mWindow);
} catch (RemoteException e) {

View File

@ -478,7 +478,7 @@ public abstract class Window {
* to operate (such as for receiving input events). The given SurfaceHolder
* callback will be used to tell you about state changes to the surface.
*/
public abstract void takeSurface(SurfaceHolder.Callback callback);
public abstract void takeSurface(SurfaceHolder.Callback2 callback);
/**
* Take ownership of this window's InputQueue. The window will no

View File

@ -5,7 +5,7 @@ import android.view.SurfaceHolder;
/** hahahah */
public interface RootViewSurfaceTaker {
SurfaceHolder.Callback willYouTakeTheSurface();
SurfaceHolder.Callback2 willYouTakeTheSurface();
void setSurfaceType(int type);
void setSurfaceFormat(int format);
void setSurfaceKeepScreenOn(boolean keepOn);

View File

@ -19,6 +19,7 @@
#include <poll.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_view_Surface.h>
@ -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<android::InputChannel>& channel, int workWrite)
: AInputQueue(channel), mWorkWrite(workWrite) {
} // namespace android
using namespace android;
AInputQueue::AInputQueue(const sp<InputChannel>& 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<android::PollLoop*>(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<N; i++) {
if (mDeliveringKeys[i] == event) {
delete event;
mDeliveringKeys.removeAt(i);
needFinished = false;
break;
}
}
Mutex mLock;
Vector<KeyEvent*> 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<InputChannel> 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<ANativeWindow> 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<NativeCode*>(activity);
write_work(code->mainWorkWrite, CMD_SHOW_SOFT_INPUT, flags);
}
void android_NativeActivity_hideSoftInput(
ANativeActivity* activity, int32_t flags) {
NativeCode* code = static_cast<NativeCode*>(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<ANativeWindow> 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,

View File

@ -17,6 +17,8 @@
#ifndef _ANDROID_APP_NATIVEACTIVITY_H
#define _ANDROID_APP_NATIVEACTIVITY_H
#include <ui/InputTransport.h>
#include <android/native_activity.h>
#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<android::InputChannel>& 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<android::PollLoop> 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
//<2F>delivered to the app.
android::Vector<android::KeyEvent*> mDeliveringKeys;
android::Mutex mLock;
android::Vector<android::KeyEvent*> mPendingKeys;
android::Vector<android::KeyEvent*> mDispatchingKeys;
};
#endif // _ANDROID_APP_NATIVEACTIVITY_H

View File

@ -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 {

View File

@ -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<android::InputChannel>& 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<android::PollLoop>& pollLoop) { mPollLoop = pollLoop; }
const android::sp<android::PollLoop> getPollLoop() const { return mPollLoop; }
virtual void doDefaultKey(android::KeyEvent* keyEvent) = 0;
private:
android::InputConsumer mConsumer;
android::PreallocatedInputEventFactory mInputEventFactory;
android::sp<android::PollLoop> mPollLoop;
};
#endif // _UI_INPUT_TRANSPORT_H

View File

@ -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<InputChannel>& channel) :
mConsumer(channel) {
}
AInputQueue::~AInputQueue() {
}
status_t AInputQueue::consume(InputEvent** event) {
return mConsumer.consume(&mInputEventFactory, event);
}

View File

@ -22,6 +22,8 @@
#include <ui/InputTransport.h>
#include <utils/PollLoop.h>
#include <android_runtime/android_app_NativeActivity.h>
#include <poll.h>
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<android::PollLoop*>(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);
}

View File

@ -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);
}

View File

@ -18,6 +18,7 @@
#include <jni.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/resource.h>
@ -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);
}

View File

@ -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

View File

@ -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.
*/

View File

@ -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;
}