Major refactor: Make resources access through app.

1. Make the camera device avaiability a callback event so the process can be
event-driven.
2. Add ModuleManager interface and implementation.
3. Make AndroidCameraManagerImpl implementation package private.

This CL is the refactoring work on Gallery2 side. The other part is
http://ag/389297/

Known issue: only work for normal photo mode and video mode for now.

Change-Id: Ib0a89ca56c00f6a7294192e9737a70dec5143ee3
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index b1d6957..82a82c2 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -55,7 +55,6 @@
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.MotionEvent;
-import android.view.OrientationEventListener;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
@@ -67,12 +66,17 @@
 
 import com.android.camera.app.AppController;
 import com.android.camera.app.AppManagerFactory;
+import com.android.camera.app.CameraController;
+import com.android.camera.app.CameraManager;
+import com.android.camera.app.CameraManagerFactory;
+import com.android.camera.app.CameraProvider;
 import com.android.camera.app.ImageTaskManager;
 import com.android.camera.app.MediaSaver;
+import com.android.camera.app.ModuleManagerImpl;
 import com.android.camera.app.OrientationManager;
 import com.android.camera.app.OrientationManagerImpl;
-import com.android.camera.app.PlaceholderManager;
 import com.android.camera.app.PanoramaStitchingManager;
+import com.android.camera.app.PlaceholderManager;
 import com.android.camera.crop.CropActivity;
 import com.android.camera.data.CameraDataAdapter;
 import com.android.camera.data.CameraPreviewData;
@@ -87,6 +91,7 @@
 import com.android.camera.filmstrip.FilmstripController;
 import com.android.camera.filmstrip.FilmstripImageData;
 import com.android.camera.filmstrip.FilmstripListener;
+import com.android.camera.module.ModuleController;
 import com.android.camera.tinyplanet.TinyPlanetFragment;
 import com.android.camera.ui.DetailsDialog;
 import com.android.camera.ui.FilmstripView;
@@ -99,13 +104,12 @@
 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
 import com.android.camera.util.RefocusHelper;
 import com.android.camera.util.UsageStatistics;
-
 import com.android.camera2.R;
 
 import java.io.File;
 
 public class CameraActivity extends Activity
-        implements AppController, ModeListView.ModeSwitchListener,
+        implements AppController, ModeListView.ModeSwitchListener, CameraManager.CameraOpenCallback,
         ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener,
         OrientationManager.OnOrientationChangeListener {
 
@@ -126,8 +130,8 @@
     public static final String SECURE_CAMERA_EXTRA = "secure_camera";
 
     /**
-     * Request code from an activity we started that indicated that we do not
-     * want to reset the view to the preview in onResume.
+     * Request code from an activity we started that indicated that we do not want
+     * to reset the view to the preview in onResume.
      */
     public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
 
@@ -136,7 +140,9 @@
     private static final int HIDE_ACTION_BAR = 1;
     private static final long SHOW_ACTION_BAR_TIMEOUT_MS = 3000;
 
-    /** Whether onResume should reset the view to the preview. */
+    /**
+     * Whether onResume should reset the view to the preview.
+     */
     private boolean mResetToPreviewOnResume = true;
 
     // Supported operations at FilmStripView. Different data has different
@@ -153,15 +159,21 @@
     private static final int SUPPORT_SHOW_ON_MAP = 1 << 9;
     private static final int SUPPORT_ALL = 0xffffffff;
 
-    /** This data adapter is used by FilmStripView. */
+    /**
+     * This data adapter is used by FilmStripView.
+     */
     private LocalDataAdapter mDataAdapter;
-    /** This data adapter represents the real local camera data. */
+    /**
+     * This data adapter represents the real local camera data.
+     */
     private LocalDataAdapter mWrappedDataAdapter;
 
     private PanoramaStitchingManager mPanoramaManager;
     private PlaceholderManager mPlaceholderManager;
     private int mCurrentModuleIndex;
     private CameraModule mCurrentModule;
+    private ModuleController mCurrentModule2;
+    private ModuleManagerImpl mModuleManager;
     private FrameLayout mAboveFilmstripControlLayout;
     private FrameLayout mCameraModuleRootView;
     private FilmstripController mFilmstripController;
@@ -202,6 +214,8 @@
     private Intent mVideoShareIntent;
     private Intent mImageShareIntent;
 
+    private CameraController mCameraController;
+
     private MediaSaver mMediaSaver;
     private ServiceConnection mConnection = new ServiceConnection() {
         @Override
@@ -219,40 +233,6 @@
         }
     };
 
-    private CameraManager.CameraOpenCallback mCameraOpenCallback =
-            new CameraManager.CameraOpenCallback() {
-                @Override
-                public void onCameraOpened(CameraManager.CameraProxy camera) {
-                    // TODO: implement this.
-                }
-
-                @Override
-                public void onCameraDisabled(int cameraId) {
-                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
-                            UsageStatistics.ACTION_OPEN_FAIL, "security");
-
-                    CameraUtil.showErrorAndFinish(CameraActivity.this,
-                            R.string.camera_disabled);
-                }
-
-                @Override
-                public void onDeviceOpenFailure(int cameraId) {
-                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
-                            UsageStatistics.ACTION_OPEN_FAIL, "open");
-
-                    CameraUtil.showErrorAndFinish(CameraActivity.this,
-                            R.string.cannot_connect_camera);
-                }
-
-                @Override
-                public void onReconnectionFailure(CameraManager mgr) {
-                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
-                            UsageStatistics.ACTION_OPEN_FAIL, "reconnect");
-
-                    CameraUtil.showErrorAndFinish(CameraActivity.this,
-                            R.string.cannot_connect_camera);
-                }
-            };
 
     // close activity when screen turns off
     private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
@@ -264,6 +244,43 @@
 
     private static BroadcastReceiver sScreenOffReceiver;
 
+    @Override
+    public void onCameraOpened(CameraManager.CameraProxy camera) {
+        if (!mModuleManager.getModuleAgent(mCurrentModuleIndex).requestAppForCamera()) {
+            // We shouldn't be here. Just close the camera and leave.
+            camera.release(false);
+            throw new IllegalStateException("Camera opened but the module shouldn't be " +
+                    "requesting");
+        }
+        if (mCurrentModule2 != null) {
+            mCurrentModule2.onCameraAvailable(camera);
+        }
+    }
+
+    @Override
+    public void onCameraDisabled(int cameraId) {
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_OPEN_FAIL,
+                "security");
+
+        CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
+    }
+
+    @Override
+    public void onDeviceOpenFailure(int cameraId) {
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+                UsageStatistics.ACTION_OPEN_FAIL, "open");
+
+        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
+    }
+
+    @Override
+    public void onReconnectionFailure(CameraManager mgr) {
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+                UsageStatistics.ACTION_OPEN_FAIL, "reconnect");
+
+        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
+    }
+
     private static class ScreenOffReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -373,7 +390,7 @@
                         return;
                     }
 
-                    if(!arePreviewControlsVisible()) {
+                    if (!arePreviewControlsVisible()) {
                         setPreviewControlsVisibility(true);
                         CameraActivity.this.setSystemBarsVisibility(false);
                     }
@@ -437,8 +454,7 @@
                                     hidePanoStitchingProgress();
                                     return;
                                 }
-                                int panoStitchingProgress = mPanoramaManager.getTaskProgress(
-                                        contentUri);
+                                int panoStitchingProgress = mPanoramaManager.getTaskProgress(contentUri);
                                 if (panoStitchingProgress < 0) {
                                     hidePanoStitchingProgress();
                                     return;
@@ -731,8 +747,9 @@
 
     private void setMenuItemVisible(Menu menu, int itemId, boolean visible) {
         MenuItem item = menu.findItem(itemId);
-        if (item != null)
+        if (item != null) {
             item.setVisible(visible);
+        }
     }
 
     private ImageTaskManager.TaskListener mPlaceholderListener =
@@ -768,7 +785,7 @@
                 public void onTaskProgress(String filePath, Uri imageUri, int progress) {
                     // Do nothing
                 }
-    };
+            };
 
     private ImageTaskManager.TaskListener mStitchingListener =
             new ImageTaskManager.TaskListener() {
@@ -886,12 +903,6 @@
     }
 
     @Override
-    public CameraManager.CameraProxy getCameraProxy() {
-        // TODO: implement this
-        return null;
-    }
-
-    @Override
     public MediaSaver getMediaSaver() {
         return mMediaSaver;
     }
@@ -937,6 +948,11 @@
         }
     }
 
+    @Override
+    public CameraProvider getCameraProvider() {
+        return mCameraController;
+    }
+
     private void removeData(int dataID) {
         mDataAdapter.removeData(CameraActivity.this, dataID);
         if (mDataAdapter.getTotalNumber() > 1) {
@@ -1102,6 +1118,14 @@
         setContentView(R.layout.activity_main);
         mActionBar = getActionBar();
         mActionBar.addOnMenuVisibilityListener(this);
+        mMainHandler = new MainHandler(getMainLooper());
+        mCameraController =
+                new CameraController(this, this, mMainHandler,
+                        CameraManagerFactory.getAndroidCameraManager());
+        // TODO: Try to move all the resources allocation to happen as soon as
+        // possible so we can call module.init() at the earliest time.
+        mModuleManager = new ModuleManagerImpl();
+        setupModules();
 
         ModeListView modeListView = (ModeListView) findViewById(R.id.mode_list_layout);
         if (modeListView != null) {
@@ -1114,7 +1138,6 @@
             setRotationAnimation();
         }
 
-        mMainHandler = new MainHandler(getMainLooper());
         // Check if this is in the secure camera mode.
         Intent intent = getIntent();
         String action = intent.getAction();
@@ -1283,7 +1306,7 @@
             // users can click the undo button to bring back the image that they
             // chose to delete.
             if (mPendingDeletion && !mIsUndoingDeletion) {
-                 performDeletion();
+                performDeletion();
             }
         }
         return result;
@@ -1293,8 +1316,12 @@
     public void onPause() {
         // Delete photos that are pending deletion
         performDeletion();
+        // TODO: call mCurrentModule.pause() instead after all the modules
+        // support pause().
         mCurrentModule.onPauseBeforeSuper();
         mOrientationManager.pause();
+        // Close the camera and wait for the operation done.
+        mCameraController.closeCamera();
         super.onPause();
         mCurrentModule.onPauseAfterSuper();
 
@@ -1328,6 +1355,8 @@
                 UsageStatistics.ACTION_FOREGROUNDED, this.getClass().getSimpleName());
 
         mOrientationManager.resume();
+        // TODO: call mCurrentModule.resume() instead after all the modules
+        // support resume().
         mCurrentModule.onResumeBeforeSuper();
         super.onResume();
         mCurrentModule.onResumeAfterSuper();
@@ -1492,7 +1521,6 @@
             return;
         }
 
-        CameraHolder.instance().keep();
         closeModule(mCurrentModule);
         setModuleFromIndex(moduleIndex);
 
@@ -1513,37 +1541,16 @@
      * index an sets it as mCurrentModule.
      */
     private void setModuleFromIndex(int moduleIndex) {
-        mCurrentModuleIndex = moduleIndex;
-        switch (moduleIndex) {
-            case ModeListView.MODE_VIDEO:
-                mCurrentModule = new VideoModule();
-                break;
-
-            case ModeListView.MODE_PHOTO:
-                mCurrentModule = new PhotoModule();
-                break;
-
-            case ModeListView.MODE_WIDEANGLE:
-                mCurrentModule = new WideAnglePanoramaModule();
-                break;
-
-            case ModeListView.MODE_PHOTOSPHERE:
-                mCurrentModule = PhotoSphereHelper.createPanoramaModule();
-                break;
-            case ModeListView.MODE_GCAM:
-                // Force immediate release of Camera instance
-                CameraHolder.instance().strongRelease();
-                mCurrentModule = GcamHelper.createGcamModule();
-                break;
-            case ModeListView.MODE_CRAFT:
-                mCurrentModule = RefocusHelper.createRefocusModule();
-                break;
-            default:
-                // Fall back to photo mode.
-                mCurrentModule = new PhotoModule();
-                mCurrentModuleIndex = ModeListView.MODE_PHOTO;
-                break;
+        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(moduleIndex);
+        if (agent == null) {
+            return;
         }
+        if (!agent.requestAppForCamera()) {
+            mCameraController.closeCamera();
+        }
+        mCurrentModuleIndex = agent.getModuleId();
+        mCurrentModule2 = agent.createModule();
+        mCurrentModule = (CameraModule) mCurrentModule2;
     }
 
     /**
@@ -1564,9 +1571,9 @@
     /**
      * Launch the tiny planet editor.
      *
-     * @param data the data must be a 360 degree stereographically mapped
-     *            panoramic image. It will not be modified, instead a new item
-     *            with the result will be added to the filmstrip.
+     * @param data The data must be a 360 degree stereographically mapped
+     *             panoramic image. It will not be modified, instead a new item
+     *             with the result will be added to the filmstrip.
      */
     public void launchTinyPlanetEditor(LocalData data) {
         TinyPlanetFragment fragment = new TinyPlanetFragment();
@@ -1579,11 +1586,15 @@
 
     private void openModule(CameraModule module) {
         module.init(this, mCameraModuleRootView);
+        // TODO: call mCurrentModule.resume() instead after all the modules
+        // support resume().
         module.onResumeBeforeSuper();
         module.onResumeAfterSuper();
     }
 
     private void closeModule(CameraModule module) {
+        // TODO: call mCurrentModule.pause() instead after all the modules
+        // support pause().
         module.onPauseBeforeSuper();
         module.onPauseAfterSuper();
         ((ViewGroup) mCameraModuleRootView).removeAllViews();
@@ -1608,8 +1619,8 @@
         Log.v(TAG, "showing undo bar");
         mPendingDeletion = true;
         if (mUndoDeletionBar == null) {
-            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(
-                    R.layout.undo_bar, mAboveFilmstripControlLayout, true);
+            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
+                    mAboveFilmstripControlLayout, true);
             mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
             View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
             button.setOnClickListener(new View.OnClickListener() {
@@ -1630,7 +1641,7 @@
                     if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                         mIsUndoingDeletion = true;
                     } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
-                        mIsUndoingDeletion =false;
+                        mIsUndoingDeletion = false;
                     }
                     return false;
                 }
@@ -1646,9 +1657,7 @@
         mPendingDeletion = false;
         if (mUndoDeletionBar != null) {
             if (withAnimation) {
-                mUndoDeletionBar.animate()
-                        .setDuration(200)
-                        .alpha(0f)
+                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
                         .setListener(new Animator.AnimatorListener() {
                             @Override
                             public void onAnimationStart(Animator animation) {
@@ -1669,8 +1678,7 @@
                             public void onAnimationRepeat(Animator animation) {
                                 // Do nothing.
                             }
-                        })
-                        .start();
+                        }).start();
             } else {
                 mUndoDeletionBar.setVisibility(View.GONE);
             }
@@ -1762,11 +1770,131 @@
     }
 
     public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
-        return mCameraOpenCallback;
+        return mCameraController;
     }
 
     // For debugging purposes only.
     public CameraModule getCurrentModule() {
         return mCurrentModule;
     }
+
+    private void setupModules() {
+        // PhotoModule, the default module.
+        mModuleManager.registerModule(new ModuleManagerImpl.ModuleAgent() {
+            @Override
+            public int getModuleId() {
+                return ModeListView.MODE_PHOTO;
+            }
+
+            @Override
+            public boolean requestAppForCamera() {
+                return true;
+            }
+
+            @Override
+            public ModuleController createModule() {
+                return new PhotoModule();
+            }
+        });
+        mModuleManager.setDefaultModuleIndex(ModeListView.MODE_PHOTO);
+
+        // VideoModule.
+        mModuleManager.registerModule(new ModuleManagerImpl.ModuleAgent() {
+            @Override
+            public int getModuleId() {
+                return ModeListView.MODE_VIDEO;
+            }
+
+            @Override
+            public boolean requestAppForCamera() {
+                return true;
+            }
+
+            @Override
+            public ModuleController createModule() {
+                return new VideoModule();
+            }
+        });
+
+        // WideAngle.
+        mModuleManager.registerModule(new ModuleManagerImpl.ModuleAgent() {
+            @Override
+            public int getModuleId() {
+                return ModeListView.MODE_WIDEANGLE;
+            }
+
+            @Override
+            public boolean requestAppForCamera() {
+                return false;
+            }
+
+            @Override
+            public ModuleController createModule() {
+                // TODO: remove the type casting.
+                return new WideAnglePanoramaModule();
+            }
+        });
+
+        // PhotoSphere.
+        if (PhotoSphereHelper.hasLightCycleCapture(this)) {
+            mModuleManager.registerModule(new ModuleManagerImpl.ModuleAgent() {
+                @Override
+                public int getModuleId() {
+                    return ModeListView.MODE_PHOTOSPHERE;
+                }
+
+                @Override
+                public boolean requestAppForCamera() {
+                    return false;
+                }
+
+                @Override
+                public ModuleController createModule() {
+                    // TODO: remove the type casting.
+                    return (ModuleController) PhotoSphereHelper.createPanoramaModule();
+                }
+            });
+        }
+
+        // Refocus.
+        if (RefocusHelper.hasRefocusCapture(this)) {
+            mModuleManager.registerModule(new ModuleManagerImpl.ModuleAgent() {
+                @Override
+                public int getModuleId() {
+                    return ModeListView.MODE_CRAFT;
+                }
+
+                @Override
+                public boolean requestAppForCamera() {
+                    return false;
+                }
+
+                @Override
+                public ModuleController createModule() {
+                    // TODO: remove the type casting.
+                    return (ModuleController) RefocusHelper.createRefocusModule();
+                }
+            });
+        }
+
+        // Gcam for HDR+.
+        if (GcamHelper.hasGcamCapture()) {
+            mModuleManager.registerModule(new ModuleManagerImpl.ModuleAgent() {
+                @Override
+                public int getModuleId() {
+                    return ModeListView.MODE_GCAM;
+                }
+
+                @Override
+                public boolean requestAppForCamera() {
+                    return false;
+                }
+
+                @Override
+                public ModuleController createModule() {
+                    return (ModuleController) GcamHelper.createGcamModule();
+                }
+            });
+        }
+    }
 }
diff --git a/src/com/android/camera/CameraHolder.java b/src/com/android/camera/CameraHolder.java
index 64b18ea..4603dd8 100644
--- a/src/com/android/camera/CameraHolder.java
+++ b/src/com/android/camera/CameraHolder.java
@@ -16,8 +16,6 @@
 
 package com.android.camera;
 
-import static com.android.camera.util.CameraUtil.Assert;
-
 import android.hardware.Camera.CameraInfo;
 import android.hardware.Camera.Parameters;
 import android.os.Handler;
@@ -26,12 +24,16 @@
 import android.os.Message;
 import android.util.Log;
 
-import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.app.CameraManager;
+import com.android.camera.app.CameraManager.CameraProxy;
+import com.android.camera.app.CameraManagerFactory;
 
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
 
+import static com.android.camera.util.CameraUtil.Assert;
+
 /**
  * The class is used to hold an {@code android.hardware.Camera} instance.
  *
@@ -198,7 +200,7 @@
         }
         Assert(!mCameraOpened);
         if (mCameraDevice != null && mCameraId != cameraId) {
-            mCameraDevice.release();
+            mCameraDevice.release(true);
             mCameraDevice = null;
             mCameraId = -1;
         }
@@ -206,7 +208,7 @@
             Log.v(TAG, "open camera " + cameraId);
             if (mMockCameraInfo == null) {
                 mCameraDevice = CameraManagerFactory
-                        .getAndroidCameraManager().cameraOpen(handler, cameraId, cb);
+                        .getAndroidCameraManager().cameraOpenOld(handler, cameraId, cb);
             } else {
                 if (mMockCamera != null) {
                     mCameraDevice = mMockCamera[cameraId];
@@ -222,7 +224,7 @@
             mCameraId = cameraId;
             mParameters = mCameraDevice.getParameters();
         } else {
-            if (!mCameraDevice.reconnect(handler, cb)) {
+            if (!mCameraDevice.reconnectOld(handler, cb)) {
                 Log.e(TAG, "fail to reconnect Camera:" + mCameraId + ", aborting.");
                 return null;
             }
@@ -250,16 +252,6 @@
 
         if (mCameraDevice == null) return;
 
-        long now = System.currentTimeMillis();
-        if (now < mKeepBeforeTime) {
-            if (mCameraOpened) {
-                mCameraOpened = false;
-                mCameraDevice.stopPreview();
-            }
-            mHandler.sendEmptyMessageDelayed(RELEASE_CAMERA,
-                    mKeepBeforeTime - now);
-            return;
-        }
         strongRelease();
     }
 
@@ -267,7 +259,7 @@
         if (mCameraDevice == null) return;
 
         mCameraOpened = false;
-        mCameraDevice.release();
+        mCameraDevice.release(true);
         mCameraDevice = null;
         // We must set this to null because it has a reference to Camera.
         // Camera has references to the listeners.
diff --git a/src/com/android/camera/CameraModule.java b/src/com/android/camera/CameraModule.java
index b6fb0e6..340fea1 100644
--- a/src/com/android/camera/CameraModule.java
+++ b/src/com/android/camera/CameraModule.java
@@ -23,6 +23,7 @@
 
 import com.android.camera.app.MediaSaver;
 
+@Deprecated
 public interface CameraModule {
 
     public void init(CameraActivity activity, View frame);
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index a2d7566..67bd698 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -51,16 +51,18 @@
 import android.view.View;
 import android.view.WindowManager;
 
-import com.android.camera.CameraManager.CameraAFCallback;
-import com.android.camera.CameraManager.CameraAFMoveCallback;
-import com.android.camera.CameraManager.CameraPictureCallback;
-import com.android.camera.CameraManager.CameraProxy;
-import com.android.camera.CameraManager.CameraShutterCallback;
+import com.android.camera.app.CameraManager.CameraAFCallback;
+import com.android.camera.app.CameraManager.CameraAFMoveCallback;
+import com.android.camera.app.CameraManager.CameraPictureCallback;
+import com.android.camera.app.CameraManager.CameraProxy;
+import com.android.camera.app.CameraManager.CameraShutterCallback;
 import com.android.camera.PhotoModule.NamedImages.NamedEntity;
+import com.android.camera.app.AppController;
 import com.android.camera.app.MediaSaver;
 import com.android.camera.exif.ExifInterface;
 import com.android.camera.exif.ExifTag;
 import com.android.camera.exif.Rational;
+import com.android.camera.module.ModuleController;
 import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
 import com.android.camera.ui.ModeListView;
 import com.android.camera.ui.RotateTextToast;
@@ -79,13 +81,9 @@
 import java.util.Vector;
 
 public class PhotoModule
-        implements CameraModule,
-        PhotoController,
-        FocusOverlayManager.Listener,
-        CameraPreference.OnPreferenceChangedListener,
-        ShutterButton.OnShutterButtonListener, MediaSaver.QueueListener,
-        OnCountDownFinishedListener,
-        SensorEventListener {
+        implements CameraModule, ModuleController, PhotoController, FocusOverlayManager.Listener,
+        CameraPreference.OnPreferenceChangedListener, ShutterButton.OnShutterButtonListener,
+        MediaSaver.QueueListener, OnCountDownFinishedListener, SensorEventListener {
 
     private static final String TAG = "CAM_PhotoModule";
 
@@ -372,6 +370,9 @@
 
         mPreferences.setLocalId(mActivity, mCameraId);
         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+
+        mActivity.getCameraProvider().requestCamera(mCameraId);
+
         // we need to reset exposure for the preview
         resetExposureCompensation();
 
@@ -456,9 +457,8 @@
         mCameraId = mPendingSwitchCameraId;
         mPendingSwitchCameraId = -1;
         setCameraId(mCameraId);
+        mActivity.getCameraProvider().requestCamera(mCameraId);
 
-        // from onPause
-        closeCamera();
         mUI.collapseCameraControls();
         mUI.clearFaces();
         if (mFocusManager != null) mFocusManager.removeMessages();
@@ -466,26 +466,9 @@
         // Restart the camera and initialize the UI. From onCreate.
         mPreferences.setLocalId(mActivity, mCameraId);
         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
-        mCameraDevice = CameraUtil.openCamera(
-                mActivity, mCameraId, mHandler,
-                mActivity.getCameraOpenErrorCallback());
-
-        if (mCameraDevice == null) {
-            Log.e(TAG, "Failed to open camera:" + mCameraId + ", aborting.");
-            return;
-        }
-        mParameters = mCameraDevice.getParameters();
-        initializeCapabilities();
-        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+        CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
         mMirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
         mFocusManager.setMirror(mMirror);
-        mFocusManager.setParameters(mInitialParams);
-        setupPreview();
-
-        // reset zoom value index
-        mZoomValue = 0;
-        openCameraCommon();
-
         // Start switch camera animation. Post a message because
         // onFrameAvailable from the old camera may already exist.
         mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
@@ -507,8 +490,6 @@
         }
         updateSceneMode();
         showTapToFocusToastIfNeeded();
-
-
     }
 
     @Override
@@ -897,7 +878,7 @@
         } else {
             orientation = mOrientation;
         }
-        mJpegRotation = CameraUtil.getJpegRotation(mCameraId, orientation);
+        mJpegRotation = CameraUtil.getJpegRotation(mActivity, mCameraId, orientation);
         mParameters.setRotation(mJpegRotation);
         Location loc = mLocationManager.getCurrentLocation();
         CameraUtil.setGpsParameters(mParameters, loc);
@@ -978,6 +959,30 @@
     }
 
     @Override
+    public void onCameraAvailable(CameraProxy cameraProxy) {
+        if (mPaused) {
+            return;
+        }
+        mCameraDevice = cameraProxy;
+
+        initializeCapabilities();
+
+        // Reset zoom value index.
+        mZoomValue = 0;
+        if (mFocusManager == null) {
+            initializeFocusManager();
+        }
+        mFocusManager.setParameters(mInitialParams);
+
+        mParameters = mCameraDevice.getParameters();
+        setCameraParameters(UPDATE_PARAM_ALL);
+        mCameraPreviewParamsReady = true;
+        startPreview();
+
+        onCameraOpened();
+    }
+
+    @Override
     public void onStop() {
         if (mMediaProviderClient != null) {
             mMediaProviderClient.release();
@@ -1166,41 +1171,19 @@
         mPaused = false;
     }
 
-    private boolean prepareCamera() {
-        // We need to check whether the activity is paused before long
-        // operations to ensure that onPause() can be done ASAP.
-        mCameraDevice = CameraUtil.openCamera(
-                mActivity, mCameraId, mHandler,
-                mActivity.getCameraOpenErrorCallback());
-        if (mCameraDevice == null) {
-            Log.e(TAG, "Failed to open camera:" + mCameraId);
-            return false;
-        }
-        mParameters = mCameraDevice.getParameters();
-
-        initializeCapabilities();
-        if (mFocusManager == null) initializeFocusManager();
-        setCameraParameters(UPDATE_PARAM_ALL);
-        mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);
-        mCameraPreviewParamsReady = true;
-        startPreview();
-        mOnResumeTime = SystemClock.uptimeMillis();
-        checkDisplayRotation();
-        return true;
-    }
-
     @Override
     public void onResumeAfterSuper() {
         Log.v(TAG, "On resume.");
         if (mOpenCameraFail || mCameraDisabled) return;
 
+        mActivity.getCameraProvider().requestCamera(mCameraId);
+
         mJpegPictureCallbackTime = 0;
         mZoomValue = 0;
         resetExposureCompensation();
-        if (!prepareCamera()) {
-            // Camera failure.
-            return;
-        }
+
+        mOnResumeTime = SystemClock.uptimeMillis();
+        checkDisplayRotation();
 
         // If first time initialization is not finished, put it in the
         // message queue.
@@ -1227,7 +1210,12 @@
     }
 
     @Override
+    public void onPauseAfterSuper() {
+    }
+
+    @Override
     public void onPauseBeforeSuper() {
+        Log.v(TAG, "On pause.");
         mPaused = true;
         Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
         if (gsensor != null) {
@@ -1238,11 +1226,6 @@
         if (msensor != null) {
             mSensorManager.unregisterListener(this, msensor);
         }
-    }
-
-    @Override
-    public void onPauseAfterSuper() {
-        Log.v(TAG, "On pause.");
         mUI.showPreviewCover();
         // When camera is started from secure lock screen for the first time
         // after screen on, the activity gets onCreate->onResume->onPause->onResume.
@@ -1313,6 +1296,33 @@
     }
 
     @Override
+    public void init(AppController app, boolean isSecureCamera, boolean isCaptureIntent) {
+        init((CameraActivity) app.getAndroidContext(), app.getModuleLayoutRoot());
+    }
+
+    @Override
+    public void resume() {
+        onResumeBeforeSuper();
+        onResumeAfterSuper();
+    }
+
+    @Override
+    public void pause() {
+        onPauseBeforeSuper();
+        onPauseAfterSuper();
+    }
+
+    @Override
+    public void destroy() {
+        // TODO: implement this.
+    }
+
+    @Override
+    public void onPreviewSizeChanged(int width, int height) {
+        // TODO: implement this.
+    }
+
+    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         Log.v(TAG, "onConfigurationChanged");
         setDisplayOrientation();
@@ -1444,14 +1454,8 @@
             mCameraDevice.setFaceDetectionCallback(null, null);
             mCameraDevice.setErrorCallback(null);
 
-            if (mActivity.isSecureCamera() && !CameraActivity.isFirstStartAfterScreenOn()) {
-                // Blocks until camera is actually released.
-                CameraHolder.instance().strongRelease();
-            } else {
-                CameraHolder.instance().release();
-            }
-
             mFaceDetectionStarted = false;
+            mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
             mCameraDevice = null;
             setCameraState(PREVIEW_STOPPED);
             mFocusManager.onCameraReleased();
@@ -1478,24 +1482,40 @@
         startPreview();
     }
 
-    /** This can run on a background thread, post any view updates to MainHandler. */
-    private void startPreview() {
-        if (mPaused || mCameraDevice == null) {
-            return;
+    /**
+     * Returns whether we can/should start the preview or not.
+     */
+    private boolean checkPreviewPreconditions() {
+        if (mPaused) {
+            return false;
         }
 
-         // Any decisions we make based on the surface texture state
-         // need to be protected.
+        if (mCameraDevice == null) {
+            Log.w(TAG, "startPreview: camera device not ready yet.");
+            return false;
+        }
+
         SurfaceTexture st = mUI.getSurfaceTexture();
         if (st == null) {
             Log.w(TAG, "startPreview: surfaceTexture is not ready.");
-            return;
+            return false;
         }
 
         if (!mCameraPreviewParamsReady) {
             Log.w(TAG, "startPreview: parameters for preview is not ready.");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * The start/stop preview should only run on the UI thread.
+     */
+    private void startPreview() {
+        if (!checkPreviewPreconditions()) {
             return;
         }
+
         mCameraDevice.setErrorCallback(mErrorCallback);
         // ICS camera frameworks has a bug. Face detection state is not cleared 1589
         // after taking a picture. Stop the preview to work around it. The bug
@@ -1516,10 +1536,11 @@
         }
         setCameraParameters(UPDATE_PARAM_ALL);
         // Let UI set its expected aspect ratio
-        mCameraDevice.setPreviewTexture(st);
+        mCameraDevice.setPreviewTexture(mUI.getSurfaceTexture());
 
         Log.v(TAG, "startPreview");
         mCameraDevice.startPreview();
+
         mFocusManager.onPreviewStarted();
         onPreviewStarted();
 
diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java
index 7820a93..4686d60 100644
--- a/src/com/android/camera/PhotoUI.java
+++ b/src/com/android/camera/PhotoUI.java
@@ -43,6 +43,7 @@
 
 import com.android.camera.CameraPreference.OnPreferenceChangedListener;
 import com.android.camera.FocusOverlayManager.FocusUI;
+import com.android.camera.app.CameraManager;
 import com.android.camera.ui.AbstractSettingPopup;
 import com.android.camera.ui.CameraControls;
 import com.android.camera.ui.CameraRootView;
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index f765b9c..678520b 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -53,12 +53,12 @@
 import android.view.WindowManager;
 import android.widget.Toast;
 
-import com.android.camera.CameraManager.CameraPictureCallback;
-import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.app.CameraManager.CameraPictureCallback;
+import com.android.camera.app.CameraManager.CameraProxy;
+import com.android.camera.app.AppController;
 import com.android.camera.app.MediaSaver;
-import com.android.camera.app.OrientationManager;
-import com.android.camera.app.OrientationManagerImpl;
 import com.android.camera.exif.ExifInterface;
+import com.android.camera.module.ModuleController;
 import com.android.camera.ui.RotateTextToast;
 import com.android.camera.util.AccessibilityUtils;
 import com.android.camera.util.ApiHelper;
@@ -73,12 +73,9 @@
 import java.util.Iterator;
 import java.util.List;
 
-public class VideoModule implements CameraModule,
-    VideoController,
-    CameraPreference.OnPreferenceChangedListener,
-    ShutterButton.OnShutterButtonListener,
-    MediaRecorder.OnErrorListener,
-    MediaRecorder.OnInfoListener {
+public class VideoModule implements CameraModule, ModuleController, VideoController,
+        CameraPreference.OnPreferenceChangedListener, ShutterButton.OnShutterButtonListener,
+        MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener {
 
     private static final String TAG = "CAM_VideoModule";
 
@@ -196,14 +193,6 @@
                 }
             };
 
-
-    protected class CameraOpenThread extends Thread {
-        @Override
-        public void run() {
-            openCamera();
-        }
-    }
-
     private void openCamera() {
         if (mCameraDevice == null) {
             mCameraDevice = CameraUtil.openCamera(
@@ -336,8 +325,7 @@
          * To reduce startup time, we start the preview in another thread.
          * We make sure the preview is started at the end of onCreate.
          */
-        CameraOpenThread cameraOpenThread = new CameraOpenThread();
-        cameraOpenThread.start();
+        requestCamera(mCameraId);
 
         mContentResolver = mActivity.getContentResolver();
 
@@ -346,17 +334,6 @@
         mIsVideoCaptureIntent = isVideoCaptureIntent();
         initializeSurfaceView();
 
-        // Make sure camera device is opened.
-        try {
-            cameraOpenThread.join();
-            if (mCameraDevice == null) {
-                return;
-            }
-        } catch (InterruptedException ex) {
-            // ignore
-        }
-
-        readVideoPreferences();
         mUI.setPrefChangedListener(this);
 
         mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
@@ -366,10 +343,6 @@
         setDisplayOrientation();
 
         mUI.showTimeLapseUI(mCaptureTimeLapse);
-        initializeVideoSnapshot();
-        resizeForPreviewAspectRatio();
-
-        initializeVideoControl();
         mPendingSwitchCameraId = -1;
     }
 
@@ -392,7 +365,7 @@
             }
 
             // Set rotation and gps data.
-            int rotation = CameraUtil.getJpegRotation(mCameraId, mOrientation);
+            int rotation = CameraUtil.getJpegRotation(mActivity, mCameraId, mOrientation);
             mParameters.setRotation(rotation);
             Location loc = mLocationManager.getCurrentLocation();
             CameraUtil.setGpsParameters(mParameters, loc);
@@ -413,7 +386,7 @@
 
     private void loadCameraPreferences() {
         CameraSettings settings = new CameraSettings(mActivity, mParameters,
-                mCameraId, CameraHolder.instance().getCameraInfo());
+                mCameraId, mActivity.getCameraProvider().getCameraInfo());
         // Remove the video quality preference setting when the quality is given in the intent.
         mPreferenceGroup = filterPreferenceScreenByIntent(
                 settings.getPreferenceGroup(R.xml.video_preferences));
@@ -443,6 +416,17 @@
         }
     }
 
+    @Override
+    public void onCameraAvailable(CameraProxy cameraProxy) {
+        mCameraDevice = cameraProxy;
+        readVideoPreferences();
+        resizeForPreviewAspectRatio();
+        startPreview();
+        initializeVideoSnapshot();
+        initializeVideoControl();
+        mUI.initializeZoom(mParameters);
+    }
+
     private void startPlayVideoActivity() {
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
@@ -654,21 +638,13 @@
         showVideoSnapshotUI(false);
 
         if (!mPreviewing) {
-            openCamera();
-            if (mCameraDevice == null) {
-                return;
-            }
-            readVideoPreferences();
-            resizeForPreviewAspectRatio();
-            startPreview();
+            requestCamera(mCameraId);
         } else {
             // preview already started
             mUI.enableShutter(true);
         }
 
         mUI.initDisplayChangeListener();
-        // Initializing it here after the preview is started.
-        mUI.initializeZoom(mParameters);
 
         keepScreenOnAwhile();
 
@@ -765,7 +741,7 @@
         }
         mCameraDevice.setZoomChangeListener(null);
         mCameraDevice.setErrorCallback(null);
-        CameraHolder.instance().release();
+        mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
         mCameraDevice = null;
         mPreviewing = false;
         mSnapshotInProgress = false;
@@ -786,6 +762,7 @@
             // Camera will be released in onStopVideoRecording.
             onStopVideoRecording();
         } else {
+            stopPreview();
             closeCamera();
             releaseMediaRecorder();
         }
@@ -1011,7 +988,7 @@
         // which is the orientation the graphics need to rotate in order to render correctly.
         int rotation = 0;
         if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
-            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+            CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
             if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
                 rotation = (info.orientation - mOrientation + 360) % 360;
             } else {  // back-facing camera
@@ -1111,8 +1088,7 @@
             PreferenceGroup screen) {
         Intent intent = mActivity.getIntent();
         if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
-            CameraSettings.removePreferenceFromScreen(screen,
-                    CameraSettings.KEY_VIDEO_QUALITY);
+            CameraSettings.removePreferenceFromScreen(screen, CameraSettings.KEY_VIDEO_QUALITY);
         }
 
         if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
@@ -1244,7 +1220,7 @@
         if (bitmap != null) {
             // MetadataRetriever already rotates the thumbnail. We should rotate
             // it to match the UI orientation (and mirror if it is front-facing camera).
-            CameraInfo[] info = CameraHolder.instance().getCameraInfo();
+            CameraInfo[] info = mActivity.getCameraProvider().getCameraInfo();
             boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
             bitmap = CameraUtil.rotateAndMirror(bitmap, 0, mirror);
         }
@@ -1535,6 +1511,33 @@
     }
 
     @Override
+    public void init(AppController app, boolean isSecureCamera, boolean isCaptureIntent) {
+        init((CameraActivity) app.getAndroidContext(), app.getModuleLayoutRoot());
+    }
+
+    @Override
+    public void resume() {
+        onResumeBeforeSuper();
+        onResumeAfterSuper();
+    }
+
+    @Override
+    public void pause() {
+        onPauseBeforeSuper();
+        onPauseAfterSuper();
+    }
+
+    @Override
+    public void destroy() {
+
+    }
+
+    @Override
+    public void onPreviewSizeChanged(int width, int height) {
+        // TODO: implement this
+    }
+
+    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         Log.v(TAG, "onConfigurationChanged");
         setDisplayOrientation();
@@ -1597,20 +1600,14 @@
         setCameraId(mCameraId);
 
         closeCamera();
+        requestCamera(mCameraId);
         mUI.collapseCameraControls();
         // Restart the camera and initialize the UI. From onCreate.
         mPreferences.setLocalId(mActivity, mCameraId);
         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
-        openCamera();
-        readVideoPreferences();
-        startPreview();
-        initializeVideoSnapshot();
-        resizeForPreviewAspectRatio();
-        initializeVideoControl();
 
         // From onResume
         mZoomValue = 0;
-        mUI.initializeZoom(mParameters);
         mUI.setOrientationIndicator(0, false);
 
         // Start switch camera animation. Post a message because
@@ -1776,9 +1773,7 @@
 
         mPendingSwitchCameraId = cameraId;
         Log.d(TAG, "Start to copy texture.");
-        // We need to keep a preview frame for the animation before
-        // releasing the camera. This will trigger onPreviewTextureCopied.
-        // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
+
         // Disable all camera controls.
         mSwitchingCamera = true;
         switchCamera();
@@ -1804,4 +1799,8 @@
     public void onPreviewUIDestroyed() {
         stopPreview();
     }
+
+    private void requestCamera(int id) {
+        mActivity.getCameraProvider().requestCamera(id);
+    }
 }
diff --git a/src/com/android/camera/WideAnglePanoramaModule.java b/src/com/android/camera/WideAnglePanoramaModule.java
index 47f8ad4..bc0a582 100644
--- a/src/com/android/camera/WideAnglePanoramaModule.java
+++ b/src/com/android/camera/WideAnglePanoramaModule.java
@@ -44,12 +44,12 @@
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
-import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.app.CameraManager.CameraProxy;
+import com.android.camera.app.AppController;
 import com.android.camera.app.MediaSaver;
-import com.android.camera.app.OrientationManager;
-import com.android.camera.app.OrientationManagerImpl;
 import com.android.camera.data.LocalData;
 import com.android.camera.exif.ExifInterface;
+import com.android.camera.module.ModuleController;
 import com.android.camera.util.CameraUtil;
 import com.android.camera.util.UsageStatistics;
 import com.android.camera2.R;
@@ -64,7 +64,7 @@
  * Activity to handle panorama capturing.
  */
 public class WideAnglePanoramaModule
-        implements CameraModule, WideAnglePanoramaController,
+        implements CameraModule, ModuleController, WideAnglePanoramaController,
         SurfaceTexture.OnFrameAvailableListener {
 
     public static final int DEFAULT_SWEEP_ANGLE = 160;
@@ -844,6 +844,36 @@
     }
 
     @Override
+    public void init(AppController app, boolean isSecureCamera, boolean isCaptureIntent) {
+        // TODO: implement this.
+        init((CameraActivity) app.getAndroidContext(), app.getModuleLayoutRoot());
+    }
+
+    @Override
+    public void resume() {
+        // TODO: implement this.
+        onResumeBeforeSuper();
+        onResumeAfterSuper();
+    }
+
+    @Override
+    public void pause() {
+        // TODO: implement this.
+        onPauseBeforeSuper();
+        onPauseAfterSuper();
+    }
+
+    @Override
+    public void destroy() {
+        // TODO: implement this.
+    }
+
+    @Override
+    public void onPreviewSizeChanged(int width, int height) {
+        // TODO: implement this.
+    }
+
+    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         mUI.onConfigurationChanged(newConfig, mThreadRunning);
     }
@@ -853,6 +883,11 @@
     }
 
     @Override
+    public void onCameraAvailable(CameraProxy cameraProxy) {
+        // TODO: implement this.
+    }
+
+    @Override
     public void onResumeBeforeSuper() {
         mPaused = false;
     }
diff --git a/src/com/android/camera/AndroidCameraManagerImpl.java b/src/com/android/camera/app/AndroidCameraManagerImpl.java
similarity index 85%
rename from src/com/android/camera/AndroidCameraManagerImpl.java
rename to src/com/android/camera/app/AndroidCameraManagerImpl.java
index 308633c..cc4facb 100644
--- a/src/com/android/camera/AndroidCameraManagerImpl.java
+++ b/src/com/android/camera/app/AndroidCameraManagerImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.camera;
+package com.android.camera.app;
 
 import static com.android.camera.util.CameraUtil.Assert;
 
@@ -58,6 +58,10 @@
     private static final int RECONNECT =   3;
     private static final int UNLOCK =      4;
     private static final int LOCK =        5;
+    @Deprecated
+    private static final int OPEN_CAMERA_OLD = 6; // TODO: remove this.
+    @Deprecated
+    private static final int RECONNECT_OLD = 7; // TODO: remove this.
     // Preview
     private static final int SET_PREVIEW_TEXTURE_ASYNC =        101;
     private static final int START_PREVIEW_ASYNC =              102;
@@ -188,7 +192,30 @@
         public void handleMessage(final Message msg) {
             try {
                 switch (msg.what) {
-                    case OPEN_CAMERA:
+                    case OPEN_CAMERA: {
+                        final CameraOpenCallback openCallback = (CameraOpenCallback) msg.obj;
+                        final int cameraId = msg.arg1;
+                        mCamera = android.hardware.Camera.open(cameraId);
+                        if (mCamera != null) {
+                            mParametersIsDirty = true;
+
+                            // Get a instance of Camera.Parameters for later use.
+                            if (mParamsToSet == null) {
+                                mParamsToSet = mCamera.getParameters();
+                            }
+
+                            if (openCallback != null) {
+                                openCallback.onCameraOpened(new AndroidCameraProxyImpl(cameraId));
+                            }
+                        } else {
+                            if (openCallback != null) {
+                                openCallback.onDeviceOpenFailure(cameraId);
+                            }
+                        }
+                        return;
+                    }
+
+                    case OPEN_CAMERA_OLD: {
                         mCamera = android.hardware.Camera.open(msg.arg1);
                         if (mCamera != null) {
                             mParametersIsDirty = true;
@@ -203,13 +230,34 @@
                             }
                         }
                         return;
+                    }
 
-                    case RELEASE:
+                    case RELEASE: {
                         mCamera.release();
                         mCamera = null;
                         return;
+                    }
 
-                    case RECONNECT:
+                    case RECONNECT: {
+                        final CameraOpenCallbackForward cbForward =
+                                (CameraOpenCallbackForward) msg.obj;
+                        final int cameraId = msg.arg1;
+                        try {
+                            mCamera.reconnect();
+                        } catch (IOException ex) {
+                            if (cbForward != null) {
+                                cbForward.onReconnectionFailure(AndroidCameraManagerImpl.this);
+                            }
+                            return;
+                        }
+
+                        if (cbForward != null) {
+                            cbForward.onCameraOpened(new AndroidCameraProxyImpl(cameraId));
+                        }
+                        return;
+                    }
+
+                    case RECONNECT_OLD: {
                         mReconnectIOException = null;
                         try {
                             mCamera.reconnect();
@@ -217,108 +265,132 @@
                             mReconnectIOException = ex;
                         }
                         return;
+                    }
 
-                    case UNLOCK:
+                    case UNLOCK: {
                         mCamera.unlock();
                         return;
+                    }
 
-                    case LOCK:
+                    case LOCK: {
                         mCamera.lock();
                         return;
+                    }
 
-                    case SET_PREVIEW_TEXTURE_ASYNC:
+                    case SET_PREVIEW_TEXTURE_ASYNC: {
                         setPreviewTexture(msg.obj);
                         return;
+                    }
 
-                    case SET_PREVIEW_DISPLAY_ASYNC:
+                    case SET_PREVIEW_DISPLAY_ASYNC: {
                         try {
                             mCamera.setPreviewDisplay((SurfaceHolder) msg.obj);
                         } catch (IOException e) {
                             throw new RuntimeException(e);
                         }
                         return;
+                    }
 
-                    case START_PREVIEW_ASYNC:
+                    case START_PREVIEW_ASYNC: {
                         mCamera.startPreview();
                         return;
+                    }
 
-                    case STOP_PREVIEW:
+                    case STOP_PREVIEW: {
                         mCamera.stopPreview();
                         return;
+                    }
 
-                    case SET_PREVIEW_CALLBACK_WITH_BUFFER:
+                    case SET_PREVIEW_CALLBACK_WITH_BUFFER: {
                         mCamera.setPreviewCallbackWithBuffer(
                             (PreviewCallback) msg.obj);
                         return;
+                    }
 
-                    case ADD_CALLBACK_BUFFER:
+                    case ADD_CALLBACK_BUFFER: {
                         mCamera.addCallbackBuffer((byte[]) msg.obj);
                         return;
+                    }
 
-                    case AUTO_FOCUS:
+                    case AUTO_FOCUS: {
                         mCamera.autoFocus((AutoFocusCallback) msg.obj);
                         return;
+                    }
 
-                    case CANCEL_AUTO_FOCUS:
+                    case CANCEL_AUTO_FOCUS: {
                         mCamera.cancelAutoFocus();
                         return;
+                    }
 
-                    case SET_AUTO_FOCUS_MOVE_CALLBACK:
+                    case SET_AUTO_FOCUS_MOVE_CALLBACK: {
                         setAutoFocusMoveCallback(mCamera, msg.obj);
                         return;
+                    }
 
-                    case SET_DISPLAY_ORIENTATION:
+                    case SET_DISPLAY_ORIENTATION: {
                         mCamera.setDisplayOrientation(msg.arg1);
                         return;
+                    }
 
-                    case SET_ZOOM_CHANGE_LISTENER:
+                    case SET_ZOOM_CHANGE_LISTENER: {
                         mCamera.setZoomChangeListener(
                             (OnZoomChangeListener) msg.obj);
                         return;
+                    }
 
-                    case SET_FACE_DETECTION_LISTENER:
+                    case SET_FACE_DETECTION_LISTENER: {
                         setFaceDetectionListener((FaceDetectionListener) msg.obj);
                         return;
+                    }
 
-                    case START_FACE_DETECTION:
+                    case START_FACE_DETECTION: {
                         startFaceDetection();
                         return;
+                    }
 
-                    case STOP_FACE_DETECTION:
+                    case STOP_FACE_DETECTION: {
                         stopFaceDetection();
                         return;
+                    }
 
-                    case SET_ERROR_CALLBACK:
+                    case SET_ERROR_CALLBACK: {
                         mCamera.setErrorCallback((ErrorCallback) msg.obj);
                         return;
+                    }
 
-                    case SET_PARAMETERS:
+                    case SET_PARAMETERS: {
                         mParametersIsDirty = true;
                         mParamsToSet.unflatten((String) msg.obj);
                         mCamera.setParameters(mParamsToSet);
                         return;
+                    }
 
-                    case GET_PARAMETERS:
+                    case GET_PARAMETERS: {
                         if (mParametersIsDirty) {
                             mParameters = mCamera.getParameters();
                             mParametersIsDirty = false;
                         }
                         return;
+                    }
 
-                    case SET_PREVIEW_CALLBACK:
+                    case SET_PREVIEW_CALLBACK: {
                         mCamera.setPreviewCallback((PreviewCallback) msg.obj);
                         return;
+                    }
 
-                    case ENABLE_SHUTTER_SOUND:
+                    case ENABLE_SHUTTER_SOUND: {
                         enableShutterSound((msg.arg1 == 1) ? true : false);
                         return;
+                    }
 
-                    case REFRESH_PARAMETERS:
+                    case REFRESH_PARAMETERS: {
                         mParametersIsDirty = true;
                         return;
+                    }
 
-                    default:
+                    default: {
                         throw new RuntimeException("Invalid CameraProxy message=" + msg.what);
+                    }
                 }
             } catch (RuntimeException e) {
                 if (msg.what != RELEASE && mCamera != null) {
@@ -344,13 +416,18 @@
     }
 
     @Override
-    public CameraManager.CameraProxy cameraOpen(
-        Handler handler, int cameraId, CameraOpenCallback callback) {
+    public void cameraOpen(Handler handler, int cameraId, CameraOpenCallback callback) {
         mCameraHandler.obtainMessage(OPEN_CAMERA, cameraId, 0,
                 CameraOpenCallbackForward.getNewInstance(handler, callback)).sendToTarget();
+    }
+
+    @Override
+    public CameraProxy cameraOpenOld(Handler handler, int cameraId, CameraOpenCallback callback) {
+        mCameraHandler.obtainMessage(OPEN_CAMERA_OLD, cameraId, 0,
+                CameraOpenCallbackForward.getNewInstance(handler, callback)).sendToTarget();
         mCameraHandler.waitDone();
         if (mCamera != null) {
-            return new AndroidCameraProxyImpl();
+            return new AndroidCameraProxyImpl(cameraId);
         } else {
             return null;
         }
@@ -363,9 +440,11 @@
      * handler multiple times.
      */
     public class AndroidCameraProxyImpl implements CameraManager.CameraProxy {
+        private final int mCameraId;
 
-        private AndroidCameraProxyImpl() {
+        private AndroidCameraProxyImpl(int cameraId) {
             Assert(mCamera != null);
+            mCameraId = cameraId;
         }
 
         @Override
@@ -374,25 +453,39 @@
         }
 
         @Override
-        public void release() {
-            // release() must be synchronous so we know exactly when the camera
-            // is released and can continue on.
+        public int getCameraId() {
+            return mCameraId;
+        }
+
+        // TODO: Make this package private.
+        @Override
+        public void release(boolean sync) {
+            Log.v("DEBUG", "camera manager release");
+            mCameraHandler.removeCallbacksAndMessages(null);
             mCameraHandler.sendEmptyMessage(RELEASE);
-            mCameraHandler.waitDone();
+            if (sync) {
+                mCameraHandler.waitDone();
+            }
         }
 
         @Override
-        public boolean reconnect(Handler handler, CameraOpenCallback cb) {
-            mCameraHandler.sendEmptyMessage(RECONNECT);
+        public void reconnect(Handler handler, CameraOpenCallback cb) {
+            mCameraHandler.obtainMessage(RECONNECT, mCameraId, 0,
+                    CameraOpenCallbackForward.getNewInstance(handler, cb)).sendToTarget();
+        }
+
+        @Override
+        public boolean reconnectOld(Handler handler, CameraOpenCallback cb) {
+            mCameraHandler.sendEmptyMessage(RECONNECT_OLD);
             mCameraHandler.waitDone();
             CameraOpenCallback cbforward =
                     CameraOpenCallbackForward.getNewInstance(handler, cb);
             if (mReconnectIOException != null) {
                 if (cbforward != null) {
                     cbforward.onReconnectionFailure(AndroidCameraManagerImpl.this);
-                }
+                    }
                 return false;
-            }
+                }
             return true;
         }
 
diff --git a/src/com/android/camera/app/AppController.java b/src/com/android/camera/app/AppController.java
index a08a115..1b25397 100644
--- a/src/com/android/camera/app/AppController.java
+++ b/src/com/android/camera/app/AppController.java
@@ -22,7 +22,6 @@
 import android.net.Uri;
 import android.widget.FrameLayout;
 
-import com.android.camera.CameraManager;
 import com.android.camera.LocationManager;
 
 /**
@@ -142,11 +141,9 @@
     /********************** App-level resources **********************/
 
     /**
-     * Returns the camera device proxy.
-     *
-     * @return {@code null} if not available yet.
+     * Returns the {@link com.android.camera.app.CameraProvider}.
      */
-    public CameraManager.CameraProxy getCameraProxy();
+    public CameraProvider getCameraProvider();
 
     /**
      * Returns the {@link com.android.camera.app.MediaSaver}.
diff --git a/src/com/android/camera/app/CameraController.java b/src/com/android/camera/app/CameraController.java
new file mode 100644
index 0000000..acd2cce
--- /dev/null
+++ b/src/com/android/camera/app/CameraController.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2013 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.camera.app;
+
+import android.content.Context;
+import android.hardware.Camera;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.camera.CameraDisabledException;
+import com.android.camera.util.CameraUtil;
+
+/**
+ * A class which implements {@link com.android.camera.app.CameraProvider} used
+ * by {@link com.android.camera.CameraActivity}.
+ * TODO: Make this class package private.
+ */
+public class CameraController implements CameraManager.CameraOpenCallback, CameraProvider {
+    private final String TAG = "CameraController";
+    private Context mContext;
+    private CameraManager.CameraOpenCallback mCallbackReceiver;
+    private Handler mCallbackHandler;
+    private final CameraManager mCameraManager;
+    private final Camera.CameraInfo[] mCameraInfos;
+    private final int mNumberOfCameras;
+    private final int mFirstBackCameraId;
+    private final int mFirstFrontCameraId;
+
+    private CameraManager.CameraProxy mCameraProxy;
+    private int mRequestingCameraId = -1;
+
+    /**
+     * Constructor.
+     *
+     * @param context The {@link android.content.Context} used to check if the
+     *                camera is disabled.
+     * @param handler The {@link android.os.Handler} to post the camera
+     *                callbacks to.
+     * @param cameraManager Used for camera open/close.
+     */
+    public CameraController(Context context, CameraManager.CameraOpenCallback callbackReceiver,
+            Handler handler, CameraManager cameraManager) {
+        mContext = context;
+        mCallbackReceiver = callbackReceiver;
+        mCallbackHandler = handler;
+        mCameraManager = cameraManager;
+        mNumberOfCameras = Camera.getNumberOfCameras();
+        mCameraInfos = new Camera.CameraInfo[mNumberOfCameras];
+        for (int i = 0; i < mNumberOfCameras; i++) {
+            mCameraInfos[i] = new Camera.CameraInfo();
+            Camera.getCameraInfo(i, mCameraInfos[i]);
+        }
+
+        int firstFront = -1;
+        int firstBack = -1;
+        // Get the first (smallest) back and first front camera id.
+        for (int i = 0; i < mNumberOfCameras; i++) {
+            if (mCameraInfos[i].facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
+                firstBack = i;
+            } else {
+                if (mCameraInfos[i].facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+                    firstFront = i;
+                }
+            }
+        }
+        mFirstBackCameraId = firstBack;
+        mFirstFrontCameraId = firstFront;
+    }
+
+    @Override
+    public Camera.CameraInfo[] getCameraInfo() {
+        return mCameraInfos;
+    }
+
+    @Override
+    public int getNumberOfCameras() {
+        return mNumberOfCameras;
+    }
+
+    @Override
+    public int getFirstBackCameraId() {
+        return mFirstBackCameraId;
+    }
+
+    @Override
+    public int getFirstFrontCameraId() {
+        return mFirstFrontCameraId;
+    }
+
+    @Override
+    public void onCameraOpened(CameraManager.CameraProxy camera) {
+        mRequestingCameraId = -1;
+        mCameraProxy = camera;
+        mCallbackReceiver.onCameraOpened(camera);
+    }
+
+    @Override
+    public void onCameraDisabled(int cameraId) {
+        mCallbackReceiver.onCameraDisabled(cameraId);
+    }
+
+    @Override
+    public void onDeviceOpenFailure(int cameraId) {
+        mCallbackReceiver.onDeviceOpenFailure(cameraId);
+    }
+
+    @Override
+    public void onReconnectionFailure(CameraManager mgr) {
+        mCallbackReceiver.onReconnectionFailure(mgr);
+    }
+
+    @Override
+    public void requestCamera(int id) {
+        // Double open is avoided.
+        if (mRequestingCameraId == id) {
+            return;
+        }
+        mRequestingCameraId = id;
+        if (mCameraProxy == null) {
+            // No camera yet.
+            checkAndOpenCamera(mContext, mCameraManager, id, mCallbackHandler, this);
+        } else if (mCameraProxy.getCameraId() != id) {
+            // Already has another camera opened.
+            mCameraProxy.release(false);
+            mCameraProxy = null;
+            checkAndOpenCamera(mContext, mCameraManager, id, mCallbackHandler, this);
+        } else {
+            // The same camera, just do a reconnect.
+            mCameraProxy.reconnect(mCallbackHandler, this);
+            mCameraProxy = null;
+        }
+    }
+
+    @Override
+    public void releaseCamera(int id) {
+        if (mCameraProxy.getCameraId() != id) {
+            throw new IllegalStateException("Trying to release an unopened camera.");
+        }
+        if (mRequestingCameraId != -1) {
+            mRequestingCameraId = -1;
+        }
+    }
+
+    /**
+     * Closes the opened camera device.
+     * TODO: Make this method package private.
+     */
+    public void closeCamera() {
+        Log.v(TAG, "closing camera");
+        if (mCameraProxy == null) {
+            return;
+        }
+        mCameraProxy.release(true);
+        mCameraProxy = null;
+        mRequestingCameraId = -1;
+    }
+
+    private static void checkAndOpenCamera(Context context, CameraManager cameraManager,
+            final int cameraId, Handler handler, final CameraManager.CameraOpenCallback cb) {
+        try {
+            CameraUtil.throwIfCameraDisabled(context);
+            cameraManager.cameraOpen(handler, cameraId, cb);
+        } catch (CameraDisabledException ex) {
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    cb.onCameraDisabled(cameraId);
+                }
+            });
+        }
+    }
+}
diff --git a/src/com/android/camera/CameraManager.java b/src/com/android/camera/app/CameraManager.java
similarity index 88%
rename from src/com/android/camera/CameraManager.java
rename to src/com/android/camera/app/CameraManager.java
index 131045a..532c970 100644
--- a/src/com/android/camera/CameraManager.java
+++ b/src/com/android/camera/app/CameraManager.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.camera;
+package com.android.camera.app;
 
 import android.annotation.TargetApi;
 import android.graphics.SurfaceTexture;
@@ -129,23 +129,28 @@
          * Callback when {@link java.io.IOException} is caught during
          * {@link android.hardware.Camera#reconnect()}.
          *
-         * @param mgr The {@link com.android.camera.CameraManager}
+         * @param mgr The {@link CameraManager}
          *            with the reconnect failure.
          */
         public void onReconnectionFailure(CameraManager mgr);
     }
 
     /**
-     * Opens the camera of the specified ID synchronously.
+     * Opens the camera of the specified ID asynchronously. The camera device
+     * will be opened in the camera handler thread and will be returned through
+     * the {@link CameraManager.CameraOpenCallback#
+     * onCameraOpened(com.android.camera.app.CameraManager.CameraProxy)}.
      *
      * @param handler The {@link android.os.Handler} in which the callback
      *                was handled.
      * @param callback The callback when any error happens.
      * @param cameraId The camera ID to open.
-     * @return   An instance of {@link CameraProxy} on success. null on failure.
      */
-    public CameraProxy cameraOpen(
-            Handler handler, int cameraId, CameraOpenCallback callback);
+    public void cameraOpen(Handler handler, int cameraId, CameraOpenCallback callback);
+
+    // TODO: remove this after every module supports onCameraAvailable().
+    @Deprecated
+    public CameraProxy cameraOpenOld(Handler handler, int cameraId, CameraOpenCallback callback);
 
     /**
      * An interface that takes camera operation requests and post messages to the
@@ -163,22 +168,38 @@
         public android.hardware.Camera getCamera();
 
         /**
+         * Returns the camera ID associated to by this
+         * {@link CameraManager.CameraProxy}.
+         * @return
+         */
+        public int getCameraId();
+
+        /**
          * Releases the camera device synchronously.
          * This function must be synchronous so the caller knows exactly when the camera
          * is released and can continue on.
+         * TODO: make this package-private after this interface is refactored under app.
+         *
+         * @param synchronous Whether this call should be synchronous.
          */
-        public void release();
+        public void release(boolean synchronous);
 
         /**
-         * Reconnects to the camera device.
+         * Reconnects to the camera device. On success, the camera device will
+         * be returned through {@link CameraManager
+         * .CameraOpenCallback#onCameraOpened(com.android.camera.app.CameraManager
+         * .CameraProxy)}.
          * @see android.hardware.Camera#reconnect()
          *
          * @param handler The {@link android.os.Handler} in which the callback
          *                was handled.
          * @param cb The callback when any error happens.
-         * @return {@code false} on errors.
          */
-        public boolean reconnect(Handler handler, CameraOpenCallback cb);
+        public void reconnect(Handler handler, CameraOpenCallback cb);
+
+        // TODO: remove this.
+        @Deprecated
+        public boolean reconnectOld(Handler handler, CameraOpenCallback cb);
 
         /**
          * Unlocks the camera device.
diff --git a/src/com/android/camera/CameraManagerFactory.java b/src/com/android/camera/app/CameraManagerFactory.java
similarity index 82%
rename from src/com/android/camera/CameraManagerFactory.java
rename to src/com/android/camera/app/CameraManagerFactory.java
index 914ebb2..38f6c6d 100644
--- a/src/com/android/camera/CameraManagerFactory.java
+++ b/src/com/android/camera/app/CameraManagerFactory.java
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.camera;
+package com.android.camera.app;
+
+import com.android.camera.app.AndroidCameraManagerImpl;
+import com.android.camera.app.CameraManager;
 
 /**
  * A factory class for {@link CameraManager}.
@@ -24,7 +27,7 @@
     private static AndroidCameraManagerImpl sAndroidCameraManager;
 
     /**
-     * Returns the android camera implementation of {@link CameraManager}.
+     * Returns the android camera implementation of {@link com.android.camera.app.CameraManager}.
      *
      * @return The {@link CameraManager} to control the camera device.
      */
diff --git a/src/com/android/camera/app/CameraProvider.java b/src/com/android/camera/app/CameraProvider.java
new file mode 100644
index 0000000..7c81557
--- /dev/null
+++ b/src/com/android/camera/app/CameraProvider.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 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.camera.app;
+
+import android.hardware.Camera;
+
+/**
+ * An interface which defines the camera provider.
+ */
+public interface CameraProvider {
+
+    /**
+     * Requests the camera device. If the camera device of the same ID is
+     * already requested, then no-op here.
+     *
+     * @param id The ID of the requested camera device.
+     */
+    public void requestCamera(int id);
+
+    /**
+     * Releases the camera device.
+     *
+     * @param id The camera ID.
+     */
+    public void releaseCamera(int id);
+
+    /**
+     * Get the {@link android.hardware.Camera.CameraInfo} of all the cameras.
+     *
+     * @return An array of the {@link android.hardware.Camera.CameraInfo}.
+     */
+    public Camera.CameraInfo[] getCameraInfo();
+
+    /**
+     * Returns the total number of cameras available on the device.
+     */
+    public int getNumberOfCameras();
+
+    /**
+     * Returns the lowest ID of the back camera.
+     */
+    public int getFirstBackCameraId();
+
+    /**
+     * Returns the lowest ID of the front camera.
+     */
+    public int getFirstFrontCameraId();
+}
diff --git a/src/com/android/camera/app/ModuleManager.java b/src/com/android/camera/app/ModuleManager.java
new file mode 100644
index 0000000..2c08e14
--- /dev/null
+++ b/src/com/android/camera/app/ModuleManager.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013 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.camera.app;
+
+import com.android.camera.module.ModuleController;
+
+/**
+ * The module manager which maintains the
+ * {@link ModuleManagerImpl.ModuleAgent}.
+ */
+public interface ModuleManager {
+    public static int MODULE_INDEX_NONE = -1;
+
+    /**
+     * The module agent which is responsible for maintaining the static
+     * characteristics and the creation of the module.
+     */
+    public static interface ModuleAgent {
+
+        /**
+         * @return The module ID.
+         */
+        public int getModuleId();
+
+        /**
+         * @return Whether the module will request the app for the camera.
+         */
+        public boolean requestAppForCamera();
+
+        /**
+         * Creates the module.
+         * @return The module.
+         */
+        public ModuleController createModule();
+    }
+
+    /**
+     * Registers a module. A module will be available only if its agent is
+     * registered. The registration might fail.
+     *
+     * @param agent The {@link com.android.camera.app.ModuleManager.ModuleAgent}
+     *              of the module.
+     * @throws java.lang.NullPointerException if the {@code agent} is null.
+     * @throws java.lang.IllegalArgumentException if the module ID is
+     * {@code MODULE_INDEX} or another module with the sameID is registered
+     * already.
+     */
+    void registerModule(ModuleAgent agent);
+
+    /**
+     * Unregister a module.
+     *
+     * @param moduleId The module ID.
+     * @return Whether the un-registration succeeds.
+     */
+    boolean unregisterModule(int moduleId);
+
+    /**
+     * Sets the default module index. No-op if the module index does not exist.
+     *
+     * @param moduleId The ID of the default module.
+     * @return Whether the {@code moduleId} exists.
+     */
+    boolean setDefaultModuleIndex(int moduleId);
+
+    /**
+     * @return The default module index. {@code MODULE_INDEX_NONE} if not set.
+     */
+    int getDefaultModuleIndex();
+
+    /**
+     * Returns the {@link com.android.camera.app.ModuleManager.ModuleAgent} by
+     * the module ID.
+     *
+     * @param moduleId The module ID.
+     * @return The agent.
+     */
+    ModuleAgent getModuleAgent(int moduleId);
+}
diff --git a/src/com/android/camera/app/ModuleManagerImpl.java b/src/com/android/camera/app/ModuleManagerImpl.java
new file mode 100644
index 0000000..7087520
--- /dev/null
+++ b/src/com/android/camera/app/ModuleManagerImpl.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2013 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.camera.app;
+
+import android.util.SparseArray;
+
+/**
+ * A class which implements {@link com.android.camera.app.ModuleManager}.
+ */
+public class ModuleManagerImpl implements ModuleManager {
+    private static final String TAG = "ModuleManagerImpl";
+
+    private SparseArray<ModuleAgent> mRegisteredModuleAgents = new
+            SparseArray<ModuleAgent>(2);
+    private int mDefaultModuleId = MODULE_INDEX_NONE;
+
+    public ModuleManagerImpl() {
+    }
+
+    @Override
+    public void registerModule(ModuleAgent agent) {
+        if (agent == null) {
+            throw new NullPointerException("Registering a null ModuleAgent.");
+        }
+        final int moduleId = agent.getModuleId();
+        if (moduleId == MODULE_INDEX_NONE) {
+            throw new IllegalArgumentException(
+                    "ModuleManager: The module ID can not be " + "MODULE_INDEX_NONE");
+        }
+        if (mRegisteredModuleAgents.get(moduleId) != null) {
+            throw new IllegalArgumentException("Module ID is registered already:" + moduleId);
+        }
+        mRegisteredModuleAgents.put(moduleId, agent);
+    }
+
+    @Override
+    public boolean unregisterModule(int moduleId) {
+        if (mRegisteredModuleAgents.get(moduleId) == null) {
+            return false;
+        }
+        mRegisteredModuleAgents.delete(moduleId);
+        if (moduleId == mDefaultModuleId) {
+            mDefaultModuleId = -1;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean setDefaultModuleIndex(int moduleId) {
+        if (mRegisteredModuleAgents.get(moduleId) != null) {
+            mDefaultModuleId = moduleId;
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public int getDefaultModuleIndex() {
+        return mDefaultModuleId;
+    }
+
+    @Override
+    public ModuleAgent getModuleAgent(int moduleId) {
+        ModuleAgent agent = mRegisteredModuleAgents.get(moduleId);
+        if (agent == null) {
+            return mRegisteredModuleAgents.get(mDefaultModuleId);
+        }
+        return mRegisteredModuleAgents.get(moduleId);
+    }
+}
diff --git a/src/com/android/camera/module/ModuleController.java b/src/com/android/camera/module/ModuleController.java
index 63f2a8d..a0aedbf 100644
--- a/src/com/android/camera/module/ModuleController.java
+++ b/src/com/android/camera/module/ModuleController.java
@@ -18,7 +18,9 @@
 
 import android.content.res.Configuration;
 
+import com.android.camera.app.CameraManager;
 import com.android.camera.app.AppController;
+import com.android.camera.app.MediaSaver;
 
 /**
  * The controller at app level.
@@ -52,7 +54,7 @@
      * Destroys the module. Always call this method to release the resources used
      * by this module.
      */
-    public void destory();
+    public void destroy();
 
     /********************** UI / Camera preview **********************/
 
@@ -83,12 +85,16 @@
     /**
      * Called by the app when the camera is available. The module should use
      * {@link com.android.camera.app.AppController#}
+     *
+     * @param cameraProxy The camera device proxy.
      */
-    public void onCameraAvailable();
+    public void onCameraAvailable(CameraManager.CameraProxy cameraProxy);
 
     /**
      * Called by the app when the {@link com.android.camera.app.MediaSaver} is
      * available.
+     *
+     * @param mediaSaver The {@link com.android.camera.app.MediaSaver} to use.
      */
-    public void onMediaSaverAvailable();
+    public void onMediaSaverAvailable(MediaSaver mediaSaver);
 }
diff --git a/src/com/android/camera/util/CameraUtil.java b/src/com/android/camera/util/CameraUtil.java
index f90a1a5..0ed6b59 100644
--- a/src/com/android/camera/util/CameraUtil.java
+++ b/src/com/android/camera/util/CameraUtil.java
@@ -55,7 +55,8 @@
 import com.android.camera.CameraActivity;
 import com.android.camera.CameraDisabledException;
 import com.android.camera.CameraHolder;
-import com.android.camera.CameraManager;
+import com.android.camera.app.CameraManager;
+import com.android.camera.app.CameraManagerFactory;
 import com.android.camera2.R;
 
 import java.io.Closeable;
@@ -608,13 +609,15 @@
 
         if (isFrontCameraIntent(intentCameraId)) {
             // Check if the front camera exist
-            int frontCameraId = CameraHolder.instance().getFrontCameraId();
+            int frontCameraId = ((CameraActivity) currentActivity).getCameraProvider()
+                    .getFirstFrontCameraId();
             if (frontCameraId != -1) {
                 cameraId = frontCameraId;
             }
         } else if (isBackCameraIntent(intentCameraId)) {
             // Check if the back camera exist
-            int backCameraId = CameraHolder.instance().getBackCameraId();
+            int backCameraId = ((CameraActivity) currentActivity).getCameraProvider()
+                    .getFirstBackCameraId();
             if (backCameraId != -1) {
                 cameraId = backCameraId;
             }
@@ -754,12 +757,12 @@
         view.setVisibility(View.GONE);
     }
 
-    public static int getJpegRotation(int cameraId, int orientation) {
+    public static int getJpegRotation(CameraActivity activity, int cameraId, int orientation) {
         // See android.hardware.Camera.Parameters.setRotation for
         // documentation.
         int rotation = 0;
         if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
-            CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId];
+            CameraInfo info = activity.getCameraProvider().getCameraInfo()[cameraId];
             if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
                 rotation = (info.orientation - orientation + 360) % 360;
             } else {  // back-facing camera
@@ -881,6 +884,15 @@
         return new int[0];
     }
 
+    public static void throwIfCameraDisabled(Context context) throws CameraDisabledException {
+        // Check if device policy has disabled the camera.
+        DevicePolicyManager dpm =
+                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        if (dpm.getCameraDisabled(null)) {
+            throw new CameraDisabledException();
+        }
+    }
+
     private static class ImageFileNamer {
         private final SimpleDateFormat mFormat;
 
diff --git a/src_pd/com/android/camera/util/SmartCameraHelper.java b/src_pd/com/android/camera/util/SmartCameraHelper.java
index 2b48388..e5942f4 100644
--- a/src_pd/com/android/camera/util/SmartCameraHelper.java
+++ b/src_pd/com/android/camera/util/SmartCameraHelper.java
@@ -20,8 +20,7 @@
 import android.app.Activity;
 import android.hardware.Camera;
 
-import com.android.camera.CameraManager.CameraPreviewDataCallback;
-import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.app.CameraManager.CameraProxy;
 
 public class SmartCameraHelper {
     public static void register(CameraProxy camera, Camera.Size previewSize, Activity activity) {}
diff --git a/tests_camera/src/com/android/camera/activity/CameraTestCase.java b/tests_camera/src/com/android/camera/activity/CameraTestCase.java
index 4aae6b4..339f005 100644
--- a/tests_camera/src/com/android/camera/activity/CameraTestCase.java
+++ b/tests_camera/src/com/android/camera/activity/CameraTestCase.java
@@ -30,7 +30,7 @@
 import android.view.View;
 
 import com.android.camera.CameraHolder;
-import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.app.CameraManager.CameraProxy;
 import com.android.camera.util.CameraUtil;
 import com.android.gallery3d.R;