OSDN Git Service

IME events are now dispatched to native applications.
authorDianne Hackborn <hackbod@google.com>
Wed, 14 Jul 2010 00:48:30 +0000 (17:48 -0700)
committerDianne Hackborn <hackbod@google.com>
Wed, 14 Jul 2010 01:36:46 +0000 (18:36 -0700)
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

19 files changed:
api/current.xml
core/java/android/app/NativeActivity.java
core/java/android/service/wallpaper/WallpaperService.java
core/java/android/view/SurfaceHolder.java
core/java/android/view/SurfaceView.java
core/java/android/view/ViewRoot.java
core/java/android/view/Window.java
core/java/com/android/internal/view/RootViewSurfaceTaker.java
core/jni/android_app_NativeActivity.cpp
include/android_runtime/android_app_NativeActivity.h
include/ui/Input.h
include/ui/InputTransport.h
libs/ui/InputTransport.cpp
native/android/input.cpp
native/android/native_activity.cpp
native/glue/threaded_app/threaded_app.c
native/include/android/native_activity.h
native/include/android_glue/threaded_app.h
policy/src/com/android/internal/policy/impl/PhoneWindow.java

index d32a108..9e62637 100644 (file)
 >
 <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"
  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"
 <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"
 <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"
 </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"
  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"
index d72dda7..ccc9ae3 100644 (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);
+    }
 }
index 6f12f19..2d120e8 100644 (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);
             }
         }
         
@@ -421,6 +421,13 @@ public abstract class WallpaperService extends Service {
         }
 
         /**
+         * 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;
index 34e4638..0d38f7b 100644 (file)
@@ -119,6 +119,23 @@ public interface SurfaceHolder {
     }
 
     /**
+     * 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.
      * 
index e4d1ae1..c469bcc 100644 (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;
             }
index fb45971..260bf7b 100644 (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) {
index f40734b..11c09c1 100644 (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
index 7ff8d4c..9c1b558 100644 (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);
index 54a9c2a..acbf854 100644 (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);
+        if (mDispatchingKeys.size() > 0) {
+            KeyEvent* kevent = mDispatchingKeys[0];
+            *outEvent = kevent;
+            mDispatchingKeys.removeAt(0);
+            mDeliveringKeys.add(kevent);
         }
-        mPendingKeys.add(keyEvent);
         mLock.unlock();
+        if (*outEvent != NULL) {
+            return 0;
+        }
     }
     
-    KeyEvent* getNextEvent() {
-        KeyEvent* event = NULL;
-        
-        mLock.lock();
-        if (mPendingKeys.size() > 0) {
-            event = mPendingKeys[0];
-            mPendingKeys.removeAt(0);
+    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;
         }
-        mLock.unlock();
-        
-        return event;
     }
     
-    int mWorkWrite;
+    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();
     
-    Mutex mLock;
-    Vector<KeyEvent*> mPendingKeys;
-};
+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,17 +650,49 @@ 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());
+        }
+    }
+}
+
+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,
index f808328..d7a9a2c 100644 (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
+    //Ê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
index a2e0ba0..a7d23d4 100644 (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 {
 
index 11714d5..226d1d5 100644 (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
index 25def3c..fc83e31 100644 (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);
-}
index 89d53e2..a4dde51 100644 (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;
-}
-
-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);
-    }
+    return queue->getEvent(outEvent);
+}
+
+void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled) {
+    queue->finishEvent(event, handled != 0);
 }
index 509cc33..0c6823a 100644 (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);
+}
index 2411e93..452c735 100644 (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);
 }
index d0ff052..ea6f05f 100644 (file)
@@ -147,6 +147,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
      * from this function: in the common case of drawing to the window from
@@ -170,6 +185,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
      * important processes.
@@ -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
index adfdbea..2b58e9c 100644 (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 {
@@ -105,6 +111,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.
      */
@@ -117,6 +143,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.
      */
     APP_CMD_START,
index 5642588..b9232c8 100644 (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;
         }