Handle events in the main thread and use GLThread only as a rendering thread.

Coordinate the two threads by synchronizing on the GLRootView instance.

Change-Id: I94459f6afeb468660df7219800bc48b621edecd4
diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java
index 7f928bd..b42c1a5 100644
--- a/src/com/android/camera/Camera.java
+++ b/src/com/android/camera/Camera.java
@@ -77,7 +77,7 @@
 import com.android.camera.ui.CameraHeadUpDisplay;
 import com.android.camera.ui.GLRootView;
 import com.android.camera.ui.HeadUpDisplay;
-import com.android.camera.ui.ZoomController;
+import com.android.camera.ui.ZoomControllerListener;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -316,11 +316,7 @@
                         setOrientationIndicator(mLastOrientation);
                     }
                     if (mGLRootView != null) {
-                        mGLRootView.queueEvent(new Runnable() {
-                            public void run() {
-                                mHeadUpDisplay.setOrientation(mLastOrientation);
-                            }
-                        });
+                        mHeadUpDisplay.setOrientation(mLastOrientation);
                     }
                 }
             }
@@ -396,10 +392,8 @@
         mOrientationListener.enable();
 
         // Start location update if needed.
-        synchronized (mPreferences) {
-            mRecordLocation = RecordLocationPreference.get(
-                    mPreferences, getContentResolver());
-        }
+        mRecordLocation = RecordLocationPreference.get(
+                mPreferences, getContentResolver());
         if (mRecordLocation) startReceivingLocationUpdates();
 
         installIntentFilter();
@@ -998,22 +992,17 @@
 
     private void overrideHudSettings(final String flashMode,
             final String whiteBalance, final String focusMode) {
-        mGLRootView.queueEvent(new Runnable() {
-            public void run() {
-                mHeadUpDisplay.overrideSettings(
-                        CameraSettings.KEY_FLASH_MODE, flashMode);
-                mHeadUpDisplay.overrideSettings(
-                        CameraSettings.KEY_WHITE_BALANCE, whiteBalance);
-                mHeadUpDisplay.overrideSettings(
-                        CameraSettings.KEY_FOCUS_MODE, focusMode);
-            }});        
+        mHeadUpDisplay.overrideSettings(
+                CameraSettings.KEY_FLASH_MODE, flashMode,
+                CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
+                CameraSettings.KEY_FOCUS_MODE, focusMode);
     }
 
-    private void updateSceneModeInHud() {        
+    private void updateSceneModeInHud() {
         // If scene mode is set, we cannot set flash mode, white balance, and
-        // focus mode, instead, we read it from driver        
+        // focus mode, instead, we read it from driver
         if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
-            overrideHudSettings(mParameters.getFlashMode(), 
+            overrideHudSettings(mParameters.getFlashMode(),
                     mParameters.getWhiteBalance(), mParameters.getFocusMode());
         } else {
             overrideHudSettings(null, null, null);
@@ -1035,14 +1024,10 @@
         if (mParameters.isZoomSupported()) {
             mHeadUpDisplay.setZoomRatios(getZoomRatios());
             mHeadUpDisplay.setZoomIndex(mZoomValue);
-            mHeadUpDisplay.setZoomListener(new ZoomController.ZoomListener() {
+            mHeadUpDisplay.setZoomListener(new ZoomControllerListener() {
                 public void onZoomChanged(
-                        final int index, float ratio, boolean isMoving) {
-                    mHandler.post(new Runnable() {
-                        public void run() {
-                            onZoomValueChanged(index);
-                        }
-                    });
+                        int index, float ratio, boolean isMoving) {
+                    onZoomValueChanged(index);
                 }
             });
         }
@@ -1908,9 +1893,7 @@
         }
 
         if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
-            synchronized (mPreferences) {
-                updateCameraParametersPreference();
-            }
+            updateCameraParametersPreference();
         }
 
         mCameraDevice.setParameters(mParameters);
@@ -2110,11 +2093,9 @@
 
         boolean recordLocation;
 
-        synchronized (mPreferences) {
-            recordLocation = RecordLocationPreference.get(
-                    mPreferences, getContentResolver());
-            mQuickCapture = getQuickCaptureSettings();
-        }
+        recordLocation = RecordLocationPreference.get(
+                mPreferences, getContentResolver());
+        mQuickCapture = getQuickCaptureSettings();
 
         if (mRecordLocation != recordLocation) {
             if (mRecordLocation) {
@@ -2153,22 +2134,12 @@
 
     private class MyHeadUpDisplayListener implements HeadUpDisplay.Listener {
 
-        // The callback functions here will be called from the GLThread. So,
-        // we need to post these runnables to the main thread
         public void onSharedPreferencesChanged() {
-            mHandler.post(new Runnable() {
-                public void run() {
-                    Camera.this.onSharedPreferenceChanged();
-                }
-            });
+            Camera.this.onSharedPreferenceChanged();
         }
 
         public void onRestorePreferencesClicked() {
-            mHandler.post(new Runnable() {
-                public void run() {
-                    Camera.this.onRestorePreferencesClicked();
-                }
-            });
+            Camera.this.onRestorePreferencesClicked();
         }
 
         public void onPopupWindowVisibilityChanged(int visibility) {
diff --git a/src/com/android/camera/ui/AbstractIndicator.java b/src/com/android/camera/ui/AbstractIndicator.java
index 583c974..d805c96 100644
--- a/src/com/android/camera/ui/AbstractIndicator.java
+++ b/src/com/android/camera/ui/AbstractIndicator.java
@@ -9,7 +9,7 @@
 
 import javax.microedition.khronos.opengles.GL11;
 
-public abstract class AbstractIndicator extends GLView {
+abstract class AbstractIndicator extends GLView {
     private static final int DEFAULT_PADDING = 3;
     private int mOrientation = 0;
 
diff --git a/src/com/android/camera/ui/BasicIndicator.java b/src/com/android/camera/ui/BasicIndicator.java
index 43761b3..dc52af7 100644
--- a/src/com/android/camera/ui/BasicIndicator.java
+++ b/src/com/android/camera/ui/BasicIndicator.java
@@ -8,7 +8,7 @@
 import com.android.camera.Util;
 import com.android.camera.ui.GLListView.OnItemSelectedListener;
 
-public class BasicIndicator extends AbstractIndicator {
+class BasicIndicator extends AbstractIndicator {
 
     private final ResourceTexture mIcon[];
     private final IconListPreference mPreference;
diff --git a/src/com/android/camera/ui/CameraEGLConfigChooser.java b/src/com/android/camera/ui/CameraEGLConfigChooser.java
index 44d7394..6a3bb2a 100644
--- a/src/com/android/camera/ui/CameraEGLConfigChooser.java
+++ b/src/com/android/camera/ui/CameraEGLConfigChooser.java
@@ -26,7 +26,7 @@
  * choose a configuration that support RGBA_8888 format and if possible,
  * with stencil buffer, but is not required.
  */
-public class CameraEGLConfigChooser implements EGLConfigChooser {
+class CameraEGLConfigChooser implements EGLConfigChooser {
 
     private static final int COLOR_BITS = 8;
 
diff --git a/src/com/android/camera/ui/CameraHeadUpDisplay.java b/src/com/android/camera/ui/CameraHeadUpDisplay.java
index 9883698..4d98302 100644
--- a/src/com/android/camera/ui/CameraHeadUpDisplay.java
+++ b/src/com/android/camera/ui/CameraHeadUpDisplay.java
@@ -53,22 +53,29 @@
         addIndicator(context, group, CameraSettings.KEY_FLASH_MODE);
     }
 
-    public void setZoomListener(ZoomController.ZoomListener listener) {
+    public void setZoomListener(ZoomControllerListener listener) {
+        // The rendering thread won't access listener variable, so we don't
+        // need to do concurrency protection here
         mZoomIndicator.setZoomListener(listener);
     }
 
     public void setZoomIndex(int index) {
-        mZoomIndicator.setZoomIndex(index);
+        GLRootView root = getGLRootView();
+        if (root != null) {
+            synchronized (root) {
+                mZoomIndicator.setZoomIndex(index);
+            }
+        } else {
+            mZoomIndicator.setZoomIndex(index);
+        }
     }
 
     public void setGpsHasSignal(final boolean hasSignal) {
         GLRootView root = getGLRootView();
         if (root != null) {
-            root.queueEvent(new Runnable() {
-                public void run() {
-                    mGpsIndicator.setHasSignal(hasSignal);
-                }
-            });
+            synchronized (root) {
+                mGpsIndicator.setHasSignal(hasSignal);
+            }
         } else {
             mGpsIndicator.setHasSignal(hasSignal);
         }
@@ -80,6 +87,17 @@
      * <code>setZoomIndex()</code>
      */
     public void setZoomRatios(float[] zoomRatios) {
+        GLRootView root = getGLRootView();
+        if (root != null) {
+            synchronized(root) {
+                setZoomRatiosLocked(zoomRatios);
+            }
+        } else {
+            setZoomRatiosLocked(zoomRatios);
+        }
+    }
+
+    private void setZoomRatiosLocked(float[] zoomRatios) {
         if (mZoomIndicator == null) {
             mZoomIndicator = new ZoomIndicator(mContext);
             mIndicatorBar.addComponent(mZoomIndicator);
diff --git a/src/com/android/camera/ui/CanvasTexture.java b/src/com/android/camera/ui/CanvasTexture.java
index fa0b76e..32ec8f1 100644
--- a/src/com/android/camera/ui/CanvasTexture.java
+++ b/src/com/android/camera/ui/CanvasTexture.java
@@ -5,7 +5,7 @@
 import android.graphics.Bitmap.Config;
 
 /** Using a canvas to draw the texture */
-public abstract class CanvasTexture extends Texture {
+abstract class CanvasTexture extends Texture {
     protected Canvas mCanvas;
 
     public CanvasTexture(int width, int height) {
diff --git a/src/com/android/camera/ui/FrameTexture.java b/src/com/android/camera/ui/FrameTexture.java
index cc56f8c..ab7ace3 100644
--- a/src/com/android/camera/ui/FrameTexture.java
+++ b/src/com/android/camera/ui/FrameTexture.java
@@ -4,7 +4,7 @@
 
 import javax.microedition.khronos.opengles.GL11;
 
-public abstract class FrameTexture extends Texture {
+abstract class FrameTexture extends Texture {
 
     public FrameTexture() {
     }
diff --git a/src/com/android/camera/ui/GLListView.java b/src/com/android/camera/ui/GLListView.java
index dcba22f..c4a607c 100644
--- a/src/com/android/camera/ui/GLListView.java
+++ b/src/com/android/camera/ui/GLListView.java
@@ -18,7 +18,7 @@
 
 import javax.microedition.khronos.opengles.GL11;
 
-public class GLListView extends GLView {
+class GLListView extends GLView {
     @SuppressWarnings("unused")
     private static final String TAG = "GLListView";
     private static final int INDEX_NONE = -1;
@@ -61,13 +61,30 @@
 
     public GLListView(Context context) {
         mScroller = new Scroller(context);
-    }
+        mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                GLRootView root = getGLRootView();
+                if (root != null) {
+                    synchronized (root) {
+                        handleMessageLocked(msg);
+                    }
+                } else {
+                    handleMessageLocked(msg);
+                }
+            }
 
-    private final Runnable mHideScrollBar = new Runnable() {
-        public void run() {
-            setScrollBarVisible(false);
-        }
-    };
+            private void handleMessageLocked(Message msg) {
+                switch(msg.what) {
+                    case HIDE_SCROLL_BAR:
+                        setScrollBarVisible(false);
+                        break;
+                }
+            }
+        };
+        mGestureDetector = new GestureDetector(
+                context, new MyGestureListener(), mHandler);
+    }
 
     @Override
     protected void onVisibilityChanged(int visibility) {
@@ -79,26 +96,6 @@
         }
     }
 
-    @Override
-    protected void onAttachToRoot(GLRootView root) {
-        super.onAttachToRoot(root);
-        mHandler = new Handler(root.getTimerLooper()) {
-            @Override
-            public void handleMessage(Message msg) {
-                GLRootView root = getGLRootView();
-                switch(msg.what) {
-                    case HIDE_SCROLL_BAR:
-                        root.queueEvent(mHideScrollBar);
-                        break;
-                }
-            }
-        };
-        mGestureDetector =
-            new GestureDetector(root.getContext(),
-            new MyGestureListener(), mHandler);
-    }
-
-
     private void setScrollBarVisible(boolean visible) {
         if (mScrollBarVisible == visible || mScrollbar == null) return;
         mScrollBarVisible = visible;
@@ -357,13 +354,8 @@
 
         @Override
         public void onShowPress(MotionEvent e) {
-            if (!mScrollable) return;
-            final int y = (int) e.getY();
-            getGLRootView().queueEvent(new Runnable() {
-                public void run() {
-                    if (mIsPressed) findAndSetHighlightItem(y);
-                }
-            });
+            if (!mScrollable || !mIsPressed) return;
+            findAndSetHighlightItem((int) e.getY());
         }
 
         @Override
diff --git a/src/com/android/camera/ui/GLOptionHeader.java b/src/com/android/camera/ui/GLOptionHeader.java
index a11935c..a922026 100644
--- a/src/com/android/camera/ui/GLOptionHeader.java
+++ b/src/com/android/camera/ui/GLOptionHeader.java
@@ -8,7 +8,7 @@
 
 import javax.microedition.khronos.opengles.GL11;
 
-public class GLOptionHeader extends GLView {
+class GLOptionHeader extends GLView {
     private static final int FONT_COLOR = 0xFF979797;
     private static final float FONT_SIZE = 12;
     private static final int HORIZONTAL_PADDINGS = 4;
diff --git a/src/com/android/camera/ui/GLOptionItem.java b/src/com/android/camera/ui/GLOptionItem.java
index 9d2deee..2176121 100644
--- a/src/com/android/camera/ui/GLOptionItem.java
+++ b/src/com/android/camera/ui/GLOptionItem.java
@@ -10,7 +10,7 @@
 
 import javax.microedition.khronos.opengles.GL11;
 
-public class GLOptionItem extends GLView {
+class GLOptionItem extends GLView {
     private static final int FONT_COLOR = Color.WHITE;
     private static final float FONT_SIZE = 18;
 
diff --git a/src/com/android/camera/ui/GLOutOfMemoryException.java b/src/com/android/camera/ui/GLOutOfMemoryException.java
index 2e2bac7..e262b1d 100644
--- a/src/com/android/camera/ui/GLOutOfMemoryException.java
+++ b/src/com/android/camera/ui/GLOutOfMemoryException.java
@@ -1,5 +1,4 @@
 package com.android.camera.ui;
 
 public class GLOutOfMemoryException extends Exception {
-
 }
diff --git a/src/com/android/camera/ui/GLRootView.java b/src/com/android/camera/ui/GLRootView.java
index dc10be4..0bbc3ed 100644
--- a/src/com/android/camera/ui/GLRootView.java
+++ b/src/com/android/camera/ui/GLRootView.java
@@ -8,9 +8,6 @@
 import android.graphics.PixelFormat;
 import android.opengl.GLSurfaceView;
 import android.opengl.GLU;
-import android.os.ConditionVariable;
-import android.os.Looper;
-import android.os.Process;
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -23,14 +20,20 @@
 import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.Stack;
-import java.util.concurrent.Callable;
-import java.util.concurrent.FutureTask;
 
 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";
@@ -72,15 +75,8 @@
     private int mFlags = FLAG_NEED_LAYOUT;
     private long mAnimationTime;
 
-    private Thread mGLThread;
-
-    private boolean mIsQueueActive = true;
     private CameraEGLConfigChooser mEglConfigChooser = new CameraEGLConfigChooser();
 
-
-    // TODO: move this part (handler) into GLSurfaceView
-    private final Looper mLooper;
-
     public GLRootView(Context context) {
         this(context, null);
     }
@@ -88,7 +84,6 @@
     public GLRootView(Context context, AttributeSet attrs) {
         super(context, attrs);
         initialize();
-        mLooper = Looper.getMainLooper();
     }
 
     void registerLaunchedAnimation(Animation animation) {
@@ -147,14 +142,6 @@
         freeTransformation(trans);
     }
 
-    public void runInGLThread(Runnable runnable) {
-        if (Thread.currentThread() == mGLThread) {
-            runnable.run();
-        } else {
-            queueEvent(runnable);
-        }
-    }
-
     public CameraEGLConfigChooser getEGLConfigChooser() {
         return mEglConfigChooser;
     }
@@ -240,10 +227,6 @@
             setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
         }
 
-        // Increase the priority of the render thread
-        Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
-        mGLThread = Thread.currentThread();
-
         // Disable unused state
         gl.glDisable(GL11.GL_LIGHTING);
 
@@ -409,8 +392,7 @@
         drawTexture(texture, x, y, width, height, mTransformation.getAlpha());
     }
 
-    // This is a GLSurfaceView.Renderer callback
-    public void onDrawFrame(GL10 gl) {
+    public synchronized void onDrawFrame(GL10 gl) {
         if (ENABLE_FPS_TEST) {
             long now = System.nanoTime();
             if (mFrameCountingStart == 0) {
@@ -442,31 +424,11 @@
     }
 
     @Override
-    public boolean dispatchTouchEvent(MotionEvent event) {
+    public synchronized boolean dispatchTouchEvent(MotionEvent event) {
         // If this has been detached from root, we don't need to handle event
-        if (!mIsQueueActive) return false;
-        FutureTask<Boolean> task = new FutureTask<Boolean>(
-                new TouchEventHandler(event));
-        queueEventOrThrowException(task);
-        try {
-            return task.get();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private class TouchEventHandler implements Callable<Boolean> {
-
-        private final MotionEvent mEvent;
-
-        public TouchEventHandler(MotionEvent event) {
-            mEvent = event;
-        }
-
-        public Boolean call() throws Exception {
-            if (mContentView == null) return false;
-            return mContentView.dispatchTouchEvent(mEvent);
-        }
+        return mContentView != null
+                ? mContentView.dispatchTouchEvent(event)
+                : false;
     }
 
     public DisplayMetrics getDisplayMetrics() {
@@ -528,31 +490,4 @@
         texture.setTextureSize(newWidth, newHeight);
     }
 
-    public synchronized void queueEventOrThrowException(Runnable runnable) {
-        if (!mIsQueueActive) {
-            throw new IllegalStateException("GLThread has exit");
-        }
-        super.queueEvent(runnable);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        final ConditionVariable var = new ConditionVariable();
-        synchronized (this) {
-            mIsQueueActive = false;
-            queueEvent(new Runnable() {
-                public void run() {
-                    var.open();
-                }
-            });
-        }
-
-        // Make sure all the runnables in the event queue is executed.
-        var.block();
-        super.onDetachedFromWindow();
-    }
-
-    protected Looper getTimerLooper() {
-        return mLooper;
-    }
 }
diff --git a/src/com/android/camera/ui/GpsIndicator.java b/src/com/android/camera/ui/GpsIndicator.java
index a97ed2a..51cc04a 100644
--- a/src/com/android/camera/ui/GpsIndicator.java
+++ b/src/com/android/camera/ui/GpsIndicator.java
@@ -6,7 +6,7 @@
 import com.android.camera.IconListPreference;
 import com.android.camera.PreferenceGroup;
 
-public class GpsIndicator extends BasicIndicator {
+class GpsIndicator extends BasicIndicator {
 
     private static final int GPS_ON_INDEX = 1;
 
diff --git a/src/com/android/camera/ui/HeadUpDisplay.java b/src/com/android/camera/ui/HeadUpDisplay.java
index 2c6a00b..819fde7 100644
--- a/src/com/android/camera/ui/HeadUpDisplay.java
+++ b/src/com/android/camera/ui/HeadUpDisplay.java
@@ -3,8 +3,6 @@
 import static com.android.camera.ui.GLRootView.dpToPixel;
 
 import java.util.ArrayList;
-import java.util.concurrent.Callable;
-import java.util.concurrent.FutureTask;
 
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -27,12 +25,9 @@
 import com.android.camera.PreferenceGroup;
 import com.android.camera.R;
 
-// This is the UI for the on-screen settings. It mainly run in the GLThread. It
-// will modify the shared-preferences. The concurrency rule is: The shared-
-// preference will be updated in the GLThread. And an event will be trigger in
-// the main UI thread so that the camera settings can be updated by reading the
-// updated preferences. The two threads synchronize on the monitor of the
-// default SharedPrefernce instance.
+// This is the UI for the on-screen settings. Since the rendering is run in the
+// GL thread. If any values will be changed in the main thread, it needs to
+// synchronize on the <code>GLRootView</code> instance.
 public class HeadUpDisplay extends GLView {
     private static final int INDICATOR_BAR_TIMEOUT = 5500;
     private static final int POPUP_WINDOW_TIMEOUT = 5000;
@@ -65,7 +60,32 @@
 
     protected Listener mListener;
 
-    private Handler mHandler;
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            GLRootView root = getGLRootView();
+            if (root != null) {
+                synchronized (root) {
+                    handleMessageLocked(msg);
+                }
+            } else {
+                handleMessageLocked(msg);
+            }
+        }
+
+        private void handleMessageLocked(Message msg) {
+            switch(msg.what) {
+                case DESELECT_INDICATOR:
+                    mIndicatorBar.setSelectedIndex(IndicatorBar.INDEX_NONE);
+                    break;
+                case DEACTIVATE_INDICATOR_BAR:
+                    if (mIndicatorBar != null) {
+                        mIndicatorBar.setActivated(false);
+                    }
+                    break;
+            }
+        }
+    };
 
     private final OnSharedPreferenceChangeListener mSharedPreferenceChangeListener =
             new OnSharedPreferenceChangeListener() {
@@ -81,27 +101,6 @@
         initializeStaticVariables(context);
     }
 
-    @Override
-    protected void onAttachToRoot(GLRootView root) {
-        super.onAttachToRoot(root);
-        mHandler = new Handler(root.getTimerLooper()) {
-            @Override
-            public void handleMessage(Message msg) {
-                GLRootView root = getGLRootView();
-                Runnable runnable = null;
-                switch(msg.what) {
-                    case DESELECT_INDICATOR:
-                        runnable = mDeselectIndicator;
-                        break;
-                    case DEACTIVATE_INDICATOR_BAR:
-                        runnable = mDeactivateIndicatorBar;
-                        break;
-                }
-                if (runnable != null) root.queueEvent(runnable);
-            }
-        };
-    }
-
     private static void initializeStaticVariables(Context context) {
         if (sIndicatorBarRightMargin >= 0) return;
 
@@ -110,18 +109,6 @@
         sPopupTriangleOffset = dpToPixel(context, POPUP_TRIANGLE_OFFSET);
     }
 
-    private final Runnable mDeselectIndicator = new Runnable () {
-        public void run() {
-            mIndicatorBar.setSelectedIndex(IndicatorBar.INDEX_NONE);
-       }
-    };
-
-    private final Runnable mDeactivateIndicatorBar = new Runnable () {
-        public void run() {
-            if (mIndicatorBar != null) mIndicatorBar.setActivated(false);
-        }
-    };
-
     /**
      * The callback interface. All the callbacks will be called from the
      * GLThread.
@@ -133,23 +120,22 @@
     }
 
     public void overrideSettings(final String ... keyvalues) {
+        GLRootView root = getGLRootView();
+        if (root != null) {
+            synchronized (root) {
+                overrideSettingsLocked(keyvalues);
+            }
+        } else {
+            overrideSettingsLocked(keyvalues);
+        }
+    }
+
+    public void overrideSettingsLocked(final String ... keyvalues) {
         if (keyvalues.length % 2 != 0) {
             throw new IllegalArgumentException();
         }
-        GLRootView root = getGLRootView();
-        if (root != null) {
-            root.queueEvent(new Runnable() {
-                public void run() {
-                    for (int i = 0, n = keyvalues.length; i < n; i += 2) {
-                        mIndicatorBar.overrideSettings(
-                                keyvalues[i], keyvalues[i + 1]);
-                    }
-                }
-            });
-        } else {
-            for (int i = 0, n = keyvalues.length; i < n; i += 2) {
-                mIndicatorBar.overrideSettings(keyvalues[i], keyvalues[i + 1]);
-            }
+        for (int i = 0, n = keyvalues.length; i < n; i += 2) {
+            mIndicatorBar.overrideSettings(keyvalues[i], keyvalues[i + 1]);
         }
     }
 
@@ -240,12 +226,18 @@
                 DEACTIVATE_INDICATOR_BAR, INDICATOR_BAR_TIMEOUT);
     }
 
-    public void deactivateIndicatorBar() {
-        if (mIndicatorBar == null) return;
-        mIndicatorBar.setActivated(false);
+    public void setOrientation(int orientation) {
+        GLRootView root = getGLRootView();
+        if (root != null) {
+            synchronized (root) {
+                setOrientationLocked(orientation);
+            }
+        } else {
+            setOrientationLocked(orientation);
+        }
     }
 
-    public void setOrientation(int orientation) {
+    private void setOrientationLocked(int orientation) {
         mOrientation = orientation;
         mIndicatorBar.setOrientation(orientation);
         if (mPopupWindow == null) return;
@@ -279,6 +271,8 @@
     }
 
     public void setEnabled(boolean enabled) {
+        // The mEnabled variable is not related to the rendering thread, so we
+        // don't need to synchronize on the GLRootView.
         if (mEnabled == enabled) return;
         mEnabled = enabled;
     }
@@ -357,59 +351,63 @@
         }
     }
 
-    private final Callable<Boolean> mCollapse = new Callable<Boolean>() {
-        public Boolean call() {
-            if (!mIndicatorBar.isActivated()) return false;
-            mHandler.removeMessages(DESELECT_INDICATOR);
-            mHandler.removeMessages(DEACTIVATE_INDICATOR_BAR);
+    public boolean collapse() {
+        // We don't need to synchronize on GLRootView, since both the
+        // <code>isActivated()</code> and rendering thread are read-only to
+        // the variables inside.
+        if (!mIndicatorBar.isActivated()) return false;
+        mHandler.removeMessages(DESELECT_INDICATOR);
+        mHandler.removeMessages(DEACTIVATE_INDICATOR_BAR);
+        GLRootView root = getGLRootView();
+        if (root != null) {
+            synchronized (root) {
+                mIndicatorBar.setSelectedIndex(IndicatorBar.INDEX_NONE);
+                mIndicatorBar.setActivated(false);
+            }
+        } else {
             mIndicatorBar.setSelectedIndex(IndicatorBar.INDEX_NONE);
             mIndicatorBar.setActivated(false);
-            return true;
         }
-    };
-
-    public boolean collapse() {
-        FutureTask<Boolean> task = new FutureTask<Boolean>(mCollapse);
-        getGLRootView().runInGLThread(task);
-        try {
-            return task.get().booleanValue();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+        return true;
     }
 
     public void setListener(Listener listener) {
+        // No synchronization: mListener won't be accessed in rendering thread
         mListener = listener;
     }
 
     public void restorePreferences(final Parameters param) {
-        getGLRootView().runInGLThread(new Runnable() {
-            public void run() {
-                OnSharedPreferenceChangeListener l =
-                        mSharedPreferenceChangeListener;
-                // Unregister the listener since "upgrade preference" will
-                // change bunch of preferences. We can handle them with one
-                // onSharedPreferencesChanged();
-                mSharedPrefs.unregisterOnSharedPreferenceChangeListener(l);
-                Context context = getGLRootView().getContext();
-                synchronized (mSharedPrefs) {
-                    Editor editor = mSharedPrefs.edit();
-                    editor.clear();
-                    editor.commit();
-                }
-                CameraSettings.upgradePreferences(mSharedPrefs);
-                CameraSettings.initialCameraPictureSize(context, param);
-                reloadPreferences();
-                if (mListener != null) {
-                    mListener.onSharedPreferencesChanged();
-                }
-                mSharedPrefs.registerOnSharedPreferenceChangeListener(l);
-            }
-        });
+        // Do synchronization in "reloadPreferences()"
+
+        OnSharedPreferenceChangeListener l =
+                mSharedPreferenceChangeListener;
+        // Unregister the listener since "upgrade preference" will
+        // change bunch of preferences. We can handle them with one
+        // onSharedPreferencesChanged();
+        mSharedPrefs.unregisterOnSharedPreferenceChangeListener(l);
+        Context context = getGLRootView().getContext();
+        Editor editor = mSharedPrefs.edit();
+        editor.clear();
+        editor.commit();
+        CameraSettings.upgradePreferences(mSharedPrefs);
+        CameraSettings.initialCameraPictureSize(context, param);
+        reloadPreferences();
+        if (mListener != null) {
+            mListener.onSharedPreferencesChanged();
+        }
+        mSharedPrefs.registerOnSharedPreferenceChangeListener(l);
     }
 
     public void reloadPreferences() {
-        mPreferenceGroup.reloadValue();
-        mIndicatorBar.reloadPreferences();
+        GLRootView root = getGLRootView();
+        if (root != null) {
+            synchronized (root) {
+                mPreferenceGroup.reloadValue();
+                mIndicatorBar.reloadPreferences();
+            }
+        } else {
+            mPreferenceGroup.reloadValue();
+            mIndicatorBar.reloadPreferences();
+        }
     }
 }
diff --git a/src/com/android/camera/ui/IndicatorBar.java b/src/com/android/camera/ui/IndicatorBar.java
index e5ee82a..0b29c23 100644
--- a/src/com/android/camera/ui/IndicatorBar.java
+++ b/src/com/android/camera/ui/IndicatorBar.java
@@ -7,7 +7,7 @@
 import android.view.View.MeasureSpec;
 import android.view.animation.AlphaAnimation;
 
-public class IndicatorBar extends GLView {
+class IndicatorBar extends GLView {
 
     public static final int INDEX_NONE = -1;
 
diff --git a/src/com/android/camera/ui/LinearLayout.java b/src/com/android/camera/ui/LinearLayout.java
index 4f251d9..1da665f 100644
--- a/src/com/android/camera/ui/LinearLayout.java
+++ b/src/com/android/camera/ui/LinearLayout.java
@@ -3,7 +3,7 @@
 import android.graphics.Rect;
 import android.view.View.MeasureSpec;
 
-public class LinearLayout extends GLView {
+class LinearLayout extends GLView {
 
     @Override
     protected void onMeasure(int widthSpec, int heightSpec) {
diff --git a/src/com/android/camera/ui/MeasureHelper.java b/src/com/android/camera/ui/MeasureHelper.java
index 1749443..51b49c1 100644
--- a/src/com/android/camera/ui/MeasureHelper.java
+++ b/src/com/android/camera/ui/MeasureHelper.java
@@ -3,7 +3,7 @@
 import android.graphics.Rect;
 import android.view.View.MeasureSpec;
 
-public class MeasureHelper {
+class MeasureHelper {
 
     private final GLView mComponent;
     private int mPreferredWidth;
diff --git a/src/com/android/camera/ui/NinePatchTexture.java b/src/com/android/camera/ui/NinePatchTexture.java
index 60d6f30..7c27737 100644
--- a/src/com/android/camera/ui/NinePatchTexture.java
+++ b/src/com/android/camera/ui/NinePatchTexture.java
@@ -8,7 +8,7 @@
 
 import javax.microedition.khronos.opengles.GL11;
 
-public class NinePatchTexture extends FrameTexture {
+class NinePatchTexture extends FrameTexture {
 
     private MyTexture mDelegate;
 
diff --git a/src/com/android/camera/ui/OtherSettingsIndicator.java b/src/com/android/camera/ui/OtherSettingsIndicator.java
index 3fbef39..807143f 100644
--- a/src/com/android/camera/ui/OtherSettingsIndicator.java
+++ b/src/com/android/camera/ui/OtherSettingsIndicator.java
@@ -7,7 +7,7 @@
 
 import java.util.HashMap;
 
-public class OtherSettingsIndicator extends AbstractIndicator {
+class OtherSettingsIndicator extends AbstractIndicator {
 
     private final ListPreference mPreference[];
     private final GLListView.Model mAdapters[];
diff --git a/src/com/android/camera/ui/PopupWindow.java b/src/com/android/camera/ui/PopupWindow.java
index c12dce9..cc6fce2 100644
--- a/src/com/android/camera/ui/PopupWindow.java
+++ b/src/com/android/camera/ui/PopupWindow.java
@@ -10,7 +10,7 @@
 
 import javax.microedition.khronos.opengles.GL11;
 
-public class PopupWindow extends GLView {
+class PopupWindow extends GLView {
 
     protected Texture mAnchor;
     protected int mAnchorOffset;
diff --git a/src/com/android/camera/ui/PreferenceAdapter.java b/src/com/android/camera/ui/PreferenceAdapter.java
index ddac613..e13e9f3 100644
--- a/src/com/android/camera/ui/PreferenceAdapter.java
+++ b/src/com/android/camera/ui/PreferenceAdapter.java
@@ -8,7 +8,7 @@
 
 import java.util.ArrayList;
 
-public class PreferenceAdapter
+class PreferenceAdapter
         implements GLListView.Model, GLListView.OnItemSelectedListener {
 
     private static final int ICON_NONE = 0;
diff --git a/src/com/android/camera/ui/RawTexture.java b/src/com/android/camera/ui/RawTexture.java
index aecea9d..d01a8ba 100644
--- a/src/com/android/camera/ui/RawTexture.java
+++ b/src/com/android/camera/ui/RawTexture.java
@@ -4,7 +4,7 @@
 
 import javax.microedition.khronos.opengles.GL11;
 
-public class RawTexture extends Texture {
+class RawTexture extends Texture {
 
     private RawTexture(GL11 gl, int id) {
         super(gl, id, STATE_LOADED);
diff --git a/src/com/android/camera/ui/ResourceTexture.java b/src/com/android/camera/ui/ResourceTexture.java
index e63a2ef..8a947ee 100644
--- a/src/com/android/camera/ui/ResourceTexture.java
+++ b/src/com/android/camera/ui/ResourceTexture.java
@@ -6,7 +6,7 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 
-public class ResourceTexture extends Texture {
+class ResourceTexture extends Texture {
 
     private final Context mContext;
     private final int mResId;
diff --git a/src/com/android/camera/ui/RestoreSettingsItem.java b/src/com/android/camera/ui/RestoreSettingsItem.java
index 2c74149..f1a2342 100644
--- a/src/com/android/camera/ui/RestoreSettingsItem.java
+++ b/src/com/android/camera/ui/RestoreSettingsItem.java
@@ -7,7 +7,7 @@
 
 import javax.microedition.khronos.opengles.GL11;
 
-public class RestoreSettingsItem extends GLView {
+class RestoreSettingsItem extends GLView {
     private static final int FONT_COLOR = Color.WHITE;
     private static final float FONT_SIZE = 18;
 
diff --git a/src/com/android/camera/ui/RotatePane.java b/src/com/android/camera/ui/RotatePane.java
index 0a7eefd..fa41412 100644
--- a/src/com/android/camera/ui/RotatePane.java
+++ b/src/com/android/camera/ui/RotatePane.java
@@ -6,7 +6,7 @@
 import javax.microedition.khronos.opengles.GL11;
 
 
-public class RotatePane extends GLView {
+class RotatePane extends GLView {
 
     public static final int UP = 0;
     public static final int RIGHT = 1;
diff --git a/src/com/android/camera/ui/StringTexture.java b/src/com/android/camera/ui/StringTexture.java
index 12334b4..8b01df6 100644
--- a/src/com/android/camera/ui/StringTexture.java
+++ b/src/com/android/camera/ui/StringTexture.java
@@ -5,7 +5,7 @@
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
 
-public class StringTexture extends CanvasTexture {
+class StringTexture extends CanvasTexture {
     private static int DEFAULT_PADDING = 1;
 
     private final String mText;
diff --git a/src/com/android/camera/ui/Texture.java b/src/com/android/camera/ui/Texture.java
index e1d6e39..0106337 100644
--- a/src/com/android/camera/ui/Texture.java
+++ b/src/com/android/camera/ui/Texture.java
@@ -8,7 +8,7 @@
 import javax.microedition.khronos.opengles.GL11;
 import javax.microedition.khronos.opengles.GL11Ext;
 
-public abstract class Texture {
+abstract class Texture {
 
     @SuppressWarnings("unused")
     private static final String TAG = "Texture";
diff --git a/src/com/android/camera/ui/ZoomController.java b/src/com/android/camera/ui/ZoomController.java
index 83e2d09..e62cfb8 100644
--- a/src/com/android/camera/ui/ZoomController.java
+++ b/src/com/android/camera/ui/ZoomController.java
@@ -14,7 +14,7 @@
 
 import javax.microedition.khronos.opengles.GL11;
 
-public class ZoomController extends GLView {
+class ZoomController extends GLView {
     private static final int LABEL_COLOR = Color.WHITE;
 
     private static final DecimalFormat sZoomFormat = new DecimalFormat("#.#x");
@@ -52,11 +52,7 @@
     private int mSliderLeft;
     private int mSliderPosition = INVALID_POSITION;
     private float mValueGap;
-    private ZoomListener mZoomListener;
-
-    public interface ZoomListener {
-        public void onZoomChanged(int index, float ratio, boolean isMoving);
-    }
+    private ZoomControllerListener mZoomListener;
 
     public ZoomController(Context context) {
         initializeStaticVariable(context);
@@ -255,7 +251,7 @@
         }
     }
 
-    public void setZoomListener(ZoomListener listener) {
+    public void setZoomListener(ZoomControllerListener listener) {
         mZoomListener = listener;
     }
 
diff --git a/src/com/android/camera/ui/ZoomControllerListener.java b/src/com/android/camera/ui/ZoomControllerListener.java
new file mode 100644
index 0000000..76209d7
--- /dev/null
+++ b/src/com/android/camera/ui/ZoomControllerListener.java
@@ -0,0 +1,6 @@
+package com.android.camera.ui;
+
+public interface ZoomControllerListener {
+    public void onZoomChanged(int index, float ratio, boolean isMoving);
+}
+
diff --git a/src/com/android/camera/ui/ZoomIndicator.java b/src/com/android/camera/ui/ZoomIndicator.java
index 4e7f1f4..c115f25 100644
--- a/src/com/android/camera/ui/ZoomIndicator.java
+++ b/src/com/android/camera/ui/ZoomIndicator.java
@@ -3,11 +3,11 @@
 import android.content.Context;
 
 import com.android.camera.R;
-import com.android.camera.ui.ZoomController.ZoomListener;
+import com.android.camera.ui.ZoomControllerListener;
 
 import java.text.DecimalFormat;
 
-public class ZoomIndicator extends AbstractIndicator {
+class ZoomIndicator extends AbstractIndicator {
     private static final DecimalFormat sZoomFormat = new DecimalFormat("#.#x");
     private static final float FONT_SIZE = 18;
     private static final int FONT_COLOR = 0xA8FFFFFF;
@@ -18,7 +18,7 @@
 
     private ZoomController mZoomController;
     private LinearLayout mPopupContent;
-    private ZoomListener mZoomListener;
+    private ZoomControllerListener mZoomListener;
     private int mZoomIndex = 0;
     private int mDrawIndex = -1;
     private float mZoomRatios[];
@@ -97,7 +97,7 @@
         requestLayout();
     }
 
-    private class MyZoomListener implements ZoomController.ZoomListener {
+    private class MyZoomListener implements ZoomControllerListener {
         public void onZoomChanged(int index, float value, boolean isMoving) {
             if (mZoomListener != null) {
                 mZoomListener.onZoomChanged(index, value, isMoving);
@@ -112,7 +112,7 @@
         invalidate();
     }
 
-    public void setZoomListener(ZoomListener listener) {
+    public void setZoomListener(ZoomControllerListener listener) {
         mZoomListener = listener;
     }