Project: android_packages_apps_Gallery3D
/*
 * 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.cooliris.media; 
 
import android.content.Context; 
import android.graphics.Bitmap; 
import android.graphics.Canvas; 
import android.graphics.PixelFormat; 
import android.graphics.Rect; 
import android.hardware.Sensor; 
import android.hardware.SensorEvent; 
import android.hardware.SensorEventListener; 
import android.hardware.SensorManager; 
import android.opengl.GLSurfaceView; 
import android.opengl.GLU; 
import android.opengl.GLUtils; 
import android.os.Process; 
import android.os.SystemClock; 
import android.util.Log; 
import android.util.SparseArray; 
import android.view.KeyEvent; 
import android.view.MotionEvent; 
import android.view.SurfaceHolder; 
 
import java.lang.ref.ReferenceQueue; 
import java.lang.ref.WeakReference; 
import java.util.ArrayList; 
 
import javax.microedition.khronos.egl.EGLConfig; 
import javax.microedition.khronos.opengles.GL10; 
import javax.microedition.khronos.opengles.GL11; 
import javax.microedition.khronos.opengles.GL11Ext; 
 
public final class RenderView extends GLSurfaceView implements GLSurfaceView.Renderer, SensorEventListener { 
    private static final String TAG = "RenderView"
    private static final int NUM_TEXTURE_LOAD_THREADS = 4
    private static final int MAX_LOADING_COUNT = 8
 
    private static final int EVENT_NONE = 0
    // private static final int EVENT_TOUCH = 1; 
    private static final int EVENT_KEY = 2
    private static final int EVENT_FOCUS = 3
 
    private final SensorManager mSensorManager; 
 
    private GL11 mGL = null
    private int mViewWidth = 0
    private int mViewHeight = 0
 
    private RootLayer mRootLayer = null
    private boolean mListsDirty = false
    private static final Lists sLists = new Lists(); 
 
    private Layer mTouchEventTarget = null
 
    private int mCurrentEventType = EVENT_NONE; 
    private KeyEvent mCurrentKeyEvent = null
    private boolean mCurrentKeyEventResult = false
    private volatile boolean mPendingSensorEvent = false
 
    private int mLoadingCount = 0
    private static final Deque<Texture> sLoadInputQueue = new Deque<Texture>(); 
    private static final Deque<Texture> sLoadInputQueueCached = new Deque<Texture>(); 
    private static final Deque<Texture> sLoadInputQueueVideo = new Deque<Texture>(); 
    private static final Deque<Texture> sLoadOutputQueue = new Deque<Texture>(); 
    private static TextureLoadThread sCachedTextureLoadThread = null
    private static TextureLoadThread sVideoTextureLoadThread = null
    private static final TextureLoadThread[] sTextureLoadThreads = new TextureLoadThread[NUM_TEXTURE_LOAD_THREADS]; 
 
    private final Deque<MotionEvent> mTouchEventQueue = new Deque<MotionEvent>(); 
    private final DirectLinkedList<TextureReference> mActiveTextureList = new DirectLinkedList<TextureReference>(); 
    @SuppressWarnings("unchecked"
    private final ReferenceQueue mUnreferencedTextureQueue = new ReferenceQueue(); 
 
    // Frame time in milliseconds and delta since last frame in seconds. Uses 
    // SystemClock.getUptimeMillis(). 
    private long mFrameTime = 0
    private float mFrameInterval = 0.0f
    private float mAlpha; 
 
    private long mLoadingExpensiveTexturesStartTime = 0
    private final SparseArray<ResourceTexture> sCacheScaled = new SparseArray<ResourceTexture>(); 
    private final SparseArray<ResourceTexture> sCacheUnscaled = new SparseArray<ResourceTexture>(); 
 
    private boolean mFirstDraw; 
    // The cached texture that is bound to Texture Unit 0. 
    // We need to reset this to null whenever the active texture unit changes. 
    private Texture mBoundTexture; 
 
    // Weak reference to a texture that stores the associated texture ID. 
    private static final class TextureReference extends WeakReference<Texture> { 
        @SuppressWarnings("unchecked"
        public TextureReference(Texture texture, GL11 gl, ReferenceQueue referenceQueue, int textureId) { 
            super(texture, referenceQueue); 
            this.textureId = textureId; 
            this.gl = gl; 
        } 
 
        public final int textureId; 
        public final GL11 gl; 
        public final DirectLinkedList.Entry<TextureReference> activeListEntry = new DirectLinkedList.Entry<TextureReference>(this); 
    } 
 
    public static final class Lists { 
        public final ArrayList<Layer> updateList = new ArrayList<Layer>(); 
        public final ArrayList<Layer> opaqueList = new ArrayList<Layer>(); 
        public final ArrayList<Layer> blendedList = new ArrayList<Layer>(); 
        public final ArrayList<Layer> hitTestList = new ArrayList<Layer>(); 
        public final ArrayList<Layer> systemList = new ArrayList<Layer>(); 
 
        void clear() { 
            updateList.clear(); 
            opaqueList.clear(); 
            blendedList.clear(); 
            hitTestList.clear(); 
            systemList.clear(); 
        } 
    } 
 
    public RenderView(final Context context) { 
        super(context); 
        setBackgroundDrawable(null); 
        setFocusable(true); 
        if (getResources().getBoolean(R.bool.use_32bpp_display)) { 
            setEGLConfigChooser(8888160); 
            getHolder().setFormat(PixelFormat.RGBA_8888); 
        } 
        setRenderer(this); 
        mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); 
        if (sCachedTextureLoadThread == null) { 
            for (int i = 0; i != NUM_TEXTURE_LOAD_THREADS; ++i) { 
                TextureLoadThread thread = new TextureLoadThread(); 
                if (i == 0) { 
                    sCachedTextureLoadThread = thread; 
                } 
                if (i == 1) { 
                    sVideoTextureLoadThread = thread; 
                } 
                sTextureLoadThreads[i] = thread; 
                thread.start(); 
            } 
        } 
    } 
 
    public void setRootLayer(RootLayer layer) { 
        if (mRootLayer != layer) { 
            mRootLayer = layer; 
            mListsDirty = true
            if (layer != null) { 
                mRootLayer.setSize(mViewWidth, mViewHeight); 
            } 
        } 
    } 
 
    public ResourceTexture getResource(int resourceId) { 
        return getResourceInternal(resourceId, true); 
    } 
 
    public ResourceTexture getResource(int resourceId, boolean scaled) { 
        return getResourceInternal(resourceId, scaled); 
    } 
 
    private ResourceTexture getResourceInternal(int resourceId, boolean scaled) { 
        final SparseArray<ResourceTexture> cache = (scaled) ? sCacheScaled : sCacheUnscaled; 
        ResourceTexture texture = cache.get(resourceId); 
        if (texture == null && resourceId != 0) { 
            texture = new ResourceTexture(resourceId, scaled); 
            cache.put(resourceId, texture); 
        } 
        return texture; 
    } 
 
    public void clearCache() { 
        clearTextureArray(sCacheScaled); 
        clearTextureArray(sCacheUnscaled); 
    } 
 
    private void clearTextureArray(SparseArray<ResourceTexture> array) { 
        /*
         * final int size = array.size(); for (int i = 0; i < size; ++i) { 
         * ResourceTexture texture = array.get(array.keyAt(i)); if (texture != 
         * null) { texture.clear(); } } 
         */
 
        array.clear(); 
    } 
 
    /** Render API */ 
 
    public long getFrameTime() { 
        return mFrameTime; 
    } 
 
    public float getFrameInterval() { 
        return mFrameInterval; 
    } 
 
    public void prime(Texture texture, boolean highPriority) { 
        if (texture != null && texture.mState == Texture.STATE_UNLOADED && (highPriority || mLoadingCount < MAX_LOADING_COUNT)) { 
            queueLoad(texture, highPriority); 
        } 
    } 
 
    public void loadTexture(Texture texture) { 
        if (texture != null) { 
            switch (texture.mState) { 
            case Texture.STATE_UNLOADED: 
            case Texture.STATE_QUEUED: 
                int[] textureId = new int[1]; 
                texture.mState = Texture.STATE_LOADING; 
                loadTextureAsync(texture); 
                uploadTexture(texture, textureId); 
                break
            } 
        } 
    } 
 
    private void loadTextureAsync(Texture texture) { 
        try { 
            Bitmap bitmap = texture.load(this); 
            if (bitmap != null) { 
                int width = bitmap.getWidth(); 
                int height = bitmap.getHeight(); 
                texture.mWidth = width; 
                texture.mHeight = height; 
                // Create a padded bitmap if the natural size is not a power of 
                // 2. 
                if (!Shared.isPowerOf2(width) || !Shared.isPowerOf2(height)) { 
                    int paddedWidth = Shared.nextPowerOf2(width); 
                    int paddedHeight = Shared.nextPowerOf2(height); 
                    Bitmap.Config config = bitmap.getConfig(); 
                    if (config == null
                        config = Bitmap.Config.RGB_565; 
                    if (width * height >= 512 * 512) { 
                        if (getResources().getBoolean(R.bool.use_32bpp_display)) 
                            config = Bitmap.Config.ARGB_8888; 
                        else 
                            config = Bitmap.Config.RGB_565; 
                    } 
                    Bitmap padded = Bitmap.createBitmap(paddedWidth, paddedHeight, config); 
                    Canvas canvas = new Canvas(padded); 
                    canvas.drawBitmap(bitmap, 00null); 
                    bitmap.recycle(); 
                    bitmap = padded; 
                    // Store normalized width and height for use in texture 
                    // coordinates. 
                    texture.mNormalizedWidth = (float) width / (float) paddedWidth; 
                    texture.mNormalizedHeight = (float) height / (float) paddedHeight; 
                } else { 
                    texture.mNormalizedWidth = 1.0f
                    texture.mNormalizedHeight = 1.0f
                } 
            } 
            texture.mBitmap = bitmap; 
        } catch (Exception e) { 
            texture.mBitmap = null
        } catch (OutOfMemoryError eMem) { 
            Log.i(TAG, "Bitmap power of 2 creation fail, outofmemory"); 
            handleLowMemory(); 
        } 
    } 
 
    public boolean bind(Texture texture) { 
        if (texture != null) { 
            if (texture == mBoundTexture) 
                return true
            switch (texture.mState) { 
            case Texture.STATE_UNLOADED: 
                if (texture.getClass().equals(ResourceTexture.class)) { 
                    loadTexture(texture); 
                    return false
                } 
                if (mLoadingCount < MAX_LOADING_COUNT) { 
                    queueLoad(texture, false); 
                } 
                break
            case Texture.STATE_LOADED: 
                mGL.glBindTexture(GL11.GL_TEXTURE_2D, texture.mId); 
                mBoundTexture = texture; 
                return true
            default
                break
            } 
        } 
        return false
    } 
 
    public void setAlpha(float alpha) { 
        GL11 gl = mGL; 
        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); 
        gl.glColor4f(alpha, alpha, alpha, alpha); 
        mAlpha = alpha; 
    } 
 
    public float getAlpha() { 
        return mAlpha; 
    } 
 
    public void setColor(float red, float green, float blue, float alpha) { 
        GL11 gl = mGL; 
        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); 
        gl.glColor4f(red, green, blue, alpha); 
        mAlpha = alpha; 
    } 
 
    public void resetColor() { 
        GL11 gl = mGL; 
        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE); 
        gl.glColor4f(1111); 
    } 
 
    public boolean isLoadingExpensiveTextures() { 
        return mLoadingExpensiveTexturesStartTime != 0
    } 
 
    public long elapsedLoadingExpensiveTextures() { 
        long startTime = mLoadingExpensiveTexturesStartTime; 
        if (startTime != 0) { 
            return SystemClock.uptimeMillis() - startTime; 
        } else { 
            return -1
        } 
    } 
 
    private void queueLoad(final Texture texture, boolean highPriority) { 
     // Allow the texture to defer queuing. 
        if (!texture.shouldQueue()) { 
            return
        } 
 
        // Change the texture state to loading. 
        texture.mState = Texture.STATE_LOADING; 
 
        // Push the texture onto the load input queue. 
        Deque<Texture> inputQueue = (texture.isUncachedVideo()) ? sLoadInputQueueVideo 
                : (texture.isCached()) ? sLoadInputQueueCached : sLoadInputQueue; 
        ; 
        synchronized (inputQueue) { 
            if (highPriority) { 
                inputQueue.addFirst(texture); 
                // Enforce the maximum loading count by removing something from the end of 
                // the loading queue, if necessary. 
                if (mLoadingCount >= MAX_LOADING_COUNT) { 
                 Texture unloadTexture = inputQueue.pollLast(); 
                 unloadTexture.mState = Texture.STATE_UNLOADED; 
                 --mLoadingCount; 
                } 
            } else { 
                inputQueue.addLast(texture); 
            } 
            inputQueue.notify(); 
        } 
        ++mLoadingCount; 
    } 
 
    public void draw2D(Texture texture, float x, float y) { 
        if (bind(texture)) { 
            final float width = texture.getWidth(); 
            final float height = texture.getHeight(); 
            ((GL11Ext) mGL).glDrawTexfOES(x, mViewHeight - y - height, 0f, width, height); 
        } 
    } 
 
    public void draw2D(Texture texture, float x, float y, float width, float height) { 
        if (bind(texture)) { 
            ((GL11Ext) mGL).glDrawTexfOES(x, mViewHeight - y - height, 0f, width, height); 
        } 
    } 
 
    public void draw2D(Texture texture, int x, int y, int width, int height) { 
        if (bind(texture)) { 
            ((GL11Ext) mGL).glDrawTexiOES(x, (mViewHeight - y - height), 0, width, height); 
        } 
    } 
 
    public void draw2D(float x, float y, float z, float width, float height) { 
        ((GL11Ext) mGL).glDrawTexfOES(x, mViewHeight - y - height, z, width, height); 
    } 
 
    public boolean bindMixed(Texture from, Texture to, float ratio) { 
        // Bind "from" and "to" to TEXTURE0 and TEXTURE1, respectively. 
        final GL11 gl = mGL; 
        boolean bind = true
        bind &= bind(from); 
        gl.glActiveTexture(GL11.GL_TEXTURE1); 
        mBoundTexture = null
        bind &= bind(to); 
        if (!bind) { 
            return false
        } 
 
        // Enable TEXTURE1. 
        gl.glEnable(GL11.GL_TEXTURE_2D); 
 
        // Interpolate the RGB and alpha values between both textures. 
        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_COMBINE); 
        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_INTERPOLATE); 
        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_ALPHA, GL11.GL_INTERPOLATE); 
 
        // Specify the interpolation factor via the alpha component of 
        // GL_TEXTURE_ENV_COLORes. 
        final float[] color = { 1f1f1f, ratio }; 
        gl.glTexEnvfv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, color, 0); 
 
        // Wire up the interpolation factor for RGB. 
        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_RGB, GL11.GL_CONSTANT); 
        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA); 
 
        // Wire up the interpolation factor for alpha. 
        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_ALPHA, GL11.GL_CONSTANT); 
        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA); 
        return true
    } 
 
    public void unbindMixed() { 
        // Disable TEXTURE1. 
        final GL11 gl = mGL; 
        gl.glDisable(GL11.GL_TEXTURE_2D); 
 
        // Switch back to the default texture unit. 
        gl.glActiveTexture(GL11.GL_TEXTURE0); 
        mBoundTexture = null
    } 
 
    public void drawMixed2D(Texture from, Texture to, float ratio, float x, float y, float z, float width, float height) { 
        final GL11 gl = mGL; 
 
        // Bind "from" and "to" to TEXTURE0 and TEXTURE1, respectively. 
        if (bind(from)) { 
            gl.glActiveTexture(GL11.GL_TEXTURE1); 
            mBoundTexture = null
            if (bind(to)) { 
                // Enable TEXTURE1. 
                gl.glEnable(GL11.GL_TEXTURE_2D); 
 
                // Interpolate the RGB and alpha values between both textures. 
                gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_COMBINE); 
                gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_INTERPOLATE); 
                gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_ALPHA, GL11.GL_INTERPOLATE); 
 
                // Specify the interpolation factor via the alpha component of 
                // GL_TEXTURE_ENV_COLORes. 
                final float[] color = { 1f1f1f, ratio }; 
                gl.glTexEnvfv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, color, 0); 
 
                // Wire up the interpolation factor for RGB. 
                gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_RGB, GL11.GL_CONSTANT); 
                gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA); 
 
                // Wire up the interpolation factor for alpha. 
                gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_ALPHA, GL11.GL_CONSTANT); 
                gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA); 
 
                // Draw the combined texture. 
                ((GL11Ext) gl).glDrawTexfOES(x, mViewHeight - y - height, z, width, height); 
 
                // Disable TEXTURE1. 
                gl.glDisable(GL11.GL_TEXTURE_2D); 
            } 
 
            // Switch back to the default texture unit. 
            gl.glActiveTexture(GL11.GL_TEXTURE0); 
            mBoundTexture = null
        } 
    } 
 
    public void processAllTextures() { 
        processTextures(true); 
    } 
 
    final static int[] textureId = new int[1]; 
 
    /** Uploads at most one texture to GL. */ 
    private void processTextures(boolean processAll) { 
        // Destroy any textures that are no longer referenced. 
        GL11 gl = mGL; 
        TextureReference textureReference; 
        while ((textureReference = (TextureReference) mUnreferencedTextureQueue.poll()) != null) { 
            textureId[0] = textureReference.textureId; 
            GL11 glOld = textureReference.gl; 
            if (glOld == gl) { 
                gl.glDeleteTextures(1, textureId, 0); 
            } 
            mActiveTextureList.remove(textureReference.activeListEntry); 
        } 
        Deque<Texture> outputQueue = sLoadOutputQueue; 
        Texture texture; 
        do { 
            // Upload loaded textures to the GPU one frame at a time. 
            synchronized (outputQueue) { 
                texture = outputQueue.pollFirst(); 
            } 
            if (texture != null) { 
                // Extract the bitmap from the texture. 
                uploadTexture(texture, textureId); 
 
                // Decrement the loading count. 
                --mLoadingCount; 
            } else { 
                break
            } 
        } while (processAll); 
    } 
 
    private void uploadTexture(Texture texture, int[] textureId) { 
        Bitmap bitmap = texture.mBitmap; 
        GL11 gl = mGL; 
        int glError = GL11.GL_NO_ERROR; 
        if (bitmap != null) { 
            final int width = texture.mWidth; 
            final int height = texture.mHeight; 
 
            // Define a vertically flipped crop rectangle for OES_draw_texture. 
            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); 
            GLUtils.texImage2D(GL11.GL_TEXTURE_2D, 0, bitmap, 0); 
            glError = gl.glGetError(); 
 
            bitmap.recycle(); 
            if (glError == GL11.GL_OUT_OF_MEMORY) { 
                handleLowMemory(); 
            } 
            if (glError != GL11.GL_NO_ERROR) { 
                // There was an error, we need to retry this texture at some 
                // later time 
                Log.i(TAG, "Texture creation fail, glError " + glError); 
                texture.mId = 0
                texture.mBitmap = null
                texture.mState = Texture.STATE_UNLOADED; 
            } else { 
                // Update texture state. 
                texture.mBitmap = null
                texture.mId = textureId[0]; 
                texture.mState = Texture.STATE_LOADED; 
 
                // Add to the active list. 
                final TextureReference textureRef = new TextureReference(texture, gl, mUnreferencedTextureQueue, textureId[0]); 
                mActiveTextureList.add(textureRef.activeListEntry); 
                requestRender(); 
            } 
        } else { 
            texture.mState = Texture.STATE_ERROR; 
        } 
 
    } 
 
    @Override 
    public void onResume() { 
        super.onResume(); 
        Sensor sensorAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 
        if (sensorAccelerometer != null) { 
            mSensorManager.registerListener(this, sensorAccelerometer, SensorManager.SENSOR_DELAY_UI); 
        } 
        if (mRootLayer != null) { 
            mRootLayer.onResume(); 
        } 
    } 
 
    @Override 
    public void onPause() { 
        super.onPause(); 
        Log.i(TAG, "OnPause RenderView " + this); 
        mSensorManager.unregisterListener(this); 
        if (mRootLayer != null) { 
            mRootLayer.onPause(); 
        } 
    } 
 
    private final boolean ENABLE_FPS_TEST = false
    private int mFrameCount = 0
    private long mFrameCountingStart = 0
 
    /** Renders a frame of the UI. */ 
    // @Override 
    public void onDrawFrame(GL10 gl1) { 
 
        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; 
        } 
 
        GL11 gl = (GL11) gl1; 
        if (!mFirstDraw) { 
            Log.i(TAG, "First Draw"); 
        } 
        mFirstDraw = true
        //setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); 
        // Rebuild the display lists if the render tree has changed. 
        if (mListsDirty) { 
            updateLists(); 
        } 
 
        boolean wasLoadingExpensiveTextures = isLoadingExpensiveTextures(); 
        boolean loadingExpensiveTextures = false
        int numTextureThreads = sTextureLoadThreads.length; 
        for (int i = 2; i < numTextureThreads; ++i) { 
            if (sTextureLoadThreads[i].mIsLoading) { 
                loadingExpensiveTextures = true
                break
            } 
        } 
        if (loadingExpensiveTextures != wasLoadingExpensiveTextures) { 
            mLoadingExpensiveTexturesStartTime = loadingExpensiveTextures ? SystemClock.uptimeMillis() : 0
        } 
 
        // Upload new textures. 
        processTextures(false); 
 
        // Update the current time and frame time interval. 
        long now = SystemClock.uptimeMillis(); 
        final float dt = 0.001f * Math.min(50, now - mFrameTime); 
        mFrameInterval = dt; 
        mFrameTime = now; 
 
        // Dispatch the current touch event. 
        processCurrentEvent(); 
        processTouchEvent(); 
        // Run the update pass. 
        final Lists lists = sLists; 
        synchronized (lists) { 
            final ArrayList<Layer> updateList = lists.updateList; 
            boolean isDirty = false
            for (int i = 0, size = updateList.size(); i != size; ++i) { 
                boolean retVal = updateList.get(i).update(this, mFrameInterval); 
                isDirty |= retVal; 
            } 
            if (isDirty) { 
                requestRender(); 
            } 
 
            // Clear the depth buffer. 
            gl.glClear(GL11.GL_DEPTH_BUFFER_BIT); 
            gl.glEnable(GL11.GL_SCISSOR_TEST); 
            gl.glScissor(00, getWidth(), getHeight()); 
 
            // Run the opaque pass. 
            gl.glDisable(GL11.GL_BLEND); 
            final ArrayList<Layer> opaqueList = lists.opaqueList; 
            for (int i = opaqueList.size() - 1; i >= 0; --i) { 
                final Layer layer = opaqueList.get(i); 
                if (!layer.mHidden) { 
                    layer.renderOpaque(this, gl); 
                } 
            } 
 
            // Run the blended pass. 
            gl.glEnable(GL11.GL_BLEND); 
            final ArrayList<Layer> blendedList = lists.blendedList; 
            for (int i = 0, size = blendedList.size(); i != size; ++i) { 
                final Layer layer = blendedList.get(i); 
                if (!layer.mHidden) { 
                    layer.renderBlended(this, gl); 
                } 
            } 
            gl.glDisable(GL11.GL_BLEND); 
        } 
    } 
 
    private void processCurrentEvent() { 
        final int type = mCurrentEventType; 
        switch (type) { 
        case EVENT_KEY: 
            processKeyEvent(); 
            break
        case EVENT_FOCUS: 
            processFocusEvent(); 
            break
        default
            break
        } 
        synchronized (this) { 
            mCurrentEventType = EVENT_NONE; 
            this.notify(); 
        } 
    } 
 
    private void processTouchEvent() { 
        MotionEvent event = null
        int numEvents = mTouchEventQueue.size(); 
        int i = 0
        do { 
            // We look at the touch event queue and process one event at a time 
            synchronized (mTouchEventQueue) { 
                event = mTouchEventQueue.pollFirst(); 
            } 
            if (event == null
                return
 
            // Detect the hit layer. 
            final int action = event.getAction(); 
            Layer target; 
            if (action == MotionEvent.ACTION_DOWN) { 
                target = hitTest(event.getX(), event.getY()); 
                mTouchEventTarget = target; 
            } else { 
                target = mTouchEventTarget; 
            } 
 
            // Dispatch event to the hit layer. 
            if (target != null) { 
                target.onTouchEvent(event); 
            } 
 
            // Clear the hit layer. 
            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 
                mTouchEventTarget = null
            } 
            event.recycle(); 
            ++i; 
        } while (event != null && i < numEvents); 
        synchronized (this) { 
            this.notify(); 
        } 
    } 
 
    private void processKeyEvent() { 
        // Get the event. 
        final KeyEvent event = mCurrentKeyEvent; 
        boolean result = false
        mCurrentKeyEvent = null
 
        // Dispatch the event to the root layer. 
        if (mRootLayer != null) { 
            if (event.getAction() == KeyEvent.ACTION_DOWN) { 
                result = mRootLayer.onKeyDown(event.getKeyCode(), event); 
            } else { 
                result = mRootLayer.onKeyUp(event.getKeyCode(), event); 
            } 
        } 
        mCurrentKeyEventResult = result; 
    } 
 
    private void processFocusEvent() { 
        // Get event information. 
        if (mRootLayer != null) { 
 
        } 
    } 
 
    private Layer hitTest(float x, float y) { 
        final ArrayList<Layer> hitTestList = sLists.hitTestList; 
        for (int i = hitTestList.size() - 1; i >= 0; --i) { 
            final Layer layer = hitTestList.get(i); 
            if (layer != null && !layer.mHidden) { 
                final float layerX = layer.mX; 
                final float layerY = layer.mY; 
                if (x >= layerX && y >= layerY && x < layerX + layer.mWidth && y < layerY + layer.mHeight 
                        && layer.containsPoint(x, y)) { 
                    return layer; 
                } 
            } 
        } 
        return null
    } 
 
    private void updateLists() { 
        if (mRootLayer != null) { 
            synchronized (sLists) { 
                sLists.clear(); 
                mRootLayer.generate(this, sLists); 
            } 
        } 
    } 
 
    /**
     * Called when the OpenGL surface is recreated without destroying the 
     * context. 
     */
 
    public void onSurfaceChanged(GL10 gl1, int width, int height) { 
        GL11 gl = (GL11) gl1; 
        mFirstDraw = false
        mViewWidth = width; 
        mViewHeight = height; 
        if (mRootLayer != null) { 
            mRootLayer.setSize(width, height); 
        } 
 
        // Set the viewport and projection matrix. 
        final float zNear = 0.1f
        final float zFar = 100.0f
        gl.glViewport(00, width, height); 
        gl.glMatrixMode(GL11.GL_PROJECTION); 
        gl.glLoadIdentity(); 
        GLU.gluPerspective(gl, 45.0f, (float) width / height, zNear, zFar); 
        if (mRootLayer != null) { 
            mRootLayer.onSurfaceChanged(this, width, height); 
        } 
        gl.glMatrixMode(GL11.GL_MODELVIEW); 
    } 
 
    public void setFov(float fov) { 
        GL11 gl = mGL; 
        gl.glMatrixMode(GL11.GL_PROJECTION); 
        gl.glLoadIdentity(); 
        final float zNear = 0.1f
        final float zFar = 100.0f
        GLU.gluPerspective(gl, fov, (float) getWidth() / getHeight(), zNear, zFar); 
        gl.glMatrixMode(GL11.GL_MODELVIEW); 
    } 
 
    /**
     * Called when the context is created, possibly after automatic destruction. 
     */
 
    public void onSurfaceCreated(GL10 gl1, EGLConfig config) { 
        // Clear the resource texture cache. 
        clearCache(); 
 
        GL11 gl = (GL11) gl1; 
        if (mGL == null) { 
            mGL = gl; 
        } else { 
            // The GL Object has changed. 
            Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl); 
            mGL = gl; 
        } 
 
        if (ENABLE_FPS_TEST) { 
            setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); 
        } else { 
            setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); 
        } 
 
        // Increase the priority of the render thread. 
        // This is commented out to give other threads more CPU. 
        //Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY); 
 
        // Disable unused state. 
        gl.glEnable(GL11.GL_DITHER); 
        gl.glDisable(GL11.GL_LIGHTING); 
 
        // Set global state. 
        // gl.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST); 
 
        // Enable textures. 
        gl.glEnable(GL11.GL_TEXTURE_2D); 
        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE); 
 
        // Set up state for multitexture operations. Since multitexture is 
        // currently used 
        // only for layered crossfades the needed state can be factored out into 
        // one-time 
        // initialization. This section may need to be folded into drawMixed2D() 
        // if multitexture 
        // is used for other effects. 
 
        // Enable Vertex Arrays 
        gl.glEnableClientState(GL11.GL_VERTEX_ARRAY); 
        gl.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY); 
        gl.glClientActiveTexture(GL11.GL_TEXTURE1); 
        gl.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY); 
        gl.glClientActiveTexture(GL11.GL_TEXTURE0); 
 
        // Enable depth test. 
        gl.glEnable(GL11.GL_DEPTH_TEST); 
        gl.glDepthFunc(GL11.GL_LEQUAL); 
 
        // Set the blend function for premultiplied alpha. 
        gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA); 
 
        // Set the background color. 
        gl.glClearColor(0.0f0.0f0.0f1.0f); 
        gl.glClear(GL11.GL_COLOR_BUFFER_BIT); 
 
        // Start reloading textures if the context was automatically destroyed. 
        if (!mActiveTextureList.isEmpty()) { 
            DirectLinkedList.Entry<TextureReference> iter = mActiveTextureList.getHead(); 
            while (iter != null) { 
                final Texture texture = iter.value.get(); 
                if (texture != null) { 
                    texture.mState = Texture.STATE_UNLOADED; 
                } 
                iter = iter.next; 
            } 
        } 
        mActiveTextureList.clear(); 
        if (mRootLayer != null) { 
            mRootLayer.onSurfaceCreated(this, gl); 
        } 
        synchronized (sLists) { 
            ArrayList<Layer> systemList = sLists.systemList; 
            for (int i = systemList.size() - 1; i >= 0; --i) { 
                systemList.get(i).onSurfaceCreated(this, gl); 
            } 
        } 
    } 
 
    /** Indicates that the accuracy of a sensor value has changed. */ 
    public void onAccuracyChanged(Sensor sensor, int accuracy) { 
    } 
 
    /** Indicates that a sensor value has changed. */ 
    public void onSensorChanged(SensorEvent event) { 
        final int type = event.sensor.getType(); 
        if (!mPendingSensorEvent && type == Sensor.TYPE_ACCELEROMETER) { 
            final SensorEvent e = event; 
            if (mRootLayer != null
                mRootLayer.onSensorChanged(RenderView.this, e); 
        } 
    } 
 
    @Override 
    public boolean onTouchEvent(MotionEvent event) { 
        // Ignore events received before the surface is created to avoid 
        // deadlocking with GLSurfaceView's needToWait(). 
        if (mGL == null) { 
            return false
        } 
        // Wait for the render thread to process this event. 
        if (mTouchEventQueue.size() > 8 && event.getAction() == MotionEvent.ACTION_MOVE) 
            return true
        synchronized (mTouchEventQueue) { 
            MotionEvent eventCopy = MotionEvent.obtain(event); 
            mTouchEventQueue.addLast(eventCopy); 
            requestRender(); 
        } 
        return true
    } 
 
    @Override 
    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 
 
        // Ignore events received before the surface is created to avoid 
        // deadlocking with GLSurfaceView's needToWait(). 
        /*
         * if (mGL == null) { return; } 
         * 
         * // Wait for the render thread to process this event. try { 
         * synchronized (this) { mCurrentFocusEventGain = gainFocus; 
         * mCurrentFocusEventDirection = direction; mCurrentEventType = 
         * EVENT_FOCUS; do { wait(); } while (mCurrentEventType != EVENT_NONE); 
         * } } catch (InterruptedException e) { // Stop waiting for the render 
         * thread if interrupted. } 
         */
 
        requestRender(); 
    } 
 
    @Override 
    public boolean onKeyDown(int keyCode, KeyEvent event) { 
        // Ignore events received before the surface is created to avoid 
        // deadlocking with GLSurfaceView's needToWait(). 
        if (mGL == null) { 
            return false
        } 
 
        // Wait for the render thread to process this event. 
        try { 
            synchronized (this) { 
                mCurrentKeyEvent = event; 
                mCurrentEventType = EVENT_KEY; 
                requestRender(); 
                long timeout = SystemClock.uptimeMillis() + 50
                do { 
                    wait(50); 
                } while (mCurrentEventType != EVENT_NONE && SystemClock.uptimeMillis() < timeout); 
            } 
        } catch (InterruptedException e) { 
            // Stop waiting for the render thread if interrupted. 
        } 
 
        // Key events are handled on the main thread. 
        boolean retVal = false
        if (!mCurrentKeyEventResult) { 
            retVal = super.onKeyDown(keyCode, event); 
        } else { 
            retVal = true
        } 
        requestRender(); 
        return retVal; 
    } 
 
    @Override 
    public void surfaceDestroyed(SurfaceHolder holder) { 
        super.surfaceDestroyed(holder); 
    } 
 
    @Override 
    protected void onAttachedToWindow() { 
        super.onAttachedToWindow(); 
    } 
 
    @Override 
    protected void onDetachedFromWindow() { 
        super.onDetachedFromWindow(); 
    } 
 
    private final class TextureLoadThread extends Thread { 
        public boolean mIsLoading; 
 
        public TextureLoadThread() { 
            super("TextureLoad"); 
        } 
 
        @Override 
        public void run() { 
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 
            Deque<Texture> inputQueue = (sVideoTextureLoadThread == this) ? sLoadInputQueueVideo 
                    : ((sCachedTextureLoadThread == this) ? sLoadInputQueueCached : sLoadInputQueue); 
            Deque<Texture> outputQueue = sLoadOutputQueue; 
            try { 
                for (;;) { 
                    // Pop the next texture from the input queue. 
                    Texture texture = null
                    synchronized (inputQueue) { 
                        while ((texture = inputQueue.pollFirst()) == null) { 
                            inputQueue.wait(); 
                        } 
                    } 
                    if (sCachedTextureLoadThread != this
                        mIsLoading = true
                    // Load the texture bitmap. 
                    load(texture); 
                    mIsLoading = false
 
                    // Push the texture onto the output queue. 
                    synchronized (outputQueue) { 
                        outputQueue.addLast(texture); 
                    } 
                } 
            } catch (InterruptedException e) { 
                // Terminate the thread. 
            } 
        } 
 
        private void load(Texture texture) { 
            // Generate the texture bitmap. 
            RenderView view = RenderView.this
            view.loadTextureAsync(texture); 
            view.requestRender(); 
        } 
    } 
 
    public void shutdown() { 
        mRootLayer = null
        synchronized (sLists) { 
            sLists.clear(); 
        } 
    } 
 
    public void handleLowMemory() { 
        Log.i(TAG, "Handling low memory condition"); 
        if (mRootLayer != null) { 
            mRootLayer.handleLowMemory(); 
        } 
    } 
 
    public Lists getLists() { 
        return sLists; 
    } 
 
 public int getViewWidth() { 
  return mViewWidth; 
 
 
 public int getViewHeight() { 
  return mViewHeight; 
 
}