Merge "Stop activity leaks" into ub-camera-glacier
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index 8b2a256..314840f 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -280,7 +280,10 @@
         return mModuleManager;
     }
 
-    // close activity when secure app passes lock screen or screen turns off
+    /**
+     * Close activity when secure app passes lock screen or screen turns
+     * off.
+     */
     private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -288,15 +291,6 @@
         }
     };
 
-    private final ActionBar.OnMenuVisibilityListener mOnMenuVisibilityListener =
-            new ActionBar.OnMenuVisibilityListener() {
-        @Override
-        public void onMenuVisibilityChanged(boolean isVisible) {
-            // TODO: Remove this or bring back the original implementation: cancel
-            // auto-hide actionbar.
-        }
-    };
-
     /**
      * Whether the screen is kept turned on.
      */
@@ -1320,6 +1314,10 @@
         }
     }
 
+    /**
+     * Note: Make sure this callback is unregistered properly when the activity
+     * is destroyed since we're otherwise leaking the Activity reference.
+     */
     private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback
         = new CameraExceptionHandler.CameraExceptionCallback() {
                 @Override
@@ -1369,14 +1367,15 @@
     @Override
     public void onCreateTasks(Bundle state) {
         CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
+        mAppContext = getApplication().getBaseContext();
+
         if (!Glide.isSetup()) {
-            Glide.setup(new GlideBuilder(this)
+            Glide.setup(new GlideBuilder(getAndroidContext())
                 .setResizeService(new FifoPriorityThreadPoolExecutor(2)));
-            Glide.get(this).setMemoryCategory(MemoryCategory.HIGH);
+            Glide.get(getAndroidContext()).setMemoryCategory(MemoryCategory.HIGH);
         }
 
         mOnCreateTime = System.currentTimeMillis();
-        mAppContext = getApplicationContext();
         mSoundPlayer = new SoundPlayer(mAppContext);
 
         try {
@@ -1408,12 +1407,13 @@
         } else {
             mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000));
         }
-        mActionBar.addOnMenuVisibilityListener(mOnMenuVisibilityListener);
 
         mMainHandler = new MainHandler(this, getMainLooper());
         mCameraController = new CameraController(mAppContext, this, mMainHandler,
-                CameraAgentFactory.getAndroidCameraAgent(this, CameraAgentFactory.CameraApi.API_1),
-                CameraAgentFactory.getAndroidCameraAgent(this, CameraAgentFactory.CameraApi.AUTO));
+                CameraAgentFactory.getAndroidCameraAgent(mAppContext,
+                        CameraAgentFactory.CameraApi.API_1),
+                CameraAgentFactory.getAndroidCameraAgent(mAppContext,
+                        CameraAgentFactory.CameraApi.AUTO));
         mCameraController.setCameraExceptionHandler(
                 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
 
@@ -1791,7 +1791,8 @@
         mOrientationManager.resume();
         mPeekAnimationThread = new HandlerThread("Peek animation");
         mPeekAnimationThread.start();
-        mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper());
+        mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper(),
+                mMainHandler, mAboveFilmstripControlLayout);
 
         mCurrentModule.hardResetSettings(mSettingsManager);
         mCurrentModule.resume();
@@ -1913,9 +1914,9 @@
         if (mSecureCamera) {
             unregisterReceiver(mShutdownReceiver);
         }
-        mActionBar.removeOnMenuVisibilityListener(mOnMenuVisibilityListener);
         mSettingsManager.removeAllListeners();
         mCameraController.removeCallbackReceiver();
+        mCameraController.setCameraExceptionHandler(null);
         getContentResolver().unregisterContentObserver(mLocalImagesObserver);
         getContentResolver().unregisterContentObserver(mLocalVideosObserver);
         getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
@@ -2713,7 +2714,7 @@
         filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
     }
 
-    private class PeekAnimationHandler extends Handler {
+    private static class PeekAnimationHandler extends Handler {
         private class DataAndCallback {
             LocalData mData;
             com.android.camera.util.Callback<Bitmap> mCallback;
@@ -2725,8 +2726,14 @@
             }
         }
 
-        public PeekAnimationHandler(Looper looper) {
+        private final Handler mMainHandler;
+        private final FrameLayout mAboveFilmstripControlLayout;
+
+        public PeekAnimationHandler(Looper looper, Handler mainHandler,
+                FrameLayout aboveFilmstripControlLayout) {
             super(looper);
+            mMainHandler = mainHandler;
+            mAboveFilmstripControlLayout = aboveFilmstripControlLayout;
         }
 
         /**
diff --git a/src/com/android/camera/FocusOverlayManager.java b/src/com/android/camera/FocusOverlayManager.java
index 1c5e00a..4e4d54c 100644
--- a/src/com/android/camera/FocusOverlayManager.java
+++ b/src/com/android/camera/FocusOverlayManager.java
@@ -38,6 +38,7 @@
 import com.android.camera.util.UsageStatistics;
 import com.android.ex.camera2.portability.CameraCapabilities;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -133,17 +134,33 @@
         public void setFocusParameters();
     }
 
-    private class MainHandler extends Handler {
-        public MainHandler(Looper looper) {
+    /**
+     * TODO: Refactor this so that we either don't need a handler or make
+     * mListener not be the activity.
+     */
+    private static class MainHandler extends Handler {
+        /**
+         * The outer mListener at the moment is actually the CameraActivity,
+         * which we would leak if we didn't break the GC path here using a
+         * WeakReference.
+         */
+        final WeakReference<FocusOverlayManager> mManager;
+        public MainHandler(FocusOverlayManager manager, Looper looper) {
             super(looper);
+            mManager = new WeakReference<FocusOverlayManager>(manager);
         }
 
         @Override
         public void handleMessage(Message msg) {
+            FocusOverlayManager manager = mManager.get();
+            if (manager == null) {
+                return;
+            }
+
             switch (msg.what) {
                 case RESET_TOUCH_FOCUS: {
-                    cancelAutoFocus();
-                    mListener.startFaceDetection();
+                    manager.cancelAutoFocus();
+                    manager.mListener.startFaceDetection();
                     break;
                 }
             }
@@ -155,7 +172,7 @@
             Listener listener, boolean mirror, Looper looper, FocusUI ui) {
         mAppController = appController;
         mSettingsManager = appController.getSettingsManager();
-        mHandler = new MainHandler(looper);
+        mHandler = new MainHandler(this, looper);
         mMatrix = new Matrix();
         mDefaultFocusModes = new ArrayList<CameraCapabilities.FocusMode>(defaultFocusModes);
         updateCapabilities(capabilities);
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 6863d33..9e6299f 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -256,6 +256,9 @@
     private final float[] mR = new float[16];
     private int mHeading = -1;
 
+    /** Used to detect motion. We use this to release focus lock early. */
+    private MotionManager mMotionManager;
+
     /** True if all the parameters needed to start preview is ready. */
     private boolean mCameraPreviewParamsReady = false;
 
@@ -1663,9 +1666,9 @@
                     new FocusOverlayManager(mAppController, defaultFocusModes,
                             mCameraCapabilities, this, mMirror, mActivity.getMainLooper(),
                             mUI.getFocusUI());
-            MotionManager motionManager = getServices().getMotionManager();
-            if (motionManager != null) {
-                motionManager.addListener(mFocusManager);
+            mMotionManager = getServices().getMotionManager();
+            if (mMotionManager != null) {
+                mMotionManager.addListener(mFocusManager);
             }
         }
         mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
@@ -1719,6 +1722,11 @@
         // Remove the messages and runnables in the queue.
         mHandler.removeCallbacksAndMessages(null);
 
+        if (mMotionManager != null) {
+            mMotionManager.removeListener(mFocusManager);
+            mMotionManager = null;
+        }
+
         closeCamera();
         mActivity.enableKeepScreenOn(false);
         mUI.onPause();
diff --git a/src/com/android/camera/one/OneCameraManager.java b/src/com/android/camera/one/OneCameraManager.java
index 7cd57ff..607e6a0 100644
--- a/src/com/android/camera/one/OneCameraManager.java
+++ b/src/com/android/camera/one/OneCameraManager.java
@@ -96,7 +96,7 @@
             int maxMemoryMB = activity.getServices().getMemoryManager()
                     .getMaxAllowedNativeMemoryAllocation();
             return new com.android.camera.one.v2.OneCameraManagerImpl(
-                    activity.getApplicationContext(), cameraManager, maxMemoryMB,
+                    activity.getAndroidContext(), cameraManager, maxMemoryMB,
                     displayMetrics, activity.getSoundPlayer());
         } else {
             return new com.android.camera.one.v1.OneCameraManagerImpl();
diff --git a/src/com/android/camera/widget/FilmstripView.java b/src/com/android/camera/widget/FilmstripView.java
index 809881d..70c2b86 100644
--- a/src/com/android/camera/widget/FilmstripView.java
+++ b/src/com/android/camera/widget/FilmstripView.java
@@ -813,7 +813,7 @@
 
         data.prepare();
         View recycled = getRecycledView(dataID);
-        View v = mDataAdapter.getView(mActivity, recycled, dataID);
+        View v = mDataAdapter.getView(mActivity.getAndroidContext(), recycled, dataID);
         if (v == null) {
             return null;
         }
@@ -1902,7 +1902,7 @@
 
         MyController(Context context) {
             TimeInterpolator decelerateInterpolator = new DecelerateInterpolator(1.5f);
-            mScroller = new MyScroller(mActivity,
+            mScroller = new MyScroller(mActivity.getAndroidContext(),
                     new Handler(mActivity.getMainLooper()),
                     mScrollerListener, decelerateInterpolator);
             mCanStopScroll = true;
diff --git a/src/com/android/camera/widget/VideoRecordingHints.java b/src/com/android/camera/widget/VideoRecordingHints.java
index c931dfb..1c0b7a3 100644
--- a/src/com/android/camera/widget/VideoRecordingHints.java
+++ b/src/com/android/camera/widget/VideoRecordingHints.java
@@ -29,6 +29,8 @@
 import com.android.camera.util.CameraUtil;
 import com.android.camera2.R;
 
+import java.lang.ref.WeakReference;
+
 /**
  * This class is designed to show the video recording hint when device is held in
  * portrait before video recording. The rotation device indicator will start rotating
@@ -58,6 +60,87 @@
     private int mCenterY = UNSET;
     private int mLastOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
 
+    private static class RotationAnimatorListener implements Animator.AnimatorListener {
+        private final WeakReference<VideoRecordingHints> mHints;
+        private boolean mCanceled = false;
+
+        public RotationAnimatorListener(VideoRecordingHints hint) {
+            mHints = new WeakReference<VideoRecordingHints>(hint);
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            mCanceled = false;
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            VideoRecordingHints hint = mHints.get();
+            if (hint == null) {
+                return;
+            }
+
+            hint.mRotation = ((int) hint.mRotation) % 360;
+            // If animation is canceled, do not restart it.
+            if (mCanceled) {
+                return;
+            }
+            hint.post(new Runnable() {
+                @Override
+                public void run() {
+                    VideoRecordingHints hint = mHints.get();
+                    if (hint != null) {
+                        hint.continueRotationAnimation();
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            mCanceled = true;
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+            // Do nothing.
+        }
+    }
+
+    private static class AlphaAnimatorListener implements Animator.AnimatorListener {
+        private final WeakReference<VideoRecordingHints> mHints;
+        AlphaAnimatorListener(VideoRecordingHints hint) {
+            mHints = new WeakReference<VideoRecordingHints>(hint);
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            // Do nothing.
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            VideoRecordingHints hint = mHints.get();
+            if (hint == null) {
+                return;
+            }
+
+            hint.invalidate();
+            hint.setAlpha(1f);
+            hint.mRotation = 0;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            // Do nothing.
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+            // Do nothing.
+        }
+    }
+
     public VideoRecordingHints(Context context, AttributeSet attrs) {
         super(context, attrs);
         mRotateArrows = getResources().getDrawable(R.drawable.rotate_arrows);
@@ -80,64 +163,11 @@
             }
         });
 
-        mRotationAnimation.addListener(new Animator.AnimatorListener() {
-            private boolean mCanceled = false;
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mCanceled = false;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mRotation = ((int) mRotation) % 360;
-                // If animation is canceled, do not restart it.
-                if (mCanceled) {
-                    return;
-                }
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        continueRotationAnimation();
-                    }
-                });
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mCanceled = true;
-            }
-
-            @Override
-            public void onAnimationRepeat(Animator animation) {
-                // Do nothing.
-            }
-        });
+        mRotationAnimation.addListener(new RotationAnimatorListener(this));
 
         mAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f);
         mAlphaAnimator.setDuration(FADE_OUT_DURATION_MS);
-        mAlphaAnimator.addListener(new Animator.AnimatorListener() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                // Do nothing.
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                invalidate();
-                setAlpha(1f);
-                mRotation = 0;
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                // Do nothing.
-            }
-
-            @Override
-            public void onAnimationRepeat(Animator animation) {
-                // Do nothing.
-            }
-        });
+        mAlphaAnimator.addListener(new AlphaAnimatorListener(this));
         mIsDefaultToPortrait = CameraUtil.isDefaultToPortrait(context);
     }