Move some GL codes from the Camera project and add some interface classes.

Change-Id: Idc66812aef128e036cff9ee7ee874717b4a85197
diff --git a/new3d/src/com/android/gallery3d/data/MediaItem.java b/new3d/src/com/android/gallery3d/data/MediaItem.java
new file mode 100644
index 0000000..13f3166
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/data/MediaItem.java
@@ -0,0 +1,22 @@
+package com.android.gallery3d.data;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+public interface MediaItem {
+    public static final int TYPE_FULL_IMAGE = 0;
+    public static final int TYPE_SCREEN_NAIL = 1;
+    public static final int TYPE_THUMBNAIL = 2;
+
+    public interface MediaItemListener {
+        public void onImageReady(MediaItem item, int type, Bitmap bitmap);
+    }
+
+    public Uri getMediaUri();
+
+    public String getTitle();
+
+    public Bitmap getImage(int type);
+
+    public void setListener(MediaItemListener listener);
+}
diff --git a/new3d/src/com/android/gallery3d/data/MediaSet.java b/new3d/src/com/android/gallery3d/data/MediaSet.java
new file mode 100644
index 0000000..4a3df37
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/data/MediaSet.java
@@ -0,0 +1,22 @@
+package com.android.gallery3d.data;
+
+public interface MediaSet {
+
+    public interface MediaSetListener {
+        public void onContentChanged();
+    }
+
+    public int getSubMediaSetCount();
+
+    public MediaSet getSubMediaSet(int index);
+
+    public int getMediaCount();
+
+    public MediaItem getMediaItem(int index);
+
+    public int getTotalMediaItemCount();
+
+    public String getTitle();
+
+    public MediaItem[] getCoverMediaItems();
+}
diff --git a/new3d/src/com/android/gallery3d/ui/BasicTexture.java b/new3d/src/com/android/gallery3d/ui/BasicTexture.java
new file mode 100644
index 0000000..a438bff
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/BasicTexture.java
@@ -0,0 +1,77 @@
+package com.android.gallery3d.ui;
+
+import javax.microedition.khronos.opengles.GL11;
+
+abstract class BasicTexture implements Texture {
+
+    protected static final int UNSPECIFIED = -1;
+
+    public static final int STATE_UNLOADED = 0;
+    public static final int STATE_LOADED = 1;
+    public static final int STATE_ERROR = -1;
+
+    protected GL11 mGL;
+
+    protected int mId;
+    protected int mState;
+
+    protected int mWidth = UNSPECIFIED;
+    protected int mHeight = UNSPECIFIED;
+
+    protected int mTextureWidth;
+    protected int mTextureHeight;
+
+    protected BasicTexture(GL11 gl, int id, int state) {
+        mGL = gl;
+        mId = id;
+        mState = state;
+    }
+
+    protected BasicTexture() {
+        this(null, 0, STATE_UNLOADED);
+    }
+
+    protected void setSize(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+    }
+
+    /**
+     * Sets the size of the texture. Due to the limit of OpenGL, the texture
+     * size must be of power of 2, the size of the content may not be the size
+     * of the texture.
+     */
+    protected void setTextureSize(int width, int height) {
+        mTextureWidth = width;
+        mTextureHeight = height;
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    public int getWidth() {
+        return mWidth;
+    }
+
+    public int getHeight() {
+        return mHeight;
+    }
+
+    public void deleteFromGL() {
+        if (mState == STATE_LOADED) {
+            mGL.glDeleteTextures(1, new int[]{mId}, 0);
+        }
+        mState = STATE_UNLOADED;
+    }
+
+    public void draw(GLRootView root, int x, int y) {
+        root.drawTexture(this, x, y, mWidth, mHeight);
+    }
+
+    public void draw(GLRootView root, int x, int y, int w, int h) {
+        root.drawTexture(this, x, y, w, h);
+    }
+
+    abstract protected boolean bind(GLRootView root, GL11 gl);
+}
diff --git a/new3d/src/com/android/gallery3d/ui/BitmapTexture.java b/new3d/src/com/android/gallery3d/ui/BitmapTexture.java
new file mode 100644
index 0000000..f455a78
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/BitmapTexture.java
@@ -0,0 +1,109 @@
+package com.android.gallery3d.ui;
+
+import android.graphics.Bitmap;
+import android.opengl.GLUtils;
+
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11Ext;
+
+abstract class BitmapTexture extends BasicTexture {
+
+    @SuppressWarnings("unused")
+    private static final String TAG = "Texture";
+
+    protected BitmapTexture() {
+        super(null, 0, STATE_UNLOADED);
+    }
+
+    @Override
+    public int getWidth() {
+        if (mWidth == UNSPECIFIED) getBitmap();
+        return mWidth;
+    }
+
+    @Override
+    public int getHeight() {
+        if (mWidth == UNSPECIFIED) getBitmap();
+        return mHeight;
+    }
+
+    protected abstract Bitmap getBitmap();
+
+    protected abstract void freeBitmap(Bitmap bitmap);
+
+    private void uploadToGL(GL11 gl) throws GLOutOfMemoryException {
+        Bitmap bitmap = getBitmap();
+        int glError = GL11.GL_NO_ERROR;
+        if (bitmap != null) {
+            int[] textureId = new int[1];
+            try {
+                // Define a vertically flipped crop rectangle for
+                // OES_draw_texture.
+                int width = bitmap.getWidth();
+                int height = bitmap.getHeight();
+                int[] cropRect = {0,  height, width, -height};
+
+                // Upload the bitmap to a new texture.
+                gl.glGenTextures(1, textureId, 0);
+                gl.glBindTexture(GL11.GL_TEXTURE_2D, textureId[0]);
+                gl.glTexParameteriv(GL11.GL_TEXTURE_2D,
+                        GL11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect, 0);
+                gl.glTexParameteri(GL11.GL_TEXTURE_2D,
+                        GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
+                gl.glTexParameteri(GL11.GL_TEXTURE_2D,
+                        GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
+                gl.glTexParameterf(GL11.GL_TEXTURE_2D,
+                        GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
+                gl.glTexParameterf(GL11.GL_TEXTURE_2D,
+                        GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
+
+                int widthExt = Util.nextPowerOf2(width);
+                int heightExt = Util.nextPowerOf2(height);
+                int format = GLUtils.getInternalFormat(bitmap);
+                int type = GLUtils.getType(bitmap);
+
+                mTextureWidth = widthExt;
+                mTextureHeight = heightExt;
+                gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format,
+                        widthExt, heightExt, 0, format, type, null);
+                GLUtils.texSubImage2D(
+                        GL11.GL_TEXTURE_2D, 0, 0, 0, bitmap, format, type);
+            } finally {
+                freeBitmap(bitmap);
+            }
+            if (glError == GL11.GL_OUT_OF_MEMORY) {
+                throw new GLOutOfMemoryException();
+            }
+            if (glError != GL11.GL_NO_ERROR) {
+                mId = 0;
+                mState = STATE_UNLOADED;
+                throw new RuntimeException(
+                        "Texture upload fail, glError " + glError);
+            } else {
+                // Update texture state.
+                mGL = gl;
+                mId = textureId[0];
+                mState = BitmapTexture.STATE_LOADED;
+            }
+        } else {
+            mState = STATE_ERROR;
+            throw new RuntimeException("Texture load fail, no bitmap");
+        }
+    }
+
+    @Override
+    protected boolean bind(GLRootView root, GL11 gl) {
+        if (mState == BitmapTexture.STATE_UNLOADED || mGL != gl) {
+            mState = BitmapTexture.STATE_UNLOADED;
+            try {
+                uploadToGL(gl);
+            } catch (GLOutOfMemoryException e) {
+                root.handleLowMemory();
+                return false;
+            }
+        } else {
+            gl.glBindTexture(GL11.GL_TEXTURE_2D, getId());
+        }
+        return true;
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/CameraEGLConfigChooser.java b/new3d/src/com/android/gallery3d/ui/CameraEGLConfigChooser.java
new file mode 100644
index 0000000..355a248
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/CameraEGLConfigChooser.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.gallery3d.ui;
+
+import android.opengl.GLSurfaceView.EGLConfigChooser;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLDisplay;
+
+/*
+ * The code is copied/adapted from
+ * <code>android.opengl.GLSurfaceView.BaseConfigChooser</code>. Here we try to
+ * choose a configuration that support RGBA_8888 format and if possible,
+ * with stencil buffer, but is not required.
+ */
+class CameraEGLConfigChooser implements EGLConfigChooser {
+
+    private static final int COLOR_BITS = 8;
+
+    private int mStencilBits;
+
+    private final int mConfigSpec[] = new int[] {
+            EGL10.EGL_RED_SIZE, COLOR_BITS,
+            EGL10.EGL_GREEN_SIZE, COLOR_BITS,
+            EGL10.EGL_BLUE_SIZE, COLOR_BITS,
+            EGL10.EGL_ALPHA_SIZE, COLOR_BITS,
+            EGL10.EGL_NONE
+    };
+
+    public int getStencilBits() {
+        return mStencilBits;
+    }
+
+    public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
+        int[] numConfig = new int[1];
+        if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, numConfig)) {
+            throw new RuntimeException("eglChooseConfig failed");
+        }
+
+        if (numConfig[0] <= 0) {
+            throw new RuntimeException("No configs match configSpec");
+        }
+
+        EGLConfig[] configs = new EGLConfig[numConfig[0]];
+        if (!egl.eglChooseConfig(display,
+                mConfigSpec, configs, configs.length, numConfig)) {
+            throw new RuntimeException();
+        }
+
+        return chooseConfig(egl, display, configs);
+    }
+
+    private EGLConfig chooseConfig(
+            EGL10 egl, EGLDisplay display, EGLConfig configs[]) {
+
+        EGLConfig result = null;
+        int minStencil = Integer.MAX_VALUE;
+        int value[] = new int[1];
+
+        // Because we need only one bit of stencil, try to choose a config that
+        // has stencil support but with smallest number of stencil bits. If
+        // none is found, choose any one.
+        for (int i = 0, n = configs.length; i < n; ++i) {
+            if (egl.eglGetConfigAttrib(
+                    display, configs[i], EGL10.EGL_STENCIL_SIZE, value)) {
+                if (value[0] == 0) continue;
+                if (value[0] < minStencil) {
+                    minStencil = value[0];
+                    result = configs[i];
+                }
+            } else {
+                throw new RuntimeException(
+                        "eglGetConfigAttrib error: " + egl.eglGetError());
+            }
+        }
+        if (result == null) result = configs[0];
+        egl.eglGetConfigAttrib(
+                display, result, EGL10.EGL_STENCIL_SIZE, value);
+        mStencilBits = value[0];
+        return result;
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/CanvasTexture.java b/new3d/src/com/android/gallery3d/ui/CanvasTexture.java
new file mode 100644
index 0000000..ab1829b
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/CanvasTexture.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Bitmap.Config;
+
+/** Using a canvas to draw the texture */
+abstract class CanvasTexture extends BitmapTexture {
+    protected Canvas mCanvas;
+
+    public CanvasTexture(int width, int height) {
+        setSize(width, height);
+    }
+
+    @Override
+    protected Bitmap getBitmap() {
+        Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Config.ARGB_8888);
+        mCanvas = new Canvas(bitmap);
+        onDraw(mCanvas, bitmap);
+        return bitmap;
+    }
+
+    @Override
+    protected void freeBitmap(Bitmap bitmap) {
+        bitmap.recycle();
+    }
+
+    abstract protected void onDraw(Canvas canvas, Bitmap backing);
+}
diff --git a/new3d/src/com/android/gallery3d/ui/ColorTexture.java b/new3d/src/com/android/gallery3d/ui/ColorTexture.java
new file mode 100644
index 0000000..a037685
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/ColorTexture.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+class ColorTexture implements Texture {
+
+    private int mColor;
+
+    public ColorTexture(int color) {
+        mColor = color;
+    }
+
+    public void draw(GLRootView root, int x, int y) {
+    }
+
+    public void draw(GLRootView root, int x, int y, int w, int h) {
+        root.drawColor(x, y, w, h, mColor);
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/DisplayItemPanel.java b/new3d/src/com/android/gallery3d/ui/DisplayItemPanel.java
new file mode 100644
index 0000000..1b25fc9
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/DisplayItemPanel.java
@@ -0,0 +1,5 @@
+package com.android.gallery3d.ui;
+
+public class DisplayItemPanel {
+
+}
diff --git a/new3d/src/com/android/gallery3d/ui/GLOutOfMemoryException.java b/new3d/src/com/android/gallery3d/ui/GLOutOfMemoryException.java
new file mode 100644
index 0000000..12d0ec5
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/GLOutOfMemoryException.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+public class GLOutOfMemoryException extends Exception {
+}
diff --git a/new3d/src/com/android/gallery3d/ui/GLRootView.java b/new3d/src/com/android/gallery3d/ui/GLRootView.java
new file mode 100644
index 0000000..9fb4f7e
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/GLRootView.java
@@ -0,0 +1,729 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.opengl.GLSurfaceView;
+import android.opengl.GLU;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Stack;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11Ext;
+
+// The root component of all <code>GLView</code>s. The rendering is done in GL
+// thread while the event handling is done in the main thread.  To synchronize
+// the two threads, the entry points of this package need to synchronize on the
+// <code>GLRootView</code> instance unless it can be proved that the rendering
+// thread won't access the same thing as the method. The entry points include:
+// (1) The public methods of HeadUpDisplay
+// (2) The public methods of CameraHeadUpDisplay
+// (3) The overridden methods in GLRootView.
+public class GLRootView extends GLSurfaceView
+        implements GLSurfaceView.Renderer {
+    private static final String TAG = "GLRootView";
+
+    private final boolean ENABLE_FPS_TEST = false;
+    private int mFrameCount = 0;
+    private long mFrameCountingStart = 0;
+
+    // We need 16 vertices for a normal nine-patch image (the 4x4 vertices)
+    private static final int VERTEX_BUFFER_SIZE = 16 * 2;
+
+    // We need 22 indices for a normal nine-patch image
+    private static final int INDEX_BUFFER_SIZE = 22;
+
+    private static final int FLAG_INITIALIZED = 1;
+    private static final int FLAG_NEED_LAYOUT = 2;
+
+    private static boolean mTexture2DEnabled;
+
+    private static float sPixelDensity = -1f;
+
+    private GL11 mGL;
+    private GLView mContentView;
+    private DisplayMetrics mDisplayMetrics;
+
+    private final ArrayList<Animation> mAnimations = new ArrayList<Animation>();
+
+    private final Stack<Transformation> mFreeTransform =
+            new Stack<Transformation>();
+
+    private final Transformation mTransformation = new Transformation();
+    private final Stack<Transformation> mTransformStack =
+            new Stack<Transformation>();
+
+    private float mLastAlpha = mTransformation.getAlpha();
+
+    private final float mMatrixValues[] = new float[16];
+
+    private final float mUvBuffer[] = new float[VERTEX_BUFFER_SIZE];
+    private final float mXyBuffer[] = new float[VERTEX_BUFFER_SIZE];
+    private final byte mIndexBuffer[] = new byte[INDEX_BUFFER_SIZE];
+
+    private int mNinePatchX[] = new int[4];
+    private int mNinePatchY[] = new int[4];
+    private float mNinePatchU[] = new float[4];
+    private float mNinePatchV[] = new float[4];
+
+    private ByteBuffer mXyPointer;
+    private ByteBuffer mUvPointer;
+    private ByteBuffer mIndexPointer;
+
+    private int mFlags = FLAG_NEED_LAYOUT;
+    private long mAnimationTime;
+
+    private CameraEGLConfigChooser mEglConfigChooser = new CameraEGLConfigChooser();
+
+    public GLRootView(Context context) {
+        this(context, null);
+    }
+
+    public GLRootView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initialize();
+    }
+
+    void registerLaunchedAnimation(Animation animation) {
+        // Register the newly launched animation so that we can set the start
+        // time more precisely. (Usually, it takes much longer for the first
+        // rendering, so we set the animation start time as the time we
+        // complete rendering)
+        mAnimations.add(animation);
+    }
+
+    public long currentAnimationTimeMillis() {
+        return mAnimationTime;
+    }
+
+    public synchronized static float dpToPixel(Context context, float dp) {
+        if (sPixelDensity < 0) {
+            DisplayMetrics metrics = new DisplayMetrics();
+            ((Activity) context).getWindowManager()
+                    .getDefaultDisplay().getMetrics(metrics);
+            sPixelDensity =  metrics.density;
+        }
+        return sPixelDensity * dp;
+    }
+
+    public static int dpToPixel(Context context, int dp) {
+        return (int)(dpToPixel(context, (float) dp) + .5f);
+    }
+
+    public Transformation obtainTransformation() {
+        if (!mFreeTransform.isEmpty()) {
+            Transformation t = mFreeTransform.pop();
+            t.clear();
+            return t;
+        }
+        return new Transformation();
+    }
+
+    public void freeTransformation(Transformation freeTransformation) {
+        mFreeTransform.push(freeTransformation);
+    }
+
+    public Transformation getTransformation() {
+        return mTransformation;
+    }
+
+    public Transformation pushTransform() {
+        Transformation trans = obtainTransformation();
+        trans.set(mTransformation);
+        mTransformStack.push(trans);
+        return mTransformation;
+    }
+
+    public void popTransform() {
+        Transformation trans = mTransformStack.pop();
+        mTransformation.set(trans);
+        freeTransformation(trans);
+    }
+
+    public CameraEGLConfigChooser getEGLConfigChooser() {
+        return mEglConfigChooser;
+    }
+
+    private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
+        return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
+    }
+
+    private void initialize() {
+        mFlags |= FLAG_INITIALIZED;
+        setEGLConfigChooser(mEglConfigChooser);
+        getHolder().setFormat(PixelFormat.TRANSLUCENT);
+        setZOrderOnTop(true);
+
+        setRenderer(this);
+
+        int size = VERTEX_BUFFER_SIZE * Float.SIZE / Byte.SIZE;
+        mXyPointer = allocateDirectNativeOrderBuffer(size);
+        mUvPointer = allocateDirectNativeOrderBuffer(size);
+        mIndexPointer = allocateDirectNativeOrderBuffer(INDEX_BUFFER_SIZE);
+    }
+
+    public void setContentPane(GLView content) {
+        mContentView = content;
+        content.onAttachToRoot(this);
+
+        // no parent for the content pane
+        content.onAddToParent(null);
+        requestLayoutContentPane();
+    }
+
+    public GLView getContentPane() {
+        return mContentView;
+    }
+
+    void handleLowMemory() {
+        //TODO: delete texture from GL
+    }
+
+    public synchronized void requestLayoutContentPane() {
+        if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;
+
+        // "View" system will invoke onLayout() for initialization(bug ?), we
+        // have to ignore it since the GLThread is not ready yet.
+        if ((mFlags & FLAG_INITIALIZED) == 0) return;
+
+        mFlags |= FLAG_NEED_LAYOUT;
+        requestRender();
+    }
+
+    private synchronized void layoutContentPane() {
+        mFlags &= ~FLAG_NEED_LAYOUT;
+        int width = getWidth();
+        int height = getHeight();
+        Log.v(TAG, "layout content pane " + width + "x" + height);
+        if (mContentView != null && width != 0 && height != 0) {
+            mContentView.layout(0, 0, width, height);
+        }
+    }
+
+    @Override
+    protected void onLayout(
+            boolean changed, int left, int top, int right, int bottom) {
+        if (changed) requestLayoutContentPane();
+    }
+
+    /**
+     * Called when the context is created, possibly after automatic destruction.
+     */
+    // This is a GLSurfaceView.Renderer callback
+    public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
+        GL11 gl = (GL11) gl1;
+        if (mGL != null) {
+            // The GL Object has changed
+            Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
+        }
+        mGL = gl;
+
+        if (!ENABLE_FPS_TEST) {
+            setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+        } else {
+            setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+        }
+
+        // Disable unused state
+        gl.glDisable(GL11.GL_LIGHTING);
+
+        // Enable used features
+        gl.glEnable(GL11.GL_BLEND);
+        gl.glEnable(GL11.GL_SCISSOR_TEST);
+        gl.glEnable(GL11.GL_STENCIL_TEST);
+        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+        gl.glEnable(GL11.GL_TEXTURE_2D);
+        mTexture2DEnabled = true;
+
+        gl.glTexEnvf(GL11.GL_TEXTURE_ENV,
+                GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE);
+
+        // Set the background color
+        gl.glClearColor(0f, 0f, 0f, 0f);
+        gl.glClearStencil(0);
+
+        gl.glVertexPointer(2, GL11.GL_FLOAT, 0, mXyPointer);
+        gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, mUvPointer);
+
+    }
+
+    /**
+     * Called when the OpenGL surface is recreated without destroying the
+     * context.
+     */
+    // This is a GLSurfaceView.Renderer callback
+    public void onSurfaceChanged(GL10 gl1, int width, int height) {
+        Log.v(TAG, "onSurfaceChanged: " + width + "x" + height
+                + ", gl10: " + gl1.toString());
+        GL11 gl = (GL11) gl1;
+        mGL = gl;
+        gl.glViewport(0, 0, width, height);
+
+        gl.glMatrixMode(GL11.GL_PROJECTION);
+        gl.glLoadIdentity();
+
+        GLU.gluOrtho2D(gl, 0, width, 0, height);
+        Matrix matrix = mTransformation.getMatrix();
+        matrix.reset();
+        matrix.preTranslate(0, getHeight());
+        matrix.preScale(1, -1);
+    }
+
+    private void setAlphaValue(float alpha) {
+        if (mLastAlpha == alpha) return;
+
+        GL11 gl = mGL;
+        mLastAlpha = alpha;
+        if (alpha >= 0.95f) {
+            gl.glTexEnvf(GL11.GL_TEXTURE_ENV,
+                    GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE);
+        } else {
+            gl.glTexEnvf(GL11.GL_TEXTURE_ENV,
+                    GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
+            gl.glColor4f(alpha, alpha, alpha, alpha);
+        }
+    }
+
+    public void drawRect(int x, int y, int width, int height) {
+        float matrix[] = mMatrixValues;
+        mTransformation.getMatrix().getValues(matrix);
+        drawRect(x, y, width, height, matrix);
+    }
+
+    private static void putRectangle(float x, float y,
+            float width, float height, float[] buffer, ByteBuffer pointer) {
+        buffer[0] = x;
+        buffer[1] = y;
+        buffer[2] = x + width;
+        buffer[3] = y;
+        buffer[4] = x;
+        buffer[5] = y + height;
+        buffer[6] = x + width;
+        buffer[7] = y + height;
+        pointer.asFloatBuffer().put(buffer, 0, 8).position(0);
+    }
+
+    private void drawRect(
+            int x, int y, int width, int height, float matrix[]) {
+        GL11 gl = mGL;
+        gl.glPushMatrix();
+        gl.glMultMatrixf(toGLMatrix(matrix), 0);
+        putRectangle(x, y, width, height, mXyBuffer, mXyPointer);
+        gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, 4);
+        gl.glPopMatrix();
+    }
+
+    public void drawNinePatch(
+            NinePatchTexture tex, int x, int y, int width, int height) {
+
+        NinePatchChunk chunk = tex.getNinePatchChunk();
+
+        // The code should be easily extended to handle the general cases by
+        // allocating more space for buffers. But let's just handle the only
+        // use case.
+        if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) {
+            throw new RuntimeException("unsupported nine patch");
+        }
+        if (!tex.bind(this, mGL)) {
+            throw new RuntimeException("cannot bind" + tex.toString());
+        }
+        if (width <= 0 || height <= 0) return ;
+
+        int divX[] = mNinePatchX;
+        int divY[] = mNinePatchY;
+        float divU[] = mNinePatchU;
+        float divV[] = mNinePatchV;
+
+        int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
+        int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
+
+        setAlphaValue(mTransformation.getAlpha());
+        Matrix matrix = mTransformation.getMatrix();
+        matrix.getValues(mMatrixValues);
+        GL11 gl = mGL;
+        gl.glPushMatrix();
+        gl.glMultMatrixf(toGLMatrix(mMatrixValues), 0);
+        gl.glTranslatef(x, y, 0);
+        drawMesh(divX, divY, divU, divV, nx, ny);
+        gl.glPopMatrix();
+    }
+
+    /**
+     * Stretches the texture according to the nine-patch rules. It will
+     * linearly distribute the strechy parts defined in the nine-patch chunk to
+     * the target area.
+     *
+     * <pre>
+     *                      source
+     *          /--------------^---------------\
+     *         u0    u1       u2  u3     u4   u5
+     * div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
+     *          |    div0    div1 div2   div3  |
+     *          |     |       /   /      /    /
+     *          |     |      /   /     /    /
+     *          |     |     /   /    /    /
+     *          |fffff|ssss|fff|sss|ffff| ---> x
+     *         x0    x1   x2  x3  x4   x5
+     *          \----------v------------/
+     *                  target
+     *
+     * f: fixed segment
+     * s: stretchy segment
+     * </pre>
+     *
+     * @param div the stretch parts defined in nine-patch chunk
+     * @param source the length of the texture
+     * @param target the length on the drawing plan
+     * @param u output, the positions of these dividers in the texture
+     *        coordinate
+     * @param x output, the corresponding position of these dividers on the
+     *        drawing plan
+     * @return the number of these dividers.
+     */
+    private int stretch(
+            int x[], float u[], int div[], int source, int target) {
+        int textureSize = Util.nextPowerOf2(source);
+        float textureBound = (source - 0.5f) / textureSize;
+
+        int stretch = 0;
+        for (int i = 0, n = div.length; i < n; i += 2) {
+            stretch += div[i + 1] - div[i];
+        }
+
+        float remaining = target - source + stretch;
+
+        int lastX = 0;
+        int lastU = 0;
+
+        x[0] = 0;
+        u[0] = 0;
+        for (int i = 0, n = div.length; i < n; i += 2) {
+            // fixed segment
+            x[i + 1] = lastX + (div[i] - lastU);
+            u[i + 1] = Math.min((float) div[i] / textureSize, textureBound);
+
+            // stretchy segment
+            float partU = div[i + 1] - div[i];
+            int partX = (int)(remaining * partU / stretch + 0.5f);
+            remaining -= partX;
+            stretch -= partU;
+
+            lastX = x[i + 1] + partX;
+            lastU = div[i + 1];
+            x[i + 2] = lastX;
+            u[i + 2] = Math.min((float) lastU / textureSize, textureBound);
+        }
+        // the last fixed segment
+        x[div.length + 1] = target;
+        u[div.length + 1] = textureBound;
+
+        // remove segments with length 0.
+        int last = 0;
+        for (int i = 1, n = div.length + 2; i < n; ++i) {
+            if (x[last] == x[i]) continue;
+            x[++last] = x[i];
+            u[last] = u[i];
+        }
+        return last + 1;
+    }
+
+    private void drawMesh(
+            int x[], int y[], float u[], float v[], int nx, int ny) {
+        /*
+         * Given a 3x3 nine-patch image, the vertex order is defined as the
+         * following graph:
+         *
+         * (0) (1) (2) (3)
+         *  |  /|  /|  /|
+         *  | / | / | / |
+         * (4) (5) (6) (7)
+         *  | \ | \ | \ |
+         *  |  \|  \|  \|
+         * (8) (9) (A) (B)
+         *  |  /|  /|  /|
+         *  | / | / | / |
+         * (C) (D) (E) (F)
+         *
+         * And we draw the triangle strip in the following index order:
+         *
+         * index: 04152637B6A5948C9DAEBF
+         */
+        int pntCount = 0;
+        float xy[] = mXyBuffer;
+        float uv[] = mUvBuffer;
+        for (int j = 0; j < ny; ++j) {
+            for (int i = 0; i < nx; ++i) {
+                int xIndex = (pntCount++) << 1;
+                int yIndex = xIndex + 1;
+                xy[xIndex] = x[i];
+                xy[yIndex] = y[j];
+                uv[xIndex] = u[i];
+                uv[yIndex] = v[j];
+            }
+        }
+        mUvPointer.asFloatBuffer().put(uv, 0, pntCount << 1).position(0);
+        mXyPointer.asFloatBuffer().put(xy, 0, pntCount << 1).position(0);
+
+        int idxCount = 1;
+        byte index[] = mIndexBuffer;
+        for (int i = 0, bound = nx * (ny - 1); true;) {
+            // normal direction
+            --idxCount;
+            for (int j = 0; j < nx; ++j, ++i) {
+                index[idxCount++] = (byte) i;
+                index[idxCount++] = (byte) (i + nx);
+            }
+            if (i >= bound) break;
+
+            // reverse direction
+            int sum = i + i + nx - 1;
+            --idxCount;
+            for (int j = 0; j < nx; ++j, ++i) {
+                index[idxCount++] = (byte) (sum - i);
+                index[idxCount++] = (byte) (sum - i + nx);
+            }
+            if (i >= bound) break;
+        }
+        mIndexPointer.put(index, 0, idxCount).position(0);
+
+        mGL.glDrawElements(GL11.GL_TRIANGLE_STRIP,
+                idxCount, GL11.GL_UNSIGNED_BYTE, mIndexPointer);
+    }
+
+    private float[] mapPoints(Matrix matrix, int x1, int y1, int x2, int y2) {
+        float[] point = mXyBuffer;
+        point[0] = x1; point[1] = y1; point[2] = x2; point[3] = y2;
+        matrix.mapPoints(point, 0, point, 0, 4);
+        return point;
+    }
+
+    public void clipRect(int x, int y, int width, int height) {
+        float point[] = mapPoints(
+                mTransformation.getMatrix(), x, y + height, x + width, y);
+
+        // mMatrix could be a rotation matrix. In this case, we need to find
+        // the boundaries after rotation. (only handle 90 * n degrees)
+        if (point[0] > point[2]) {
+            x = (int) point[2];
+            width = (int) point[0] - x;
+        } else {
+            x = (int) point[0];
+            width = (int) point[2] - x;
+        }
+        if (point[1] > point[3]) {
+            y = (int) point[3];
+            height = (int) point[1] - y;
+        } else {
+            y = (int) point[1];
+            height = (int) point[3] - y;
+        }
+        mGL.glScissor(x, y, width, height);
+    }
+
+    public void clearClip() {
+        mGL.glScissor(0, 0, getWidth(), getHeight());
+    }
+
+    private static float[] toGLMatrix(float v[]) {
+        v[15] = v[8]; v[13] = v[5]; v[5] = v[4]; v[4] = v[1];
+        v[12] = v[2]; v[1] = v[3]; v[3] = v[6];
+        v[2] = v[6] = v[8] = v[9] = 0;
+        v[10] = 1;
+        return v;
+    }
+
+    public void drawColor(int x, int y, int width, int height, int color) {
+        float alpha = mTransformation.getAlpha();
+        GL11 gl = mGL;
+        if (mTexture2DEnabled) {
+            // Set mLastAlpha to an invalid value, so that it will reset again
+            // in setAlphaValue(float) later.
+            mLastAlpha = -1.0f;
+            gl.glDisable(GL11.GL_TEXTURE_2D);
+            mTexture2DEnabled = false;
+        }
+        alpha /= 256.0f;
+        gl.glColor4f(Color.red(color) * alpha, Color.green(color) * alpha,
+                Color.blue(color) * alpha, Color.alpha(color) * alpha);
+        drawRect(x, y, width, height);
+    }
+
+    public void drawTexture(
+            BasicTexture texture, int x, int y, int width, int height) {
+        drawTexture(texture, x, y, width, height, mTransformation.getAlpha());
+    }
+
+    public void drawTexture(BasicTexture texture,
+            int x, int y, int width, int height, float alpha) {
+
+        if (!mTexture2DEnabled) {
+            mGL.glEnable(GL11.GL_TEXTURE_2D);
+            mTexture2DEnabled = true;
+        }
+
+        if (!texture.bind(this, mGL)) {
+            throw new RuntimeException("cannot bind" + texture.toString());
+        }
+        if (width <= 0 || height <= 0) return ;
+
+        Matrix matrix = mTransformation.getMatrix();
+        matrix.getValues(mMatrixValues);
+
+        // Test whether it has been rotated or flipped, if so, glDrawTexiOES
+        // won't work
+        if (isMatrixRotatedOrFlipped(mMatrixValues)) {
+            putRectangle(0, 0,
+                    (texture.mWidth - 0.5f) / texture.mTextureWidth,
+                    (texture.mHeight - 0.5f) / texture.mTextureHeight,
+                    mUvBuffer, mUvPointer);
+            setAlphaValue(alpha);
+            drawRect(x, y, width, height, mMatrixValues);
+        } else {
+            // draw the rect from bottom-left to top-right
+            float points[] = mapPoints(matrix, x, y + height, x + width, y);
+            x = (int) points[0];
+            y = (int) points[1];
+            width = (int) points[2] - x;
+            height = (int) points[3] - y;
+            if (width > 0 && height > 0) {
+                setAlphaValue(alpha);
+                ((GL11Ext) mGL).glDrawTexiOES(x, y, 0, width, height);
+            }
+        }
+    }
+
+    private static boolean isMatrixRotatedOrFlipped(float matrix[]) {
+        return matrix[Matrix.MSKEW_X] != 0 || matrix[Matrix.MSKEW_Y] != 0
+                || matrix[Matrix.MSCALE_X] < 0 || matrix[Matrix.MSCALE_Y] > 0;
+    }
+
+    public synchronized void onDrawFrame(GL10 gl) {
+        if (ENABLE_FPS_TEST) {
+            long now = System.nanoTime();
+            if (mFrameCountingStart == 0) {
+                mFrameCountingStart = now;
+            } else if ((now - mFrameCountingStart) > 1000000000) {
+                Log.v(TAG, "fps: " + (double) mFrameCount
+                        * 1000000000 / (now - mFrameCountingStart));
+                mFrameCountingStart = now;
+                mFrameCount = 0;
+            }
+            ++mFrameCount;
+        }
+
+        if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane();
+        clearClip();
+        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_STENCIL_BUFFER_BIT);
+        gl.glEnable(GL11.GL_BLEND);
+        gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
+
+        mAnimationTime = SystemClock.uptimeMillis();
+        if (mContentView != null) {
+            mContentView.render(GLRootView.this, (GL11) gl);
+        }
+        long now = SystemClock.uptimeMillis();
+        for (Animation animation : mAnimations) {
+            animation.setStartTime(now);
+        }
+        mAnimations.clear();
+    }
+
+    @Override
+    public synchronized boolean dispatchTouchEvent(MotionEvent event) {
+        // If this has been detached from root, we don't need to handle event
+        return mContentView != null
+                ? mContentView.dispatchTouchEvent(event)
+                : false;
+    }
+
+    public DisplayMetrics getDisplayMetrics() {
+        if (mDisplayMetrics == null) {
+            mDisplayMetrics = new DisplayMetrics();
+            ((Activity) getContext()).getWindowManager()
+                    .getDefaultDisplay().getMetrics(mDisplayMetrics);
+        }
+        return mDisplayMetrics;
+    }
+
+    public void copyTexture2D(
+            RawTexture texture, int x, int y, int width, int height)
+            throws GLOutOfMemoryException {
+        Matrix matrix = mTransformation.getMatrix();
+        matrix.getValues(mMatrixValues);
+
+        if (isMatrixRotatedOrFlipped(mMatrixValues)) {
+            throw new IllegalArgumentException("cannot support rotated matrix");
+        }
+        float points[] = mapPoints(matrix, x, y + height, x + width, y);
+        x = (int) points[0];
+        y = (int) points[1];
+        width = (int) points[2] - x;
+        height = (int) points[3] - y;
+
+        GL11 gl = mGL;
+        int newWidth = Util.nextPowerOf2(width);
+        int newHeight = Util.nextPowerOf2(height);
+        int glError = GL11.GL_NO_ERROR;
+
+        gl.glBindTexture(GL11.GL_TEXTURE_2D, texture.getId());
+
+        int[] cropRect = {0,  0, width, height};
+        gl.glTexParameteriv(GL11.GL_TEXTURE_2D,
+                GL11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect, 0);
+        gl.glTexParameteri(GL11.GL_TEXTURE_2D,
+                GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
+        gl.glTexParameteri(GL11.GL_TEXTURE_2D,
+                GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
+        gl.glTexParameterf(GL11.GL_TEXTURE_2D,
+                GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
+        gl.glTexParameterf(GL11.GL_TEXTURE_2D,
+                GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
+        gl.glCopyTexImage2D(GL11.GL_TEXTURE_2D, 0,
+                GL11.GL_RGBA, x, y, newWidth, newHeight, 0);
+        glError = gl.glGetError();
+
+        if (glError == GL11.GL_OUT_OF_MEMORY) {
+            throw new GLOutOfMemoryException();
+        }
+
+        if (glError != GL11.GL_NO_ERROR) {
+            throw new RuntimeException(
+                    "Texture copy fail, glError " + glError);
+        }
+
+        texture.setSize(width, height);
+        texture.setTextureSize(newWidth, newHeight);
+    }
+
+}
diff --git a/new3d/src/com/android/gallery3d/ui/GLView.java b/new3d/src/com/android/gallery3d/ui/GLView.java
new file mode 100644
index 0000000..76fbf59
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/GLView.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+import java.util.ArrayList;
+
+import javax.microedition.khronos.opengles.GL11;
+
+public class GLView {
+    @SuppressWarnings("unused")
+    private static final String TAG = "GLView";
+
+    public static final int VISIBLE = 0;
+    public static final int INVISIBLE = 1;
+
+    public static final int FLAG_INVISIBLE = 1;
+    public static final int FLAG_SET_MEASURED_SIZE = 2;
+    public static final int FLAG_LAYOUT_REQUESTED = 4;
+
+    protected final Rect mBounds = new Rect();
+    protected final Rect mPaddings = new Rect();
+
+    private GLRootView mRootView;
+    private GLView mParent;
+    private ArrayList<GLView> mComponents;
+    private GLView mMotionTarget;
+
+    private OnTouchListener mOnTouchListener;
+    private Animation mAnimation;
+
+    protected int mViewFlags = 0;
+
+    protected int mMeasuredWidth = 0;
+    protected int mMeasuredHeight = 0;
+
+    private int mLastWidthSpec = -1;
+    private int mLastHeightSpec = -1;
+
+    protected int mScrollY = 0;
+    protected int mScrollX = 0;
+    protected int mScrollHeight = 0;
+    protected int mScrollWidth = 0;
+
+    public void startAnimation(Animation animation) {
+        GLRootView root = getGLRootView();
+        if (root == null) throw new IllegalStateException();
+
+        mAnimation = animation;
+        animation.initialize(getWidth(),
+                getHeight(), mParent.getWidth(), mParent.getHeight());
+        mAnimation.start();
+        root.registerLaunchedAnimation(animation);
+        invalidate();
+    }
+
+    public void setVisibility(int visibility) {
+        if (visibility == getVisibility()) return;
+        if (visibility == VISIBLE) {
+            mViewFlags &= ~FLAG_INVISIBLE;
+        } else {
+            mViewFlags |= FLAG_INVISIBLE;
+        }
+        onVisibilityChanged(visibility);
+        invalidate();
+    }
+
+    public int getVisibility() {
+        return (mViewFlags & FLAG_INVISIBLE) == 0 ? VISIBLE : INVISIBLE;
+    }
+
+    public static interface OnTouchListener {
+        public boolean onTouch(GLView view, MotionEvent event);
+    }
+
+    private boolean setBounds(int left, int top, int right, int bottom) {
+        boolean sizeChanged = (right - left) != (mBounds.right - mBounds.left)
+                || (bottom - top) != (mBounds.bottom - mBounds.top);
+        mBounds.set(left, top, right, bottom);
+        return sizeChanged;
+    }
+
+    protected void onAddToParent(GLView parent) {
+        // TODO: enable the check
+        // if (mParent != null) throw new IllegalStateException();
+        mParent = parent;
+        if (parent != null && parent.mRootView != null) {
+            onAttachToRoot(parent.mRootView);
+        }
+    }
+
+    protected void onRemoveFromParent(GLView parent) {
+        if (parent != null && parent.mMotionTarget == this) {
+            long now = SystemClock.uptimeMillis();
+            dispatchTouchEvent(MotionEvent.obtain(
+                    now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0));
+            parent.mMotionTarget = null;
+        }
+        onDetachFromRoot();
+        mParent = null;
+    }
+
+    public void clearComponents() {
+        mComponents = null;
+    }
+
+    public int getComponentCount() {
+        return mComponents == null ? 0 : mComponents.size();
+    }
+
+    public GLView getComponent(int index) {
+        if (mComponents == null) {
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        return mComponents.get(index);
+    }
+
+    public void addComponent(GLView component) {
+        if (mComponents == null) {
+            mComponents = new ArrayList<GLView>();
+        }
+        mComponents.add(component);
+        component.onAddToParent(this);
+    }
+
+    public boolean removeComponent(GLView component) {
+        if (mComponents == null) return false;
+        if (mComponents.remove(component)) {
+            component.onRemoveFromParent(this);
+            return true;
+        }
+        return false;
+    }
+
+    public Rect bounds() {
+        return mBounds;
+    }
+
+    public int getWidth() {
+        return mBounds.right - mBounds.left;
+    }
+
+    public int getHeight() {
+        return mBounds.bottom - mBounds.top;
+    }
+
+    public GLRootView getGLRootView() {
+        return mRootView;
+    }
+
+    public void setOnTouchListener(OnTouchListener listener) {
+        mOnTouchListener = listener;
+    }
+
+    public void invalidate() {
+        GLRootView root = getGLRootView();
+        if (root != null) root.requestRender();
+    }
+
+    public void requestLayout() {
+        mViewFlags |= FLAG_LAYOUT_REQUESTED;
+        if (mParent != null) {
+            mParent.requestLayout();
+        } else {
+            // Is this a content pane ?
+            GLRootView root = getGLRootView();
+            if (root != null) root.requestLayoutContentPane();
+        }
+    }
+
+    protected void render(GLRootView view, GL11 gl) {
+        renderBackground(view, gl);
+        for (int i = 0, n = getComponentCount(); i < n; ++i) {
+            GLView component = getComponent(i);
+            if (component.getVisibility() != GLView.VISIBLE
+                    && component.mAnimation == null) continue;
+            renderChild(view, gl, component);
+        }
+    }
+
+    protected void renderBackground(GLRootView view, GL11 gl) {
+    }
+
+    protected void renderChild(GLRootView root, GL11 gl, GLView component) {
+        int xoffset = component.mBounds.left - mScrollX;
+        int yoffset = component.mBounds.top - mScrollY;
+
+        Transformation transform = root.getTransformation();
+        Matrix matrix = transform.getMatrix();
+        matrix.preTranslate(xoffset, yoffset);
+
+        Animation anim = component.mAnimation;
+        if (anim != null) {
+            long now = root.currentAnimationTimeMillis();
+            Transformation temp = root.obtainTransformation();
+            if (!anim.getTransformation(now, temp)) {
+                component.mAnimation = null;
+            }
+            invalidate();
+            root.pushTransform();
+            transform.compose(temp);
+            root.freeTransformation(temp);
+        }
+        component.render(root, gl);
+        if (anim != null) root.popTransform();
+        matrix.preTranslate(-xoffset, -yoffset);
+    }
+
+    protected boolean onTouch(MotionEvent event) {
+        if (mOnTouchListener != null) {
+            return mOnTouchListener.onTouch(this, event);
+        }
+        return false;
+    }
+
+    private boolean dispatchTouchEvent(MotionEvent event,
+            int x, int y, GLView component, boolean checkBounds) {
+        Rect rect = component.mBounds;
+        int left = rect.left;
+        int top = rect.top;
+        if (!checkBounds || rect.contains(x, y)) {
+            event.offsetLocation(-left, -top);
+            if (component.dispatchTouchEvent(event)) {
+                event.offsetLocation(left, top);
+                return true;
+            }
+            event.offsetLocation(left, top);
+        }
+        return false;
+    }
+
+    protected boolean dispatchTouchEvent(MotionEvent event) {
+        int x = (int) event.getX();
+        int y = (int) event.getY();
+        int action = event.getAction();
+        if (mMotionTarget != null) {
+            if (action == MotionEvent.ACTION_DOWN) {
+                MotionEvent cancel = MotionEvent.obtain(event);
+                cancel.setAction(MotionEvent.ACTION_CANCEL);
+                mMotionTarget = null;
+            } else {
+                dispatchTouchEvent(event, x, y, mMotionTarget, false);
+                if (action == MotionEvent.ACTION_CANCEL
+                        || action == MotionEvent.ACTION_UP) {
+                    mMotionTarget = null;
+                }
+                return true;
+            }
+        }
+        if (action == MotionEvent.ACTION_DOWN) {
+            for (int i = 0, n = getComponentCount(); i < n; ++i) {
+                GLView component = getComponent(i);
+                if (component.getVisibility() != GLView.VISIBLE) continue;
+                if (dispatchTouchEvent(event, x, y, component, true)) {
+                    mMotionTarget = component;
+                    return true;
+                }
+            }
+        }
+        return onTouch(event);
+    }
+
+    public Rect getPaddings() {
+        return mPaddings;
+    }
+
+    public void setPaddings(Rect paddings) {
+        mPaddings.set(paddings);
+    }
+
+    public void setPaddings(int left, int top, int right, int bottom) {
+        mPaddings.set(left, top, right, bottom);
+    }
+
+    public void layout(int left, int top, int right, int bottom) {
+        boolean sizeChanged = setBounds(left, top, right, bottom);
+        if (sizeChanged) {
+            mViewFlags &= ~FLAG_LAYOUT_REQUESTED;
+            onLayout(true, left, top, right, bottom);
+        } else if ((mViewFlags & FLAG_LAYOUT_REQUESTED)!= 0) {
+            mViewFlags &= ~FLAG_LAYOUT_REQUESTED;
+            onLayout(false, left, top, right, bottom);
+        }
+    }
+
+    public void measure(int widthSpec, int heightSpec) {
+        if (widthSpec == mLastWidthSpec && heightSpec == mLastHeightSpec
+                && (mViewFlags & FLAG_LAYOUT_REQUESTED) == 0) {
+            return;
+        }
+
+        mLastWidthSpec = widthSpec;
+        mLastHeightSpec = heightSpec;
+
+        mViewFlags &= ~FLAG_SET_MEASURED_SIZE;
+        onMeasure(widthSpec, heightSpec);
+        if ((mViewFlags & FLAG_SET_MEASURED_SIZE) == 0) {
+            throw new IllegalStateException(getClass().getName()
+                    + " should call setMeasuredSize() in onMeasure()");
+        }
+    }
+
+    protected void onMeasure(int widthSpec, int heightSpec) {
+    }
+
+    protected void setMeasuredSize(int width, int height) {
+        mViewFlags |= FLAG_SET_MEASURED_SIZE;
+        mMeasuredWidth = width;
+        mMeasuredHeight = height;
+    }
+
+    public int getMeasuredWidth() {
+        return mMeasuredWidth;
+    }
+
+    public int getMeasuredHeight() {
+        return mMeasuredHeight;
+    }
+
+    protected void onLayout(
+            boolean changeSize, int left, int top, int right, int bottom) {
+    }
+
+    /**
+     * Gets the bounds of the given descendant that relative to this view.
+     */
+    public boolean getBoundsOf(GLView descendant, Rect out) {
+        int xoffset = 0;
+        int yoffset = 0;
+        GLView view = descendant;
+        while (view != this) {
+            if (view == null) return false;
+            Rect bounds = view.mBounds;
+            xoffset += bounds.left;
+            yoffset += bounds.top;
+            view = view.mParent;
+        }
+        out.set(xoffset, yoffset, xoffset + descendant.getWidth(),
+                yoffset + descendant.getHeight());
+        return true;
+    }
+
+    protected void onVisibilityChanged(int visibility) {
+        for (int i = 0, n = getComponentCount(); i < n; ++i) {
+            GLView child = getComponent(i);
+            if (child.getVisibility() == GLView.VISIBLE) {
+                child.onVisibilityChanged(visibility);
+            }
+        }
+    }
+
+    protected void onAttachToRoot(GLRootView root) {
+        mRootView = root;
+        for (int i = 0, n = getComponentCount(); i < n; ++i) {
+            getComponent(i).onAttachToRoot(root);
+        }
+    }
+
+    protected void onDetachFromRoot() {
+        for (int i = 0, n = getComponentCount(); i < n; ++i) {
+            getComponent(i).onDetachFromRoot();
+        }
+        mRootView = null;
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/NinePatchChunk.java b/new3d/src/com/android/gallery3d/ui/NinePatchChunk.java
new file mode 100644
index 0000000..c1cf1e4
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/NinePatchChunk.java
@@ -0,0 +1,66 @@
+package com.android.gallery3d.ui;
+
+import android.graphics.Rect;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+// See "frameworks/base/include/utils/ResourceTypes.h" for the format of
+// NinePatch chunk.
+class NinePatchChunk {
+
+    public static final int NO_COLOR = 0x00000001;
+    public static final int TRANSPARENT_COLOR = 0x00000000;
+
+    public Rect mPaddings = new Rect();
+
+    public int mDivX[];
+    public int mDivY[];
+    public int mColor[];
+
+    private static void readIntArray(int[] data, ByteBuffer buffer) {
+        for (int i = 0, n = data.length; i < n; ++i) {
+            data[i] = buffer.getInt();
+        }
+    }
+
+    private static void checkDivCount(int length) {
+        if (length == 0 || (length & 0x01) != 0) {
+            throw new RuntimeException("invalid nine-patch: " + length);
+        }
+    }
+
+    public static NinePatchChunk deserialize(byte[] data) {
+        ByteBuffer byteBuffer =
+                ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
+
+        byte wasSerialized = byteBuffer.get();
+        if (wasSerialized == 0) return null;
+
+        NinePatchChunk chunk = new NinePatchChunk();
+        chunk.mDivX = new int[byteBuffer.get()];
+        chunk.mDivY = new int[byteBuffer.get()];
+        chunk.mColor = new int[byteBuffer.get()];
+
+        checkDivCount(chunk.mDivX.length);
+        checkDivCount(chunk.mDivY.length);
+
+        // skip 8 bytes
+        byteBuffer.getInt();
+        byteBuffer.getInt();
+
+        chunk.mPaddings.left = byteBuffer.getInt();
+        chunk.mPaddings.right = byteBuffer.getInt();
+        chunk.mPaddings.top = byteBuffer.getInt();
+        chunk.mPaddings.bottom = byteBuffer.getInt();
+
+        // skip 4 bytes
+        byteBuffer.getInt();
+
+        readIntArray(chunk.mDivX, byteBuffer);
+        readIntArray(chunk.mDivY, byteBuffer);
+        readIntArray(chunk.mColor, byteBuffer);
+
+        return chunk;
+    }
+}
\ No newline at end of file
diff --git a/new3d/src/com/android/gallery3d/ui/NinePatchTexture.java b/new3d/src/com/android/gallery3d/ui/NinePatchTexture.java
new file mode 100644
index 0000000..318b500
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/NinePatchTexture.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+
+class NinePatchTexture extends ResourceTexture {
+    private NinePatchChunk mChunk;
+
+    public NinePatchTexture(Context context, int resId) {
+        super(context, resId);
+    }
+
+    @Override
+    protected Bitmap getBitmap() {
+        if (mBitmap != null) return mBitmap;
+
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        Bitmap bitmap = BitmapFactory.decodeResource(
+                mContext.getResources(), mResId, options);
+        mBitmap = bitmap;
+        setSize(bitmap.getWidth(), bitmap.getHeight());
+        mChunk = NinePatchChunk.deserialize(bitmap.getNinePatchChunk());
+        if (mChunk == null) {
+            throw new RuntimeException("invalid nine-patch image: " + mResId);
+        }
+        return bitmap;
+    }
+
+    public Rect getPaddings() {
+        // get the paddings from nine patch
+        if (mChunk == null) getBitmap();
+        return mChunk.mPaddings;
+    }
+
+    public NinePatchChunk getNinePatchChunk() {
+        if (mChunk == null) getBitmap();
+        return mChunk;
+    }
+
+    @Override
+    public void draw(GLRootView root, int x, int y, int w, int h) {
+        root.drawNinePatch(this, x, y, w, h);
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/RawTexture.java b/new3d/src/com/android/gallery3d/ui/RawTexture.java
new file mode 100644
index 0000000..35eb3bf
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/RawTexture.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+
+import javax.microedition.khronos.opengles.GL11;
+
+class RawTexture extends BasicTexture {
+
+    private RawTexture(GL11 gl, int id) {
+        super(gl, id, STATE_LOADED);
+    }
+
+    public GL11 getBoundGL() {
+        return mGL;
+    }
+
+    public static RawTexture newInstance(GL11 gl) {
+        int[] textureId = new int[1];
+        gl.glGenTextures(1, textureId, 0);
+        int glError = gl.glGetError();
+        if (glError != GL11.GL_NO_ERROR) {
+            throw new RuntimeException("GL_ERROR: " + glError);
+        }
+        return new RawTexture(gl, textureId[0]);
+    }
+
+    @Override
+    protected boolean bind(GLRootView glRootView, GL11 gl) {
+        if (mGL == gl) {
+            gl.glBindTexture(GL11.GL_TEXTURE_2D, getId());
+            return true;
+        }
+        return false;
+    }
+
+    public void drawBack(GLRootView root, int x, int y, int w, int h) {
+        root.drawTexture(this, x, y, w, h, 1f);
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/ResourceTexture.java b/new3d/src/com/android/gallery3d/ui/ResourceTexture.java
new file mode 100644
index 0000000..46a35d7
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/ResourceTexture.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+class ResourceTexture extends BitmapTexture {
+
+    protected final Context mContext;
+    protected final int mResId;
+    protected Bitmap mBitmap;
+
+    public ResourceTexture(Context context, int resId) {
+        mContext = Util.checkNotNull(context);
+        mResId = resId;
+    }
+
+    @Override
+    protected Bitmap getBitmap() {
+        if (mBitmap != null) return mBitmap;
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        mBitmap = BitmapFactory.decodeResource(
+                mContext.getResources(), mResId, options);
+        setSize(mBitmap.getWidth(), mBitmap.getHeight());
+        return mBitmap;
+    }
+
+    @Override
+    protected void freeBitmap(Bitmap bitmap) {
+        Util.Assert(bitmap == mBitmap);
+        bitmap.recycle();
+        mBitmap = null;
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/SlotView.java b/new3d/src/com/android/gallery3d/ui/SlotView.java
new file mode 100644
index 0000000..ce4d73f
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/SlotView.java
@@ -0,0 +1,16 @@
+package com.android.gallery3d.ui;
+
+public class SlotView {
+
+    public static interface Slot {
+        public void putOn(int x, int y, DisplayItemPanel panel);
+    }
+
+    public static interface Model {
+        public int size();
+        public int getSlotHeight();
+        public int getSlotWidth();
+        public Slot getSlot(int index);
+        public void free(Slot slot);
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/StringTexture.java b/new3d/src/com/android/gallery3d/ui/StringTexture.java
new file mode 100644
index 0000000..62d84cf
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/StringTexture.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
+
+class StringTexture extends CanvasTexture {
+    private static int DEFAULT_PADDING = 1;
+
+    private final String mText;
+    private final Paint mPaint;
+    private final FontMetricsInt mMetrics;
+
+    public StringTexture(String text, Paint paint,
+            FontMetricsInt metrics, int width, int height) {
+        super(width, height);
+        mText = text;
+        mPaint = paint;
+        mMetrics = metrics;
+    }
+
+
+    public static StringTexture newInstance(String text, Paint paint) {
+        FontMetricsInt metrics = paint.getFontMetricsInt();
+        int width = (int) (.5f + paint.measureText(text)) + DEFAULT_PADDING * 2;
+        int height = metrics.bottom - metrics.top + DEFAULT_PADDING * 2;
+        return new StringTexture(text, paint, metrics, width, height);
+    }
+
+    public static StringTexture newInstance(
+            String text, float textSize, int color) {
+        Paint paint = new Paint();
+        paint.setTextSize(textSize);
+        paint.setAntiAlias(true);
+        paint.setColor(color);
+
+        return newInstance(text, paint);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas, Bitmap backing) {
+        canvas.translate(DEFAULT_PADDING, DEFAULT_PADDING - mMetrics.ascent);
+        canvas.drawText(mText, 0, 0, mPaint);
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/Texture.java b/new3d/src/com/android/gallery3d/ui/Texture.java
new file mode 100644
index 0000000..1515bf0
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/Texture.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+interface Texture {
+    public void draw(GLRootView root, int x, int y);
+    public void draw(GLRootView root, int x, int y, int w, int h);
+}
diff --git a/new3d/src/com/android/gallery3d/ui/Util.java b/new3d/src/com/android/gallery3d/ui/Util.java
new file mode 100644
index 0000000..a3f1d55
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/Util.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+/**
+ * Collection of utility functions used in this package.
+ */
+class Util {
+    @SuppressWarnings("unused")
+    private static final String TAG = "Util";
+
+    private Util() {
+    }
+
+    public static void Assert(boolean cond) {
+        if (!cond) {
+            throw new AssertionError();
+        }
+    }
+
+    public static <T> T checkNotNull(T object) {
+        if (object == null) throw new NullPointerException();
+        return object;
+    }
+
+    public static boolean equals(Object a, Object b) {
+        return (a == b) || (a == null ? false : a.equals(b));
+    }
+
+    public static boolean isPowerOf2(int n) {
+        return (n & -n) == n;
+    }
+
+    public static int nextPowerOf2(int n) {
+        n -= 1;
+        n |= n >>> 16;
+        n |= n >>> 8;
+        n |= n >>> 4;
+        n |= n >>> 2;
+        n |= n >>> 1;
+        return n + 1;
+    }
+
+    public static float distance(float x, float y, float sx, float sy) {
+        float dx = x - sx;
+        float dy = y - sy;
+        return (float) Math.sqrt(dx * dx + dy * dy);
+    }
+
+    public static int clamp(int x, int min, int max) {
+        if (x > max) return max;
+        if (x < min) return min;
+        return x;
+    }
+}