merge in jb-mr2-release history after reset to master
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 6bfe75e..a052e46 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -42,7 +42,7 @@
   </plurals>
     <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
     <skip />
-    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Emet un so durant el compte enrere"</string>
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Sona en compte enr."</string>
     <string name="setting_off" msgid="4480039384202951946">"Desactivat"</string>
     <string name="setting_on" msgid="8602246224465348901">"Activat"</string>
     <string name="pref_video_quality_title" msgid="8245379279801096922">"Qualitat de vídeo"</string>
diff --git a/src/com/android/camera/MediaSaver.java b/src/com/android/camera/MediaSaver.java
new file mode 100644
index 0000000..a3d582e
--- /dev/null
+++ b/src/com/android/camera/MediaSaver.java
@@ -0,0 +1,149 @@
+/*
+ * 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;
+
+import android.content.ContentResolver;
+import android.location.Location;
+import android.net.Uri;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+// We use a queue to store the SaveRequests that have not been completed
+// yet. The main thread puts the request into the queue. The saver thread
+// gets it from the queue, does the work, and removes it from the queue.
+//
+// The main thread needs to wait for the saver thread to finish all the work
+// in the queue, when the activity's onPause() is called, we need to finish
+// all the work, so other programs (like Gallery) can see all the images.
+//
+// If the queue becomes too long, adding a new request will block the main
+// thread until the queue length drops below the threshold (QUEUE_LIMIT).
+// If we don't do this, we may face several problems: (1) We may OOM
+// because we are holding all the jpeg data in memory. (2) We may ANR
+// when we need to wait for saver thread finishing all the work (in
+// onPause() or gotoGallery()) because the time to finishing a long queue
+// of work may be too long.
+class MediaSaver extends Thread {
+    private static final int SAVE_QUEUE_LIMIT = 3;
+    private static final String TAG = "MediaSaver";
+
+    private ArrayList<SaveRequest> mQueue;
+    private boolean mStop;
+    private ContentResolver mContentResolver;
+
+    public interface OnMediaSavedListener {
+        public void onMediaSaved(Uri uri);
+    }
+
+    public MediaSaver(ContentResolver resolver) {
+        mContentResolver = resolver;
+        mQueue = new ArrayList<SaveRequest>();
+        start();
+    }
+
+    // Runs in main thread
+    public synchronized boolean queueFull() {
+        return (mQueue.size() >= SAVE_QUEUE_LIMIT);
+    }
+
+    // Runs in main thread
+    public void addImage(final byte[] data, String title, long date, Location loc,
+                         int width, int height, int orientation, OnMediaSavedListener l) {
+        SaveRequest r = new SaveRequest();
+        r.data = data;
+        r.date = date;
+        r.title = title;
+        r.loc = (loc == null) ? null : new Location(loc);  // make a copy
+        r.width = width;
+        r.height = height;
+        r.orientation = orientation;
+        r.listener = l;
+        synchronized (this) {
+            while (mQueue.size() >= SAVE_QUEUE_LIMIT) {
+                try {
+                    wait();
+                } catch (InterruptedException ex) {
+                    // ignore.
+                }
+            }
+            mQueue.add(r);
+            notifyAll();  // Tell saver thread there is new work to do.
+        }
+    }
+
+    // Runs in saver thread
+    @Override
+    public void run() {
+        while (true) {
+            SaveRequest r;
+            synchronized (this) {
+                if (mQueue.isEmpty()) {
+                    notifyAll();  // notify main thread in waitDone
+
+                    // Note that we can only stop after we saved all images
+                    // in the queue.
+                    if (mStop) break;
+
+                    try {
+                        wait();
+                    } catch (InterruptedException ex) {
+                        // ignore.
+                    }
+                    continue;
+                }
+                if (mStop) break;
+                r = mQueue.remove(0);
+                notifyAll();  // the main thread may wait in addImage
+            }
+            Uri uri = storeImage(r.data, r.title, r.date, r.loc, r.width, r.height,
+                    r.orientation);
+            r.listener.onMediaSaved(uri);
+        }
+        if (!mQueue.isEmpty()) {
+            Log.e(TAG, "Media saver thread stopped with " + mQueue.size() + " images unsaved");
+            mQueue.clear();
+        }
+    }
+
+    // Runs in main thread
+    public void finish() {
+        synchronized (this) {
+            mStop = true;
+            notifyAll();
+        }
+    }
+
+    // Runs in saver thread
+    private Uri storeImage(final byte[] data, String title, long date,
+                           Location loc, int width, int height, int orientation) {
+        Uri uri = Storage.addImage(mContentResolver, title, date, loc,
+                                   orientation, data, width, height);
+        return uri;
+    }
+
+    // Each SaveRequest remembers the data needed to save an image.
+    private static class SaveRequest {
+        byte[] data;
+        String title;
+        long date;
+        Location loc;
+        int width, height;
+        int orientation;
+        OnMediaSavedListener listener;
+    }
+}
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 640f840..a283e59 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -200,9 +200,9 @@
     // A view group that contains all the small indicators.
     private View mOnScreenIndicators;
 
-    // We use a thread in ImageSaver to do the work of saving images. This
+    // We use a thread in MediaSaver to do the work of saving images. This
     // reduces the shot-to-shot time.
-    private ImageSaver mImageSaver;
+    private MediaSaver mMediaSaver;
     // Similarly, we use a thread to generate the name of the picture and insert
     // it into MediaStore while picture taking is still in progress.
     private NamedImages mNamedImages;
@@ -304,6 +304,17 @@
 
     private PreviewGestures mGestures;
 
+    private MediaSaver.OnMediaSavedListener mOnMediaSavedListener = new MediaSaver.OnMediaSavedListener() {
+        @Override
+
+        public void onMediaSaved(Uri uri) {
+            if (uri != null) {
+                mHandler.obtainMessage(UPDATE_SECURE_ALBUM_ITEM, uri).sendToTarget();
+                Util.broadcastNewPicture(mActivity, uri);
+            }
+        }
+    };
+
     // The purpose is not to block the main thread in onCreate and onResume.
     private class CameraStartUpThread extends Thread {
         private volatile boolean mCancelled;
@@ -639,7 +650,7 @@
         mShutterButton.setOnShutterButtonListener(this);
         mShutterButton.setVisibility(View.VISIBLE);
 
-        mImageSaver = new ImageSaver();
+        mMediaSaver = new MediaSaver(mContentResolver);
         mNamedImages = new NamedImages();
 
         mFirstTimeInitialized = true;
@@ -677,7 +688,7 @@
                 mPreferences, mContentResolver);
         mLocationManager.recordLocation(recordLocation);
 
-        mImageSaver = new ImageSaver();
+        mMediaSaver = new MediaSaver(mContentResolver);
         mNamedImages = new NamedImages();
         initializeZoom();
         keepMediaProviderInstance();
@@ -983,8 +994,8 @@
                     Log.e(TAG, "Unbalanced name/data pair");
                 } else {
                     if (date == -1) date = mCaptureStartTime;
-                    mImageSaver.addImage(jpegData, title, date, mLocation, width, height,
-                            orientation);
+                    mMediaSaver.addImage(jpegData, title, date, mLocation, width, height,
+                            orientation, mOnMediaSavedListener);
                 }
             } else {
                 mJpegImageData = jpegData;
@@ -1033,142 +1044,6 @@
         }
     }
 
-    // Each SaveRequest remembers the data needed to save an image.
-    private static class SaveRequest {
-        byte[] data;
-        String title;
-        long date;
-        Location loc;
-        int width, height;
-        int orientation;
-    }
-
-    // We use a queue to store the SaveRequests that have not been completed
-    // yet. The main thread puts the request into the queue. The saver thread
-    // gets it from the queue, does the work, and removes it from the queue.
-    //
-    // The main thread needs to wait for the saver thread to finish all the work
-    // in the queue, when the activity's onPause() is called, we need to finish
-    // all the work, so other programs (like Gallery) can see all the images.
-    //
-    // If the queue becomes too long, adding a new request will block the main
-    // thread until the queue length drops below the threshold (QUEUE_LIMIT).
-    // If we don't do this, we may face several problems: (1) We may OOM
-    // because we are holding all the jpeg data in memory. (2) We may ANR
-    // when we need to wait for saver thread finishing all the work (in
-    // onPause() or gotoGallery()) because the time to finishing a long queue
-    // of work may be too long.
-    private class ImageSaver extends Thread {
-        private static final int QUEUE_LIMIT = 3;
-
-        private ArrayList<SaveRequest> mQueue;
-        private boolean mStop;
-
-        // Runs in main thread
-        public ImageSaver() {
-            mQueue = new ArrayList<SaveRequest>();
-            start();
-        }
-
-        // Runs in main thread
-        public synchronized boolean queueFull() {
-            return (mQueue.size() >= QUEUE_LIMIT);
-        }
-
-        // Runs in main thread
-        public void addImage(final byte[] data, String title, long date, Location loc,
-                int width, int height, int orientation) {
-            SaveRequest r = new SaveRequest();
-            r.data = data;
-            r.date = date;
-            r.title = title;
-            r.loc = (loc == null) ? null : new Location(loc);  // make a copy
-            r.width = width;
-            r.height = height;
-            r.orientation = orientation;
-            synchronized (this) {
-                while (mQueue.size() >= QUEUE_LIMIT) {
-                    try {
-                        wait();
-                    } catch (InterruptedException ex) {
-                        // ignore.
-                    }
-                }
-                mQueue.add(r);
-                notifyAll();  // Tell saver thread there is new work to do.
-            }
-        }
-
-        // Runs in saver thread
-        @Override
-        public void run() {
-            while (true) {
-                SaveRequest r;
-                synchronized (this) {
-                    if (mQueue.isEmpty()) {
-                        notifyAll();  // notify main thread in waitDone
-
-                        // Note that we can only stop after we saved all images
-                        // in the queue.
-                        if (mStop) break;
-
-                        try {
-                            wait();
-                        } catch (InterruptedException ex) {
-                            // ignore.
-                        }
-                        continue;
-                    }
-                    r = mQueue.get(0);
-                }
-                storeImage(r.data, r.title, r.date, r.loc, r.width, r.height,
-                        r.orientation);
-                synchronized (this) {
-                    mQueue.remove(0);
-                    notifyAll();  // the main thread may wait in addImage
-                }
-            }
-        }
-
-        // Runs in main thread
-        public void waitDone() {
-            synchronized (this) {
-                while (!mQueue.isEmpty()) {
-                    try {
-                        wait();
-                    } catch (InterruptedException ex) {
-                        // ignore.
-                    }
-                }
-            }
-        }
-
-        // Runs in main thread
-        public void finish() {
-            waitDone();
-            synchronized (this) {
-                mStop = true;
-                notifyAll();
-            }
-            try {
-                join();
-            } catch (InterruptedException ex) {
-                // ignore.
-            }
-        }
-
-        // Runs in saver thread
-        private void storeImage(final byte[] data, String title, long date,
-                Location loc, int width, int height, int orientation) {
-            Uri uri = Storage.addImage(mContentResolver, title, date, loc,
-                    orientation, data, width, height);
-            if (uri != null) {
-                mHandler.sendMessage(mHandler.obtainMessage(UPDATE_SECURE_ALBUM_ITEM, uri));
-                Util.broadcastNewPicture(mActivity, uri);
-            }
-        }
-    }
-
     private static class NamedImages {
         private ArrayList<NamedEntity> mQueue;
         private boolean mStop;
@@ -1242,7 +1117,7 @@
         // If we are already in the middle of taking a snapshot or the image save request
         // is full then ignore.
         if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
-                || mCameraState == SWITCHING_CAMERA || mImageSaver.queueFull()) {
+                || mCameraState == SWITCHING_CAMERA || mMediaSaver.queueFull()) {
             return false;
         }
         mCaptureStartTime = System.currentTimeMillis();
@@ -1707,9 +1582,9 @@
         if (mFaceView != null) mFaceView.clear();
 
         if (mFirstTimeInitialized) {
-            if (mImageSaver != null) {
-                mImageSaver.finish();
-                mImageSaver = null;
+            if (mMediaSaver != null) {
+                mMediaSaver.finish();
+                mMediaSaver = null;
                 mNamedImages = null;
             }
         }