1package android.app;
2
3import com.android.internal.view.IInputMethodSession;
4
5import android.content.Context;
6import android.content.pm.ActivityInfo;
7import android.content.pm.PackageManager;
8import android.content.res.AssetManager;
9import android.content.res.Configuration;
10import android.graphics.PixelFormat;
11import android.os.Build;
12import android.os.Bundle;
13import android.os.Environment;
14import android.os.Looper;
15import android.os.MessageQueue;
16import android.util.AttributeSet;
17import android.view.InputChannel;
18import android.view.InputQueue;
19import android.view.KeyEvent;
20import android.view.Surface;
21import android.view.SurfaceHolder;
22import android.view.View;
23import android.view.WindowManager;
24import android.view.ViewTreeObserver.OnGlobalLayoutListener;
25import android.view.inputmethod.InputMethodManager;
26
27import java.io.File;
28import java.lang.ref.WeakReference;
29
30/**
31 * Convenience for implementing an activity that will be implemented
32 * purely in native code.  That is, a game (or game-like thing).  There
33 * is no need to derive from this class; you can simply declare it in your
34 * manifest, and use the NDK APIs from there.
35 *
36 * <p>A typical manifest would look like:
37 *
38 * {@sample development/ndk/platforms/android-9/samples/native-activity/AndroidManifest.xml
39 *      manifest}
40 *
41 * <p>A very simple example of native code that is run by NativeActivity
42 * follows.  This reads input events from the user and uses OpenGLES to
43 * draw into the native activity's window.
44 *
45 * {@sample development/ndk/platforms/android-9/samples/native-activity/jni/main.c all}
46 */
47public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
48        InputQueue.Callback, OnGlobalLayoutListener {
49    /**
50     * Optional meta-that can be in the manifest for this component, specifying
51     * the name of the native shared library to load.  If not specified,
52     * "main" is used.
53     */
54    public static final String META_DATA_LIB_NAME = "android.app.lib_name";
55
56    /**
57     * Optional meta-that can be in the manifest for this component, specifying
58     * the name of the main entry point for this native activity in the
59     * {@link #META_DATA_LIB_NAME} native code.  If not specified,
60     * "ANativeActivity_onCreate" is used.
61     */
62    public static final String META_DATA_FUNC_NAME = "android.app.func_name";
63
64    private static final String KEY_NATIVE_SAVED_STATE = "android:native_state";
65
66    private NativeContentView mNativeContentView;
67    private InputMethodManager mIMM;
68    private InputMethodCallback mInputMethodCallback;
69
70    private int mNativeHandle;
71
72    private InputQueue mCurInputQueue;
73    private SurfaceHolder mCurSurfaceHolder;
74
75    final int[] mLocation = new int[2];
76    int mLastContentX;
77    int mLastContentY;
78    int mLastContentWidth;
79    int mLastContentHeight;
80
81    private boolean mDispatchingUnhandledKey;
82
83    private boolean mDestroyed;
84
85    private native int loadNativeCode(String path, String funcname, MessageQueue queue,
86            String internalDataPath, String obbPath, String externalDataPath, int sdkVersion,
87            AssetManager assetMgr, byte[] savedState);
88    private native void unloadNativeCode(int handle);
89
90    private native void onStartNative(int handle);
91    private native void onResumeNative(int handle);
92    private native byte[] onSaveInstanceStateNative(int handle);
93    private native void onPauseNative(int handle);
94    private native void onStopNative(int handle);
95    private native void onConfigurationChangedNative(int handle);
96    private native void onLowMemoryNative(int handle);
97    private native void onWindowFocusChangedNative(int handle, boolean focused);
98    private native void onSurfaceCreatedNative(int handle, Surface surface);
99    private native void onSurfaceChangedNative(int handle, Surface surface,
100            int format, int width, int height);
101    private native void onSurfaceRedrawNeededNative(int handle, Surface surface);
102    private native void onSurfaceDestroyedNative(int handle);
103    private native void onInputChannelCreatedNative(int handle, InputChannel channel);
104    private native void onInputChannelDestroyedNative(int handle, InputChannel channel);
105    private native void onContentRectChangedNative(int handle, int x, int y, int w, int h);
106    private native void dispatchKeyEventNative(int handle, KeyEvent event);
107    private native void finishPreDispatchKeyEventNative(int handle, int seq, boolean handled);
108
109    static class NativeContentView extends View {
110        NativeActivity mActivity;
111
112        public NativeContentView(Context context) {
113            super(context);
114        }
115
116        public NativeContentView(Context context, AttributeSet attrs) {
117            super(context, attrs);
118        }
119    }
120
121    static final class InputMethodCallback implements InputMethodManager.FinishedEventCallback {
122        WeakReference<NativeActivity> mNa;
123
124        InputMethodCallback(NativeActivity na) {
125            mNa = new WeakReference<NativeActivity>(na);
126        }
127
128        @Override
129        public void finishedEvent(int seq, boolean handled) {
130            NativeActivity na = mNa.get();
131            if (na != null) {
132                na.finishPreDispatchKeyEventNative(na.mNativeHandle, seq, handled);
133            }
134        }
135    }
136
137    @Override
138    protected void onCreate(Bundle savedInstanceState) {
139        String libname = "main";
140        String funcname = "ANativeActivity_onCreate";
141        ActivityInfo ai;
142
143        mIMM = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
144        mInputMethodCallback = new InputMethodCallback(this);
145
146        getWindow().takeSurface(this);
147        getWindow().takeInputQueue(this);
148        getWindow().setFormat(PixelFormat.RGB_565);
149        getWindow().setSoftInputMode(
150                WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
151                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
152
153        mNativeContentView = new NativeContentView(this);
154        mNativeContentView.mActivity = this;
155        setContentView(mNativeContentView);
156        mNativeContentView.requestFocus();
157        mNativeContentView.getViewTreeObserver().addOnGlobalLayoutListener(this);
158
159        try {
160            ai = getPackageManager().getActivityInfo(
161                    getIntent().getComponent(), PackageManager.GET_META_DATA);
162            if (ai.metaData != null) {
163                String ln = ai.metaData.getString(META_DATA_LIB_NAME);
164                if (ln != null) libname = ln;
165                ln = ai.metaData.getString(META_DATA_FUNC_NAME);
166                if (ln != null) funcname = ln;
167            }
168        } catch (PackageManager.NameNotFoundException e) {
169            throw new RuntimeException("Error getting activity info", e);
170        }
171
172        String path = null;
173
174        File libraryFile = new File(ai.applicationInfo.nativeLibraryDir,
175                System.mapLibraryName(libname));
176        if (libraryFile.exists()) {
177            path = libraryFile.getPath();
178        }
179
180        if (path == null) {
181            throw new IllegalArgumentException("Unable to find native library: " + libname);
182        }
183
184        byte[] nativeSavedState = savedInstanceState != null
185                ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null;
186
187        mNativeHandle = loadNativeCode(path, funcname, Looper.myQueue(),
188                 getFilesDir().toString(), getObbDir().toString(),
189                 Environment.getExternalStorageAppFilesDirectory(ai.packageName).toString(),
190                 Build.VERSION.SDK_INT, getAssets(), nativeSavedState);
191
192        if (mNativeHandle == 0) {
193            throw new IllegalArgumentException("Unable to load native library: " + path);
194        }
195        super.onCreate(savedInstanceState);
196    }
197
198    @Override
199    protected void onDestroy() {
200        mDestroyed = true;
201        if (mCurSurfaceHolder != null) {
202            onSurfaceDestroyedNative(mNativeHandle);
203            mCurSurfaceHolder = null;
204        }
205        if (mCurInputQueue != null) {
206            onInputChannelDestroyedNative(mNativeHandle, mCurInputQueue.getInputChannel());
207            mCurInputQueue = null;
208        }
209        unloadNativeCode(mNativeHandle);
210        super.onDestroy();
211    }
212
213    @Override
214    protected void onPause() {
215        super.onPause();
216        onPauseNative(mNativeHandle);
217    }
218
219    @Override
220    protected void onResume() {
221        super.onResume();
222        onResumeNative(mNativeHandle);
223    }
224
225    @Override
226    protected void onSaveInstanceState(Bundle outState) {
227        super.onSaveInstanceState(outState);
228        byte[] state = onSaveInstanceStateNative(mNativeHandle);
229        if (state != null) {
230            outState.putByteArray(KEY_NATIVE_SAVED_STATE, state);
231        }
232    }
233
234    @Override
235    protected void onStart() {
236        super.onStart();
237        onStartNative(mNativeHandle);
238    }
239
240    @Override
241    protected void onStop() {
242        super.onStop();
243        onStopNative(mNativeHandle);
244    }
245
246    @Override
247    public void onConfigurationChanged(Configuration newConfig) {
248        super.onConfigurationChanged(newConfig);
249        if (!mDestroyed) {
250            onConfigurationChangedNative(mNativeHandle);
251        }
252    }
253
254    @Override
255    public void onLowMemory() {
256        super.onLowMemory();
257        if (!mDestroyed) {
258            onLowMemoryNative(mNativeHandle);
259        }
260    }
261
262    @Override
263    public void onWindowFocusChanged(boolean hasFocus) {
264        super.onWindowFocusChanged(hasFocus);
265        if (!mDestroyed) {
266            onWindowFocusChangedNative(mNativeHandle, hasFocus);
267        }
268    }
269
270    @Override
271    public boolean dispatchKeyEvent(KeyEvent event) {
272        if (mDispatchingUnhandledKey) {
273            return super.dispatchKeyEvent(event);
274        } else {
275            // Key events from the IME do not go through the input channel;
276            // we need to intercept them here to hand to the application.
277            dispatchKeyEventNative(mNativeHandle, event);
278            return true;
279        }
280    }
281
282    public void surfaceCreated(SurfaceHolder holder) {
283        if (!mDestroyed) {
284            mCurSurfaceHolder = holder;
285            onSurfaceCreatedNative(mNativeHandle, holder.getSurface());
286        }
287    }
288
289    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
290        if (!mDestroyed) {
291            mCurSurfaceHolder = holder;
292            onSurfaceChangedNative(mNativeHandle, holder.getSurface(), format, width, height);
293        }
294    }
295
296    public void surfaceRedrawNeeded(SurfaceHolder holder) {
297        if (!mDestroyed) {
298            mCurSurfaceHolder = holder;
299            onSurfaceRedrawNeededNative(mNativeHandle, holder.getSurface());
300        }
301    }
302
303    public void surfaceDestroyed(SurfaceHolder holder) {
304        mCurSurfaceHolder = null;
305        if (!mDestroyed) {
306            onSurfaceDestroyedNative(mNativeHandle);
307        }
308    }
309
310    public void onInputQueueCreated(InputQueue queue) {
311        if (!mDestroyed) {
312            mCurInputQueue = queue;
313            onInputChannelCreatedNative(mNativeHandle, queue.getInputChannel());
314        }
315    }
316
317    public void onInputQueueDestroyed(InputQueue queue) {
318        mCurInputQueue = null;
319        if (!mDestroyed) {
320            onInputChannelDestroyedNative(mNativeHandle, queue.getInputChannel());
321        }
322    }
323
324    public void onGlobalLayout() {
325        mNativeContentView.getLocationInWindow(mLocation);
326        int w = mNativeContentView.getWidth();
327        int h = mNativeContentView.getHeight();
328        if (mLocation[0] != mLastContentX || mLocation[1] != mLastContentY
329                || w != mLastContentWidth || h != mLastContentHeight) {
330            mLastContentX = mLocation[0];
331            mLastContentY = mLocation[1];
332            mLastContentWidth = w;
333            mLastContentHeight = h;
334            if (!mDestroyed) {
335                onContentRectChangedNative(mNativeHandle, mLastContentX,
336                        mLastContentY, mLastContentWidth, mLastContentHeight);
337            }
338        }
339    }
340
341    boolean dispatchUnhandledKeyEvent(KeyEvent event) {
342        try {
343            mDispatchingUnhandledKey = true;
344            View decor = getWindow().getDecorView();
345            if (decor != null) {
346                return decor.dispatchKeyEvent(event);
347            } else {
348                return false;
349            }
350        } finally {
351            mDispatchingUnhandledKey = false;
352        }
353    }
354
355    void preDispatchKeyEvent(KeyEvent event, int seq) {
356        mIMM.dispatchKeyEvent(this, seq, event,
357                mInputMethodCallback);
358    }
359
360    void setWindowFlags(int flags, int mask) {
361        getWindow().setFlags(flags, mask);
362    }
363
364    void setWindowFormat(int format) {
365        getWindow().setFormat(format);
366    }
367
368    void showIme(int mode) {
369        mIMM.showSoftInput(mNativeContentView, mode);
370    }
371
372    void hideIme(int mode) {
373        mIMM.hideSoftInputFromWindow(mNativeContentView.getWindowToken(), mode);
374    }
375}
376